---
name: m12-w1-progress
description: "M12 Wave 1 (spt endpoint run + spt rc) build state, decisions, and the self-dial-dead blocker"
metadata: 
  node_type: memory
  type: project
  originSessionId: 1ae6a063-4622-4f5b-86b8-b4506d12ccfb
---

M12 W1 (spt-hosted harness bringup + `spt rc` PTY attach) — doyle handed execution to todlando 2026-06-14. Full spec: `M12-PLAN.md` + `M12-W1-PLAN.md` (durable build plan, resumable).

**Built + green:**
- Daemon `spt-daemon/src/harnesshost.rs`: `prepare_harness_spawn` (validate kind=harness + fill `[session.self]` template) + `launch_harness_brokered_in` (spawn into broker PTY via `spawn_session_pid`, mirrors `shellhost::launch_shell_brokered_in`) + `mint_session_id`. 3 unit tests.
- `spt/src/rc.rs`: operator attach pump (single-Brain main thread + stdin reader thread→mpsc, `cold_start_pump` Split carrier for bounded reads, seq-dedup, ctrl-b detach state machine). 3 unit tests.
- cli.rs: `EndpointCmd::Run` + `cmd_endpoint_run`; top-level `Cmd::Rc`; crossterm dep added.
- REQs registered: REQ-HOST-RUN-1 (spawn), REQ-RC-1 (attach), REQ-HOST-RUN-2 (project cwd — doyle-mandated, must land before M12 gate; W1 ships broker-inherited cwd as proof-shortcut only).

**KEY DECISIONS (doyle-validated):** spawn-first (harness self-registers its perch on bind via `api/startup.rs establish_perch`, no daemon pre-register); id rides argv via `{id}` template (NO SpawnReq wire change); one-pump invariant = transport swappable under one `serve_attach` pump.

