# M9 Wave 1 — `adapter_name` consumer survey (composite addressing)

> **G1 artifact** (doyle reviews this list whole, not just the diff — composite
> `<adapter>:<profile>` is a rename-class change; a missed consumer silently
> resolves a profile to its parent). Produced before T1 wiring; the source of
> truth for "every place a bare `adapter_name` rides today" (CONTEXT.md §adapter
> profile). Branch `m9-adapter-custom`.

## Resolution rule (the contract every seam below must honor)

```
address := "<adapter>"            → parent manifest, UNMODIFIED (short-circuit)
        |  "<adapter>:<profile>"  → parent lookup → profile::resolve(parent, profile)
```

- **Split on the first `:`** to get `(adapter, Option<profile>)`. Bare name ⇒ no
  profile ⇒ parent unmodified (never calls `resolve`).
- **Registry stays parent-keyed** (confirmed with doyle): profiles are *runtime
  selection*, not registration identity. Lookups split the composite, then look
  up the **parent** in the registry, then overlay.
- The **composite string** is what persists in perch identity (`info.json`,
  shell canon id, telemetry) — so a resumed/relaunched endpoint re-resolves the
  same merged view.

## Crate layout (one line each)

`spt-runtime` manifest parse/validate/registry/**profile** · `spt-store` perch
storage (info.json, shellinfo, seed) · `spt` CLI (adapter/shell/api) · `spt-daemon`
shell launch/discovery/wake/link · `spt-proto` envelope · `spt-live` live session ·
`spt-msg` delivery · `spt-net` transport · `spt-term` terminal · `xtask` build.

## Consumers — classified

### A. MUST resolve through the merged view (split → parent lookup → overlay)

| # | Site | File | Note |
|---|------|------|------|
| 1 | shell manifest resolution seam | `spt-daemon/src/shellhost.rs` `shell_section_of()` (~446), match `r.name == adapter_name` (449) | the core seam: composite → parent → overlay; bare = parent |
| 2 | link-host capability resolution | `spt-daemon/src/linkhost.rs` (~92,105,109) | resolve shell capabilities from merged manifest |
| 3 | wake relaunch resolution | `spt-daemon/src/shellwake.rs` `shell_section_of()` (~397) | persistent-offline relaunch must re-resolve same view |
| 4 | api `--adapter` validation | `spt/src/api/mod.rs` (~388-402) `manifest.adapter.name != adapter` | compare **parent part** of composite, not whole |
| 5 | api capability advertise + sensory/env | `spt/src/api/reporting.rs` (~68-82,273-288,388-410) | hostable_types/sensory/[env] from merged manifest |
| 6 | shell instance filter (parent) | `spt/src/cli.rs` (~4496) `i.adapter_name == adapter` | filter must compare correctly under composite |

### B. MUST carry/store the composite string (identity — opaque, but composite-valued)

| # | Site | File | Note |
|---|------|------|------|
| 7 | perch `info.json` adapter field | `spt-store/src/info.rs` (~117-124) `adapter: Option<String>` | store composite; re-resolved on bind/resume |
| 8 | shell canon id + ordinal | `spt-store/src/shellinfo.rs` `adapter_name` (50), `mint_shell_id`/`instance_ordinal`/`format!("{adapter}-{n}")` (117,130,150) | **HAZARD**: `:` separator vs `-<n>` ordinal — id parse must not confuse them |
| 9 | seed contract | `spt-store/src/seed.rs` (~31) `adapter: String` | opaque; composite flows through |
| 10 | spawn/wake template `{adapter_name}` subst | `shellhost.rs` (129), `shellwake.rs` (~418) | shell binary sees the full composite string |
| 11 | `ShellCmd::Spawn { adapter }` CLI arg + handler | `spt/src/cli.rs` (459-465, 4350-4382) | accept `<adapter>:<profile>`, split before registry lookup |
| 12 | api `ApiArgs { adapter }` | `spt/src/api/mod.rs` (34-41) | accept composite, store on context |

### C. Parent-name only (registry/registration — NO composite)

| Site | File | Note |
|------|------|------|
| registry record/dirs | `spt-runtime/src/registry.rs` (50,92-98,138,173) | `adapters/<name>/` keyed by parent; split composite *before* `load_record` |
| `spt adapter add/remove/list` | `spt/src/cli.rs` `cmd_adapter` (4184-4340) | parent name only (profiles are not registered as adapters) |
| manifest `adapter.name` field | `spt-runtime/src/manifest.rs` (63, validate 570) | parent identity |

### D. Display-only (render the composite; no logic change)

| Site | File |
|------|------|
| shell discovery/list lines | `spt-daemon/src/shelldisc.rs` (32,86-90,112-116,136-137,228,248) |
| notif routing names | `spt-daemon/src/notif.rs` (~334) |
| adapter add/remove/list logs | `spt/src/cli.rs` (4276-4335) — parent names |

## Risks flagged

1. **Canon-id `:` vs `-<n>` ambiguity** (consumer #8). `format!("{adapter}-{n}")`
   with a composite `claude-spt:work` → `claude-spt:work-0`. `instance_ordinal`
   parses the trailing `-<n>`; the `:` is inside the adapter segment, so trailing
   `-<n>` parse is unaffected **iff** profile names cannot contain `-<n>`-like
   suffixes that confuse the split. Mitigation: split id on the **last** `-`, and
   forbid `:` / `-` in profile names at create-profile (validate).
2. **#4/#6 comparison** must extract the parent segment, never string-equal the
   whole composite against `manifest.adapter.name`.
3. **Silent parent fallback** is the headline failure mode — any A-row that skips
   the overlay resolves to parent silently. G1 reviews this table for completeness.

## Wiring order (T1 remaining commits)

1. Local profiles + registration (shadow-refusal, floor at registration) — `registry.rs`.
2. Composite resolver entry point (split + parent lookup + `resolve`) — a single
   `spt-runtime` helper every A/B site calls; then wire sites #1–#12.
3. CLI `create-profile`/`delete-profile` + `adapter list` parent+profiles render.
