1242	id = "REQ-MSG-IDLE-TRANSLATION-BINARY"
1243	title = "spt-hosted idle message delivery via an adapter TRANSLATION BINARY (ADR-0022). New opt-in manifest section `[message-idle-translation-binary]` = a TABLE carrying a `path` scalar (doyle OPT-B ruling: modeled as a table, not a bare top-level scalar, so a preceding section cannot silently absorb it + N+1 extensible; spt-core does NOT deny_unknown_fields, so a future key degrades gracefully); spt-core LIFECYCLE-manages it (spawn when the endpoint comes up, terminate when it goes down). The binary is a PURE stdin→stdout filter; spt-core owns EVERY PTY write. stdin (JSON-lines): `{type:\"init\",endpoint_id,node}` first · `{type:\"event\",envelope:\"<EVENT…>\"}` per inbound message (ADR-0020 envelope) · `{type:\"input\"}` content-free ping on each operator keystroke (binary tracks user-idle for its own idle-gated buffering; PTY input content NOT duplicated). stdout (JSON-lines): keystroke-commands `{key:…}`/`{delay_ms:…}`/`{text:…}` (extensible). spt-core applies the emitted sequence to the broker PTY ATOMICALLY (the W1 coordination — REQ-HAZARD-INJECT-CONTROL-COEXIST). The daemon poll feed is the ONE idle substrate for both topologies (Q1=A): harness-hosted consumer = the Monitor child, spt-hosted consumer = this binary; spt-core PREFERS a perch's poll listener if one exists (so spt-hosted can run a listener AND keep `spt rc`). Idle-only; busy/mid-turn = adapter hook-injection. Closes the current grounding gap: `api bind` registers no listener port → a listener-less spt-hosted perch SPOOLS inbound (only spooling+adapter-poll works today) → this delivers real inbound into the PTY. The v0.11.0 raw inject is the degenerate `{text:payload}{key:enter}` case. (v0.13.0)"
1244	required_stages = ["doc", "impl", "unit", "int"]  # ACTIVATED v0.13.0 W2 (todlando) — all layers landed. Layers A(@c73eb6a)+B(@611205c)+D(@7166f52)+C-kernel(@79f5a40)+C-proper/E(@f9fe5bb)+G(int). impl = translation.rs (ToBinary/KeyCmd/key_to_bytes/TranslationChild/InjectFloor) + broker.rs (Translation/run_inject_worker/flush_inject_floor/fault_translation + dispatch_spawn bring-up + dispatch_input floor-buffer&ping + dispatch_endpoint_input event routing); unit = translation.rs (wire round-trips, content-free ping, KeyCmd shapes, InjectFloor buffer/drain, send-keys map). int = inject_control_wedge.rs g1-g4 via the REAL src/bin/xlate_choreo_fixture.rs translation binary: g1 choreography happy-path (delivered + session serviceable + fresh attach served, all platforms; byte-receipt of XLATE_OK hard on cfg(unix)/gravity-linux, captured on cfg(windows) ConPTY per the file's banked park-(b) divergence stance) · g2 no-commit FAULT (SPT_INJECT_COMMIT_DEADLINE_MS=400: buffered operator keys flush after deadline = no wedge + raw-inject fallback, hard both platforms) · g3 content-free ping (secret reaches PTY but NEVER the binary stdin-log + saw_input_ping non-vacuous, hard both) · g4 no-zombie reap on session-down (hard both). Layer F (Unix park b/c bound) = gravity-linux follow-up, shared w/ W1b F3, tracked by the cfg(unix) park-(b) assert. — staged: doc DONE (manifest section row in MANIFEST.md inject-seam family + CONTEXT.md:39 delivery section reworded W0 + ADR-0022 wording table-not-scalar + manifest.schema.json regen); impl/unit/int bump in as each layer lands (Layer A manifest section + validate + schema = impl/unit foundation; B translation.rs protocol+driver; C atomic PTY apply w/ controller buffering; D lifecycle via SpawnReq.translation_binary; E routing; F Unix park (b)/(c) bound; G int gate). int gate: a real dummy translation-binary delivers an inbound message into an rc-attached spt-hosted session via the choreography WITHOUT breaking control (composes with W1's invariant). ALSO OWNS the Unix-only park (b)/(c) PTY-write bound deferred from W1 (doyle ruling 2026-06-19): the 'spt-core owns every PTY write, applied atomically' substrate is where the forkpty input-queue park gets bounded — bounded/fail-fast write_input + DSR-answer must not hold the writer mutex across a blocking write; gate on gravity-linux (the cfg(unix) park-(b) assert in inject_control_wedge.rs proves the park is real + keeps it tracked).
1245
1246	# ── translate-proof: the author-time EMIT-half proof for the translation binary ──
1247	[[requirements]]
1248	id = "REQ-ADAPTER-TRANSLATE-PROOF"
1249	title = "`spt adapter translate-proof <adapter> --event <envelope> [--session <id>]` — the author-time EMIT-half proof tool for `[message-idle-translation-binary]` (ADR-0022), symmetric to `spt adapter digest-proof` (REQ-TERM-5). It spawns and feeds the adapter's declared translation binary EXACTLY as the daemon does at idle-delivery — running the REAL `spt_daemon::translation` driver VERBATIM (no protocol reimplementation): `TranslationChild::spawn` the binary, send the `{type:\"init\",endpoint_id,node}` line then the `{type:\"event\",envelope}` line, and read back the emitted `{key}`/`{text}`/`{delay_ms}`/`{commit}` keystroke-command stream — then prints it author-readable (each Key with its `key_to_bytes` rendering, Text quoted, Delay in ms, Commit marker) with counts. It fills the SAME `{id}`→option and `{session_id}`→(--session, else a placeholder) keys into the `--event` envelope the daemon fills at runtime, so an envelope that proofs here feeds faithfully live. EMIT-half ONLY: it proves the binary's spawn+feed+emit contract; it does NOT exercise the daemon's atomic PTY apply / controller-buffering (that stays covered by the W2 inject_control_wedge int gate) — `--help` says so. Exit codes mirror digest-proof: 0 ok, 1 on spawn-fail / zero commands / no-commit-or-output / unparseable, 2 when the adapter declares no `[message-idle-translation-binary]` section. The `TranslationChild` Drop does the bounded no-zombie reap. (v0.13.x)"
1250	required_stages = ["doc", "impl", "unit", "int"]
1251
1252	# ── W3: a fresh spt-hosted endpoint shows under its own project (info.cwd on bind) ──
1253	[[requirements]]
1254	id = "REQ-HAZARD-BIND-CWD-UNSET"
1255	title = "A bound endpoint's `info.cwd` is SET at bind so a freshly-created perch appears under its own project tab. ROOT (found, v0.13.0): `info.cwd` is NEVER set on bind — `cmd_bind` (spt-hosted) and `bind_from_seed` (harness-hosted) never thread cwd into `establish_perch`/`rec.cwd`. FIX: `cmd_bind` reads its own `current_dir` (the broker spawned it in `project_cwd`); `bind_from_seed` passes `seed.cwd` (already captured at seed time, currently DISCARDED). DISTINCT from REQ-PICKER-HISTORY-FRESH (v0.12.1) — that unioned cwd-origin into picker MEMBERSHIP but tested merge_origin_project with a PROVIDED origin; it never asserted `info.cwd` is actually set on bind, so a real `endpoint run` perch still had an empty cwd and the union had nothing to union. This is the v0.12.1 P1 'appears under its own project right away' claim that was REFUTED in the changelog — delivered for real here. (v0.13.0)"
1256	required_stages = ["impl", "unit", "int"]  # ACTIVATED v0.13.0 W3 (todlando). ROOT (doyle RCA): info.cwd NEVER set on bind. FIX: establish_perch gained a trailing cwd param + sets rec.cwd = cwd.or_else(prior.cwd) (revive carries forward); cmd_bind (spt-hosted) passes its own current_dir (broker spawned it in project_cwd); bind_from_seed (harness-hosted) passes seed.cwd (captured at api seed, previously DISCARDED). impl = api/startup.rs establish_perch/cmd_bind/bind_from_seed. unit = startup.rs bind_records_cwd_so_picker_can_group_by_project + revive_without_cwd_preserves_prior_cwd + seed_bind_records_seed_captured_cwd (mutation-verified — each guards a distinct branch). int = bind_cwd_project_e2e.rs (REAL detached daemon + endpoint run: live perch read_info().cwd is Some+non-empty AND appears under its project via project_id_for_dir membership). Delivers the v0.12.1 P1 that was REFUTED in the changelog. NOTE: cmd_bind stores raw current_dir (native slashes) while cmd_seed forward-slash-normalizes — slash-agnostic through project_id_for_dir, follow-up to unify.
1257
1258	# ── W4: `spt endpoint run` picker UX ──
1259	[[requirements]]
1260	id = "REQ-PICKER-UX-V013"
1261	title = "`spt endpoint run` picker UX (v0.13.0 operator dogfooding): (1) SKIP the first screen — open directly on 'Pick existing'; `n` jumps to 'Create new'. (2) AUTO-ATTACH after both Start-new AND Resume-from-history (both currently don't attach and show no stdout); add an `h` shortcut to run headless (no attach). (3) 'controlled by' shows the node NAME (node_label_display), not the raw hex. (4) Clean up Start-new output — drop the Rust `pid=Some(142748)` leak and the 'harness binds its perch on startup' internals; user-friendly, not a process log. (v0.13.0)"
1262	required_stages = []  # RETIRED/REDIRECTED (doyle W4 gate 2026-06-19): the W4 picker UX scope was DELIVERED as refinements of the EXISTING picker reqs, NOT under this id — fixes (1) open-on-Pick-existing, (2) attach-by-default + `h` headless, (4) terse bringup line landed under REQ-RUN-PICKER (v0.13.0-W4 extension); fix (3) controlled-by node-name landed under REQ-PICKER-4 (v0.13.0-W4 extension, the D item). This seed carries no evidence by design (folded, not duplicated) — kept as a redirect breadcrumb, not an open requirement. Live keypress path = operator HITL.
1263
1264	# ── W5: driven_by self-heal (ONLINE+CONTROLLED clears without the detach IPC) ──
1265	[[requirements]]
1266	id = "REQ-HAZARD-DRIVEN-BY-SELFHEAL"
1267	title = "An spt-hosted endpoint's ONLINE+CONTROLLED state (`driven_by`) must CLEAR even when the detach IPC is lost — do NOT rely on the detach signal (same lesson as REQ-HAZARD-HOSTED-LIVENESS-RECONCILE B2): the reconcile loop clears `driven_by` when the endpoint has no live controller/session. Today a wedged or lost pump never delivers the detach, so the endpoint stays latched CONTROLLED forever. Composes with W1 (the wedge no longer blocks the detach) and rides the same pull-primary reconcile substrate as B2. (v0.13.0)"
1268	required_stages = ["impl", "unit", "int"]  # ACTIVATED v0.13.0 W5 (repro-first, todlando). SCOPE landed = Gap B (no-session self-heal): reconcile_hosted_liveness, when it offlines a sessionless controllable perch (B2 keystone), ALSO clears a stale driven_by (set_driven_by None) — an OFFLINE endpoint can no longer render phantom CONTROLLED. RACE-FREE (no live session ⇒ no controller to re-stamp concurrently). impl = livehost.rs reconcile_hosted_liveness Gap-B clear + the additive SessionInfo.controller_by observability field (broker.rs KIND_SESSIONS). unit = pull_liveness unit extended (offlined sessionless perch clears driven_by; live/relay perches untouched). int = real-broker gates: driven_by_selfheal.rs gap_b (GREEN: reconcile offlines AND clears driven_by) + the A1 gate + the A2 conn-close test (inject_control_wedge.rs w5_a1 / w5_a2_abandoned_remote_controller_conn_close_eviction_is_platform_divergent). EMPIRICAL VERDICT (updated 2026-06-21, doyle forkpty re-run + todlando Windows run REFRAMED A2): controller_by==None is NOT a usable Gap-A signal for the brain reconcile — it is AMBIGUOUS (dispatch_spawn pre-attaches the spawner with by=None, so a LIVE LOCAL session also reads None). The abandoned-REMOTE-controller clear is CONN-CLOSE detection (dropping the operator brain sends a loopback QUIC FIN → broker clear_controller). Whether it lands in the 2s read window is TIMING-VARIABLE on BOTH platforms (matrix run-1 correction to the earlier unix-hard-assert flip): QUIC FIN-detection latency vs the window is NON-DETERMINISTIC — unix loopback USUALLY but NOT ALWAYS clears <2s (a run that didn't = Some(Some) broke the unix Some(None) hard-assert = the QUIC-timing flake class), windows typically needs the ~30s idle-timeout. So w5_a2 is CHARACTERIZATION-ONLY on BOTH platforms (report cby_after_abandon, NEVER hard-assert either polarity). This is CONSISTENT with the REQ's int structure: the HARD int is gap_b (driven_by_selfheal.rs reconcile offlines AND clears driven_by) + w5_a1; w5_a1/w5_a2 are the characterization arm (no coverage loss). W1 bounds the active-OUTPUT wedge; a clean disconnect self-heals via detach_if→clear_controller. The TRUE no-FIN half-open (clears on NEITHER platform at ANY window) needs the BROKER-SIDE D4c presence oracle → DEFERRED to REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT. KNOWN-HAZARDS 7.15.
1269
1270	# ── W5 follow-up: the idle wedged-REMOTE-controller latch (A2, repro-proven, deferred) ──
1271	[[requirements]]
1272	id = "REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT"
1273	title = "An spt-hosted endpoint driven by a REMOTE controller whose remote is gone but whose broker connection stays OPEN (a wedged/lost pump that never delivers the detach) AND whose session is IDLE (no output) stays latched ONLINE+CONTROLLED forever: the W1 drain-evict only fires on OUTPUT (CONTROLLER_WRITE_DEADLINE on a backed-up write), a clean disconnect self-heals via detach_if→clear_controller, but an idle session with a half-open/wedged controller connection produces neither signal. PROVED repro-first on a real broker (v0.13.0 W5, inject_control_wedge.rs w5_a2): controller_by STAYS Some(origin) and driven_by STAYS Some after the remote is abandoned without a clean EOF on an idle session — so the brain reconcile CANNOT detect it from KIND_SESSIONS controller_by (the broker still reports it controlled). FIX DIRECTION (doyle ruling 2026-06-19, broker-side single-writer — the broker owns driven_by/clear_controller): wire the EXISTING D4c NetPresence connection-disconnect event → clear_controller for any session whose controller identity == the dead origin (become_controller already stores Some(origin); presence events already exist — modest wiring, NOT a new probe). The liveness ORACLE is QUIC's own keepalive/idle-timeout: a presence-disconnect IS a real QUIC conn close, already tolerant of transient blips within the keepalive window, so NO heavy partition ADR is needed UNLESS the QUIC timeout proves too slow for the UX (then mint an ADR for a faster controller-heartbeat + its false-evict bound). Composes with W1 (output path) + W5 Gap B (no-session) — this is the third, idle-remote, leg. (v0.13.0 follow-up)"
1274	required_stages = []  # SEED — repro already exists (inject_control_wedge.rs w5_a2 characterization, under REQ-HAZARD-DRIVEN-BY-SELFHEAL int). doyle BOUNDARY RULING (2026-06-19): does NOT fold into W6 — W6 is the idle-DELIVERY consumer (translation binary injection), orthogonal to controller liveness; and a pump→clean-detach can't heal A2 either (A2 is a HALF-OPEN conn — remote gone, NO EOF — so serve_attach's read just blocks, no detach signal to propagate). Needs the broker-side D4c NetPresence oracle above. Schedule as a controller-liveness follow-up post W4/W6 (likely v0.13.x); doyle tracks. Activate [impl,unit,int] when the eviction lands.
1275
1276	# ── W7 (operator /diagnose 2026-06-19): rc Backspace key forwards the wrong byte ──
1277	[[requirements]]
1278	id = "REQ-HAZARD-RC-INPUT-KEY-ENCODING"
1279	title = "An `spt rc` session forwards the Backspace key as the VT DEL byte (0x7f), so the hosted TUI (Claude Code) deletes ONE character — never a whole word. SYMPTOM (operator dogfooding): Backspace in an rc session always behaves like ctrl+Backspace — deletes the entire last word. ROOT (doyle /diagnose, code-grounded, byte PENDING HITL confirm): rc is a RAW VERBATIM byte pump — spawn_stdin_reader (rc.rs:152) reads std::io::stdin() bytes under crossterm raw mode and forwards them unchanged (parse_stdin_chunk only intercepts the ctrl-b detach prefix); there is NO key-event encoding and NO 0x08↔0x7f normalization ANYWHERE in the tree (grep: zero SetConsoleMode / ENABLE_VIRTUAL_TERMINAL_INPUT). On Windows, crossterm enable_raw_mode does NOT set ENABLE_VIRTUAL_TERMINAL_INPUT, so the LEGACY console delivers ^H (0x08, ctrl+h) for Backspace instead of VT DEL (0x7f); Claude Code maps ^H → backward-kill-word → the observed whole-word delete. CONFIRM-FIRST (build the loop): an env-gated hexdump in spawn_stdin_reader (SPT_RC_DEBUG_KEYS) prints the forwarded byte; operator presses Backspace + ctrl+Backspace in a real rc session. FIX CANDIDATES: (a) enable ENABLE_VIRTUAL_TERMINAL_INPUT on the rc stdin console on Windows so the console emits proper VT (Backspace→0x7f, arrows/Home/End as CSI) — cleanest, fixes the whole key map not just Backspace; (b) narrow normalize bare 0x08→0x7f in the rc input path (riskier — a real ctrl+h is also 0x08). Prefer (a) unless it regresses other keys. Add a KNOWN-HAZARDS.md entry on landing. (v0.13.0)"
1280	required_stages = ["impl", "unit"]  # ACTIVATED v0.13.0 W7 (todlando). BYTE CONFIRMED (operator HITL via SPT_RC_DEBUG_KEYS, real Windows Terminal): pre-fix Backspace=0x08 (^H), Ctrl+Backspace=0x7f — legacy console sends ^H, CC maps ^H → backward-kill-word = whole-word delete. REJECTED FIX (dc07c39, REVERTED): enabling ENABLE_VIRTUAL_TERMINAL_INPUT BACKFIRED — on Windows Terminal it yields win32-input-mode (6-field ESC[Vk;Sc;Uc;Kd;Cs;Rc_ records), NOT legacy xterm VT; it broke the ctrl-b detach (0x02 wrapped → parse_stdin_chunk never sees the raw prefix). (Operator note: CC itself DID understand win32-input-mode; the only regression was detach. But the VT flag is harness-SPECIFIC — a non-CC adapter could choke — so it's rejected on agnosticism grounds, not just the detach break.) SHIPPED FIX (doyle ruling, revert+narrow): stay a raw byte pump; normalize_key_byte maps a lone 0x08→0x7f in spawn_stdin_reader's forward path AFTER the detach state machine (0x02 + every other byte untouched). cfg(windows) only (Unix already VT; a real ctrl+h must stay 0x08). Accepted loss: ctrl+h conflated with Backspace; Ctrl+Backspace (0x7f here) stays char-delete (word-delete lost on the secondary key). impl = rc.rs normalize_key_byte (in spawn_stdin_reader). unit = normalize_maps_backspace_but_leaves_detach_and_others. doc = KNOWN-HAZARDS 7.13. SPT_RC_DEBUG_KEYS hexdump (f05566b) gated the root. FOLLOW-UP (not yet a stage): the IDEAL is clean xterm VT (Backspace→0x7f, arrows→ESC[, ctrl-b→0x02 RAW so detach survives) = agnostic + full key fidelity + working detach; narrow-normalize is backspace-ONLY (arrows/Home/End stay legacy). Investigate next session whether Windows Terminal can be driven to legacy xterm VT instead of win32-input-mode; mint a follow-up REQ if pursued. UPGRADE (2026-06-19, doyle-dispatched, operator-requested): one-way map → SWAP. normalize_key_byte now maps 0x08→0x7f AND 0x7f→0x08 (else passthrough; 0x02 detach still consumed upstream). Because CC reads ^H=backward-kill-word and DEL=char-delete, swapping the input bytes recovers Backspace=char-delete AND Ctrl+Backspace=word-delete (native Win11 — the operator rejected losing word-delete). unit assertions extended (0x7f→0x08). [impl,unit] unchanged (extend, not re-mint). SUPERSEDED on Windows by REQ-RC-KEY-VT-TRANSLATE (v0.13.0 bug 2): the byte-swap normalize_key_byte is REMOVED — the agnostic key-event→xterm-VT translator emits 0x7f for Backspace and 0x08 for Ctrl+Backspace NATIVELY, so the swap's intent is folded into translate_key_event. This req's [impl]/[unit] evidence RELOCATES there (translate_key_event's Backspace/Ctrl+Backspace arm + its mapping unit carry the [impl->...]/[unit->REQ-HAZARD-RC-INPUT-KEY-ENCODING] tags) — kept covered, not dropped. Unix is untouched (it was already cfg(windows)-only)."
1281
1282	# ── W1b (operator /diagnose 2026-06-19, post-W1 dogfood escape): the effect-journal PTY-input wedge ──
1283	[[requirements]]
1284	id = "REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE"
1285	title = "The effect journal serializes EVERY PTY effect under one mutex held ACROSS two fsyncs AND the blocking PTY write — so interactive input stutters and ultimately wedges the daemon hard. ROOT (doyle /diagnose, code-grounded + MEASURED on the operator's real Windows box, 2026-06-19): EffectJournal::apply_once (effect.rs:168-188) takes `inner.lock()` and holds it across `write_line(PENDING)` → `effect()` → `write_line(DONE)`, where write_line (effect.rs:235-239) does flush()+sync_all() (a full FlushFileBuffers) — so each effect pays TWO fsyncs under a GLOBAL lock, and the closure `effect()` (the actual PTY write, broker.rs:1257 EffectKind::PtyWrite via attach.rs:197 send_effect) runs while the lock is held. Two operator-visible facets, ONE root: (A) STUTTER/LAG — every keystroke is a PtyWrite effect = 2× sync_all serialized; measured fsync on %LOCALAPPDATA%\\spt-core = median 6.5ms, spikes to 198ms (C: was recently at 100%), so ~13ms+ per keystroke best case, hundreds under contention → 'many but not all keypresses take 100s of ms, choppy, worsens with volume'. (B) HARD PERMANENT WEDGE — when a PtyWrite `effect()` blocks (ConPTY input buffer full / harness not draining stdin), the journal lock is held INDEFINITELY → the single-threaded inbound-stream dispatch (dispatch.rs serve_attach, which both applies input effects AND opens attaches) can never progress → EVERY subsequent attach (`spt rc --view`/`--take`) fails with 'attach request: brain IPC read deadline elapsed' (confirmed: two retries deadline identically; broker control-plane KIND queries still answer — different thread). This REFUTES the W2-deferred ruling that park-(b)/(c) is 'Windows-benign because ConPTY absorbs 4MiB' — on the real box the input path wedges regardless. DISTINCT from W1 (REQ-HAZARD-INJECT-CONTROL-COEXIST = the OUTPUT drain, correctly fixed @8b5583e; output uses broker.rs:1106 append, NOT the fsync journal). This is the INPUT/effect-journal path W1 never touched, and it is THE wedge the operator hits with --take/--view. FIX DIRECTION (candidates, repro-first — extend inject_control_wedge.rs to a REAL backed-up-PTY-consumer + a real rc-client attach assertion, the gap W1's gate missed): (1) do NOT hold the journal lock across effect() — reserve the key + fsync PENDING under lock, RELEASE, run effect(), re-acquire to fsync DONE + mark applied (preserve crash-idempotency via the per-key reservation, not a global hold); (2) bound/fail-fast the PtyWrite itself (the W2-deferred park bound — write_input must never block indefinitely, DSR-answer must not hold the writer mutex across a blocking write); (3) drop per-keystroke fsync on the interactive path — PtyWrite effects are EPHEMERAL (a keystroke lost on a broker crash is retyped; PTY state is not reconstructed from keystroke replay), so in-memory applied-set dedup suffices (the broker survives the brain — that IS the dedup anchor), with async/batched fsync or no-fsync for EffectKind::PtyWrite while durable kinds (NetSend/NetDial/Registry/Spool) keep their fsync. Combine (1)+(3) at minimum. Add a KNOWN-HAZARDS.md entry on landing. (v0.13.0)"
1286	required_stages = ["impl", "unit", "int"]  # ACTIVATED v0.13.0 W1b (todlando). FIX (1)+(3): apply_once releases inner.lock() across effect() (reserve key + fsync PENDING for DURABLE under lock -> RELEASE -> effect() OFF lock -> re-acquire -> finalize: fsync DONE for durable + mark applied); EffectKind::PtyWrite is EPHEMERAL (is_durable()=false) = NO journal lines / NO fsync, in-memory dedup only (broker-survives-brain is the anchor); durable kinds keep fsync. impl = effect.rs apply_once + EffectKind::is_durable. unit = apply_once_does_not_hold_the_journal_lock_across_effect (barrier proves a 2nd key completes while one effect blocks) + ptywrite_skips_the_durable_journal_write_while_netsend_persists (file-content proxy: PtyWrite writes no PENDING/DONE line, NetSend does); both cross-platform RED pre-fix, GREEN post-fix; gauntlet + exactly-once preserved (7/7). int = inject_control_wedge.rs a_journaled_input_wedge_does_not_starve_a_concurrent_rc_attach (REAL broker+PTY+rc pump, NO mocks: a concurrent rc attach stays SERVICED + actually RECEIVES PTY bytes while a journaled input effect is in-flight — the assertion W1's gate lacked; GATE on Windows journaled_ops_pumped=117/subscribed=true/got_output=true). FIX (2) the Unix-forkpty park bound (write_input bounded + DSR off writer-mutex) = gravity-linux FOLLOW-UP within W1b (doyle minimum = (1)+(3); the cfg(unix) park-(b) liveness assert + the forkpty-RED note are folded into the int and tracked). KNOWN-HAZARDS 7.14. Refutes the W2-deferred 'ConPTY-benign' ruling (the wedge is the LOCK HOLD, not the PTY write blocking). FIX (2) DELIVERED v0.13.0 P0 under REQ-HAZARD-PTY-INPUT-WRITER-WEDGE: write_input is now bounded/never-blocks-the-dispatch via a per-session single input-writer thread (callers enqueue to a bounded FIFO, DROP+INPUT_BACKPRESSURE on saturation) — the paste-burst park that fix (2) was deferred on.
1287
1288	# ── bugs 3+4 (operator HITL 2026-06-19, doyle /diagnose, ONE root): endpoint-run attaches before online ──
1289	[[requirements]]
1290	id = "REQ-HAZARD-RC-ATTACH-ONLINE-RACE"
1291	title = "`spt endpoint run` in an ATTACH/VIEW terminal action attaches BEFORE the freshly-spawned endpoint is online, so the attach races (or outright loses to) the harness bind. ROOT (doyle /diagnose, code-grounded): cmd_endpoint_run (cli.rs) does launch_harness_brokered_in -> (if start: return) -> run_attach with NO await-online between them. launch_harness_brokered_in returns once the harness PROCESS is spawned, but the broker-PTY bind (info status -> STATUS_ONLINE + the live session) lands ASYNC. Both picker attach paths route here with start=false (RunMode::Attach -> cmd_endpoint_run start=false,view=false): Start-now catches the endpoint mid-bringup -> run_attach attempts + loses the handshake race; Resume-from-history catches it still fully OFFLINE -> run_attach's status-gate (REQ-HAZARD-RC-ATTACH-FAILFAST) short-circuits 'offline - nothing to attach' and NEVER attempts. SAME root, two faces (the W4 attach-by-default surfaced both; an online endpoint is unaffected - the picker returns Outcome::Attach, not Run). FIX: in cmd_endpoint_run, when the terminal action is attach/view (NOT start), AWAIT the endpoint online between launch_harness_brokered_in success and run_attach - poll spt_store::info read_info().status to STATUS_ONLINE with a bounded harness-boot deadline (~25s) at a tight interval; on online -> run_attach; on timeout -> ENDPOINT_RUN_ONLINE_TIMEOUT err (do NOT attach a dead bringup). (v0.13.0)"
1292	required_stages = ["impl", "unit", "int"]  # ACTIVATED v0.13.0 (todlando, doyle-dispatched bugs 3+4). impl = cmd_endpoint_run await_endpoint_online gate (poll_online combinator) before run_attach on the attach/view path, FAIL-LOUD ENDPOINT_RUN_ONLINE_TIMEOUT on timeout (start/headless return above, untouched; only the offline-bringup Run path reaches here — an already-online endpoint routes Outcome::Attach, never double-counting the wait). unit = poll_online_fastpath_timeout_and_later_success (injected store-read + sleep, no wall-clock: online-immediate ⇒ true + ZERO sleeps; never-online ⇒ false timeout, every attempt checked + slept-between-only; later-online ⇒ true, stops at once — doyle gate (a) structural guard). int = dummy_harness_e2e endpoint_run_attach_awaits_online_before_attaching: a fresh `endpoint run` with NO --start (attach-by-default) DELIVERS the live DUMMY_HARNESS_TICK post-bringup (RED pre-fix had a THIRD race face — attach connects but EOF-detaches on an empty ring before any output, saw_tick=false; the heartbeat-delivery gate is robust to all three faces; GREEN post-fix), full daemon-tree reap hygiene. Repro-first.
1293
1294	# ── bug 2 (operator HITL 2026-06-19, doyle /diagnose + design ruling): rc Windows special keys dead → xterm-VT translator ──
1295	[[requirements]]
1296	id = "REQ-RC-KEY-VT-TRANSLATE"
1297	title = "On Windows, `spt rc` translates CONSOLE KEY EVENTS to standard xterm VT so ALL keys reach the hosted harness — arrows/Home/End/PgUp/PgDn/Insert/Delete/F-keys, every modifier combo, Backspace/Ctrl+Backspace — not just the byte-emitting ones. ROOT (operator HITL, doyle /diagnose): `spt rc` reads raw STDIN BYTES (spawn_stdin_reader, std::io::stdin().read); on the Windows LEGACY console (no ENABLE_VIRTUAL_TERMINAL_INPUT) the special keys produce console KEY_EVENTs, NOT stdin bytes, so the byte-pump sees nothing → those keys are DEAD. Enabling ENABLE_VIRTUAL_TERMINAL_INPUT was rejected (W7 dc07c39): on Windows Terminal it yields harness-specific win32-input-mode + broke ctrl-b detach. FIX (agnostic, full fidelity): on Windows, replace the stdin byte-read with a crossterm EVENT source (crossterm 0.28 already a dep; the picker already reads events) and translate each KeyEvent → STANDARD xterm VT bytes via a PURE translate_key_event(KeyEvent)->Vec<u8> (copy a known-correct xterm table verbatim, ADR-0001 spirit), forwarded through the SAME rc pump — the harness receives ordinary xterm VT (harness-AGNOSTIC, no win32-input-mode). Press-only (drop Repeat/Release). Detach stays the ctrl-b+'d' PREFIX, event-sourced (doyle Option B): Ctrl+B arms; armed+plain-'d'⇒Detach; armed+Ctrl+B⇒emit literal 0x02; armed+other⇒0x02 then translate(other). Non-tty stdin (piped/tests) → FALL BACK to the byte-read path (keeps e2e byte-injection working). UNIX UNCHANGED (its raw-mode byte stream already delivers proper VT; cfg-split, zero Unix regression). SUPERSEDES the W7 normalize_key_byte swap on Windows — the translator emits 0x7f for Backspace and 0x08 for Ctrl+Backspace natively (REQ-HAZARD-RC-INPUT-KEY-ENCODING folded in). NO int (a live interactive console can't be driven in CI — HITL, REQ-RUN-PICKER/RC-1 precedent); the exhaustive non-vacuous translate_key_event mapping unit + the event-detach unit ARE the surface. (v0.13.0)"
1298	required_stages = ["doc", "impl", "unit"]  # ACTIVATED v0.13.0 bug 2 (todlando, operator-ruled proper translator, ship-blocker; doyle gates). doc = KNOWN-HAZARDS + CONTEXT note (Windows rc translates console key events to standard xterm VT; Unix passes through). impl = rc.rs translate_key_event (pure xterm-VT table) + spawn_stdin_reader Windows cfg-split (tty→crossterm events / non-tty→byte fallback / Unix byte-pump unchanged) + event-sourced ctrl-b+'d' detach SM (Option B) + normalize_key_byte removed (Backspace/Ctrl+Backspace mapping relocated into translate_key_event). unit = exhaustive translate_key_event mapping (every arrow/Home/End/PgUp/PgDn/Del/Insert/F-key/modifier-combo/Ctrl+char/Backspace/Ctrl+Backspace asserts exact xterm bytes) + the event-detach SM (armed+'d'⇒Detach; armed+Ctrl+B⇒0x02; armed+other⇒0x02+translated). HITL acceptance (operator) = real CC rc session: arrows/Home/End/PgUp/PgDn/Del/F-keys work, Backspace=char, Ctrl+Backspace=word, ctrl-b d detach works, typing smooth (W1b).
1299
1300	# ── P0 (operator HITL 2026-06-19, post-deploy dogfood; doyle /diagnose + design): pasting into rc WEDGES the broker ──
1301	[[requirements]]
1302	id = "REQ-HAZARD-PTY-INPUT-WRITER-WEDGE"
1303	title = "Pasting into an `spt rc` session WEDGES the broker — after a paste the operator can no longer type AND can no longer attach to NEW or EXISTING sessions (`brain IPC read deadline`). ROOT (doyle /diagnose, code-grounded): the operator-keystroke path rc -> net-stream Input -> serve_attach (attach.rs:197 brain.send_effect) -> KIND_INPUT -> broker dispatch loop (broker.rs:1091) -> dispatch_input (broker.rs:1459) -> session.write_input(&bytes) runs SYNCHRONOUSLY on the broker request-handling thread. W1b (REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE) released the journal lock across the effect (fix 1) + made PtyWrite ephemeral/no-fsync (fix 3) but EXPLICITLY DEFERRED fix (2) — bound/fail-fast the PtyWrite itself. A single keystroke never fills the ConPTY input buffer; a PASTE BURST does -> write_input blocks -> the dispatch thread cannot service the next frame (a re-attach subscribe, a become_controller restore-write, an inject-floor flush) -> wedge. Not a bug-2 regression (the byte path funnels to the same write_input; paste just reliably fills the buffer). FIX (doyle design, V0.13.0-P0-PTY-INPUT-WRITER-DESIGN.md, CONTEXT L33 broker-owns-PTY/minimal + L435 SessionSurface + single-writer pattern): one dedicated per-session INPUT-WRITER THREAD = the SOLE caller of the blocking write_input, fed by a BOUNDED FIFO channel; every caller (dispatch_input, serve_attach->send_effect, inject-floor flush) ENQUEUES + returns immediately, never blocks. A blocked/slow harness blocks ONLY its own writer thread, never the broker dispatch. Backpressure (operator ruling): queue full => DROP excess input + stamp the session INPUT_BACKPRESSURE (visible health signal); the daemon NEVER wedges; a merely-slow harness self-heals as the writer drains. Exactly-once preserved (PtyWrite ephemeral: apply_once effect = the non-blocking enqueue => Applied; ack now means accepted+ordered, benign — rc does not gate on landing); order preserved (single FIFO + single writer); inject-floor (W2 Layer C) choreography moves to the lone writer. Completes the W1b-deferred fix (2), cross-platform (cfg(unix) forkpty park folds in). (v0.13.0)"
1304	required_stages = ["impl", "unit", "int"]  # ACTIVATED v0.13.0 P0 (todlando, the active ship-blocker; doyle gates). Completes the W1b-deferred fix (2) of REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE. impl = per-session input-writer thread (sole write_input caller) + bounded FIFO (sync_channel, SPT_INPUT_QUEUE_DEPTH override for the int) + InputWriter::enqueue try_send at dispatch_input (apply_once PtyWrite effect + None branch) / dispatch_endpoint_input / run_inject_worker (KeyCmd) / flush_inject_floor; DROP+stamp INPUT_BACKPRESSURE on a full queue, HEAL (clear stamp) on the next accepted enqueue; spawned in dispatch_spawn, held on HostedSession.input (Arc<InputWriter>), thread exits when the last tx drops on teardown. unit = drain ORDER (single FIFO + single writer) + bounded-queue DROP/HEAL decision (injected channel, no wall-clock) + exactly-once enqueue (reserve->enqueue->Applied, dedup). int = REPRO-FIRST (W1b's int ESCAPED this — it relied on ConPTY absorbing the write, never an indefinite block): a real PTY consumer that STOPS draining so write_input genuinely parks (RED pre-fix: a concurrent rc attach gets brain IPC read deadline / no output) + GREEN post-fix (concurrent attach OPENS + RECEIVES output, operator keystroke still accepted, DROP+INPUT_BACKPRESSURE on saturation + heal on resume). Real broker+PTY+rc, NO mocks. cfg(unix) forkpty park leg covered. doc = KNOWN-HAZARDS 7.17 + CONTEXT note (PTY input single-writer; callers enqueue never block; drop+signal on a wedged harness). PLANNED stages: impl = per-session input-writer thread (sole write_input caller) + bounded FIFO + try-enqueue at dispatch_input/serve_attach send_effect path/inject-floor flush + DROP+INPUT_BACKPRESSURE stamp on saturation + apply_once PtyWrite effect=enqueue. unit = writer drain order + bounded-queue drop/heal decision (injected channel, no wall-clock) + exactly-once enqueue (reserve->enqueue->Applied, dedup). int = REPRO-FIRST (W1b's int ESCAPED this — it relied on ConPTY absorbing the write, never an indefinite block): a real PTY consumer that STOPS draining so write_input genuinely parks (RED pre-fix: a concurrent rc attach gets brain IPC read deadline / no output) + GREEN post-fix (concurrent attach OPENS + RECEIVES output, operator keystroke still accepted, DROP+INPUT_BACKPRESSURE on saturation + heal on resume). Real broker+PTY+rc, NO mocks. cfg(unix) forkpty park leg covered. doc = KNOWN-HAZARDS 7.17 + CONTEXT note (PTY input single-writer; callers enqueue never block; drop+signal on a wedged harness). BLOCKS v0.13.0 ship."
1305
1306	# ── P2 (operator HITL 2026-06-19, post-deploy dogfood; doyle /diagnose + design): resume brings up a BLANK session ──
1307	[[requirements]]
1308	id = "REQ-SESSION-RESUME-TEMPLATE"
1309	title = "Resuming an endpoint session that HAS conversation history brings up a BLANK session. ROOT (doyle, code-grounded + CONTEXT — case-3 spt-core MISSING feature, NOT a perri docs-miss): CONTEXT L127-129 already defines the resume-session seam ('continue-existing: resume an existing harness session under the adapter — its NATIVE resume'), and the manifest already has the resume-variant pattern (Session has BOTH psyche_init AND psyche_resume, manifest.rs:217-219) — but the agent's own session has ONLY self_ (`[session.self]`, no resume sibling). cmd_endpoint_run (cli.rs:1304) re-passes the session_id through `[session.self]` on resume (resume.unwrap_or_else(mint_session_id)), so the adapter's FRESH command (e.g. `claude --session-id ..`) runs again instead of the harness NATIVE resume (`claude -r ..`) -> CC starts a fresh transcript -> blank. spt-core forwards session_id + cwd faithfully; it just has no way to express the native-resume invocation. SECOND GAP: CC resolves a transcript by session_id + cwd, but the session ledger records only {ts, session_id, trigger} (no cwd), so picker Resume-from-history (cross-project rows) can't restore the right cwd. FIX (doyle design, V0.13.0-P2-SESSION-RESUME-DESIGN.md, mirrors psyche_init->psyche_resume exactly): (A) add a `[session.resume]` role (resume: Option<SessionRole> on Session + roles()/is_empty()); cmd_endpoint_run selects it when --resume is set AND it's declared (fill {id}/{session_id}=resumed id/{session_name} + the resume cwd), else FALL BACK to `[session.self]` (full back-compat). (B) record cwd PER ledger row (operator ruling): {ts, session_id, trigger, cwd} additive serde-default; resume cwd = resumed row cwd -> else perch info.cwd -> else current_dir (back-compat for old rows + single-project endpoints); picker threads the selected row's cwd through Outcome::Run -> cmd_endpoint_run. (C) public docs (MANIFEST + harness-contract) teach `[session.resume]` so perri builds the adapter side BLIND. Adapter follow-on (perri, AFTER spt-core ships+docs): declare `[session.resume] command = claude -r {session_id} --remote-control {id} --dangerously-skip-permissions` from the resume cwd. Completes REQ-READY-AGENT-RESUME / REQ-RUN-PICKER resume-from-history. (v0.13.0)"
1310	required_stages = ["doc", "impl", "unit", "int"]  # ACTIVATED v0.13.0 P2 (todlando, resume-blank ship-blocker; doyle gates). doc = MANIFEST + harness-contract `[session.resume]` note (perri-buildable). impl = Session.resume role + roles()/is_empty() + cmd_endpoint_run resume-vs-self selection (+self fallback) + per-row ledger cwd (additive serde-default) + cwd fallback chain (row->info.cwd->current_dir) + picker threads row cwd. unit = manifest parse/roundtrip of `[session.resume]` + resume-vs-self selection + cwd fallback chain + ledger row back-compat (old row no cwd deserializes). int = dummy-harness whose resume command differs from self: --resume runs the RESUME command in the recorded cwd (marker assert), fresh runs self. clippy/traceable/docs-drift. doyle gates; then operator HITL (picker Resume reloads transcript in its project dir)."
1311
1312	# ── P1 (operator HITL 2026-06-19, post-deploy dogfood; doyle /diagnose + design): rc paste broken (ctrl+V + right-click) ──
1313	[[requirements]]
1314	id = "REQ-RC-WIN-PASTE"
1315	title = "In an `spt rc` session neither ctrl+V nor right-click pastes (CC explicitly supports ctrl+V). ROOT (doyle /diagnose): RawGuard does only enable_raw_mode (no bracketed paste / no mouse capture / no clipboard interception); the Windows console delivers a paste as synthetic per-char KEY EVENTs (no crossterm Event::Paste), and ctrl+V translates to bare ^V forwarded to CC — but CC runs DAEMON-SIDE with NO access to the operator's LOCAL clipboard, so remote paste is fundamentally CLIENT-ORIGINATED. A multi-line paste-as-keys also becomes a \\r submit-storm. FIX (doyle design, V0.13.0-P1-RC-PASTE-DESIGN.md, cfg(windows), folds into the bug-2 event path): on a paste gesture rc reads the LOCAL clipboard + forwards a BRACKETED PASTE (ESC[200~ + content + ESC[201~); CC has bracketed-paste mode on (its TUI sets ESC[?2004h) so it treats it as a paste — content intact, no submit-storm, harness-AGNOSTIC. ctrl+V: intercept Char('v')+CONTROL in the event loop -> read_clipboard -> bracketed paste. Right-click: RawGuard also EnableMouseCapture (disables console QuickEdit + enables ENABLE_MOUSE_INPUT so right-click surfaces as Event::Mouse on legacy cmd/powershell) -> right-button -> read_clipboard -> bracketed paste; DROP all other mouse (CC has no mouse features, operator-confirmed, so capture costs nothing). read_clipboard = clipboard-win crate (cfg(windows), minimal); empty/failed = clean no-op. Content forwarded VERBATIM (literal pasted text, no per-char translation). Unix UNCHANGED (its terminal pastes natively through the byte pump). DEPENDS ON P0 (a paste chunk must not wedge the broker). (v0.13.0)"
1316	required_stages = ["doc", "impl", "unit"]  # ACTIVATED v0.13.0 P1 (todlando, last build item; doyle gates). DEPENDS ON P0 (@0675b37, landed) — a paste chunk rides P0's single-writer thread without wedging. NO int (live clipboard + console mouse = HITL, REQ-RUN-PICKER/RC-1 precedent; pure pieces unit-tested). impl = (cfg(windows), folds into the bug-2 event path) `wrap_bracketed_paste` (ESC[200~ + content + ESC[201~) + ctrl+V intercept (Char('v')+CONTROL, when not detach-armed) + right-mouse paste in `spawn_stdin_reader_events` (other mouse dropped) + `read_clipboard` (clipboard-win, empty/fail = clean no-op) + RawGuard EnableMouseCapture(+DisableMouseCapture on drop). unit = bracketed framing exact + content verbatim (no translation) + ctrl+V detect + mouse classify (right-down ⇒ paste, all else ⇒ drop) + injected-reader paste builder (Some ⇒ wrapped, empty/None ⇒ no-op). doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated: ctrl+V/right-click read the LOCAL clipboard + inject a bracketed paste; Unix rides the terminal byte stream). PLANNED: impl = RawGuard EnableMouseCapture (+restore on drop) + ctrl+V/right-mouse clipboard intercept in the bug-2 event path + bracketed-paste wrap + clipboard-win read. unit = bracketed-paste framing (exact ESC[200~/ESC[201~) + gesture->paste decision (ctrl+V/right-mouse=>clipboard-wrap, other mouse=>drop, injected reader) + content verbatim. doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated; Unix rides the terminal byte stream). DEPENDS ON P0. doyle gates; operator HITL (ctrl+V AND right-click paste, multi-line, no submit-storm, no wedge). AMENDED v0.13.0 P1b (HITL re-open 2026-06-19, doyle): SCOPE narrowed to RIGHT-CLICK-ONLY for the client-originated bracketed paste — the ctrl+V interception is REMOVED (dead + redundant): Windows Terminal CONSUMES ctrl+V as its own paste accelerator (delivers only a Key kind=RELEASE, never a Press, so the Press-guarded is_ctrl_v arm could NEVER fire) AND injects the clipboard as a char-by-char KEY FLOOD spt-core cannot intercept. ctrl+V now rides WT's native paste as keystrokes (multi-line may submit-storm — acceptable per operator; bracketed fidelity = right-click). The is_ctrl_v fn + its unit are removed; impl/unit evidence relocates to the surviving right-click path (wrap_bracketed_paste/mouse_is_paste/clipboard_paste/read_clipboard/RawGuard mouse-capture + their units). The flood-no-wedge guarantee moves to REQ-HAZARD-INPUT-ACK-BACKPRESSURE (the deadlock that the ctrl+V flood exposed). Right-click bracketed paste + mouse-capture UNCHANGED."
1317
1318	# ── P1b (operator HITL re-open 2026-06-19, doyle /diagnose on the enhanced rc+broker capture): an input FLOOD deadlocks the broker via the applied-ack ──
1319	[[requirements]]
1320	id = "REQ-HAZARD-INPUT-ACK-BACKPRESSURE"
1321	title = "A FLOOD of operator input on one brain↔broker connection deadlocks the broker PERMANENTLY (entire broker — no new/existing attach; the controller stays latched because the per-conn handler can't process the detach). ROOT (doyle /diagnose, code-grounded + HITL capture, the v0.13.0 P1 ctrl+V re-open): `serve_attach` processes a whole `NetStreamData` batch of N operator `Input` records in its inner `for rec in decoder.push()` loop, calling `brain.send_effect(op_id, &bytes)` N times WITHOUT returning to `read_event()` — so the brain writes N `KIND_INPUT` frames back-to-back and drains nothing. The broker's single-threaded per-conn handler answers EACH with `send_frame(applied_envelope)` on the SAME conn (B5 exactly-once ack, KNOWN-HAZARDS 7.2). With the brain not reading, the broker→brain return direction fills (~10 frames = the IPC pipe buffer) → `send_frame` BLOCKS → the handler stops reading → the brain's writes block too → mutual full-duplex DEADLOCK. Capture pinned it: 11 input frames, write_input 11/11 (P0 holds — the PTY write is fine), ack send START=11 / END=10 (frame #11's applied-ack never returns). Same class as the v0.12.1 L0 two-conn split. Windows Terminal's ctrl+V paste accelerator was the trigger (injects the clipboard as a char-by-char key flood) but the deadlock is generic to ANY input flood, NOT ctrl+V-specific and NOT a P0 (PTY-write) or W1 (output-drain) regression. The applied-ack is load-bearing ONLY for `shellchan` (one-at-a-time spool delivery WAITS on `BrokerEvent::Applied`); `serve_attach` DISCARDS it (the operator/rc path is fire-and-forward, op_id for dedup only, never gates on the ack). FIX (doyle-approved): CONDITIONAL ACK — `InputReq` gains `ack: bool` (serde default = true, N-1-safe: an older brain's input still acks = today's behavior). `serve_attach`'s operator path calls `send_effect_no_ack` (ack=false) → `dispatch_input` writes NO applied frame → the per-conn handler never writes back while servicing the flood → it always drains → no deadlock (cures ANY input flood). `shellchan` keeps `send_effect` (ack=true) and its `Applied`-wait. Exactly-once PRESERVED: the broker still dedups by (session, op_id) at the applied-set regardless of the ack. N-1 caveat: an OLD resident broker (self-update window) ignores `ack=false` → still acks → the deadlock persists until a broker restart (inherent KNOWN-HAZARDS 7.9 broker-resident-wire-change class). (v0.13.0)"
1322	required_stages = ["doc", "impl", "unit", "int"]  # ACTIVATED v0.13.0 P1b (todlando, the real ship-blocker behind the ctrl+V wedge; doyle gates). doc = KNOWN-HAZARDS 7.19 (the deadlock + the N-1 broker-resident caveat). impl = InputReq.ack (serde default_true) + Brain::send_effect_no_ack (send_effect_inner shared body) + serve_attach operator path -> send_effect_no_ack + dispatch_input gates send_frame(applied) on req.ack. unit = InputReq.ack serde default=true on an absent field (N-1) + dispatch_input emits the applied frame when ack=true and NONE when ack=false + exactly-once on the no-ack path (a replayed/duplicate op_id still dedups at apply_once, no double PTY write). int = REPRO-FIRST (the keystone, the exact gate the wedge needed): a flood of N>pipe-buffer (deterministic, e.g. 64) operator input frames through serve_attach on ONE conn -> PRE-FIX deadlocks (ack-send-START-without-END, handler stops draining; a concurrent attach / the detach cannot be serviced) -> POST-FIX drains all N + the session stays live + a concurrent attach still OPENS. Real broker+brain, NO mocks. doc = KNOWN-HAZARDS entry (the deadlock + the N-1 broker-resident caveat). clippy/traceable/docs-drift."
1323
1324	# ── P1b sibling (operator HITL 2026-06-19): rc scroll broken — capture-stolen wheel must forward to the harness ──
1325	[[requirements]]
1326	id = "REQ-RC-MOUSE-FORWARD"
1327	title = "On Windows, `spt rc` must FORWARD scroll-wheel events to the harness when the harness has mouse reporting on. ROOT (operator HITL): P1's RawGuard EnableMouseCapture (added for right-click paste, REQ-RC-WIN-PASTE) makes Windows Terminal forward ALL mouse — including the scroll wheel — to rc instead of scrolling its own buffer, but the rc mouse handler dropped everything except right-button-down → scroll DIED (and WT's native scrollback is stolen by the capture). Operator ruling: keep mouse capture + right-click bracketed paste AND forward scroll to the harness. FIX (doyle design, cfg(windows), folds into the rc mouse handler): TRACK the harness's mouse-reporting mode by scanning its OUTPUT stream for the DECSET set/reset — ESC[?1000h/1002h/1003h (mouse on) + ESC[?1006h (SGR ext) and their ...l (off) — into a shared MouseMode{enabled,sgr} (pump writes from output, stdin reader reads); the scan survives a sequence SPLIT across output chunks (a bounded carry buffer). The mouse handler: right-button-DOWN -> bracketed clipboard paste (unchanged, REQ-RC-WIN-PASTE); ScrollUp/Down -> translate to an xterm SGR mouse report (ESC[<64;col+1;row+1M up / ESC[<65;..M down, 0-based crossterm -> 1-based xterm) and forward ONLY when enabled && sgr (else DROP — a legacy X10 report the harness may not parse is garbage); Moved/drag/left/middle -> DROP (scroll is the operator's need; click-forward risks garbage, no click-to-position). Unix UNCHANGED (no capture; the terminal scrolls natively). (v0.13.0)"
1328	required_stages = ["doc", "impl", "unit"]  # ACTIVATED v0.13.0 P1b (todlando, operator-ruled scroll fix bundled with the ack-deadlock; doyle gates). NO int (live console mouse = HITL, REQ-RUN-PICKER/RC-1 precedent; the pure translator + scan unit-tested). impl = (cfg(windows)) MouseMode{enabled,sgr atomics} + MouseModeScanner (carry-buffer DECSET scan, parse_decset_private/apply_mouse_mode) fed from the pump's output-render path + scroll_dir + scroll_sgr (SGR encode) + the spawn_stdin_reader_events scroll arm (forward when enabled&&sgr) threaded via Arc<MouseMode> (pump writes / reader reads). unit = scroll_dir classify (wheel-only) + scroll_sgr EXACT xterm bytes (up=64/down=65, col+1/row+1) + DECSET scan (1000/1002/1003/1006 set&reset, combined ESC[?1002;1006h, mixed stream, and SPLIT across two feeds — the carry-buffer edge). doc = KNOWN-HAZARDS entry (rc Windows scroll forwarded as SGR when the harness reports mouse; capture is for right-click paste). doyle gates; operator HITL (scroll works in a real CC rc session; right-click still pastes)."
1329
1330	# ── P1c (operator-ruled root-fix before v0.13.0 ship; doyle root-cause via instrumented repro on kitsubito, 2026-06-20): controller-writer reorder on brain-restart re-serve ──
1331	[[requirements]]