**BLOCKER RESOLVED (doyle ruling 2026-06-14):** loopback self-dial IMPOSSIBLE (iroh refuses `net_dial(own_addr)`, pinned green test). doyle picked **B1 (broker in-process loopback-conn — local rides byte-identical pump+serve path, covered by cross-node tests; B2's pump-branch rejected as the lesser-tested local path warned against), scoped as a tight W1.5** (NOT in W1). Rationale: cross-node attach + endpoint-run bringup + pump are built+green → gate W1 NOW (bringup verifiable via cross-node attach to the spawned PTY); B1 is a sensitive broker-layer addition (6.7 zone) deserving its own focused task+gate; local lands W1.5 before M12 close. One-pump assertion (both transports → one pump) kept in W1.5.

**REQ-RC-1 split:** cross-node int evidence at W1 gate; local int evidence at W1.5 (per-wave activation).

**W1 GATE: PASS (doyle 2026-06-14).** Independently verified: traceable-reqs check reproduced (196 complete / 0 findings; REQ-HOST-RUN-1 + REQ-RC-1 impl+unit+int, REQ-HOST-RUN-2 []); int test `attach.rs:217` inspected — real `launch_harness_brokered_in` spawns [session.self] into broker-A PTY, B attaches cross-node, HOSTED_OK round-trips, detach→Detached+survives, origin=handshake-identity (KH 7.5); self-dial-refused pin genuine. Trusted reported full-suite (46 bins/0) + clippy -D. **UNCOMMITTED** (awaiting operator).

**W1.5 GATE PASS (doyle 2026-06-14, independently verified — read shared helper + both int tests + reproduced trace).** B1 broker loopback-conn done (design in `M12-W1.5-PLAN.md` "Crystallized design"). Transport seam = `tokio::io::duplex` (ONE full-duplex pair wires a whole operator↔peer stream); only the leaf send/recv become enums (`SendHalf`/`RecvHalf`), so StreamLog/subscribe/send_stream/read-pump are byte-identical across QUIC + loopback. `nethost::dial_loopback` = reused SINGLETON, `remote_id_hex` = own-node id; `open_stream` on a `ConnKind::Loopback` mints the cross-wired PAIR (operator row + peer row); peer row (initiated_locally=false, own origin) → dispatch loop → serve_attach. Explicit `net-dial-loopback` frame (brain.net_dial_loopback + broker.dispatch_net_dial_loopback) — net_dial untouched. access same-node short-circuit (access.rs:89) Allows own-node → NO access change. rc.rs ONE self-dial line swapped → net_dial_loopback. ONE-PUMP PROOF by construction: shared `attach_drive_detach` helper called by BOTH cross-node `remote_attach_drives_a_real_pty_cross_daemon` AND new `local_attach_via_loopback_conn_rides_the_same_pump` — diverge only in conn acquisition + expected_origin. EVIDENCE: attach int 6/6 + new nethost unit `loopback_conn_is_a_reused_singleton_that_cross_wires_a_stream_pair`; full spt+spt-daemon suite **498 passed/0 failed**; clippy -D EXIT=0; traceable EXIT=0 (REQ-RC-1 [OK] impl+unit+int). **UNCOMMITTED** (operator-gated, m12-w1-bringup-rc).

**REQ-HOST-RUN-2 GATE PASS (doyle 2026-06-14) — verified end-to-end.** doyle: "the confirmed-missing gating foundation is DONE + gated; the hard part of M12 is done." CONTINUE-vs-CHECKPOINT (W2-W5) routed to OPERATOR; HOLD building until operator relays. doyle suggested order if continue: **W3 first** (file-backed strings + adapter:profile fallback — most directly unblocks spt-claude-code) → then W2/W4/W5. Additive `SpawnReq.cwd: Option<String>` (serde default+skip_if_none, N-1 safe) → spt-term `PtySession::spawn_program_in(cwd)` (portable-pty CommandBuilder::cwd; `spawn_program` delegates None) → broker `dispatch_spawn` honors `req.cwd` → harnesshost `launch_harness_brokered_in(.., cwd: Option<&str>)` → cli `cmd_endpoint_run` derives PROJECT cwd from `std::env::current_dir()` (None-fallback, never aborts). Shells unchanged (cwd:None, out of scope). 17 SpawnReq literals patched +cwd:None. EVIDENCE: unit `spawn_program_in_lands_the_child_in_the_requested_cwd` (spt-term, unix-gated like capture test); int `broker_spawns_the_pty_child_in_the_requested_cwd` (CROSS-PLATFORM, passes Windows — wire→broker→PTY cwd e2e); full suite **527/0**; clippy EXIT=0; traceable EXIT=0 (REQ-HOST-RUN-2 [OK] impl+unit+int). UNCOMMITTED.

**COMMITTED 0c687fe (2026-06-14): W1.5 + REQ-HOST-RUN-2** on m12-w1-bringup-rc (operator chose commit-then-continue; 24 files, 668+/92-). W1.x bringup+rc foundation COMPLETE + gated (spt-hosted bringup + cross-node + LOCAL rc + project cwd).

**W3 GATE PASS (doyle 2026-06-14, independently verified — reproduced traceable + read every load-bearing test; praised read-time containment re-apply, local-pointer provenance, unchanged schema).** UNCOMMITTED (operator calls commit, same as W1.5+HR2 pattern). doyle design ruling = `M12-W3-RULING.md`; plan = `M12-W3-PLAN.md`. REQ-MANIFEST-5 (T3.1 doc+impl+unit) + REQ-MANIFEST-6 (T3.2 doc+unit, contract-only) registered, traceable EXIT=0 both [OK]. **T3.1** file-backed `[strings]`: `profile::as_file_pointer` = table with EXACTLY one key `file` (else opaque — back-compat); `{file="rel"}` resolves to file contents lazily at get_string; aux dir `adapters/<adapter>/strings/` (Copy-mode copy_dir_all at register, Pointer reads live); HAZARD containment (reject `..`/absolute → register REFUSES loud via `RegistryError::BadStringPointer`, records nothing); read-time skip-diag (eprintln STRINGS_FILE_SKIP → None, never crash/error); local-profile pointers resolve to user-owned profiles dir (update-safe). **T3.2**: resolve_option ALREADY adapter:profile-aware → contract-lock unit + CONTEXT.md:137 reconcile, NO config field added (no consumer; escalation clause untriggered). EVIDENCE: spt-runtime 49/0 (6 new tests); full spt-runtime+spt-daemon+spt+spt-term suite **576/0**; clippy EXIT=0; traceable EXIT=0. manifest.schema.json UNCHANGED (strings opaque Value). UNCOMMITTED on m12-w1-bringup-rc.

**W2 GATE PASS (doyle 2026-06-14, independently verified — reproduced traceable 200/0 + `cargo test -p spt --bins` 151/0; verified SINGLE-BRINGUP-PATH two ways: grep picker/ for spawn paths EMPTY + READ dispatch mod.rs:247 = only Run→cmd_endpoint_run/Attach→run_attach, re-entrant opts = reenter_create screen transitions). COMMITTED @6e76f62** (operator pre-authorized history-tidy). Interactive `spt endpoint run` ratatui picker. doyle design ruling = `M12-W2-RULING.md`; plan = `M12-W2-PLAN.md` (crystallized). Operator authorized incremental commits ("commit as needed, history tidy") — committed pre-gate; doyle gates the committed state. In-process `spt/src/picker/` (model/data/view/shortcut/mod), action-enum single source of truth. **SINGLE-BRINGUP-PATH invariant** (doyle's gate condition): every terminal action → Outcome enum → dispatch routes Run→cmd_endpoint_run, Attach→rc::run_attach; ChangeAdapter/Instantiate/Fork = screen re-entry into create-new (still cmd_endpoint_run), NOT a 2nd spawn. Bare `spt endpoint run` (adapter/id → Option) = picker; flagged = untouched non-interactive path; additive `--create`. Deps ratatui 0.29 + nucleo-matcher 0.3. **doyle rulings absorbed:** Q1 attached blue ■ + Kick → **W2.5** (own broker attach-presence+force-detach slice AFTER W2, own JIT+gate; forward-pointer in plan + docs/DEFERRED.md); Q3 Resume-from-history BUILT (sessions::last_k ledger exists M10/REQ-TERM-6, offline+LOCAL); Q4 rev.2 shortcut basename = PARAMETER default `spt`→`spt-<id>` (adapter overrides→cc; spt-core NEVER bakes cc; .cmd not .ps1 PATHEXT; sentinel-guard); Q5 additive --create. REQ-RUN-PICKER + REQ-RUN-SHORTCUT (doc+impl+unit, NO int — Buffer+state-model unit asserts, live key loop = manual-verify per REQ-PAIR-6). EVIDENCE: 26 picker unit tests, full workspace suite green, clippy -D EXIT=0, traceable 200/200 both [OK].

**W2 FOLLOW-ON BATCH — GATE PASS (doyle 2026-06-14, independently verified: reproduced traceable 201/201 + `cargo test -p spt --bins` 153/0; read decide_bare TTY-guard + data::shortcut_basename resolve; egg verified by DIRECT BINARY RUN `./target/debug/spt.exe spt` → "Sentience Pocket Transacter\n" exact + confirmed absent from --help — note a cargo-run-via-pipe transient empty = shared-tree rebuild race, direct run authoritative).** 3 operator items ruled AFTER @6e76f62 → NOT in it; UNCOMMITTED on m12-w1-bringup-rc, operator calls commit — W2's pre-auth was one-off:
1. **bare `spt` → picker**: `Cli.cmd`→`Option<Cmd>`; run() None → `bare_invocation` → `decide_bare(stdin_tty,stdout_tty)` BOTH TTY→picker else PRINT HELP (mandatory TTY-guard: never TUI in pipe/CI → would hang scripts). `--help`/`-h`/`help` untouched (clap intercepts). ~60 existing cli tests updated `.cmd`→`.cmd.unwrap()`. Folds REQ-RUN-PICKER (+bare_spt_parses_to_none, bare_tty_guard).
2. **REQ-MANIFEST-7 — adapter-declared shortcut basename** (operator: basename must be a MANIFEST element, not just code-default — else spt-claude-code couldn't actually emit cc-<id>; gap doyle verified). Manifest `[adapter] shortcut_basename: Option<String>` (serde-default skip-if-none, N-1-safe); manifest.schema.json re-blessed (`SPT_BLESS=1 cargo test -p spt-runtime`). Picker resolves SELECTED adapter's manifest via `data::shortcut_basename` (resolve_option→field, fallback `spt`); mod.rs drops the hardcoded const. DOCS (adapter-author contract perri builds against): docs/MANIFEST.md `[adapter]` field + worked-example row (carries `[doc->REQ-MANIFEST-7]`) AND docs-site integration-checklist.md Group-3 row + claude-code clause (rides REQ-DOCS-2). doc+impl+unit.
3. **`spt spt` easter egg** (operator): hidden `Cmd::Spt` (`#[command(hide=true)]`, absent from --help) → prints exactly `Sentience Pocket Transacter\n` exit 0. REQ-LESS by design (no tag/doc; traceable only gates declared REQs). CHANGELOG unreleased: ``spt spt` — ???` + picker line. GOTCHA: first insert landed in adjacent DaemonCmd enum (E0599) → moved to real Cmd enum (closes line 213).
BATCH EVIDENCE: full workspace suite green (69 ok-lines/0 non-ok), clippy --workspace -D rc=0, traceable 201/201 (REQ-MANIFEST-7 OK), xtask gen+check OK (CLI reference regenerated for bare/--create/picker shape, drift-gate green).

**W2 + follow-on COMMITTED: @6e76f62 (picker) + @eb74b18 (bare-spt/MANIFEST-7/egg)** on m12-w1-bringup-rc (operator standing "commit as needed, history tidy").

**W4 GATE PASS (doyle 2026-06-14, independently verified — security-sensitive, gated hard: reproduced traceable 203/203 + `cargo test -p spt --bin spt elevation` 12/0; read all 3 binding-condition tests + the re-anchored sudo hazard). COMMITTED @b7cda13 on m12-w1-bringup-rc** (todlando built+committed same session after the design-check; per standing "commit gated batches myself" + operator "don't wait" [[dont-wait-proceed-autonomously]]). Ruling `M12-W4-RULING.md`. EVIDENCE: REQ-ELEVATE-1[doc+impl+unit] + REQ-HAZARD-SELF-ELEVATE[unit] + REQ-HAZARD-SUDO-SECURE-PATH STILL[impl+unit] (refactor re-anchored onto sudo_argv/print_hint_command — coverage NOT lost). 3 binding conditions green: (1) already_elevated_short_circuits_on_every_os (loop-safety grid); (2) launcher_argv_is_absolute_exe_plus_verbatim_args + launchers_never_shell_interpolate_a_crafted_arg (ADVERSARIAL: "x; rm -rf ~ #" stays 1 argv elem no sh/-c; Win `a" & calc.exe` interior-quote backslash-escaped no cmd/c); (3) launch_uac_window self-contained child self-pauses (GetConsoleProcessList), no cross-boundary stdout. Matrix correct (TTY→sudo/desktop+pkexec→pkexec/desktop+term→x-terminal-emulator/headless→print-floor; Win UAC-only-interactive). Impure launches = manual-verify (elevated desktop). KH 5.11 + CONTEXT.md doc'd. Clean refactor dropped superseded ElevateStyle/current_style/rerun_command/should_auto_elevate under clippy -D. KEY FINDING (design): most of W4 ALREADY EXISTS. T4.1 QR: `render_qr` (unicode terminal QR of otpauth URI) already on create+show-code+join — just verify/close any join gap ("window"=inline QR, NO GUI). T4.2 elevation: probe+gate+abs-path-sudo-hint+Unix-interactive-TTY auto-re-exec all exist (`spt/src/elevation.rs`, `cli.rs try_auto_elevate`). DELTA = rest of matrix. doyle rulings LOCKED: **Q2 Win** = `ShellExecuteW "runas"` on ABS-EXE + verbatim argv DIRECTLY (NO cmd/k — no lingering privileged shell); child detects relaunch→work→"You can close this window"+PAUSE; original prints "Elevated terminal launched…" exit 0; NEVER marshal child stdout across the boundary. **Q3 Linux** = TTY→sudo; DISPLAY∧pkexec→pkexec (preferred); DISPLAY∧term→x-terminal-emulator -e (gnome-terminal/konsole/xterm fallbacks); else abs-path hint — **argv ARRAY only, never sh -c**. **Q4** pure `decide_elevation_path→ElevatePath{AlreadyElevated,InlineSudo,UacWindow,Pkexec,TerminalEmulator,PrintHint}` (AlreadyElevated short-circuit when elevated EVERY os = loop-safety in the seam). **Q5** NEW REQ-ELEVATE-1 (doc+impl+unit, no int). **Q6** NEW REQ-HAZARD-SELF-ELEVATE (unit): re-run EXACT invocation, abs-path, no widen/add/alter, no bare-name, argv-array (no inject), child drops state (5.7), never re-elevates. doyle's GATE = (1) loop-safety AlreadyElevated every os (2) verbatim+abs-path+argv-array ALL launchers (3) no stdout across privilege boundary. BUILD ORDER: T4.0 add both REQs → T4.1 join-QR → T4.2a pure matrix seam → T4.2b per-OS launchers → T4.2c CONTEXT.md+KNOWN-HAZARDS doc → sweep → gate → operator commit.

**W5 GATE PASS (doyle 2026-06-14, independently verified — traceable 204/204 REQ-WHOAMI-1[doc+impl+unit]; read render_self_pin + alias; reproduced self_pin_includes_description_when_present + whoami_is_an_endpoint_list_alias green). COMMITTED @812475a.** OPERATOR OVERRODE my bare-id-contract hedge: `id=$(spt whoami)` is NOT a real pattern (env vars don't persist between tool calls) → no breakage to protect. Directive = whoami DROPPED as own command → thin alias `cmd_whoami() = cmd_endpoint_list(false,None,false)`; the ONE change = `render_self_pin(id,state,description)` adds the Self `endpoint description` (info::read_info().resources) inline after state (trim+filter-blank, clean omit), shared by endpoint list AND the whoami alias. LESSON: don't over-weight a scripting "contract" without confirming it's actually used — operator dismissed it cold.

**Remaining M12:** W2.5 attached-presence+Kick — **⛔ ON HOLD 2026-06-14: doyle RETRACTED his Q2=(a) implicit-silent-displace ruling — OPERATOR caught a LOCKED design both missed.** CONTEXT.md:317-318 "remote attach (spt rc)" ratifies a **controller/viewer model**: ONE interactive controller (viewport sets PTY size, resize controller-EXCLUSIVE for ConPTY repaint cost) + ANY NUMBER of read-only `--view` attachers (never resize, **letterbox client-side**); `spt rc kick <target>` displaces incumbent w/ **LOUD notice**; `--take`=kick+attach. _Avoid_: screen-share, resize-per-viewer, **silent controller displacement**. The impl is UNBUILT for this: broker OutputLog.subscriber is SINGULAR (silent last-wins), `--view` is client-side only. So (a) "implicit silent displace" = the exact forbidden anti-pattern. **OPERATOR CHOSE: build the FULL controller/viewer model NOW** (not a dedicated later slice). doyle RE-RULED 2026-06-14 (`M12-W2.5-RULING.md` rewritten): real BROKER wave (hazard zone) — todlando JIT-plans → pings doyle a design-check → builds → gate. DESIGN: broker `OutputLog` subscriber Option → **controller Option + viewers collection** (fan-out under log lock, strict seq; `delivered_through` brain-resume cursor tracks CONTROLLER ONLY); **REQ-HAZARD-VIEWER-ISOLATION** (slow/dead viewer evicted, NEVER stalls controller/child/drain); **resize controller-exclusive** (attach carries role); rc `--view` = output-only + client **letterbox** (broker sends PTY-size frame); **kick/--take** = default-attach-to-controlled = VIEWER (no silent displace), --take = explicit kick+LOUD-notice control-frame then detach; picker blue ■ + **viewer count**, View + "Kick and attach"(--take) via existing dispatch. REQs: REQ-RCVIEW-1 + REQ-KICK-1 + REQ-HAZARD-VIEWER-ISOLATION, all incl **int** (cross-process). PTY-ownership (child survives) still holds. LESSON: gate the documented DESIGN not just the impl — check CONTEXT.md locked sections BEFORE design-checking a wave [[gate-against-documented-design]]. **DESIGN-CHECK PASSED (doyle GO + refinements 2026-06-14, all locked in M12-W2.5-PLAN.md "DESIGN-CHECK OUTCOME"): 3-valued AttachIntent{Viewer|Control|Take} (Control→busy=clean REFUSE not auto-viewer/silent-steal; Take=loud Displaced+FULL-detach not demote); broker SINGLE-writer of driven_by+viewer_count (resolves clear-race); viewer isolation=bounded per-viewer channel+writer thread, drain never touches viewer socket, evict-on-overflow+soft cap; viewer gated SAME access_check as controller (v1, explicit); dormancy keys CONTROLLER-ONLY (viewer wake-neutral, may watch dormant as-is); rc crossterm Event::Resize + initial-size-on-attach; picker status-conditional View+Kick. BUILDING: T0(REQs)+T1(wire AttachIntent+Resize/Size/Displaced) DONE+GREEN behavior-preserving (spt-net 4/4, N-1 default-Control test, build clean). T2 broker AWAITING doyle integration-confirm (HAZARD: OutputLog subscriber shared by spawn-preattach[broker.rs:556 dies-after-spawn]/resume_sessions/handoff/operator — spawn-preattach must NOT mark session "controlled" else first attach busy-refuses; lean: controller=live authoritative reader, spawn-preattach sets none, ring covers pre-attach). Then T3 serve role/displace→T4 rc→T5 picker→T6 docs+int→W2.5 gate.** **doyle T2 integration CONFIRMED (spawn-preattach sets NO controller + DROP its subscriber-set keep ring; resume/handoff re-take controller slot; delivered_through = PERSISTENT LOG STATE advancing only on controller writes, persists across operator churn/viewer eviction/no-controller gap; viewers never touch it).** **GATE #7 added (NON-NEGOTIABLE, operator): a brain UPDATE must NOT kick attached operators — Displaced{by} fires ONLY on a genuine cross-operator Take (different node), NEVER on a brain-restart re-serve re-taking the controller slot (= self-kick, forbidden). Successor reconstructs controller(from delivered_through)+ALL viewer fan-outs; viewers re-serve from ring floor. INT: extend attach_survives_target_brain_restart_exactly_once to controller+≥1 viewer → all survive exactly-once, NO Displaced, seamless cursor resume. This is ADR-0018 seamless-update ∩ controller/viewer.** **W2.5 GATE PASS (doyle 2026-06-14, independently verified — committed @6b4859f on m12-w1-bringup-rc by todlando per standing self-commit).** Reproduced: traceable EXIT=0 (REQ-RCVIEW-1+REQ-KICK-1 doc+impl+unit+int, REQ-HAZARD-VIEWER-ISOLATION unit+int all OK); `cargo test -p spt-daemon --test attach` **11/11 green** on Windows (controller_viewer_matrix_and_loud_take, same_origin_re_subscribe_does_not_displace, resize_is_controller_exclusive, wedged_viewer_does_not_stall_controller all reproduced). READ the impl (not claims): broker.rs:211-242 append fan-out (controller authoritative blocking + delivered_through advance; viewers try_send+evict, drain NEVER touches viewer socket, soft cap MAX_VIEWERS) = gate #2,#5; broker.rs:303-364 resolve_subscribe matrix (free→controller, busy-refuse, same_identity→silent re-take, only different-remote Take fires Displaced) = gate #4,#7-mechanism; broker.rs:248-266 become_controller touches only controller/cursor, NEVER viewers map. ALL 6 binding conditions + gate-#7 self-kick NON-NEGOTIABLE verified. Scope 1823+/148- (broker+501, brain+108, msg+135 new). 2 deviations OK+doc'd (CONTEXT.md:321): letterbox=1-line indicator (true clip/pad deferred); rc resize via terminal::size() polling not Event::Resize (event-loop conflicts raw byte pump — functionally equiv). **ONE non-blocking follow-up FINDING: gate #7's NAMED int shape (extend attach_survives_target_brain_restart_exactly_once to controller+≥1 viewer) NOT delivered — self-kick property covered on a fresh re-take, viewer-survival-across-real-restart is by-construction (verified by reading become_controller) but UNTESTED as combined scenario. Cheap close-out, recommended to todlando, does NOT gate the wave.** **FOLLOW-UP CLOSED + VERIFIED (doyle 2026-06-14) @f521df2: new int controller_restart_with_viewer_no_displace_and_viewer_survives (controller+viewer attached → BEFORE marker to viewer → crash controller brain → successor re-serves same by=node-A → silent Controller no Displaced → AFTER marker survives to viewer; viewer_reads_marker_no_displace panics on ANY Displaced to a viewer). Reproduced attach 12/12 green. Gate #7 combined scenario now directly tested. W2.5 FULLY CLOSED.** **M12 NOW FULLY GATED (W1·W1.5·HR2·W3·W2·follow-on·W4·W5·W2.5 all PASS) → perri builds spt-claude-code from the PUBLIC surface.**

**T1 RE-VERIFIED (doyle 2026-06-14 post-/clear): spt-net attach 4/4 green** (request_without_intent_defaults_to_control N-1 compat + attach_intents_round_trip + round-trip/unknown-skip); wire diff matches locked design EXACTLY (AttachIntent{Viewer|Control|Take} Default=Control, #[serde(default)] intent, 4 additive frames Resize/Size/Displaced + Output/Input/Exit unchanged). **GATE-WATCH (binding): T0 activated REQ-RCVIEW-1+REQ-KICK-1 at FULL [doc,impl,unit,int] and REQ-HAZARD-VIEWER-ISOLATION at [unit,int] — `traceable-reqs check` FAILS until T2-T6 impl+int land. NOT a violation (single uncommitted batch, per-commit rule holds only at batch commit) BUT the batch MUST NOT be committed until int evidence for all 3 REQs exists** (else traceable exit-1; per [[traceable-per-wave-activation]] single-commit wave → full activation correct). **W2.5 BUILT + COMMITTED @6b4859f (todlando 2026-06-14, full sweep green, self-committed per [[dont-wait-proceed-autonomously]] — awaits doyle W2.5 gate → full M12 gate).** T2-T6 ALL DONE: T2 broker — OutputLog→controller Option + viewers HashMap; append fan-out (controller blocking authoritative advances delivered_through; viewers try_send→bounded sync_channel(256)→writer thread, evict-on-Full/Disconnected, soft cap 32, drain NEVER touches a viewer socket = REQ-HAZARD-VIEWER-ISOLATION); resolve_subscribe BY-KEYED identity matrix (same-by silent re-take = GATE #7 self-kick guard; different-remote Control→BusyControlled, Take→loud Displaced; by=None local never displaces a remote→falls to viewer; spawn pre-attaches controller by=None = undriven placeholder); broker SINGLE-writer of driven_by(REQ-REACH-1 moved here)+viewer_count; dispatch_resize controller-gated; new IPC SubscribeReq{intent,by}+KIND_SUBSCRIBED/SIZE/DISPLACED/UNSUBSCRIBE. T3 serve_attach intent→role via Subscribed (Controller/TookControl wake+flush; Viewer wake-neutral; Busy refuse), input gated on Request-intent + buffered-until-role (no PTY leak on busy, no loss on restart-replay); RACE FIX: feed_rest BEFORE detach_session so broker's driven_by=None is last write. T4 rc --take/--view/Control + client busy-guidance + crossterm size()-poll resize + Displaced loud line + viewer Size indicator. T5 picker driven_by/viewer_count + ConfirmOption::Kick + status-conditional (controlled→View+Kick only) + Outcome::Attach{intent}. T6 docs CONTEXT.md BUILT + KH 7.7. INT (tests/attach.rs): controller_viewer_matrix_and_loud_take, same_origin_re_subscribe_does_not_displace (gate#7), resize_is_controller_exclusive, wedged_viewer_does_not_stall_controller. SWEEP: workspace tests green, clippy clean, traceable EXIT=0 (all 3 REQs OK), xtask check OK. [Historical de-risk notes below superseded by the built impl.] SCOPE REALITY: W2.5 is the LARGEST+most intricate M12 wave (broker concurrency ∩ restoration/seamless-update machinery); doyle co-designing round-by-round; only T0+T1 built so far (T2-T6 = big remaining broker+rc+picker+int block). (Investigation still valid: driven_by=blue-■ source, broker.rs:161 silent drop now becomes fan-out, attach.rs:163/183/190 driven_by clear MOVES to broker.) **KEY DE-RISK (todlando investigation):** attach-presence ALREADY tracked — `serve_attach` (attach.rs) stamps `info::set_driven_by(perch, origin_node)` on attach, clears on viewport end; queryable via `spt api driven-by <id>` (cmd_driven_by, REQ-REACH-1). Broker `OutputLog` is SINGLE-subscriber (broker.rs:87) — a new attach DISPLACES the old (no multi-attach), child/PTY survive a detach by construction (broker doc: brain disconnect only detaches subscriber). So "Kick <node> and attach" may largely BE a normal attach (auto-displace); the real questions = stale-driven_by on crash, clean teardown/notify of the displaced operator, who-may-kick auth (one-subnet-all-trusted), + the KH entry. W2.5 = mostly picker UX (blue ■ tri-state from driven_by + Kick confirm option) over a near-existing surface. → full M12 gate → perri builds spt-claude-code from public surface. All gated waves PASS+COMMITTED: W1·W1.5·HR2(0c687fe)·W3·W2·follow-on(6e76f62/eb74b18)·W4(b7cda13)·W5(812475a). Commit gated batches self per [[dont-wait-proceed-autonomously]]. See [[traceable-per-wave-activation]], [[adapter-glue-model-boundary]], [[spt-claude-code-next]] (perri waits on full M12 gate).