id = "REQ-MSG-IDLE-TRANSLATION-BINARY"
title = "spt-hosted idle message delivery via an adapter TRANSLATION BINARY (ADR-0022). New opt-in manifest section `[message-idle-translation-binary]` = a TABLE carrying a `path` scalar (doyle OPT-B ruling: modeled as a table, not a bare top-level scalar, so a preceding section cannot silently absorb it + N+1 extensible; spt-core does NOT deny_unknown_fields, so a future key degrades gracefully); spt-core LIFECYCLE-manages it (spawn when the endpoint comes up, terminate when it goes down). The binary is a PURE stdin→stdout filter; spt-core owns EVERY PTY write. stdin (JSON-lines): `{type:\"init\",endpoint_id,node}` first · `{type:\"event\",envelope:\"<EVENT…>\"}` per inbound message (ADR-0020 envelope) · `{type:\"input\"}` content-free ping on each operator keystroke (binary tracks user-idle for its own idle-gated buffering; PTY input content NOT duplicated). stdout (JSON-lines): keystroke-commands `{key:…}`/`{delay_ms:…}`/`{text:…}` (extensible). spt-core applies the emitted sequence to the broker PTY ATOMICALLY (the W1 coordination — REQ-HAZARD-INJECT-CONTROL-COEXIST). The daemon poll feed is the ONE idle substrate for both topologies (Q1=A): harness-hosted consumer = the Monitor child, spt-hosted consumer = this binary; spt-core PREFERS a perch's poll listener if one exists (so spt-hosted can run a listener AND keep `spt rc`). Idle-only; busy/mid-turn = adapter hook-injection. Closes the current grounding gap: `api bind` registers no listener port → a listener-less spt-hosted perch SPOOLS inbound (only spooling+adapter-poll works today) → this delivers real inbound into the PTY. The v0.11.0 raw inject is the degenerate `{text:payload}{key:enter}` case. (v0.13.0)"
required_stages = ["doc", "impl", "unit", "int"]  # ACTIVATED v0.13.0 W2 (todlando) — all layers landed. Layers A(@c73eb6a)+B(@611205c)+D(@7166f52)+C-kernel(@79f5a40)+C-proper/E(@f9fe5bb)+G(int). impl = translation.rs (ToBinary/KeyCmd/key_to_bytes/TranslationChild/InjectFloor) + broker.rs (Translation/run_inject_worker/flush_inject_floor/fault_translation + dispatch_spawn bring-up + dispatch_input floor-buffer&ping + dispatch_endpoint_input event routing); unit = translation.rs (wire round-trips, content-free ping, KeyCmd shapes, InjectFloor buffer/drain, send-keys map). int = inject_control_wedge.rs g1-g4 via the REAL src/bin/xlate_choreo_fixture.rs translation binary: g1 choreography happy-path (delivered + session serviceable + fresh attach served, all platforms; byte-receipt of XLATE_OK hard on cfg(unix)/gravity-linux, captured on cfg(windows) ConPTY per the file's banked park-(b) divergence stance) · g2 no-commit FAULT (SPT_INJECT_COMMIT_DEADLINE_MS=400: buffered operator keys flush after deadline = no wedge + raw-inject fallback, hard both platforms) · g3 content-free ping (secret reaches PTY but NEVER the binary stdin-log + saw_input_ping non-vacuous, hard both) · g4 no-zombie reap on session-down (hard both). Layer F (Unix park b/c bound) = gravity-linux follow-up, shared w/ W1b F3, tracked by the cfg(unix) park-(b) assert. — staged: doc DONE (manifest section row in MANIFEST.md inject-seam family + CONTEXT.md:39 delivery section reworded W0 + ADR-0022 wording table-not-scalar + manifest.schema.json regen); impl/unit/int bump in as each layer lands (Layer A manifest section + validate + schema = impl/unit foundation; B translation.rs protocol+driver; C atomic PTY apply w/ controller buffering; D lifecycle via SpawnReq.translation_binary; E routing; F Unix park (b)/(c) bound; G int gate). int gate: a real dummy translation-binary delivers an inbound message into an rc-attached spt-hosted session via the choreography WITHOUT breaking control (composes with W1's invariant). ALSO OWNS the Unix-only park (b)/(c) PTY-write bound deferred from W1 (doyle ruling 2026-06-19): the 'spt-core owns every PTY write, applied atomically' substrate is where the forkpty input-queue park gets bounded — bounded/fail-fast write_input + DSR-answer must not hold the writer mutex across a blocking write; gate on gravity-linux (the cfg(unix) park-(b) assert in inject_control_wedge.rs proves the park is real + keeps it tracked).

# ── translate-proof: the author-time EMIT-half proof for the translation binary ──
[[requirements]]
id = "REQ-ADAPTER-TRANSLATE-PROOF"
title = "`spt adapter translate-proof <adapter> --event <envelope> [--session <id>]` — the author-time EMIT-half proof tool for `[message-idle-translation-binary]` (ADR-0022), symmetric to `spt adapter digest-proof` (REQ-TERM-5). It spawns and feeds the adapter's declared translation binary EXACTLY as the daemon does at idle-delivery — running the REAL `spt_daemon::translation` driver VERBATIM (no protocol reimplementation): `TranslationChild::spawn` the binary, send the `{type:\"init\",endpoint_id,node}` line then the `{type:\"event\",envelope}` line, and read back the emitted `{key}`/`{text}`/`{delay_ms}`/`{commit}` keystroke-command stream — then prints it author-readable (each Key with its `key_to_bytes` rendering, Text quoted, Delay in ms, Commit marker) with counts. It fills the SAME `{id}`→option and `{session_id}`→(--session, else a placeholder) keys into the `--event` envelope the daemon fills at runtime, so an envelope that proofs here feeds faithfully live. EMIT-half ONLY: it proves the binary's spawn+feed+emit contract; it does NOT exercise the daemon's atomic PTY apply / controller-buffering (that stays covered by the W2 inject_control_wedge int gate) — `--help` says so. Exit codes mirror digest-proof: 0 ok, 1 on spawn-fail / zero commands / no-commit-or-output / unparseable, 2 when the adapter declares no `[message-idle-translation-binary]` section. The `TranslationChild` Drop does the bounded no-zombie reap. (v0.13.x)"
required_stages = ["doc", "impl", "unit", "int"]

# ── W3: a fresh spt-hosted endpoint shows under its own project (info.cwd on bind) ──
[[requirements]]
id = "REQ-HAZARD-BIND-CWD-UNSET"
title = "A bound endpoint's `info.cwd` is SET at bind so a freshly-created perch appears under its own project tab. ROOT (found, v0.13.0): `info.cwd` is NEVER set on bind — `cmd_bind` (spt-hosted) and `bind_from_seed` (harness-hosted) never thread cwd into `establish_perch`/`rec.cwd`. FIX: `cmd_bind` reads its own `current_dir` (the broker spawned it in `project_cwd`); `bind_from_seed` passes `seed.cwd` (already captured at seed time, currently DISCARDED). DISTINCT from REQ-PICKER-HISTORY-FRESH (v0.12.1) — that unioned cwd-origin into picker MEMBERSHIP but tested merge_origin_project with a PROVIDED origin; it never asserted `info.cwd` is actually set on bind, so a real `endpoint run` perch still had an empty cwd and the union had nothing to union. This is the v0.12.1 P1 'appears under its own project right away' claim that was REFUTED in the changelog — delivered for real here. (v0.13.0)"
required_stages = ["impl", "unit", "int"]  # ACTIVATED v0.13.0 W3 (todlando). ROOT (doyle RCA): info.cwd NEVER set on bind. FIX: establish_perch gained a trailing cwd param + sets rec.cwd = cwd.or_else(prior.cwd) (revive carries forward); cmd_bind (spt-hosted) passes its own current_dir (broker spawned it in project_cwd); bind_from_seed (harness-hosted) passes seed.cwd (captured at api seed, previously DISCARDED). impl = api/startup.rs establish_perch/cmd_bind/bind_from_seed. unit = startup.rs bind_records_cwd_so_picker_can_group_by_project + revive_without_cwd_preserves_prior_cwd + seed_bind_records_seed_captured_cwd (mutation-verified — each guards a distinct branch). int = bind_cwd_project_e2e.rs (REAL detached daemon + endpoint run: live perch read_info().cwd is Some+non-empty AND appears under its project via project_id_for_dir membership). Delivers the v0.12.1 P1 that was REFUTED in the changelog. NOTE: cmd_bind stores raw current_dir (native slashes) while cmd_seed forward-slash-normalizes — slash-agnostic through project_id_for_dir, follow-up to unify.

# ── W4: `spt endpoint run` picker UX ──
[[requirements]]
id = "REQ-PICKER-UX-V013"
title = "`spt endpoint run` picker UX (v0.13.0 operator dogfooding): (1) SKIP the first screen — open directly on 'Pick existing'; `n` jumps to 'Create new'. (2) AUTO-ATTACH after both Start-new AND Resume-from-history (both currently don't attach and show no stdout); add an `h` shortcut to run headless (no attach). (3) 'controlled by' shows the node NAME (node_label_display), not the raw hex. (4) Clean up Start-new output — drop the Rust `pid=Some(142748)` leak and the 'harness binds its perch on startup' internals; user-friendly, not a process log. (v0.13.0)"
required_stages = []  # RETIRED/REDIRECTED (doyle W4 gate 2026-06-19): the W4 picker UX scope was DELIVERED as refinements of the EXISTING picker reqs, NOT under this id — fixes (1) open-on-Pick-existing, (2) attach-by-default + `h` headless, (4) terse bringup line landed under REQ-RUN-PICKER (v0.13.0-W4 extension); fix (3) controlled-by node-name landed under REQ-PICKER-4 (v0.13.0-W4 extension, the D item). This seed carries no evidence by design (folded, not duplicated) — kept as a redirect breadcrumb, not an open requirement. Live keypress path = operator HITL.

# ── W5: driven_by self-heal (ONLINE+CONTROLLED clears without the detach IPC) ──
[[requirements]]
id = "REQ-HAZARD-DRIVEN-BY-SELFHEAL"
title = "An spt-hosted endpoint's ONLINE+CONTROLLED state (`driven_by`) must CLEAR even when the detach IPC is lost — do NOT rely on the detach signal (same lesson as REQ-HAZARD-HOSTED-LIVENESS-RECONCILE B2): the reconcile loop clears `driven_by` when the endpoint has no live controller/session. Today a wedged or lost pump never delivers the detach, so the endpoint stays latched CONTROLLED forever. Composes with W1 (the wedge no longer blocks the detach) and rides the same pull-primary reconcile substrate as B2. (v0.13.0)"
required_stages = ["impl", "unit", "int"]  # ACTIVATED v0.13.0 W5 (repro-first, todlando). SCOPE landed = Gap B (no-session self-heal): reconcile_hosted_liveness, when it offlines a sessionless controllable perch (B2 keystone), ALSO clears a stale driven_by (set_driven_by None) — an OFFLINE endpoint can no longer render phantom CONTROLLED. RACE-FREE (no live session ⇒ no controller to re-stamp concurrently). impl = livehost.rs reconcile_hosted_liveness Gap-B clear + the additive SessionInfo.controller_by observability field (broker.rs KIND_SESSIONS). unit = pull_liveness unit extended (offlined sessionless perch clears driven_by; live/relay perches untouched). int = real-broker gates: driven_by_selfheal.rs gap_b (GREEN: reconcile offlines AND clears driven_by) + the A1 gate + the A2 conn-close test (inject_control_wedge.rs w5_a1 / w5_a2_abandoned_remote_controller_conn_close_eviction_is_platform_divergent). EMPIRICAL VERDICT (updated 2026-06-21, doyle forkpty re-run + todlando Windows run REFRAMED A2): controller_by==None is NOT a usable Gap-A signal for the brain reconcile — it is AMBIGUOUS (dispatch_spawn pre-attaches the spawner with by=None, so a LIVE LOCAL session also reads None). The abandoned-REMOTE-controller clear is CONN-CLOSE detection (dropping the operator brain sends a loopback QUIC FIN → broker clear_controller). Whether it lands in the 2s read window is TIMING-VARIABLE on BOTH platforms (matrix run-1 correction to the earlier unix-hard-assert flip): QUIC FIN-detection latency vs the window is NON-DETERMINISTIC — unix loopback USUALLY but NOT ALWAYS clears <2s (a run that didn't = Some(Some) broke the unix Some(None) hard-assert = the QUIC-timing flake class), windows typically needs the ~30s idle-timeout. So w5_a2 is CHARACTERIZATION-ONLY on BOTH platforms (report cby_after_abandon, NEVER hard-assert either polarity). This is CONSISTENT with the REQ's int structure: the HARD int is gap_b (driven_by_selfheal.rs reconcile offlines AND clears driven_by) + w5_a1; w5_a1/w5_a2 are the characterization arm (no coverage loss). W1 bounds the active-OUTPUT wedge; a clean disconnect self-heals via detach_if→clear_controller. The TRUE no-FIN half-open (clears on NEITHER platform at ANY window) needs the BROKER-SIDE D4c presence oracle → DEFERRED to REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT. KNOWN-HAZARDS 7.15.

# ── W5 follow-up: the idle wedged-REMOTE-controller latch (A2, repro-proven, deferred) ──
[[requirements]]
id = "REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT"
title = "An spt-hosted endpoint driven by a REMOTE controller whose remote is gone but whose broker connection stays OPEN (a wedged/lost pump that never delivers the detach) AND whose session is IDLE (no output) stays latched ONLINE+CONTROLLED forever: the W1 drain-evict only fires on OUTPUT (CONTROLLER_WRITE_DEADLINE on a backed-up write), a clean disconnect self-heals via detach_if→clear_controller, but an idle session with a half-open/wedged controller connection produces neither signal. PROVED repro-first on a real broker (v0.13.0 W5, inject_control_wedge.rs w5_a2): controller_by STAYS Some(origin) and driven_by STAYS Some after the remote is abandoned without a clean EOF on an idle session — so the brain reconcile CANNOT detect it from KIND_SESSIONS controller_by (the broker still reports it controlled). FIX DIRECTION (doyle ruling 2026-06-19, broker-side single-writer — the broker owns driven_by/clear_controller): wire the EXISTING D4c NetPresence connection-disconnect event → clear_controller for any session whose controller identity == the dead origin (become_controller already stores Some(origin); presence events already exist — modest wiring, NOT a new probe). The liveness ORACLE is QUIC's own keepalive/idle-timeout: a presence-disconnect IS a real QUIC conn close, already tolerant of transient blips within the keepalive window, so NO heavy partition ADR is needed UNLESS the QUIC timeout proves too slow for the UX (then mint an ADR for a faster controller-heartbeat + its false-evict bound). Composes with W1 (output path) + W5 Gap B (no-session) — this is the third, idle-remote, leg. (v0.13.0 follow-up)"
required_stages = []  # SEED — repro already exists (inject_control_wedge.rs w5_a2 characterization, under REQ-HAZARD-DRIVEN-BY-SELFHEAL int). doyle BOUNDARY RULING (2026-06-19): does NOT fold into W6 — W6 is the idle-DELIVERY consumer (translation binary injection), orthogonal to controller liveness; and a pump→clean-detach can't heal A2 either (A2 is a HALF-OPEN conn — remote gone, NO EOF — so serve_attach's read just blocks, no detach signal to propagate). Needs the broker-side D4c NetPresence oracle above. Schedule as a controller-liveness follow-up post W4/W6 (likely v0.13.x); doyle tracks. Activate [impl,unit,int] when the eviction lands.

# ── W7 (operator /diagnose 2026-06-19): rc Backspace key forwards the wrong byte ──
[[requirements]]
id = "REQ-HAZARD-RC-INPUT-KEY-ENCODING"
title = "An `spt rc` session forwards the Backspace key as the VT DEL byte (0x7f), so the hosted TUI (Claude Code) deletes ONE character — never a whole word. SYMPTOM (operator dogfooding): Backspace in an rc session always behaves like ctrl+Backspace — deletes the entire last word. ROOT (doyle /diagnose, code-grounded, byte PENDING HITL confirm): rc is a RAW VERBATIM byte pump — spawn_stdin_reader (rc.rs:152) reads std::io::stdin() bytes under crossterm raw mode and forwards them unchanged (parse_stdin_chunk only intercepts the ctrl-b detach prefix); there is NO key-event encoding and NO 0x08↔0x7f normalization ANYWHERE in the tree (grep: zero SetConsoleMode / ENABLE_VIRTUAL_TERMINAL_INPUT). On Windows, crossterm enable_raw_mode does NOT set ENABLE_VIRTUAL_TERMINAL_INPUT, so the LEGACY console delivers ^H (0x08, ctrl+h) for Backspace instead of VT DEL (0x7f); Claude Code maps ^H → backward-kill-word → the observed whole-word delete. CONFIRM-FIRST (build the loop): an env-gated hexdump in spawn_stdin_reader (SPT_RC_DEBUG_KEYS) prints the forwarded byte; operator presses Backspace + ctrl+Backspace in a real rc session. FIX CANDIDATES: (a) enable ENABLE_VIRTUAL_TERMINAL_INPUT on the rc stdin console on Windows so the console emits proper VT (Backspace→0x7f, arrows/Home/End as CSI) — cleanest, fixes the whole key map not just Backspace; (b) narrow normalize bare 0x08→0x7f in the rc input path (riskier — a real ctrl+h is also 0x08). Prefer (a) unless it regresses other keys. Add a KNOWN-HAZARDS.md entry on landing. (v0.13.0)"
required_stages = ["impl", "unit"]  # ACTIVATED v0.13.0 W7 (todlando). BYTE CONFIRMED (operator HITL via SPT_RC_DEBUG_KEYS, real Windows Terminal): pre-fix Backspace=0x08 (^H), Ctrl+Backspace=0x7f — legacy console sends ^H, CC maps ^H → backward-kill-word = whole-word delete. REJECTED FIX (dc07c39, REVERTED): enabling ENABLE_VIRTUAL_TERMINAL_INPUT BACKFIRED — on Windows Terminal it yields win32-input-mode (6-field ESC[Vk;Sc;Uc;Kd;Cs;Rc_ records), NOT legacy xterm VT; it broke the ctrl-b detach (0x02 wrapped → parse_stdin_chunk never sees the raw prefix). (Operator note: CC itself DID understand win32-input-mode; the only regression was detach. But the VT flag is harness-SPECIFIC — a non-CC adapter could choke — so it's rejected on agnosticism grounds, not just the detach break.) SHIPPED FIX (doyle ruling, revert+narrow): stay a raw byte pump; normalize_key_byte maps a lone 0x08→0x7f in spawn_stdin_reader's forward path AFTER the detach state machine (0x02 + every other byte untouched). cfg(windows) only (Unix already VT; a real ctrl+h must stay 0x08). Accepted loss: ctrl+h conflated with Backspace; Ctrl+Backspace (0x7f here) stays char-delete (word-delete lost on the secondary key). impl = rc.rs normalize_key_byte (in spawn_stdin_reader). unit = normalize_maps_backspace_but_leaves_detach_and_others. doc = KNOWN-HAZARDS 7.13. SPT_RC_DEBUG_KEYS hexdump (f05566b) gated the root. FOLLOW-UP (not yet a stage): the IDEAL is clean xterm VT (Backspace→0x7f, arrows→ESC[, ctrl-b→0x02 RAW so detach survives) = agnostic + full key fidelity + working detach; narrow-normalize is backspace-ONLY (arrows/Home/End stay legacy). Investigate next session whether Windows Terminal can be driven to legacy xterm VT instead of win32-input-mode; mint a follow-up REQ if pursued. UPGRADE (2026-06-19, doyle-dispatched, operator-requested): one-way map → SWAP. normalize_key_byte now maps 0x08→0x7f AND 0x7f→0x08 (else passthrough; 0x02 detach still consumed upstream). Because CC reads ^H=backward-kill-word and DEL=char-delete, swapping the input bytes recovers Backspace=char-delete AND Ctrl+Backspace=word-delete (native Win11 — the operator rejected losing word-delete). unit assertions extended (0x7f→0x08). [impl,unit] unchanged (extend, not re-mint). SUPERSEDED on Windows by REQ-RC-KEY-VT-TRANSLATE (v0.13.0 bug 2): the byte-swap normalize_key_byte is REMOVED — the agnostic key-event→xterm-VT translator emits 0x7f for Backspace and 0x08 for Ctrl+Backspace NATIVELY, so the swap's intent is folded into translate_key_event. This req's [impl]/[unit] evidence RELOCATES there (translate_key_event's Backspace/Ctrl+Backspace arm + its mapping unit carry the [impl->...]/[unit->REQ-HAZARD-RC-INPUT-KEY-ENCODING] tags) — kept covered, not dropped. Unix is untouched (it was already cfg(windows)-only)."

# ── W1b (operator /diagnose 2026-06-19, post-W1 dogfood escape): the effect-journal PTY-input wedge ──
[[requirements]]
id = "REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE"
title = "The effect journal serializes EVERY PTY effect under one mutex held ACROSS two fsyncs AND the blocking PTY write — so interactive input stutters and ultimately wedges the daemon hard. ROOT (doyle /diagnose, code-grounded + MEASURED on the operator's real Windows box, 2026-06-19): EffectJournal::apply_once (effect.rs:168-188) takes `inner.lock()` and holds it across `write_line(PENDING)` → `effect()` → `write_line(DONE)`, where write_line (effect.rs:235-239) does flush()+sync_all() (a full FlushFileBuffers) — so each effect pays TWO fsyncs under a GLOBAL lock, and the closure `effect()` (the actual PTY write, broker.rs:1257 EffectKind::PtyWrite via attach.rs:197 send_effect) runs while the lock is held. Two operator-visible facets, ONE root: (A) STUTTER/LAG — every keystroke is a PtyWrite effect = 2× sync_all serialized; measured fsync on %LOCALAPPDATA%\\spt-core = median 6.5ms, spikes to 198ms (C: was recently at 100%), so ~13ms+ per keystroke best case, hundreds under contention → 'many but not all keypresses take 100s of ms, choppy, worsens with volume'. (B) HARD PERMANENT WEDGE — when a PtyWrite `effect()` blocks (ConPTY input buffer full / harness not draining stdin), the journal lock is held INDEFINITELY → the single-threaded inbound-stream dispatch (dispatch.rs serve_attach, which both applies input effects AND opens attaches) can never progress → EVERY subsequent attach (`spt rc --view`/`--take`) fails with 'attach request: brain IPC read deadline elapsed' (confirmed: two retries deadline identically; broker control-plane KIND queries still answer — different thread). This REFUTES the W2-deferred ruling that park-(b)/(c) is 'Windows-benign because ConPTY absorbs 4MiB' — on the real box the input path wedges regardless. DISTINCT from W1 (REQ-HAZARD-INJECT-CONTROL-COEXIST = the OUTPUT drain, correctly fixed @8b5583e; output uses broker.rs:1106 append, NOT the fsync journal). This is the INPUT/effect-journal path W1 never touched, and it is THE wedge the operator hits with --take/--view. FIX DIRECTION (candidates, repro-first — extend inject_control_wedge.rs to a REAL backed-up-PTY-consumer + a real rc-client attach assertion, the gap W1's gate missed): (1) do NOT hold the journal lock across effect() — reserve the key + fsync PENDING under lock, RELEASE, run effect(), re-acquire to fsync DONE + mark applied (preserve crash-idempotency via the per-key reservation, not a global hold); (2) bound/fail-fast the PtyWrite itself (the W2-deferred park bound — write_input must never block indefinitely, DSR-answer must not hold the writer mutex across a blocking write); (3) drop per-keystroke fsync on the interactive path — PtyWrite effects are EPHEMERAL (a keystroke lost on a broker crash is retyped; PTY state is not reconstructed from keystroke replay), so in-memory applied-set dedup suffices (the broker survives the brain — that IS the dedup anchor), with async/batched fsync or no-fsync for EffectKind::PtyWrite while durable kinds (NetSend/NetDial/Registry/Spool) keep their fsync. Combine (1)+(3) at minimum. Add a KNOWN-HAZARDS.md entry on landing. (v0.13.0)"
required_stages = ["impl", "unit", "int"]  # ACTIVATED v0.13.0 W1b (todlando). FIX (1)+(3): apply_once releases inner.lock() across effect() (reserve key + fsync PENDING for DURABLE under lock -> RELEASE -> effect() OFF lock -> re-acquire -> finalize: fsync DONE for durable + mark applied); EffectKind::PtyWrite is EPHEMERAL (is_durable()=false) = NO journal lines / NO fsync, in-memory dedup only (broker-survives-brain is the anchor); durable kinds keep fsync. impl = effect.rs apply_once + EffectKind::is_durable. unit = apply_once_does_not_hold_the_journal_lock_across_effect (barrier proves a 2nd key completes while one effect blocks) + ptywrite_skips_the_durable_journal_write_while_netsend_persists (file-content proxy: PtyWrite writes no PENDING/DONE line, NetSend does); both cross-platform RED pre-fix, GREEN post-fix; gauntlet + exactly-once preserved (7/7). int = inject_control_wedge.rs a_journaled_input_wedge_does_not_starve_a_concurrent_rc_attach (REAL broker+PTY+rc pump, NO mocks: a concurrent rc attach stays SERVICED + actually RECEIVES PTY bytes while a journaled input effect is in-flight — the assertion W1's gate lacked; GATE on Windows journaled_ops_pumped=117/subscribed=true/got_output=true). FIX (2) the Unix-forkpty park bound (write_input bounded + DSR off writer-mutex) = gravity-linux FOLLOW-UP within W1b (doyle minimum = (1)+(3); the cfg(unix) park-(b) liveness assert + the forkpty-RED note are folded into the int and tracked). KNOWN-HAZARDS 7.14. Refutes the W2-deferred 'ConPTY-benign' ruling (the wedge is the LOCK HOLD, not the PTY write blocking). FIX (2) DELIVERED v0.13.0 P0 under REQ-HAZARD-PTY-INPUT-WRITER-WEDGE: write_input is now bounded/never-blocks-the-dispatch via a per-session single input-writer thread (callers enqueue to a bounded FIFO, DROP+INPUT_BACKPRESSURE on saturation) — the paste-burst park that fix (2) was deferred on.

# ── bugs 3+4 (operator HITL 2026-06-19, doyle /diagnose, ONE root): endpoint-run attaches before online ──
[[requirements]]
id = "REQ-HAZARD-RC-ATTACH-ONLINE-RACE"
title = "`spt endpoint run` in an ATTACH/VIEW terminal action attaches BEFORE the freshly-spawned endpoint is online, so the attach races (or outright loses to) the harness bind. ROOT (doyle /diagnose, code-grounded): cmd_endpoint_run (cli.rs) does launch_harness_brokered_in -> (if start: return) -> run_attach with NO await-online between them. launch_harness_brokered_in returns once the harness PROCESS is spawned, but the broker-PTY bind (info status -> STATUS_ONLINE + the live session) lands ASYNC. Both picker attach paths route here with start=false (RunMode::Attach -> cmd_endpoint_run start=false,view=false): Start-now catches the endpoint mid-bringup -> run_attach attempts + loses the handshake race; Resume-from-history catches it still fully OFFLINE -> run_attach's status-gate (REQ-HAZARD-RC-ATTACH-FAILFAST) short-circuits 'offline - nothing to attach' and NEVER attempts. SAME root, two faces (the W4 attach-by-default surfaced both; an online endpoint is unaffected - the picker returns Outcome::Attach, not Run). FIX: in cmd_endpoint_run, when the terminal action is attach/view (NOT start), AWAIT the endpoint online between launch_harness_brokered_in success and run_attach - poll spt_store::info read_info().status to STATUS_ONLINE with a bounded harness-boot deadline (~25s) at a tight interval; on online -> run_attach; on timeout -> ENDPOINT_RUN_ONLINE_TIMEOUT err (do NOT attach a dead bringup). (v0.13.0)"
required_stages = ["impl", "unit", "int"]  # ACTIVATED v0.13.0 (todlando, doyle-dispatched bugs 3+4). impl = cmd_endpoint_run await_endpoint_online gate (poll_online combinator) before run_attach on the attach/view path, FAIL-LOUD ENDPOINT_RUN_ONLINE_TIMEOUT on timeout (start/headless return above, untouched; only the offline-bringup Run path reaches here — an already-online endpoint routes Outcome::Attach, never double-counting the wait). unit = poll_online_fastpath_timeout_and_later_success (injected store-read + sleep, no wall-clock: online-immediate ⇒ true + ZERO sleeps; never-online ⇒ false timeout, every attempt checked + slept-between-only; later-online ⇒ true, stops at once — doyle gate (a) structural guard). int = dummy_harness_e2e endpoint_run_attach_awaits_online_before_attaching: a fresh `endpoint run` with NO --start (attach-by-default) DELIVERS the live DUMMY_HARNESS_TICK post-bringup (RED pre-fix had a THIRD race face — attach connects but EOF-detaches on an empty ring before any output, saw_tick=false; the heartbeat-delivery gate is robust to all three faces; GREEN post-fix), full daemon-tree reap hygiene. Repro-first.

# ── bug 2 (operator HITL 2026-06-19, doyle /diagnose + design ruling): rc Windows special keys dead → xterm-VT translator ──
[[requirements]]
id = "REQ-RC-KEY-VT-TRANSLATE"
title = "On Windows, `spt rc` translates CONSOLE KEY EVENTS to standard xterm VT so ALL keys reach the hosted harness — arrows/Home/End/PgUp/PgDn/Insert/Delete/F-keys, every modifier combo, Backspace/Ctrl+Backspace — not just the byte-emitting ones. ROOT (operator HITL, doyle /diagnose): `spt rc` reads raw STDIN BYTES (spawn_stdin_reader, std::io::stdin().read); on the Windows LEGACY console (no ENABLE_VIRTUAL_TERMINAL_INPUT) the special keys produce console KEY_EVENTs, NOT stdin bytes, so the byte-pump sees nothing → those keys are DEAD. Enabling ENABLE_VIRTUAL_TERMINAL_INPUT was rejected (W7 dc07c39): on Windows Terminal it yields harness-specific win32-input-mode + broke ctrl-b detach. FIX (agnostic, full fidelity): on Windows, replace the stdin byte-read with a crossterm EVENT source (crossterm 0.28 already a dep; the picker already reads events) and translate each KeyEvent → STANDARD xterm VT bytes via a PURE translate_key_event(KeyEvent)->Vec<u8> (copy a known-correct xterm table verbatim, ADR-0001 spirit), forwarded through the SAME rc pump — the harness receives ordinary xterm VT (harness-AGNOSTIC, no win32-input-mode). Press-only (drop Repeat/Release). Detach stays the ctrl-b+'d' PREFIX, event-sourced (doyle Option B): Ctrl+B arms; armed+plain-'d'⇒Detach; armed+Ctrl+B⇒emit literal 0x02; armed+other⇒0x02 then translate(other). Non-tty stdin (piped/tests) → FALL BACK to the byte-read path (keeps e2e byte-injection working). UNIX UNCHANGED (its raw-mode byte stream already delivers proper VT; cfg-split, zero Unix regression). SUPERSEDES the W7 normalize_key_byte swap on Windows — the translator emits 0x7f for Backspace and 0x08 for Ctrl+Backspace natively (REQ-HAZARD-RC-INPUT-KEY-ENCODING folded in). NO int (a live interactive console can't be driven in CI — HITL, REQ-RUN-PICKER/RC-1 precedent); the exhaustive non-vacuous translate_key_event mapping unit + the event-detach unit ARE the surface. (v0.13.0)"
required_stages = ["doc", "impl", "unit"]  # ACTIVATED v0.13.0 bug 2 (todlando, operator-ruled proper translator, ship-blocker; doyle gates). doc = KNOWN-HAZARDS + CONTEXT note (Windows rc translates console key events to standard xterm VT; Unix passes through). impl = rc.rs translate_key_event (pure xterm-VT table) + spawn_stdin_reader Windows cfg-split (tty→crossterm events / non-tty→byte fallback / Unix byte-pump unchanged) + event-sourced ctrl-b+'d' detach SM (Option B) + normalize_key_byte removed (Backspace/Ctrl+Backspace mapping relocated into translate_key_event). unit = exhaustive translate_key_event mapping (every arrow/Home/End/PgUp/PgDn/Del/Insert/F-key/modifier-combo/Ctrl+char/Backspace/Ctrl+Backspace asserts exact xterm bytes) + the event-detach SM (armed+'d'⇒Detach; armed+Ctrl+B⇒0x02; armed+other⇒0x02+translated). HITL acceptance (operator) = real CC rc session: arrows/Home/End/PgUp/PgDn/Del/F-keys work, Backspace=char, Ctrl+Backspace=word, ctrl-b d detach works, typing smooth (W1b).

# ── P0 (operator HITL 2026-06-19, post-deploy dogfood; doyle /diagnose + design): pasting into rc WEDGES the broker ──
[[requirements]]
id = "REQ-HAZARD-PTY-INPUT-WRITER-WEDGE"
title = "Pasting into an `spt rc` session WEDGES the broker — after a paste the operator can no longer type AND can no longer attach to NEW or EXISTING sessions (`brain IPC read deadline`). ROOT (doyle /diagnose, code-grounded): the operator-keystroke path rc -> net-stream Input -> serve_attach (attach.rs:197 brain.send_effect) -> KIND_INPUT -> broker dispatch loop (broker.rs:1091) -> dispatch_input (broker.rs:1459) -> session.write_input(&bytes) runs SYNCHRONOUSLY on the broker request-handling thread. W1b (REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE) released the journal lock across the effect (fix 1) + made PtyWrite ephemeral/no-fsync (fix 3) but EXPLICITLY DEFERRED fix (2) — bound/fail-fast the PtyWrite itself. A single keystroke never fills the ConPTY input buffer; a PASTE BURST does -> write_input blocks -> the dispatch thread cannot service the next frame (a re-attach subscribe, a become_controller restore-write, an inject-floor flush) -> wedge. Not a bug-2 regression (the byte path funnels to the same write_input; paste just reliably fills the buffer). FIX (doyle design, V0.13.0-P0-PTY-INPUT-WRITER-DESIGN.md, CONTEXT L33 broker-owns-PTY/minimal + L435 SessionSurface + single-writer pattern): one dedicated per-session INPUT-WRITER THREAD = the SOLE caller of the blocking write_input, fed by a BOUNDED FIFO channel; every caller (dispatch_input, serve_attach->send_effect, inject-floor flush) ENQUEUES + returns immediately, never blocks. A blocked/slow harness blocks ONLY its own writer thread, never the broker dispatch. Backpressure (operator ruling): queue full => DROP excess input + stamp the session INPUT_BACKPRESSURE (visible health signal); the daemon NEVER wedges; a merely-slow harness self-heals as the writer drains. Exactly-once preserved (PtyWrite ephemeral: apply_once effect = the non-blocking enqueue => Applied; ack now means accepted+ordered, benign — rc does not gate on landing); order preserved (single FIFO + single writer); inject-floor (W2 Layer C) choreography moves to the lone writer. Completes the W1b-deferred fix (2), cross-platform (cfg(unix) forkpty park folds in). (v0.13.0)"
required_stages = ["impl", "unit", "int"]  # ACTIVATED v0.13.0 P0 (todlando, the active ship-blocker; doyle gates). Completes the W1b-deferred fix (2) of REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE. impl = per-session input-writer thread (sole write_input caller) + bounded FIFO (sync_channel, SPT_INPUT_QUEUE_DEPTH override for the int) + InputWriter::enqueue try_send at dispatch_input (apply_once PtyWrite effect + None branch) / dispatch_endpoint_input / run_inject_worker (KeyCmd) / flush_inject_floor; DROP+stamp INPUT_BACKPRESSURE on a full queue, HEAL (clear stamp) on the next accepted enqueue; spawned in dispatch_spawn, held on HostedSession.input (Arc<InputWriter>), thread exits when the last tx drops on teardown. unit = drain ORDER (single FIFO + single writer) + bounded-queue DROP/HEAL decision (injected channel, no wall-clock) + exactly-once enqueue (reserve->enqueue->Applied, dedup). int = REPRO-FIRST (W1b's int ESCAPED this — it relied on ConPTY absorbing the write, never an indefinite block): a real PTY consumer that STOPS draining so write_input genuinely parks (RED pre-fix: a concurrent rc attach gets brain IPC read deadline / no output) + GREEN post-fix (concurrent attach OPENS + RECEIVES output, operator keystroke still accepted, DROP+INPUT_BACKPRESSURE on saturation + heal on resume). Real broker+PTY+rc, NO mocks. cfg(unix) forkpty park leg covered. doc = KNOWN-HAZARDS 7.17 + CONTEXT note (PTY input single-writer; callers enqueue never block; drop+signal on a wedged harness). PLANNED stages: impl = per-session input-writer thread (sole write_input caller) + bounded FIFO + try-enqueue at dispatch_input/serve_attach send_effect path/inject-floor flush + DROP+INPUT_BACKPRESSURE stamp on saturation + apply_once PtyWrite effect=enqueue. unit = writer drain order + bounded-queue drop/heal decision (injected channel, no wall-clock) + exactly-once enqueue (reserve->enqueue->Applied, dedup). int = REPRO-FIRST (W1b's int ESCAPED this — it relied on ConPTY absorbing the write, never an indefinite block): a real PTY consumer that STOPS draining so write_input genuinely parks (RED pre-fix: a concurrent rc attach gets brain IPC read deadline / no output) + GREEN post-fix (concurrent attach OPENS + RECEIVES output, operator keystroke still accepted, DROP+INPUT_BACKPRESSURE on saturation + heal on resume). Real broker+PTY+rc, NO mocks. cfg(unix) forkpty park leg covered. doc = KNOWN-HAZARDS 7.17 + CONTEXT note (PTY input single-writer; callers enqueue never block; drop+signal on a wedged harness). BLOCKS v0.13.0 ship."

# ── P2 (operator HITL 2026-06-19, post-deploy dogfood; doyle /diagnose + design): resume brings up a BLANK session ──
[[requirements]]
id = "REQ-SESSION-RESUME-TEMPLATE"
title = "Resuming an endpoint session that HAS conversation history brings up a BLANK session. ROOT (doyle, code-grounded + CONTEXT — case-3 spt-core MISSING feature, NOT a perri docs-miss): CONTEXT L127-129 already defines the resume-session seam ('continue-existing: resume an existing harness session under the adapter — its NATIVE resume'), and the manifest already has the resume-variant pattern (Session has BOTH psyche_init AND psyche_resume, manifest.rs:217-219) — but the agent's own session has ONLY self_ (`[session.self]`, no resume sibling). cmd_endpoint_run (cli.rs:1304) re-passes the session_id through `[session.self]` on resume (resume.unwrap_or_else(mint_session_id)), so the adapter's FRESH command (e.g. `claude --session-id ..`) runs again instead of the harness NATIVE resume (`claude -r ..`) -> CC starts a fresh transcript -> blank. spt-core forwards session_id + cwd faithfully; it just has no way to express the native-resume invocation. SECOND GAP: CC resolves a transcript by session_id + cwd, but the session ledger records only {ts, session_id, trigger} (no cwd), so picker Resume-from-history (cross-project rows) can't restore the right cwd. FIX (doyle design, V0.13.0-P2-SESSION-RESUME-DESIGN.md, mirrors psyche_init->psyche_resume exactly): (A) add a `[session.resume]` role (resume: Option<SessionRole> on Session + roles()/is_empty()); cmd_endpoint_run selects it when --resume is set AND it's declared (fill {id}/{session_id}=resumed id/{session_name} + the resume cwd), else FALL BACK to `[session.self]` (full back-compat). (B) record cwd PER ledger row (operator ruling): {ts, session_id, trigger, cwd} additive serde-default; resume cwd = resumed row cwd -> else perch info.cwd -> else current_dir (back-compat for old rows + single-project endpoints); picker threads the selected row's cwd through Outcome::Run -> cmd_endpoint_run. (C) public docs (MANIFEST + harness-contract) teach `[session.resume]` so perri builds the adapter side BLIND. Adapter follow-on (perri, AFTER spt-core ships+docs): declare `[session.resume] command = claude -r {session_id} --remote-control {id} --dangerously-skip-permissions` from the resume cwd. Completes REQ-READY-AGENT-RESUME / REQ-RUN-PICKER resume-from-history. (v0.13.0)"
required_stages = ["doc", "impl", "unit", "int"]  # ACTIVATED v0.13.0 P2 (todlando, resume-blank ship-blocker; doyle gates). doc = MANIFEST + harness-contract `[session.resume]` note (perri-buildable). impl = Session.resume role + roles()/is_empty() + cmd_endpoint_run resume-vs-self selection (+self fallback) + per-row ledger cwd (additive serde-default) + cwd fallback chain (row->info.cwd->current_dir) + picker threads row cwd. unit = manifest parse/roundtrip of `[session.resume]` + resume-vs-self selection + cwd fallback chain + ledger row back-compat (old row no cwd deserializes). int = dummy-harness whose resume command differs from self: --resume runs the RESUME command in the recorded cwd (marker assert), fresh runs self. clippy/traceable/docs-drift. doyle gates; then operator HITL (picker Resume reloads transcript in its project dir)."

# ── P1 (operator HITL 2026-06-19, post-deploy dogfood; doyle /diagnose + design): rc paste broken (ctrl+V + right-click) ──
[[requirements]]
id = "REQ-RC-WIN-PASTE"
title = "In an `spt rc` session neither ctrl+V nor right-click pastes (CC explicitly supports ctrl+V). ROOT (doyle /diagnose): RawGuard does only enable_raw_mode (no bracketed paste / no mouse capture / no clipboard interception); the Windows console delivers a paste as synthetic per-char KEY EVENTs (no crossterm Event::Paste), and ctrl+V translates to bare ^V forwarded to CC — but CC runs DAEMON-SIDE with NO access to the operator's LOCAL clipboard, so remote paste is fundamentally CLIENT-ORIGINATED. A multi-line paste-as-keys also becomes a \\r submit-storm. FIX (doyle design, V0.13.0-P1-RC-PASTE-DESIGN.md, cfg(windows), folds into the bug-2 event path): on a paste gesture rc reads the LOCAL clipboard + forwards a BRACKETED PASTE (ESC[200~ + content + ESC[201~); CC has bracketed-paste mode on (its TUI sets ESC[?2004h) so it treats it as a paste — content intact, no submit-storm, harness-AGNOSTIC. ctrl+V: intercept Char('v')+CONTROL in the event loop -> read_clipboard -> bracketed paste. Right-click: RawGuard also EnableMouseCapture (disables console QuickEdit + enables ENABLE_MOUSE_INPUT so right-click surfaces as Event::Mouse on legacy cmd/powershell) -> right-button -> read_clipboard -> bracketed paste; DROP all other mouse (CC has no mouse features, operator-confirmed, so capture costs nothing). read_clipboard = clipboard-win crate (cfg(windows), minimal); empty/failed = clean no-op. Content forwarded VERBATIM (literal pasted text, no per-char translation). Unix UNCHANGED (its terminal pastes natively through the byte pump). DEPENDS ON P0 (a paste chunk must not wedge the broker). (v0.13.0)"
required_stages = ["doc", "impl", "unit"]  # ACTIVATED v0.13.0 P1 (todlando, last build item; doyle gates). DEPENDS ON P0 (@0675b37, landed) — a paste chunk rides P0's single-writer thread without wedging. NO int (live clipboard + console mouse = HITL, REQ-RUN-PICKER/RC-1 precedent; pure pieces unit-tested). impl = (cfg(windows), folds into the bug-2 event path) `wrap_bracketed_paste` (ESC[200~ + content + ESC[201~) + ctrl+V intercept (Char('v')+CONTROL, when not detach-armed) + right-mouse paste in `spawn_stdin_reader_events` (other mouse dropped) + `read_clipboard` (clipboard-win, empty/fail = clean no-op) + RawGuard EnableMouseCapture(+DisableMouseCapture on drop). unit = bracketed framing exact + content verbatim (no translation) + ctrl+V detect + mouse classify (right-down ⇒ paste, all else ⇒ drop) + injected-reader paste builder (Some ⇒ wrapped, empty/None ⇒ no-op). doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated: ctrl+V/right-click read the LOCAL clipboard + inject a bracketed paste; Unix rides the terminal byte stream). PLANNED: impl = RawGuard EnableMouseCapture (+restore on drop) + ctrl+V/right-mouse clipboard intercept in the bug-2 event path + bracketed-paste wrap + clipboard-win read. unit = bracketed-paste framing (exact ESC[200~/ESC[201~) + gesture->paste decision (ctrl+V/right-mouse=>clipboard-wrap, other mouse=>drop, injected reader) + content verbatim. doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated; Unix rides the terminal byte stream). DEPENDS ON P0. doyle gates; operator HITL (ctrl+V AND right-click paste, multi-line, no submit-storm, no wedge). AMENDED v0.13.0 P1b (HITL re-open 2026-06-19, doyle): SCOPE narrowed to RIGHT-CLICK-ONLY for the client-originated bracketed paste — the ctrl+V interception is REMOVED (dead + redundant): Windows Terminal CONSUMES ctrl+V as its own paste accelerator (delivers only a Key kind=RELEASE, never a Press, so the Press-guarded is_ctrl_v arm could NEVER fire) AND injects the clipboard as a char-by-char KEY FLOOD spt-core cannot intercept. ctrl+V now rides WT's native paste as keystrokes (multi-line may submit-storm — acceptable per operator; bracketed fidelity = right-click). The is_ctrl_v fn + its unit are removed; impl/unit evidence relocates to the surviving right-click path (wrap_bracketed_paste/mouse_is_paste/clipboard_paste/read_clipboard/RawGuard mouse-capture + their units). The flood-no-wedge guarantee moves to REQ-HAZARD-INPUT-ACK-BACKPRESSURE (the deadlock that the ctrl+V flood exposed). Right-click bracketed paste + mouse-capture UNCHANGED."

# ── P1b (operator HITL re-open 2026-06-19, doyle /diagnose on the enhanced rc+broker capture): an input FLOOD deadlocks the broker via the applied-ack ──
[[requirements]]
id = "REQ-HAZARD-INPUT-ACK-BACKPRESSURE"
title = "A FLOOD of operator input on one brain↔broker connection deadlocks the broker PERMANENTLY (entire broker — no new/existing attach; the controller stays latched because the per-conn handler can't process the detach). ROOT (doyle /diagnose, code-grounded + HITL capture, the v0.13.0 P1 ctrl+V re-open): `serve_attach` processes a whole `NetStreamData` batch of N operator `Input` records in its inner `for rec in decoder.push()` loop, calling `brain.send_effect(op_id, &bytes)` N times WITHOUT returning to `read_event()` — so the brain writes N `KIND_INPUT` frames back-to-back and drains nothing. The broker's single-threaded per-conn handler answers EACH with `send_frame(applied_envelope)` on the SAME conn (B5 exactly-once ack, KNOWN-HAZARDS 7.2). With the brain not reading, the broker→brain return direction fills (~10 frames = the IPC pipe buffer) → `send_frame` BLOCKS → the handler stops reading → the brain's writes block too → mutual full-duplex DEADLOCK. Capture pinned it: 11 input frames, write_input 11/11 (P0 holds — the PTY write is fine), ack send START=11 / END=10 (frame #11's applied-ack never returns). Same class as the v0.12.1 L0 two-conn split. Windows Terminal's ctrl+V paste accelerator was the trigger (injects the clipboard as a char-by-char key flood) but the deadlock is generic to ANY input flood, NOT ctrl+V-specific and NOT a P0 (PTY-write) or W1 (output-drain) regression. The applied-ack is load-bearing ONLY for `shellchan` (one-at-a-time spool delivery WAITS on `BrokerEvent::Applied`); `serve_attach` DISCARDS it (the operator/rc path is fire-and-forward, op_id for dedup only, never gates on the ack). FIX (doyle-approved): CONDITIONAL ACK — `InputReq` gains `ack: bool` (serde default = true, N-1-safe: an older brain's input still acks = today's behavior). `serve_attach`'s operator path calls `send_effect_no_ack` (ack=false) → `dispatch_input` writes NO applied frame → the per-conn handler never writes back while servicing the flood → it always drains → no deadlock (cures ANY input flood). `shellchan` keeps `send_effect` (ack=true) and its `Applied`-wait. Exactly-once PRESERVED: the broker still dedups by (session, op_id) at the applied-set regardless of the ack. N-1 caveat: an OLD resident broker (self-update window) ignores `ack=false` → still acks → the deadlock persists until a broker restart (inherent KNOWN-HAZARDS 7.9 broker-resident-wire-change class). (v0.13.0)"
required_stages = ["doc", "impl", "unit", "int"]  # ACTIVATED v0.13.0 P1b (todlando, the real ship-blocker behind the ctrl+V wedge; doyle gates). doc = KNOWN-HAZARDS 7.19 (the deadlock + the N-1 broker-resident caveat). impl = InputReq.ack (serde default_true) + Brain::send_effect_no_ack (send_effect_inner shared body) + serve_attach operator path -> send_effect_no_ack + dispatch_input gates send_frame(applied) on req.ack. unit = InputReq.ack serde default=true on an absent field (N-1) + dispatch_input emits the applied frame when ack=true and NONE when ack=false + exactly-once on the no-ack path (a replayed/duplicate op_id still dedups at apply_once, no double PTY write). int = REPRO-FIRST (the keystone, the exact gate the wedge needed): a flood of N>pipe-buffer (deterministic, e.g. 64) operator input frames through serve_attach on ONE conn -> PRE-FIX deadlocks (ack-send-START-without-END, handler stops draining; a concurrent attach / the detach cannot be serviced) -> POST-FIX drains all N + the session stays live + a concurrent attach still OPENS. Real broker+brain, NO mocks. doc = KNOWN-HAZARDS entry (the deadlock + the N-1 broker-resident caveat). clippy/traceable/docs-drift."

# ── P1b sibling (operator HITL 2026-06-19): rc scroll broken — capture-stolen wheel must forward to the harness ──
[[requirements]]
id = "REQ-RC-MOUSE-FORWARD"
title = "On Windows, `spt rc` must FORWARD scroll-wheel events to the harness when the harness has mouse reporting on. ROOT (operator HITL): P1's RawGuard EnableMouseCapture (added for right-click paste, REQ-RC-WIN-PASTE) makes Windows Terminal forward ALL mouse — including the scroll wheel — to rc instead of scrolling its own buffer, but the rc mouse handler dropped everything except right-button-down → scroll DIED (and WT's native scrollback is stolen by the capture). Operator ruling: keep mouse capture + right-click bracketed paste AND forward scroll to the harness. FIX (doyle design, cfg(windows), folds into the rc mouse handler): TRACK the harness's mouse-reporting mode by scanning its OUTPUT stream for the DECSET set/reset — ESC[?1000h/1002h/1003h (mouse on) + ESC[?1006h (SGR ext) and their ...l (off) — into a shared MouseMode{enabled,sgr} (pump writes from output, stdin reader reads); the scan survives a sequence SPLIT across output chunks (a bounded carry buffer). The mouse handler: right-button-DOWN -> bracketed clipboard paste (unchanged, REQ-RC-WIN-PASTE); ScrollUp/Down -> translate to an xterm SGR mouse report (ESC[<64;col+1;row+1M up / ESC[<65;..M down, 0-based crossterm -> 1-based xterm) and forward ONLY when enabled && sgr (else DROP — a legacy X10 report the harness may not parse is garbage); Moved/drag/left/middle -> DROP (scroll is the operator's need; click-forward risks garbage, no click-to-position). Unix UNCHANGED (no capture; the terminal scrolls natively). (v0.13.0)"
required_stages = ["doc", "impl", "unit"]  # ACTIVATED v0.13.0 P1b (todlando, operator-ruled scroll fix bundled with the ack-deadlock; doyle gates). NO int (live console mouse = HITL, REQ-RUN-PICKER/RC-1 precedent; the pure translator + scan unit-tested). impl = (cfg(windows)) MouseMode{enabled,sgr atomics} + MouseModeScanner (carry-buffer DECSET scan, parse_decset_private/apply_mouse_mode) fed from the pump's output-render path + scroll_dir + scroll_sgr (SGR encode) + the spawn_stdin_reader_events scroll arm (forward when enabled&&sgr) threaded via Arc<MouseMode> (pump writes / reader reads). unit = scroll_dir classify (wheel-only) + scroll_sgr EXACT xterm bytes (up=64/down=65, col+1/row+1) + DECSET scan (1000/1002/1003/1006 set&reset, combined ESC[?1002;1006h, mixed stream, and SPLIT across two feeds — the carry-buffer edge). doc = KNOWN-HAZARDS entry (rc Windows scroll forwarded as SGR when the harness reports mouse; capture is for right-click paste). doyle gates; operator HITL (scroll works in a real CC rc session; right-click still pastes)."

# ── P1c (operator-ruled root-fix before v0.13.0 ship; doyle root-cause via instrumented repro on kitsubito, 2026-06-20): controller-writer reorder on brain-restart re-serve ──
[[requirements]]


I have the full REQ landscape. The change extends **REQ-MSG-IDLE-TRANSLATION-BINARY** (its title literally calls raw-inject "the degenerate case") + touches **REQ-SEND-SPT-HOSTED** — both already tag these exact functions. doyle likely minted a sibling REQ in the missing tail, but the code surfaces are already tagged, so retagging at gate is cheap. Proceeding with the unambiguous CODE spine now.

Edit 1 — the spine (drop `input` resolution, remove raw-inject fallback → spool):