0
--- if >0, tail: ---
--- /tmp/daemonlib.txt tail: ---
test seedmap::tests::request_stop_barrier_holds_until_no_listener has been running for over 60 seconds
test seedmap::tests::stop_op_acks_then_serve_returns has been running for over 60 seconds
test seedmap::tests::take_absent_is_none has been running for over 60 seconds
test serveprobe::tests::is_serving_subnet_honors_membership_and_detach has been running for over 60 seconds
test shellwake::tests::resolve_wake_revives_a_suspended_owner_without_double_launch has been running for over 60 seconds


Lib suite is running (slow `seedmap` barrier tests, unrelated to my change) — not hung. The touched modules are all green. Kick clippy in background (build lock is free now that the suite is executing):


Bash

cargo clippy --workspace --all-targets > /tmp/clippy.txt 2>&1; echo "CLIPPY_EXIT=$?" >> /tmp/clippy.txt; tail -3 /tmp/clippy.txt

Run clippy workspace all-targets


Command running in background with ID: bh0ouophw. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\22d5a4f6-16ae-4552-82b1-0f69cfb556b0\tasks\bh0ouophw.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>bh0ouophw</task-id>
<tool-use-id>toolu_019Tm5XtA3wnbbXFCaCQJ26A</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\22d5a4f6-16ae-4552-82b1-0f69cfb556b0\tasks\bh0ouophw.output</output-file>
<status>completed</status>
<summary>Background command "Run clippy workspace all-targets" completed (exit code 0)</summary>
</task-notification>


Both backgrounded — I'll be notified. While they run, record build state to memory (context-clear safety). Create the v0.14.3 build memory:


Write

---
name: v0143-raw-inject-removal
description: "v0.14.3 raw-inject REMOVAL (ADR-0022 amendment) — idle delivery = translation-binary-ONLY; no-binary/faulted/worker-gone -> message SPOOLS (delivered=false), never raw PTY inject. todlando build, doyle gates."
metadata:
  node_type: memory
  type: project
---

**v0.14.3 = raw-inject REMOVAL** (doyle ASSIGNMENT 2026-06-23, operator-GO'd; the follow-up to F-019/v0.14.2 [[f019-translation-install-dir-resolve]]). Branch `fix/v0143-raw-inject-removal` off **main @365fb0c** (where F-019/v0.14.2 lives). todlando builds, doyle gates, deployah cuts.

**CONTRACT (ADR-0022 amendment @1eaeef1, already shipped in v0.14.2 docs):** spt-hosted idle delivery = translation-binary-ONLY. A binary that is ABSENT (none declared), SPAWN-FAILED, FAULTED, or whose inject-worker channel is gone → inbound message stays SPOOLED (delivered=false → caller `deliver::send` spools), LOUD, NEVER raw PTY-injected. Raw `payload+\r` was a NON-submitting pseudo-delivery on a modern TUI — the silent degrade that MASKED F-019.

**THE SPINE (doyle):** `dispatch_endpoint_input` (broker.rs ~1890) replies `endpoint_injected_envelope(ep, delivered:bool)`; `inject_endpoint` (brain.rs:1189) returns it; `try_broker_inject` (cli.rs:3510) → false → `cmd_send` falls through to `deliver::send` = SPOOL. OLD: no-working-binary raw-injected (`input.enqueue`) + delivered=TRUE. FIX: flip to delivered=FALSE (spool). Working-binary path (Some(tr)+!faulted+event_tx.send ok → choreography, delivered=true) UNCHANGED.

**CHANGES MADE (@WIP, not yet committed):**
- broker.rs dispatch_endpoint_input: removed raw-inject fallback (`input.enqueue`+delivered=true); now loud-log `"<ep>: no working translation binary (absent/faulted/worker-gone) -> SPOOLED, not injected"` + `endpoint_injected_envelope(ep,FALSE)`. Worker-channel-dropped fall-through → spool too. **`input` Arc no longer resolved** from the sessions table (was unused after removal → clippy).
- broker.rs log rewords: build_translation spawn-fail ("no idle PTY delivery; inbound spools"), fault_translation ("idle messages spool until a working binary"). build_translation `None` now MEANS spool.
- Comment sweep raw-inject→spool: broker.rs (10 sites: 154/829/838/849/1030/1182/1686/1884/1925 + dispatch doc), translation.rs (KeyCmd Commit doc + spawn doc), harnesshost.rs (2), msg.rs (SpawnReq.translation_binary doc), manifest.rs (field doc) → **manifest.schema.json re-blessed** (`SPT_BLESS=1 cargo test -p spt-runtime checked_in_schema_is_current`; schemars embeds the doc comment, drift-gated).
- **CHANGE 5 (cli.rs try_broker_inject/cmd_send) = NO-OP** — already correct: returns inject_endpoint bool; delivered=false already falls through to deliver::send spool → honest QUEUED, never "SENT online". Verified, no edit.

**TESTS FLIPPED (behavior change, [[behavior-change-grep-tests-not-comments]]):**
- tests/broker.rs `endpoint_input_test` (wrapper renamed `endpoint_keyed_inject_reaches_hosted_pty` → `endpoint_keyed_inject_without_binary_spools_not_pty`): no-binary hosted → delivered=FALSE + nothing on PTY (was delivered=true + echo). Retagged [int->REQ-SEND-SPT-HOSTED]+[int->REQ-MSG-IDLE-TRANSLATION-BINARY].
- tests/inject_control_wedge.rs **g2** (no-commit fault): post-fault inbound → delivered2=FALSE + marker NOT on PTY (was raw_fallback reaches PTY). Wedge-proof half (delivered/op_flushed/sessions_answered) unchanged.
- tests/inject_control_wedge.rs `injecting_a_large_payload_into_a_stdin_ignoring_child` → **renamed** `large_endpoint_inject_to_a_no_binary_session_spools_promptly_without_wedging`: was a now-VACUOUS park-(b) probe (endpoint-inject no longer reaches write_input). Repurposed: 4 MiB → spools promptly (delivered=false) + broker alive, hard on BOTH platforms. Retagged [int->REQ-HAZARD-INJECT-CONTROL-COEXIST]→[int->REQ-MSG-IDLE-TRANSLATION-BINARY] (COEXIST int still covered by the park-(a) output-stall test @ inject_control_wedge.rs:343). OPERATOR-input park-(b) bound stays in `p0_paste_wedge...`.
- xlate_choreo_fixture.rs comments (raw_fallback refs) reworded; XON/XOFF restore flow UNTOUCHED (still needed for op_flushed echo).

**GATES GREEN (local Win, @WIP):** cargo check daemon+runtime EXIT0; inject_control_wedge 9/9 int (g1 delivered=true happy-path, g2 delivered2=false spool flip, +repurposed spool test); broker endpoint test; daemon --lib translation(4)+read_translation(2)+harnesshost resolve(1); runtime resolve_program 3/3; schema drift; traceable EXIT0 (REQ-MSG-IDLE-TRANSLATION-BINARY +doc+impl+unit+int, REQ-SEND-SPT-HOSTED +impl+unit+int, REQ-HAZARD-INJECT-CONTROL-COEXIST +impl+unit+int all [OK]). RUNNING at last check: full daemon --lib (377, slow seedmap barrier tests) + clippy --workspace --all-targets.

**OPEN / NEXT:** (1) confirm full --lib + clippy green; (2) xtask check (docs drift — reference.md unaffected, no CLI change); (3) commit on branch (co-author trailer `todlando`); (4) **doyle's assignment msg TRUNCATED at part 8/17** (mid-TESTS) — could NOT recover parts 9-17 from disk; doyle NO_PERCH all session. Built against EXISTING REQ-MSG-IDLE-TRANSLATION-BINARY (the contract surface) — if doyle minted a NEW REQ in the tail, retag at gate (cheap). Need doyle's exact TESTS/REQ contract before declaring GATE-ready. (5) deployah cuts v0.14.3 counter 33 after doyle GATE-PASS. GOTCHA: `spt` NO lib target (`--bin spt`); spt-daemon + spt-runtime DO (`--lib`).


<tool_use_error>File has not been read yet. Read it first before writing to it.</tool_use_error>

Error: File has not been read yet. Read it first before writing to it.


Prompt:
<task-notification>
<task-id>bh0ouophw</task-id>
<tool-use-id>toolu_019Tm5XtA3wnbbXFCaCQJ26A</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\22d5a4f6-16ae-4552-82b1-0f69cfb556b0\tasks\bh0ouophw.output</output-file>
<status>completed</status>
<summary>Background command "Run clippy workspace all-targets" completed (exit code 0)</summary>
</task-notification>


Clippy **exit 0**. Confirm + the memory file apparently exists (read-first required):


Bash

tail -4 /tmp/clippy.txt; echo "=== lib suite ==="; tail -3 /tmp/daemonlib.txt 2>/dev/null; echo "=== memory file exists? ==="; ls -la "C:\Users\decid\.ccs\instances\bigscreen\projects\C--Users-decid-Documents-projects-spt-core\memory\v0143-raw-inject-removal.md" 2>&1

Confirm clippy + lib + check memory file


    Checking spt v0.14.2 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Checking xtask v0.14.2 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 12.30s
CLIPPY_EXIT=0
=== lib suite ===
test seedmap::tests::take_absent_is_none has been running for over 60 seconds
test serveprobe::tests::is_serving_subnet_honors_membership_and_detach has been running for over 60 seconds
test shellwake::tests::resolve_wake_revives_a_suspended_owner_without_double_launch has been running for over 60 seconds
=== memory file exists? ===
-rw-r--r-- 1 decid 197609 3132 Jun 23 16:15 C:\Users\decid\.ccs\instances\bigscreen\projects\C--Users-decid-Documents-projects-spt-core\memory\v0143-raw-inject-removal.md


Clippy clean (exit 0, --all-targets). The memory file pre-exists — read before updating:


1	---
2	name: v0143-raw-inject-removal
3	description: "v0.14.3 raw-inject REMOVAL build (ADR-0022 amendment) — assigned todlando 2026-06-23; doyle scope, drive-to-release in progress"
4	metadata:
5	  node_type: memory
6	  type: project
7	  originSessionId: 77a745a3-d838-40e7-9522-5dca2858907d
8	---
9
10	v0.14.3 = raw-inject REMOVAL, the ADR-0022 amendment @1eaeef1 follow-up to F-019 ([[v0142-published]]). Operator GO 2026-06-23. doyle scoped → todlando builds → doyle gates → deployah cuts (bump-in-PR, counter 33). Branch off main @365fb0c (v0.14.2).
11
12	CONTRACT: spt-hosted idle delivery = translation-binary-ONLY. Binary absent/spawn-failed/faulted/worker-gone → message SPOOLED (not lost), NEVER raw PTY inject. Loud. Raw `payload+\r` was a non-submitting pseudo-delivery on modern TUI = the silent degrade that MASKED F-019.
13
14	SCOPE BOUNDED (doyle grounded the seam): the spool machinery ALREADY EXISTS. broker `dispatch_endpoint_input` replies `endpoint_injected_envelope(ep, delivered:bool)` → `inject_endpoint` → `try_broker_inject` (cli.rs:3510) → false = caller SPOOLS ("never lost"). Today no-working-binary RAW-INJECTS (input.enqueue ~broker.rs:1922) + delivered=TRUE. FIX = flip that case to delivered=FALSE (spool). Working-binary path (translation Some + !faulted + event_tx.send ok) UNCHANGED.
15
16	KEY SITES (broker.rs): dispatch_endpoint_input ~1875-1932 (remove input.enqueue raw-inject, reply false); fault_translation ~1172 + build_translation ~1077 (logs: no longer "revert to raw inject" → "spool"); reword degenerate-fallback comments ~154/829/837/848/872/1027/1037/1170/1882-1916; translation.rs spawn doc ~238. cli.rs try_broker_inject: verify honest spooled report (not "Sent online").
17
18	TESTS ([[behavior-change-grep-tests-not-comments]] BINDING): inject_control_wedge.rs g2 (no-commit FAULT asserts raw-inject fallback) — SEPARATE the InjectFloor operator-keystroke flush (STAYS) from the inbound MESSAGE (now spools, not raw-inject). +broker/msg units (delivered=false). +NEW int gate: hosted session, NO binary → spt send → SPOOLED + PTY got NO raw bytes + delivered=false + loud log. Run int on Win box (green-units≠green-int).
19
20	TRACEABLE: update REQ-MSG-IDLE-TRANSLATION-BINARY title (line 1242 — its "v0.11.0 raw inject is the degenerate case" line is SUPERSEDED). MINT new REQ-HAZARD-IDLE-SILENT-NONDELIVERY (silent non-delivery = KNOWN-HAZARD class, rule 4): absent/failed binary never raw-injects a pseudo-delivery reported delivered; spools loud + delivered=false. Activate stages this wave. doyle ratifies id/title at gate.
21
22	OUT OF SCOPE (doyle ruling): backlog-drain-on-binary-up (auto-redeliver already-spooled the instant a live-update binary spawns) — poll substrate + subsequent sends cover re-delivery; broker-backlog auto-drain = separate change w/ ordering/exactly-once hazards. Follow-up note only.
23
24	GATES (todlando, then doyle re-runs on real commit): clippy --workspace; spt-daemon --lib; inject_control_wedge int (real xlate_choreo_fixture) on Win; spt = `--bin spt` NOT --lib (no lib target); traceable EXIT 0. STATUS: assigned, awaiting todlando commit SHA + gate output.
25

---
name: v0143-raw-inject-removal
description: "v0.14.3 raw-inject REMOVAL build (ADR-0022 amendment) — assigned todlando 2026-06-23; doyle scope, drive-to-release in progress"
metadata:
  node_type: memory
  type: project
  originSessionId: 77a745a3-d838-40e7-9522-5dca2858907d
---

v0.14.3 = raw-inject REMOVAL, the ADR-0022 amendment @1eaeef1 follow-up to F-019 ([[v0142-published]]). Operator GO 2026-06-23. doyle scoped → todlando builds → doyle gates → deployah cuts (bump-in-PR, counter 33). Branch off main @365fb0c (v0.14.2).

CONTRACT: spt-hosted idle delivery = translation-binary-ONLY. Binary absent/spawn-failed/faulted/worker-gone → message SPOOLED (not lost), NEVER raw PTY inject. Loud. Raw `payload+\r` was a non-submitting pseudo-delivery on modern TUI = the silent degrade that MASKED F-019.

SCOPE BOUNDED (doyle grounded the seam): the spool machinery ALREADY EXISTS. broker `dispatch_endpoint_input` replies `endpoint_injected_envelope(ep, delivered:bool)` → `inject_endpoint` → `try_broker_inject` (cli.rs:3510) → false = caller SPOOLS ("never lost"). Today no-working-binary RAW-INJECTS (input.enqueue ~broker.rs:1922) + delivered=TRUE. FIX = flip that case to delivered=FALSE (spool). Working-binary path (translation Some + !faulted + event_tx.send ok) UNCHANGED.

KEY SITES (broker.rs): dispatch_endpoint_input ~1875-1932 (remove input.enqueue raw-inject, reply false); fault_translation ~1172 + build_translation ~1077 (logs: no longer "revert to raw inject" → "spool"); reword degenerate-fallback comments ~154/829/837/848/872/1027/1037/1170/1882-1916; translation.rs spawn doc ~238. cli.rs try_broker_inject: verify honest spooled report (not "Sent online").

TESTS ([[behavior-change-grep-tests-not-comments]] BINDING): inject_control_wedge.rs g2 (no-commit FAULT asserts raw-inject fallback) — SEPARATE the InjectFloor operator-keystroke flush (STAYS) from the inbound MESSAGE (now spools, not raw-inject). +broker/msg units (delivered=false). +NEW int gate: hosted session, NO binary → spt send → SPOOLED + PTY got NO raw bytes + delivered=false + loud log. Run int on Win box (green-units≠green-int).

TRACEABLE: update REQ-MSG-IDLE-TRANSLATION-BINARY title (line 1242 — its "v0.11.0 raw inject is the degenerate case" line is SUPERSEDED). MINT new REQ-HAZARD-IDLE-SILENT-NONDELIVERY (silent non-delivery = KNOWN-HAZARD class, rule 4): absent/failed binary never raw-injects a pseudo-delivery reported delivered; spools loud + delivered=false. Activate stages this wave. doyle ratifies id/title at gate.

OUT OF SCOPE (doyle ruling): backlog-drain-on-binary-up (auto-redeliver already-spooled the instant a live-update binary spawns) — poll substrate + subsequent sends cover re-delivery; broker-backlog auto-drain = separate change w/ ordering/exactly-once hazards. Follow-up note only.

GATES (todlando, then doyle re-runs on real commit): clippy --workspace; spt-daemon --lib; inject_control_wedge int (real xlate_choreo_fixture) on Win; spt = `--bin spt` NOT --lib (no lib target); traceable EXIT 0. STATUS: assigned, awaiting todlando commit SHA + gate output.


**Recovered doyle's contract** — a prior session's memory file captured the truncated TESTS/traceability tail. Key items I'd missed:
- **MINT new `REQ-HAZARD-IDLE-SILENT-NONDELIVERY`** (KNOWN-HAZARD class, rule 4 — needs a test): absent/failed binary never raw-injects a pseudo-delivery reported delivered; spools loud + delivered=false. Activate stages this wave; doyle ratifies id/title at gate.
- **Update REQ-MSG-IDLE-TRANSLATION-BINARY title** (its "raw inject is the degenerate case" line is superseded).
- **broker/msg units** (delivered=false) + the dedicated no-binary→spooled int gate.
- OUT OF SCOPE: backlog-drain-on-binary-up (follow-up note only).

I tagged everything to REQ-MSG-IDLE-TRANSLATION-BINARY — must add the new hazard REQ. Let me do the traceability work (no build needed while the lib suite finishes). Update the REQ-MSG-IDLE title first:


Edit

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)"

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. AMENDED v0.14.3 (ADR-0022 amendment, raw-inject removal): idle delivery is translation-binary-ONLY — the v0.11.0 raw `{text:payload}{key:enter}` inject is NO LONGER a delivery path; with no working binary (absent/spawn-failed/faulted/worker-gone) the inbound SPOOLS (delivered=false, poll-fed, LOUD), never a raw PTY pseudo-write (which did not submit on a modern TUI — the silent degrade that masked F-019). See REQ-HAZARD-IDLE-SILENT-NONDELIVERY. (v0.13.0, amended v0.14.3)"


The file C:\Users\decid\Documents\projects\spt-core\traceable-reqs.toml has been updated successfully. (file state is current in your context — no need to Read it back)

# ── W2 KEYSTONE: the translation-binary idle-delivery redesign (ADR-0022) ──

-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)"

+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. AMENDED v0.14.3 (ADR-0022 amendment, raw-inject removal): idle delivery is translation-binary-ONLY — the v0.11.0 raw `{text:payload}{key:enter}` inject is NO LONGER a delivery path; with no working binary (absent/spawn-failed/faulted/worker-gone) the inbound SPOOLS (delivered=false, poll-fed, LOUD), never a raw PTY pseudo-write (which did not submit on a modern TUI — the silent degrade that masked F-019). See REQ-HAZARD-IDLE-SILENT-NONDELIVERY. (v0.13.0, amended v0.14.3)"

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 ──


Mint the new hazard REQ after the REQ-MSG-IDLE block. Find the exact insertion point:


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"

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"


Insert the new hazard REQ before the translate-proof block. Also note line 1244's g2 clause ("raw-inject fallback") is now stale — I'll fix it after:


# ── translate-proof: the author-time EMIT-half proof for the translation binary ──
[[requirements]]
id = "REQ-ADAPTER-TRANSLATE-PROOF"

# ── v0.14.3: raw-inject REMOVED — a no-working-binary idle delivery SPOOLS, never a silent pseudo-write ──
[[requirements]]
id = "REQ-HAZARD-IDLE-SILENT-NONDELIVERY"
title = "spt-hosted idle delivery must NEVER report a message delivered when it was not. ROOT (F-019 post-mortem, ADR-0022 amendment): the v0.11.0 path raw-injected `payload+\\r` into the PTY whenever no working translation binary handled an inbound message (none declared, spawn-failed, FAULTED, or its inject-worker channel gone) AND acked `delivered=true` — but a bare `payload+\\r` does NOT submit on a modern TUI (Claude Code), so the message was TYPED but never sent: a silent pseudo-delivery reported as success. That silent degrade-to-raw-inject is exactly what MASKED F-019 through a multi-hour black-box hunt. FIX (operator-ruled, doyle-scoped): idle delivery is translation-binary-ONLY — `dispatch_endpoint_input` with no working binary replies `endpoint_injected_envelope(ep, delivered=false)` (the caller `try_broker_inject`→`cmd_send` then falls through to `deliver::send` = SPOOL, poll-fed, never lost) and writes NOTHING to the PTY, LOUDLY (eprintln names the absent/faulted/worker-gone cause). A failed binary becomes a VISIBLE no-delivery (spooled + honest QUEUED report), never a confident-but-false 'Sent'. The raw-inject fallback (`input.enqueue`) is REMOVED from the no-binary, worker-dropped, AND post-fault paths. OUT OF SCOPE (doyle ruling, follow-up note only): broker-side auto-redrive of already-spooled inbound the instant a live-update binary spawns (ordering/exactly-once hazards; the poll substrate + subsequent sends cover re-delivery). KNOWN-HAZARDS class (rule 4). (v0.14.3)"
required_stages = ["doc", "impl", "unit", "int"]  # ACTIVATED v0.14.3 (todlando, doyle-scoped raw-inject removal). impl = broker.rs dispatch_endpoint_input (no-working-binary → endpoint_injected_envelope delivered=false + loud log, NO input.enqueue; the `input` writer is no longer resolved from the sessions table) + build_translation/fault_translation (None/fault now MEAN spool, logs reworded). doc = KNOWN-HAZARDS.md (silent non-delivery class) + ADR-0022 Amendment (@1eaeef1, v0.14.2 docs). unit = msg.rs endpoint_injected_envelope carries delivered=false (the spool signal the CLI reads to fall through to deliver::send). int = the no-working-binary delivery gates, all on the REAL broker+PTY: broker.rs endpoint_keyed_inject_without_binary_spools_not_pty (hosted session, NO binary → delivered=false + nothing on PTY) + inject_control_wedge.rs large_endpoint_inject_to_a_no_binary_session_spools_promptly_without_wedging (4 MiB → spools promptly, never reaches write_input, broker alive) + g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input (post-FAULT: subsequent inbound delivered2=false + marker NOT on PTY — operator-keystroke floor-flush UNCHANGED). CHANGE-5 verified NO-OP: try_broker_inject already returns the inject_endpoint bool; delivered=false already rides deliver::send → honest QUEUED, never 'SENT online'. doyle ratifies id/title at gate.

# ── translate-proof: the author-time EMIT-half proof for the translation binary ──
[[requirements]]
id = "REQ-ADAPTER-TRANSLATE-PROOF"


# ── v0.14.3: raw-inject REMOVED — a no-working-binary idle delivery SPOOLS, never a silent pseudo-write ──
[[requirements]]
id = "REQ-HAZARD-IDLE-SILENT-NONDELIVERY"
title = "spt-hosted idle delivery must NEVER report a message delivered when it was not. ROOT (F-019 post-mortem, ADR-0022 amendment): the v0.11.0 path raw-injected `payload+\\r` into the PTY whenever no working translation binary handled an inbound message (none declared, spawn-failed, FAULTED, or its inject-worker channel gone) AND acked `delivered=true` — but a bare `payload+\\r` does NOT submit on a modern TUI (Claude Code), so the message was TYPED but never sent: a silent pseudo-delivery reported as success. That silent degrade-to-raw-inject is exactly what MASKED F-019 through a multi-hour black-box hunt. FIX (operator-ruled, doyle-scoped): idle delivery is translation-binary-ONLY — `dispatch_endpoint_input` with no working binary replies `endpoint_injected_envelope(ep, delivered=false)` (the caller `try_broker_inject`→`cmd_send` then falls through to `deliver::send` = SPOOL, poll-fed, never lost) and writes NOTHING to the PTY, LOUDLY (eprintln names the absent/faulted/worker-gone cause). A failed binary becomes a VISIBLE no-delivery (spooled + honest QUEUED report), never a confident-but-false 'Sent'. The raw-inject fallback (`input.enqueue`) is REMOVED from the no-binary, worker-dropped, AND post-fault paths. OUT OF SCOPE (doyle ruling, follow-up note only): broker-side auto-redrive of already-spooled inbound the instant a live-update binary spawns (ordering/exactly-once hazards; the poll substrate + subsequent sends cover re-delivery). KNOWN-HAZARDS class (rule 4). (v0.14.3)"
required_stages = ["doc", "impl", "unit", "int"]  # ACTIVATED v0.14.3 (todlando, doyle-scoped raw-inject removal). impl = broker.rs dispatch_endpoint_input (no-working-binary → endpoint_injected_envelope delivered=false + loud log, NO input.enqueue; the `input` writer is no longer resolved from the sessions table) + build_translation/fault_translation (None/fault now MEAN spool, logs reworded). doc = KNOWN-HAZARDS.md (silent non-delivery class) + ADR-0022 Amendment (@1eaeef1, v0.14.2 docs). unit = msg.rs endpoint_injected_envelope carries delivered=false (the spool signal the CLI reads to fall through to deliver::send). int = the no-working-binary delivery gates, all on the REAL broker+PTY: broker.rs endpoint_keyed_inject_without_binary_spools_not_pty (hosted session, NO binary → delivered=false + nothing on PTY) + inject_control_wedge.rs large_endpoint_inject_to_a_no_binary_session_spools_promptly_without_wedging (4 MiB → spools promptly, never reaches write_input, broker alive) + g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input (post-FAULT: subsequent inbound delivered2=false + marker NOT on PTY — operator-keystroke floor-flush UNCHANGED). CHANGE-5 verified NO-OP: try_broker_inject already returns the inject_endpoint bool; delivered=false already rides deliver::send → honest QUEUED, never 'SENT online'. doyle ratifies id/title at gate.

# ── translate-proof: the author-time EMIT-half proof for the translation binary ──
[[requirements]]
id = "REQ-ADAPTER-TRANSLATE-PROOF"

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. AMENDED v0.14.3 (ADR-0022 amendment, raw-inject removal): idle delivery is translation-binary-ONLY — the v0.11.0 raw `{text:payload}{key:enter}` inject is NO LONGER a delivery path; with no working binary (absent/spawn-failed/faulted/worker-gone) the inbound SPOOLS (delivered=false, poll-fed, LOUD), never a raw PTY pseudo-write (which did not submit on a modern TUI — the silent degrade that masked F-019). See REQ-HAZARD-IDLE-SILENT-NONDELIVERY. (v0.13.0, amended v0.14.3)"

+# ── v0.14.3: raw-inject REMOVED — a no-working-binary idle delivery SPOOLS, never a silent pseudo-write ──

+title = "spt-hosted idle delivery must NEVER report a message delivered when it was not. ROOT (F-019 post-mortem, ADR-0022 amendment): the v0.11.0 path raw-injected `payload+\\r` into the PTY whenever no working translation binary handled an inbound message (none declared, spawn-failed, FAULTED, or its inject-worker channel gone) AND acked `delivered=true` — but a bare `payload+\\r` does NOT submit on a modern TUI (Claude Code), so the message was TYPED but never sent: a silent pseudo-delivery reported as success. That silent degrade-to-raw-inject is exactly what MASKED F-019 through a multi-hour black-box hunt. FIX (operator-ruled, doyle-scoped): idle delivery is translation-binary-ONLY — `dispatch_endpoint_input` with no working binary replies `endpoint_injected_envelope(ep, delivered=false)` (the caller `try_broker_inject`→`cmd_send` then falls through to `deliver::send` = SPOOL, poll-fed, never lost) and writes NOTHING to the PTY, LOUDLY (eprintln names the absent/faulted/worker-gone cause). A failed binary becomes a VISIBLE no-delivery (spooled + honest QUEUED report), never a confident-but-false 'Sent'. The raw-inject fallback (`input.enqueue`) is REMOVED from the no-binary, worker-dropped, AND post-fault paths. OUT OF SCOPE (doyle ruling, follow-up note only): broker-side auto-redrive of already-spooled inbound the instant a live-update binary spawns (ordering/exactly-once hazards; the poll substrate + subsequent sends cover re-delivery). KNOWN-HAZARDS class (rule 4). (v0.14.3)"

+required_stages = ["doc", "impl", "unit", "int"]  # ACTIVATED v0.14.3 (todlando, doyle-scoped raw-inject removal). impl = broker.rs dispatch_endpoint_input (no-working-binary → endpoint_injected_envelope delivered=false + loud log, NO input.enqueue; the `input` writer is no longer resolved from the sessions table) + build_translation/fault_translation (None/fault now MEAN spool, logs reworded). doc = KNOWN-HAZARDS.md (silent non-delivery class) + ADR-0022 Amendment (@1eaeef1, v0.14.2 docs). unit = msg.rs endpoint_injected_envelope carries delivered=false (the spool signal the CLI reads to fall through to deliver::send). int = the no-working-binary delivery gates, all on the REAL broker+PTY: broker.rs endpoint_keyed_inject_without_binary_spools_not_pty (hosted session, NO binary → delivered=false + nothing on PTY) + inject_control_wedge.rs large_endpoint_inject_to_a_no_binary_session_spools_promptly_without_wedging (4 MiB → spools promptly, never reaches write_input, broker alive) + g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input (post-FAULT: subsequent inbound delivered2=false + marker NOT on PTY — operator-keystroke floor-flush UNCHANGED). CHANGE-5 verified NO-OP: try_broker_inject already returns the inject_endpoint bool; delivered=false already rides deliver::send → honest QUEUED, never 'SENT online'. doyle ratifies id/title at gate.


Fix the stale g2 clause in the REQ-MSG-IDLE stages comment:


g2 no-commit FAULT (SPT_INJECT_COMMIT_DEADLINE_MS=400: buffered operator keys flush after deadline = no wedge + raw-inject fallback, hard both platforms)

g2 no-commit FAULT (SPT_INJECT_COMMIT_DEADLINE_MS=400: buffered operator keys flush after deadline = no wedge, hard both platforms; post-FAULT inbound now SPOOLS — v0.14.3 raw-inject removal, see REQ-HAZARD-IDLE-SILENT-NONDELIVERY)


-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).

+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, hard both platforms; post-FAULT inbound now SPOOLS — v0.14.3 raw-inject removal, see REQ-HAZARD-IDLE-SILENT-NONDELIVERY) · 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).

# ── v0.14.3: raw-inject REMOVED — a no-working-binary idle delivery SPOOLS, never a silent pseudo-write ──


Now wire the evidence for the new REQ. Add the impl tag at the spine spool branch (broker.rs):


                // inbound is never lost (it stays spooled, poll-fed) and the failure
                // is LOUD so a mis-declared/repaired binary is diagnosable.
                // [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]
                eprintln!(
                    "ENDPOINT_INJECT:{}: no working translation binary (absent/faulted/worker-gone) -> SPOOLED, not injected",
                    req.endpoint
                );

                // inbound is never lost (it stays spooled, poll-fed) and the failure
                // is LOUD so a mis-declared/repaired binary is diagnosable.
                // [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]
                // [impl->REQ-HAZARD-IDLE-SILENT-NONDELIVERY]
                eprintln!(
                    "ENDPOINT_INJECT:{}: no working translation binary (absent/faulted/worker-gone) -> SPOOLED, not injected",
                    req.endpoint
                );


The file C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\src\broker.rs has been updated successfully. (file state is current in your context — no need to Read it back)

                // inbound is never lost (it stays spooled, poll-fed) and the failure
                // is LOUD so a mis-declared/repaired binary is diagnosable.
                // [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]
                eprintln!(
                    "ENDPOINT_INJECT:{}: no working translation binary (absent/faulted/worker-gone) -> SPOOLED, not injected",
                    req.endpoint
                );

                // inbound is never lost (it stays spooled, poll-fed) and the failure
                // is LOUD so a mis-declared/repaired binary is diagnosable.
                // [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]
                // [impl->REQ-HAZARD-IDLE-SILENT-NONDELIVERY]
                eprintln!(
                    "ENDPOINT_INJECT:{}: no working translation binary (absent/faulted/worker-gone) -> SPOOLED, not injected",
                    req.endpoint
                );

+                // [impl->REQ-HAZARD-IDLE-SILENT-NONDELIVERY]


Add int tags to the three gates. broker.rs endpoint test:


// [int->REQ-SEND-SPT-HOSTED]
// [int->REQ-MSG-IDLE-TRANSLATION-BINARY]
fn endpoint_input_test() {

// [int->REQ-SEND-SPT-HOSTED]
// [int->REQ-MSG-IDLE-TRANSLATION-BINARY]
// [int->REQ-HAZARD-IDLE-SILENT-NONDELIVERY]
fn endpoint_input_test() {


The file C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\tests\broker.rs has been updated successfully. (file state is current in your context — no need to Read it back)

// [int->REQ-SEND-SPT-HOSTED]
// [int->REQ-MSG-IDLE-TRANSLATION-BINARY]
fn endpoint_input_test() {

// [int->REQ-SEND-SPT-HOSTED]
// [int->REQ-MSG-IDLE-TRANSLATION-BINARY]
// [int->REQ-HAZARD-IDLE-SILENT-NONDELIVERY]
fn endpoint_input_test() {

/// inject_control_wedge.rs int gate, which spawns the xlate_choreo_fixture binary.)

let broker = Broker::bind(&name).expect("bind broker");


g2 + large_endpoint tags (target each by its assert context):


    // [int->REQ-MSG-IDLE-TRANSLATION-BINARY]
    assert!(delivered, "the no-commit event must still be accepted by the broker");

    // [int->REQ-MSG-IDLE-TRANSLATION-BINARY]
    // [int->REQ-HAZARD-IDLE-SILENT-NONDELIVERY]
    assert!(delivered, "the no-commit event must still be accepted by the broker");


The file C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\tests\inject_control_wedge.rs has been updated successfully. (file state is current in your context — no need to Read it back)

    // [int->REQ-MSG-IDLE-TRANSLATION-BINARY]
    assert!(delivered, "the no-commit event must still be accepted by the broker");

    // [int->REQ-MSG-IDLE-TRANSLATION-BINARY]
    // [int->REQ-HAZARD-IDLE-SILENT-NONDELIVERY]
    assert!(delivered, "the no-commit event must still be accepted by the broker");

+    // [int->REQ-HAZARD-IDLE-SILENT-NONDELIVERY]


    // [int->REQ-MSG-IDLE-TRANSLATION-BINARY]
    assert!(broker_alive, "broker process must stay alive");

    // [int->REQ-HAZARD-IDLE-SILENT-NONDELIVERY]
    assert!(broker_alive, "broker process must stay alive");


    // [int->REQ-MSG-IDLE-TRANSLATION-BINARY]
    assert!(broker_alive, "broker process must stay alive");

    // [int->REQ-HAZARD-IDLE-SILENT-NONDELIVERY]
    assert!(broker_alive, "broker process must stay alive");

-    // [int->REQ-MSG-IDLE-TRANSLATION-BINARY]


For the unit + doc stages: add a msg.rs unit asserting `endpoint_injected_envelope(ep, false)` carries delivered=false, and a KNOWN-HAZARDS.md entry. Read the msg.rs builder + its test area:


855	            applied_now,
856	        })
857	        .expect("AppliedEvent serializes"),
858	    )
859	}
860
861	/// Build an `endpoint-injected` reply envelope (REQ-SEND-SPT-HOSTED).
862	pub fn endpoint_injected_envelope(endpoint: &str, delivered: bool) -> Envelope {
863	    Envelope::new(
864	        KIND_ENDPOINT_INJECTED,
865	        serde_json::to_value(EndpointInjected {
866	            endpoint: endpoint.to_string(),
867	            delivered,
868	        })
869	        .expect("EndpointInjected serializes"),
870	    )
871	}
872
873	/// Build a `net-stream-data` event envelope (D4b) — the stream-read sibling of
874	/// [`output_envelope`].
875	pub fn net_stream_data_envelope(stream_id: u64, seq: u64, bytes: &[u8]) -> Envelope {
876	    Envelope::new(
877	        KIND_NET_STREAM_DATA,
878	        serde_json::to_value(NetStreamData {
879	            stream_id,
880	            seq,
881	            data_b64: encode_bytes(bytes),
882	        })
883	        .expect("NetStreamData serializes"),
884	    )
885	}
886
887	/// Build a `net-stream-eof` event envelope (D4b).
888	pub fn net_stream_eof_envelope(stream_id: u64, next_seq: u64) -> Envelope {
889	    Envelope::new(

            applied_now,
        })
        .expect("AppliedEvent serializes"),
    )
}

/// Build an `endpoint-injected` reply envelope (REQ-SEND-SPT-HOSTED).
pub fn endpoint_injected_envelope(endpoint: &str, delivered: bool) -> Envelope {
    Envelope::new(
        KIND_ENDPOINT_INJECTED,
        serde_json::to_value(EndpointInjected {
            endpoint: endpoint.to_string(),
            delivered,
        })
        .expect("EndpointInjected serializes"),
    )
}

/// Build a `net-stream-data` event envelope (D4b) — the stream-read sibling of
/// [`output_envelope`].
pub fn net_stream_data_envelope(stream_id: u64, seq: u64, bytes: &[u8]) -> Envelope {
    Envelope::new(
        KIND_NET_STREAM_DATA,
        serde_json::to_value(NetStreamData {
            stream_id,
            seq,
            data_b64: encode_bytes(bytes),
        })
        .expect("NetStreamData serializes"),
    )
}

/// Build a `net-stream-eof` event envelope (D4b).
pub fn net_stream_eof_envelope(stream_id: u64, next_seq: u64) -> Envelope {
    Envelope::new(


Find the msg.rs test module to add the delivered=false unit:


934:mod tests {
939:    #[test]
949:    #[test]
966:    #[test]
994:    #[test]
1019:    #[test]
1051:    #[test]
1052:    fn evicted_envelope_round_trips() {
1073:    #[test]
1074:    fn output_envelope_round_trips() {
1093:    #[test]
1125:    #[test]


1051	    #[test]
1052	    fn evicted_envelope_round_trips() {
1053	        let env = evicted_envelope(7);
1054	        assert_eq!(
1055	            env.kind, KIND_VIEWER_EVICTED,
1056	            "the evicted marker rides the viewer_evicted kind discriminator"
1057	        );
1058	        let ev: ViewerEvictedEvent = serde_json::from_value(env.payload).unwrap();
1059	        assert_eq!(
1060	            ev.session_id, 7,
1061	            "the evicted marker carries the session id so the brain re-subscribes \
1062	             the RIGHT session (skip-to-live)"
1063	        );
1064
1065	        // A newer broker's extra field is tolerated (the catalog's forward-compat
1066	        // carries to this additive event too — KH-2.3).
1067	        let wire = json!({ "session_id": 9, "reason_added_later": "overflow" });
1068	        let future: ViewerEvictedEvent = serde_json::from_value(wire).unwrap();
1069	        assert_eq!(future.session_id, 9);
1070	    }
1071
1072	    // [unit->REQ-DAEMON-2] the output-envelope helper produces a decodable event.
1073	    #[test]
1074	    fn output_envelope_round_trips() {
1075	        let env = output_envelope(7, 42, b"hello");
1076	        assert_eq!(env.kind, KIND_OUTPUT);
1077	        let ev: OutputEvent = serde_json::from_value(env.payload).unwrap();
1078	        assert_eq!(ev.session_id, 7);
1079	        assert_eq!(ev.seq, 42);
1080	        assert_eq!(decode_bytes(&ev.data_b64).unwrap(), b"hello");
1081	    }
1082
1083	    // [unit->REQ-HAZARD-INPUT-ACK-BACKPRESSURE] An `input` payload from an OLDER
1084	    // brain carries NO `ack` field on the wire (the field was added by this REQ).
1085	    // It MUST decode to `ack = true` so the old-brain path keeps acking exactly as
1086	    // it did before the fix (N-1 forward-compat: an old brain's `input` still gets
1087	    // its `KIND_APPLIED`, never silently downgraded to no-ack). The fix's whole
1088	    // contract rests on this default: `serde(default = "default_true")`.
1089	    //
1090	    // Non-vacuous: if `default_true` were `default` (bool's `false`) or the
1091	    // attribute were dropped, this absent-field decode would yield `ack = false`
1092	    // and this assert flips to RED — an old brain would silently stop being acked.
1093	    #[test]
1094	    fn input_req_absent_ack_defaults_true_n_minus_1_safe() {
1095	        // The exact shape an N-1 brain emits: session + base64 + op_id, NO `ack`.

    #[test]
    fn evicted_envelope_round_trips() {
        let env = evicted_envelope(7);
        assert_eq!(
            env.kind, KIND_VIEWER_EVICTED,
            "the evicted marker rides the viewer_evicted kind discriminator"
        );
        let ev: ViewerEvictedEvent = serde_json::from_value(env.payload).unwrap();
        assert_eq!(
            ev.session_id, 7,
            "the evicted marker carries the session id so the brain re-subscribes \
             the RIGHT session (skip-to-live)"
        );

        // A newer broker's extra field is tolerated (the catalog's forward-compat
        // carries to this additive event too — KH-2.3).
        let wire = json!({ "session_id": 9, "reason_added_later": "overflow" });
        let future: ViewerEvictedEvent = serde_json::from_value(wire).unwrap();
        assert_eq!(future.session_id, 9);
    }

    // [unit->REQ-DAEMON-2] the output-envelope helper produces a decodable event.
    #[test]
    fn output_envelope_round_trips() {
        let env = output_envelope(7, 42, b"hello");
        assert_eq!(env.kind, KIND_OUTPUT);
        let ev: OutputEvent = serde_json::from_value(env.payload).unwrap();
        assert_eq!(ev.session_id, 7);
        assert_eq!(ev.seq, 42);
        assert_eq!(decode_bytes(&ev.data_b64).unwrap(), b"hello");
    }

    // [unit->REQ-HAZARD-INPUT-ACK-BACKPRESSURE] An `input` payload from an OLDER
    // brain carries NO `ack` field on the wire (the field was added by this REQ).
    // It MUST decode to `ack = true` so the old-brain path keeps acking exactly as
    // it did before the fix (N-1 forward-compat: an old brain's `input` still gets
    // its `KIND_APPLIED`, never silently downgraded to no-ack). The fix's whole
    // contract rests on this default: `serde(default = "default_true")`.
    //
    // Non-vacuous: if `default_true` were `default` (bool's `false`) or the
    // attribute were dropped, this absent-field decode would yield `ack = false`
    // and this assert flips to RED — an old brain would silently stop being acked.
    #[test]
    fn input_req_absent_ack_defaults_true_n_minus_1_safe() {
        // The exact shape an N-1 brain emits: session + base64 + op_id, NO `ack`.


Add the delivered=false unit after `output_envelope_round_trips`:


    // [unit->REQ-DAEMON-2] the output-envelope helper produces a decodable event.
    #[test]
    fn output_envelope_round_trips() {
        let env = output_envelope(7, 42, b"hello");
        assert_eq!(env.kind, KIND_OUTPUT);
        let ev: OutputEvent = serde_json::from_value(env.payload).unwrap();
        assert_eq!(ev.session_id, 7);
        assert_eq!(ev.seq, 42);
        assert_eq!(decode_bytes(&ev.data_b64).unwrap(), b"hello");
    }

    // [unit->REQ-DAEMON-2] the output-envelope helper produces a decodable event.
    #[test]
    fn output_envelope_round_trips() {
        let env = output_envelope(7, 42, b"hello");
        assert_eq!(env.kind, KIND_OUTPUT);
        let ev: OutputEvent = serde_json::from_value(env.payload).unwrap();
        assert_eq!(ev.session_id, 7);
        assert_eq!(ev.seq, 42);
        assert_eq!(decode_bytes(&ev.data_b64).unwrap(), b"hello");
    }

    // [unit->REQ-HAZARD-IDLE-SILENT-NONDELIVERY] The `delivered` flag is the spool
    // signal: `false` is what the CLI's `try_broker_inject` reads to fall through to
    // `deliver::send` (SPOOL) instead of reporting "Sent". With raw inject removed,
    // the no-working-binary path replies `delivered=false` — this asserts the
    // envelope faithfully carries that bool BOTH ways, so a false (spool) verdict can
    // never be silently rendered as a true (delivered) one on the wire.
    #[test]
    fn endpoint_injected_envelope_carries_delivered_both_ways() {
        let spooled = endpoint_injected_envelope("wall-b", false);
        assert_eq!(spooled.kind, KIND_ENDPOINT_INJECTED);
        let ev: EndpointInjected = serde_json::from_value(spooled.payload).unwrap();
        assert_eq!(ev.endpoint, "wall-b");
        assert!(
            !ev.delivered,
            "a no-working-binary reply MUST carry delivered=false so the caller spools"
        );

        let delivered: EndpointInjected =
            serde_json::from_value(endpoint_injected_envelope("wall-b", true).payload).unwrap();
        assert!(delivered.delivered, "a binary-delivered reply carries delivered=true");
    }


The file C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\src\msg.rs has been updated successfully. (file state is current in your context — no need to Read it back)

    // [unit->REQ-DAEMON-2] the output-envelope helper produces a decodable event.
    #[test]
    fn output_envelope_round_trips() {
        let env = output_envelope(7, 42, b"hello");
        assert_eq!(env.kind, KIND_OUTPUT);
        let ev: OutputEvent = serde_json::from_value(env.payload).unwrap();
        assert_eq!(ev.session_id, 7);
        assert_eq!(ev.seq, 42);
        assert_eq!(decode_bytes(&ev.data_b64).unwrap(), b"hello");
    }

    // [unit->REQ-HAZARD-IDLE-SILENT-NONDELIVERY] The `delivered` flag is the spool
    // signal: `false` is what the CLI's `try_broker_inject` reads to fall through to
    // `deliver::send` (SPOOL) instead of reporting "Sent". With raw inject removed,
    // the no-working-binary path replies `delivered=false` — this asserts the
    // envelope faithfully carries that bool BOTH ways, so a false (spool) verdict can
    // never be silently rendered as a true (delivered) one on the wire.
    #[test]
    fn endpoint_injected_envelope_carries_delivered_both_ways() {
        let spooled = endpoint_injected_envelope("wall-b", false);
        assert_eq!(spooled.kind, KIND_ENDPOINT_INJECTED);
        let ev: EndpointInjected = serde_json::from_value(spooled.payload).unwrap();
        assert_eq!(ev.endpoint, "wall-b");
        assert!(
            !ev.delivered,
            "a no-working-binary reply MUST carry delivered=false so the caller spools"
        );

        let delivered: EndpointInjected =
            serde_json::from_value(endpoint_injected_envelope("wall-b", true).payload).unwrap();
        assert!(delivered.delivered, "a binary-delivered reply carries delivered=true");
    }

+    // [unit->REQ-HAZARD-IDLE-SILENT-NONDELIVERY] The `delivered` flag is the spool

+    // signal: `false` is what the CLI's `try_broker_inject` reads to fall through to

+    // `deliver::send` (SPOOL) instead of reporting "Sent". With raw inject removed,

+    // the no-working-binary path replies `delivered=false` — this asserts the

+    // envelope faithfully carries that bool BOTH ways, so a false (spool) verdict can

+    // never be silently rendered as a true (delivered) one on the wire.

+    fn endpoint_injected_envelope_carries_delivered_both_ways() {

+        let spooled = endpoint_injected_envelope("wall-b", false);

+        assert_eq!(spooled.kind, KIND_ENDPOINT_INJECTED);

+        let ev: EndpointInjected = serde_json::from_value(spooled.payload).unwrap();

+        assert_eq!(ev.endpoint, "wall-b");

+            "a no-working-binary reply MUST carry delivered=false so the caller spools"

+        let delivered: EndpointInjected =

+            serde_json::from_value(endpoint_injected_envelope("wall-b", true).payload).unwrap();

+        assert!(delivered.delivered, "a binary-delivered reply carries delivered=true");

// [unit->REQ-HAZARD-INPUT-ACK-BACKPRESSURE] An `input` payload from an OLDER

// brain carries NO `ack` field on the wire (the field was added by this REQ).

// It MUST decode to `ack = true` so the old-brain path keeps acking exactly as


Now the doc stage: KNOWN-HAZARDS.md entry. Find the structure + latest number:


337:## 7. Boundary & delivery integrity (added 2026-05-31 — Stage A red-team)
341:### 7.1 Local `api` mutation auth  `[REQ-HAZARD-LOCAL-API-AUTH]`
347:### 7.2 Idempotent delivery across brain restart  `[REQ-HAZARD-RESTART-IDEMPOTENT]`
353:### 7.3 Psyche outbound capture + sanitization  `[REQ-HAZARD-PSYCHE-OUTBOUND-PROXY]`
359:### 7.4 Per-agent pulse/psyche/echo scheduling must not serialize across agents  `[REQ-HAZARD-DAEMON-SCHED-NONBLOCKING]`
365:### 7.5 WAN-inbound origin is transport truth, never payload  `[REQ-HAZARD-WAN-ORIGIN-AUTH]`
373:### 7.6 Pump brain-IPC reads must be deadline-bounded (a blocked read wedges the whole pump)  `[REQ-HAZARD-PUMP-IPC-DEADLINE]`
380:### 7.7 A slow/dead/hostile remote VIEWER must never stall the controller, child, or drain  `[REQ-HAZARD-VIEWER-ISOLATION]`
387:### 7.8 The broker must never make a brain wait UNBOUNDED on a QUIC op (the pump-IPC-deadline B-half)  `[REQ-HAZARD-BROKER-QUIC-DEADLINE]`
394:### 7.9 A daemon-state wire change needs a deliberate BROKER restart (the broker is resident across a brain self-update)  `[REQ-HAZARD-BROKER-SEED-WIRE-SKEW]`
401:### 7.10 A VIEW is independent from the endpoint — closing the launching tab must NOT reap the daemon-hosted harness  `[REQ-HAZARD-VIEWER-CLOSE-DETACH]`
408:### 7.11 A dead PTY child + a dropped operator pump must NOT wedge the broker for other clients  `[REQ-HAZARD-ATTACH-WEDGE]`
414:### 7.12 Controller output must NOT be written inline on the drain thread — a backed-up controller wedges the session  `[REQ-HAZARD-INJECT-CONTROL-COEXIST]`
416:[Omitted long matching line]
420:### 7.13 `spt rc` must SWAP Windows legacy-console Backspace/Ctrl+Backspace (`^H`↔DEL); do NOT enable VT console input  `[REQ-HAZARD-RC-INPUT-KEY-ENCODING]`
427:### 7.14 The effect journal must NOT hold its lock across the PTY write (or fsync every keystroke) — interactive input stutters then wedges  `[REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE]`
433:### 7.15 An OFFLINE spt-hosted endpoint must NOT render phantom `ONLINE+CONTROLLED` — clear `driven_by` when its session is gone  `[REQ-HAZARD-DRIVEN-BY-SELFHEAL]`
441:### 7.16 `spt rc` translates Windows console KEY EVENTS to standard xterm VT (arrows/Home/End/F-keys reach the harness); supersedes the W7 byte-swap  `[REQ-RC-KEY-VT-TRANSLATE]`
448:### 7.18 `spt rc` paste is client-originated on Windows — read the LOCAL clipboard, inject a BRACKETED paste  `[REQ-RC-WIN-PASTE]`
455:### 7.19 An operator input FLOOD must not deadlock the broker via the applied-ack on the same conn  `[REQ-HAZARD-INPUT-ACK-BACKPRESSURE]`
462:### 7.20 `spt rc` must forward the scroll wheel to the harness (our mouse capture steals WT's native scroll)  `[REQ-RC-MOUSE-FORWARD]`
469:### 7.21 Exactly ONE `controller_writer` per brain↔broker connection — a superseded writer must write nothing further  `[REQ-HAZARD-CONTROLLER-WRITER-REORDER]`
471:[Omitted long matching line]
478:[Omitted long matching line]
480:- **Source:** F-019 (v0.14.2); root confirmed on real claude-spt (perri + doyle). The raw-inject FALLBACK itself (degenerate no-binary floor) is unchanged by F-019 — its removal (message stays SPOOLED + poll-fed, never raw PTY inject) rides a separate ADR-0022 amendment.


Latest is 7.21; an F-019 note sits around 471-485. Read that tail to place 7.22 correctly:


469	### 7.21 Exactly ONE `controller_writer` per brain↔broker connection — a superseded writer must write nothing further  `[REQ-HAZARD-CONTROLLER-WRITER-REORDER]`
470	- **Failure (doyle instrumented RACEDIAG repro, kitsubito):** on a brain-restart re-serve the handoff brain registered as controller on the SAME session TWICE over the SAME socket — `Brain::handoff` eagerly `subscribe(prior.next_seq=1)` → `become_controller(from_seq=1)`, spawning writer-A (writes seq 1); then `serve_attach` re-handled the replayed `Request{from_seq:0}` → `attach_as(sid,0)` → `become_controller(from_seq=0)`, spawning writer-B (writes 0,1). `become_controller` dropped the prior `ControllerSink` (its `tx`) but did NOT stop the prior writer — writer-A kept flushing its OWNED `initial` batch, and both writers held clones of one `SharedSend` (`Arc<Mutex<socket>>`) with no inter-thread ordering. When writer-A's seq 1 beat writer-B's seq 0, the strict consumer saw `output gap: got seq 1 want 0` → `attach_survives_target_brain_restart_exactly_once` panicked at `.expect("re-serve")` OR HUNG in `render_until` (serve thread died on the gap → `MARKER_TWO` never reached the wire). `prior.next_seq` is life1's CONSUMPTION cursor (life1 forwards each frame to the operator immediately on consume, so at crash it has forwarded exactly `[0, K)`); life2 resumes from that same `K`, so the boundary aligns and `[0, K)` need never be re-sent. The crash was NOT byte loss — it was the consumer running the strict **reject-gap legacy path** (handoff left `session_cursors` empty) which treats any out-of-order seq as fatal. PRE-EXISTING, surfaced by the v0.13.0 green-both-runners gate; P1b is innocent. Sibling flaky cluster: `inject_control_wedge::g2`, `broker::spawn_env_reaches_child`.
471	- **Invariant:** on a single brain↔broker connection exactly ONE `controller_writer` is ever the LIVE writer; a SUPERSEDED writer writes no further frames after the epoch bump it observes; and **every `controller_writer` emits a strictly ASCENDING seq stream** (sorted initial batch + ascending live frames). The CORRECTNESS guarantee that falls out: a snap-above consumer over any interleaving of ascending writers — where the surviving writer (`serve_attach`'s `attach_as(sid,0)`) offers the COMPLETE range `[0, end]` — delivers `[K, end]` with **no skip and no dup** (the first sighting of any seq `>M` is always preceded by a sighting of `M` on that same ascending writer, so `M` is delivered before the cursor can pass it). Enforced/relied-on three ways (NB: fix #1 — "drop handoff's eager subscribe" — was REVERTED: that subscribe is the standalone-resume mechanism the brain-only update engine + `handoff`/`idempotent`/`daemon_e2e` replay through with no `serve_attach`): (1) CORRECTNESS — `Brain::handoff` seeds `session_cursors` at `prior.next_seq` so the consumer runs dedup-below + snap-above (resume mode), never the reject-gap legacy trap; the ascending-merge property above makes this complete, not merely tolerant; (2) INVARIANT — `controller_writer`'s INITIAL-BATCH replay is EPOCH-GATED: `controller_epoch` is a shared `Arc<AtomicU64>`, the writer re-reads it UNDER `send.lock()` (atomically with `write_frame`) and returns the instant it is superseded, so a superseded writer can never flush its stale replay past the bump (W1-safe: never blocks the drain under `Mutex<OutputLog>`). The LIVE loop is deliberately NOT gated — new output only ever flows to the CURRENT controller's channel, so a superseded writer's channel holds only its pre-supersede backlog (deduped by snap-above) plus its TERMINAL `Displaced` kick, which the displaced controller MUST still receive; that loop ends naturally on `tx`-drop (gating it suppressed the loud-take `Displaced` — the cv-matrix hang); (3) EXPLICIT-RESUME / OPERATOR-STREAM BOUNDARY (the load-bearing fix — kitsubito RACEDIAG ~33% repro that the keystones missed) — `Brain::subscribe_with` (shared by `attach` AND `attach_as`) RESETS that session's dedup cursor to `from_seq` in resume mode. WHY it's load-bearing, not just the ground-truth re-read: the handoff's eager `subscribe(K)` makes `serve_attach`'s `brain` receive the replay frame at seq=K BEFORE the operator's `Request` is processed (`attached` still false); that early frame is dropped by the `if attached` forward gate but the snap-above cursor has already advanced past K, and `attach_as(sid,0)`'s re-subscribe used to leave the cursor advanced — so the broker's re-send of seq K arrived below it and was deduped → seq K never reached the operator viewport → a `no forward gap` panic at the operator render cursor (`render_until`), and SILENT content loss in the real `rc` consumer (dedup-below + snap-above). Resetting to `from_seq` on the `attach_as(0)` re-subscribe makes the broker's full re-send re-deliver from 0 (the operator dedups the overlap), so seq K is forwarded. The epoch gate (2) is sound (RACEDIAG: zero socket interleaving above K); the residual was purely this consumer-side boundary. Cold-start brains (empty map — e.g. the production dispatch serve brain) keep the legacy `next_seq` path untouched, so production is unaffected.
472	- **spt-core mapping:** impl `brain.rs` `Brain::handoff` (KEEP the eager `subscribe`; seed `session_cursors`) + `Brain::subscribe_with` (resume-mode dedup-cursor reset to `from_seq`, shared by `attach`/`attach_as` — the operator-stream boundary fix) + `broker.rs` `OutputLog.controller_epoch: Arc<AtomicU64>` / `become_controller` (atomic `fetch_add`, passes the new epoch + `Arc::clone` into the writer) / `controller_writer` (epoch gate read UNDER the lock on both loops) / `mark_controller_gone` + the `ControllerJob` epoch read; unit (white-box, `src/broker.rs`) the epoch-gated writer (a superseded writer flushes nothing — only the latest writer's monotonic stream reaches the wire) + `handoff` seeds `session_cursors`/resubscribes; int (keystone, `tests/broker.rs` + `tests/attach.rs`) deterministically force two `become_controller`-on-one-connection on a real broker+brain (no mocks) — PRE-FIX reorders/gaps, POST-FIX monotonic + byte-exact + session live, PLUS `attach_survives_target_brain_restart_exactly_once` green (doyle: 20× isolated single-threaded timeout-wrapped on Linux/kitsubito — the deterministic RED-on-revert carrier).
473	- **Source:** v0.13.0 P1c (operator-ruled root-fix before ship; doyle root-cause via instrumented repro 2026-06-20; design corrected across two gate rounds — fix #1 reverted, then the kitsubito RACEDIAG pinned the residual to the consumer-side operator-stream boundary, fixed by the `subscribe_with` cursor reset on `attach_as` re-subscribe). The last v0.13.0 ship-blocker.
474
475	---
476
477	### F-019 diagnosis lesson — confirm an adapter binary actually SPAWNED before behavioral diagnosis  `[REQ-INSTALL-11]`
478	- **Failure:** a bare/relative adapter-shipped program path — `[message-idle-translation-binary].path = "cc-spt-idle-translate"` — passed VERBATIM to the broker spawns via `Command::new` against the daemon's cwd/PATH, FAILS (not on PATH), and the spt-hosted session FAILS CLOSED to raw inject. The binary's `{text}{delay}{key:enter}{commit}` choreography never runs → idle messages are typed into the harness but **never submitted**. `build_translation` DOES log `TRANSLATION_SPAWN_FAILED:<path>:<err>` (broker.rs) — but it lands on the **DETACHED daemon's stderr, which nobody reads**, so it is "silent" in practice. This cost a multi-hour black-box hunt (byte encoding, win32-input-mode `?9001h`, bracketed-paste, focus, pacing — all moot; the binary simply never ran).
479	- **Invariant:** every adapter-manifest program path resolves against the adapter install dir BEFORE PATH (REQ-INSTALL-11, `resolve_program_in_dir`) — at the harness session spawn (`harnesshost::launch_harness_brokered_in`: the idle-translation binary AND the session program), the W3d live-update RESPAWN (`broker::read_translation_path`), and the notif command (`with_install_dir`, both the daemon `notif.rs` and the api `reporting.rs` render paths). **Diagnosis rule:** before any deep behavioral analysis of a translation/adapter binary, CONFIRM it actually spawned — a resident process exists, and a deployed path edit changes behavior. `TRANSLATION_SPAWN_FAILED` on daemon stderr is the signal today, but its visibility is poor (surfacing it on the perch / a louder channel is a candidate follow-up).
480	- **Source:** F-019 (v0.14.2); root confirmed on real claude-spt (perri + doyle). The raw-inject FALLBACK itself (degenerate no-binary floor) is unchanged by F-019 — its removal (message stays SPOOLED + poll-fed, never raw PTY inject) rides a separate ADR-0022 amendment.
481	<!-- [doc->REQ-INSTALL-11] -->
482
483	---
484
485	## Conformance checklist (condensed)
486
487	| # | Invariant | spt-core surface |
488	|---|---|---|
489	| 1.1 | Grace wait precedes INIT_SIGNOFF | daemon teardown |
490	| 1.4/4.4 | Deferred rows excluded from event-stream drain | daemon spool drain |
491	| 2.1/5.1 | Stable PID/broker-handle over ephemeral PID | liveness detection |
492	| 2.3 | Handoff argv/IPC version-tolerant (newer brain ↔ older broker) | broker↔brain IPC, self-update |
493	| 2.4 | gen_start = now() on cold-start + handoff | per-instance generation |
494	| 3.1 | Ephemeral perch cleanup on all exit paths | `ring` (RAII guard) |
495	| 3.3 | Echo-commune before INIT_SIGNOFF | daemon psyche loop |
496	| 4.1 | Envelope decode order, `&` last | spt-proto (public, wire-versioned) |

### 7.21 Exactly ONE `controller_writer` per brain↔broker connection — a superseded writer must write nothing further  `[REQ-HAZARD-CONTROLLER-WRITER-REORDER]`
- **Failure (doyle instrumented RACEDIAG repro, kitsubito):** on a brain-restart re-serve the handoff brain registered as controller on the SAME session TWICE over the SAME socket — `Brain::handoff` eagerly `subscribe(prior.next_seq=1)` → `become_controller(from_seq=1)`, spawning writer-A (writes seq 1); then `serve_attach` re-handled the replayed `Request{from_seq:0}` → `attach_as(sid,0)` → `become_controller(from_seq=0)`, spawning writer-B (writes 0,1). `become_controller` dropped the prior `ControllerSink` (its `tx`) but did NOT stop the prior writer — writer-A kept flushing its OWNED `initial` batch, and both writers held clones of one `SharedSend` (`Arc<Mutex<socket>>`) with no inter-thread ordering. When writer-A's seq 1 beat writer-B's seq 0, the strict consumer saw `output gap: got seq 1 want 0` → `attach_survives_target_brain_restart_exactly_once` panicked at `.expect("re-serve")` OR HUNG in `render_until` (serve thread died on the gap → `MARKER_TWO` never reached the wire). `prior.next_seq` is life1's CONSUMPTION cursor (life1 forwards each frame to the operator immediately on consume, so at crash it has forwarded exactly `[0, K)`); life2 resumes from that same `K`, so the boundary aligns and `[0, K)` need never be re-sent. The crash was NOT byte loss — it was the consumer running the strict **reject-gap legacy path** (handoff left `session_cursors` empty) which treats any out-of-order seq as fatal. PRE-EXISTING, surfaced by the v0.13.0 green-both-runners gate; P1b is innocent. Sibling flaky cluster: `inject_control_wedge::g2`, `broker::spawn_env_reaches_child`.
- **Invariant:** on a single brain↔broker connection exactly ONE `controller_writer` is ever the LIVE writer; a SUPERSEDED writer writes no further frames after the epoch bump it observes; and **every `controller_writer` emits a strictly ASCENDING seq stream** (sorted initial batch + ascending live frames). The CORRECTNESS guarantee that falls out: a snap-above consumer over any interleaving of ascending writers — where the surviving writer (`serve_attach`'s `attach_as(sid,0)`) offers the COMPLETE range `[0, end]` — delivers `[K, end]` with **no skip and no dup** (the first sighting of any seq `>M` is always preceded by a sighting of `M` on that same ascending writer, so `M` is delivered before the cursor can pass it). Enforced/relied-on three ways (NB: fix #1 — "drop handoff's eager subscribe" — was REVERTED: that subscribe is the standalone-resume mechanism the brain-only update engine + `handoff`/`idempotent`/`daemon_e2e` replay through with no `serve_attach`): (1) CORRECTNESS — `Brain::handoff` seeds `session_cursors` at `prior.next_seq` so the consumer runs dedup-below + snap-above (resume mode), never the reject-gap legacy trap; the ascending-merge property above makes this complete, not merely tolerant; (2) INVARIANT — `controller_writer`'s INITIAL-BATCH replay is EPOCH-GATED: `controller_epoch` is a shared `Arc<AtomicU64>`, the writer re-reads it UNDER `send.lock()` (atomically with `write_frame`) and returns the instant it is superseded, so a superseded writer can never flush its stale replay past the bump (W1-safe: never blocks the drain under `Mutex<OutputLog>`). The LIVE loop is deliberately NOT gated — new output only ever flows to the CURRENT controller's channel, so a superseded writer's channel holds only its pre-supersede backlog (deduped by snap-above) plus its TERMINAL `Displaced` kick, which the displaced controller MUST still receive; that loop ends naturally on `tx`-drop (gating it suppressed the loud-take `Displaced` — the cv-matrix hang); (3) EXPLICIT-RESUME / OPERATOR-STREAM BOUNDARY (the load-bearing fix — kitsubito RACEDIAG ~33% repro that the keystones missed) — `Brain::subscribe_with` (shared by `attach` AND `attach_as`) RESETS that session's dedup cursor to `from_seq` in resume mode. WHY it's load-bearing, not just the ground-truth re-read: the handoff's eager `subscribe(K)` makes `serve_attach`'s `brain` receive the replay frame at seq=K BEFORE the operator's `Request` is processed (`attached` still false); that early frame is dropped by the `if attached` forward gate but the snap-above cursor has already advanced past K, and `attach_as(sid,0)`'s re-subscribe used to leave the cursor advanced — so the broker's re-send of seq K arrived below it and was deduped → seq K never reached the operator viewport → a `no forward gap` panic at the operator render cursor (`render_until`), and SILENT content loss in the real `rc` consumer (dedup-below + snap-above). Resetting to `from_seq` on the `attach_as(0)` re-subscribe makes the broker's full re-send re-deliver from 0 (the operator dedups the overlap), so seq K is forwarded. The epoch gate (2) is sound (RACEDIAG: zero socket interleaving above K); the residual was purely this consumer-side boundary. Cold-start brains (empty map — e.g. the production dispatch serve brain) keep the legacy `next_seq` path untouched, so production is unaffected.
- **spt-core mapping:** impl `brain.rs` `Brain::handoff` (KEEP the eager `subscribe`; seed `session_cursors`) + `Brain::subscribe_with` (resume-mode dedup-cursor reset to `from_seq`, shared by `attach`/`attach_as` — the operator-stream boundary fix) + `broker.rs` `OutputLog.controller_epoch: Arc<AtomicU64>` / `become_controller` (atomic `fetch_add`, passes the new epoch + `Arc::clone` into the writer) / `controller_writer` (epoch gate read UNDER the lock on both loops) / `mark_controller_gone` + the `ControllerJob` epoch read; unit (white-box, `src/broker.rs`) the epoch-gated writer (a superseded writer flushes nothing — only the latest writer's monotonic stream reaches the wire) + `handoff` seeds `session_cursors`/resubscribes; int (keystone, `tests/broker.rs` + `tests/attach.rs`) deterministically force two `become_controller`-on-one-connection on a real broker+brain (no mocks) — PRE-FIX reorders/gaps, POST-FIX monotonic + byte-exact + session live, PLUS `attach_survives_target_brain_restart_exactly_once` green (doyle: 20× isolated single-threaded timeout-wrapped on Linux/kitsubito — the deterministic RED-on-revert carrier).
- **Source:** v0.13.0 P1c (operator-ruled root-fix before ship; doyle root-cause via instrumented repro 2026-06-20; design corrected across two gate rounds — fix #1 reverted, then the kitsubito RACEDIAG pinned the residual to the consumer-side operator-stream boundary, fixed by the `subscribe_with` cursor reset on `attach_as` re-subscribe). The last v0.13.0 ship-blocker.

---

### F-019 diagnosis lesson — confirm an adapter binary actually SPAWNED before behavioral diagnosis  `[REQ-INSTALL-11]`
- **Failure:** a bare/relative adapter-shipped program path — `[message-idle-translation-binary].path = "cc-spt-idle-translate"` — passed VERBATIM to the broker spawns via `Command::new` against the daemon's cwd/PATH, FAILS (not on PATH), and the spt-hosted session FAILS CLOSED to raw inject. The binary's `{text}{delay}{key:enter}{commit}` choreography never runs → idle messages are typed into the harness but **never submitted**. `build_translation` DOES log `TRANSLATION_SPAWN_FAILED:<path>:<err>` (broker.rs) — but it lands on the **DETACHED daemon's stderr, which nobody reads**, so it is "silent" in practice. This cost a multi-hour black-box hunt (byte encoding, win32-input-mode `?9001h`, bracketed-paste, focus, pacing — all moot; the binary simply never ran).
- **Invariant:** every adapter-manifest program path resolves against the adapter install dir BEFORE PATH (REQ-INSTALL-11, `resolve_program_in_dir`) — at the harness session spawn (`harnesshost::launch_harness_brokered_in`: the idle-translation binary AND the session program), the W3d live-update RESPAWN (`broker::read_translation_path`), and the notif command (`with_install_dir`, both the daemon `notif.rs` and the api `reporting.rs` render paths). **Diagnosis rule:** before any deep behavioral analysis of a translation/adapter binary, CONFIRM it actually spawned — a resident process exists, and a deployed path edit changes behavior. `TRANSLATION_SPAWN_FAILED` on daemon stderr is the signal today, but its visibility is poor (surfacing it on the perch / a louder channel is a candidate follow-up).
- **Source:** F-019 (v0.14.2); root confirmed on real claude-spt (perri + doyle). The raw-inject FALLBACK itself (degenerate no-binary floor) is unchanged by F-019 — its removal (message stays SPOOLED + poll-fed, never raw PTY inject) rides a separate ADR-0022 amendment.
<!-- [doc->REQ-INSTALL-11] -->

---

## Conformance checklist (condensed)

| # | Invariant | spt-core surface |
|---|---|---|
| 1.1 | Grace wait precedes INIT_SIGNOFF | daemon teardown |
| 1.4/4.4 | Deferred rows excluded from event-stream drain | daemon spool drain |
| 2.1/5.1 | Stable PID/broker-handle over ephemeral PID | liveness detection |
| 2.3 | Handoff argv/IPC version-tolerant (newer brain ↔ older broker) | broker↔brain IPC, self-update |
| 2.4 | gen_start = now() on cold-start + handoff | per-instance generation |
| 3.1 | Ephemeral perch cleanup on all exit paths | `ring` (RAII guard) |
| 3.3 | Echo-commune before INIT_SIGNOFF | daemon psyche loop |
| 4.1 | Envelope decode order, `&` last | spt-proto (public, wire-versioned) |


Add hazard 7.22 after 7.21 (before the `---`), with the doc tag:


- **Source:** v0.13.0 P1c (operator-ruled root-fix before ship; doyle root-cause via instrumented repro 2026-06-20; design corrected across two gate rounds — fix #1 reverted, then the kitsubito RACEDIAG pinned the residual to the consumer-side operator-stream boundary, fixed by the `subscribe_with` cursor reset on `attach_as` re-subscribe). The last v0.13.0 ship-blocker.

---

### F-019 diagnosis lesson — confirm an adapter binary actually SPAWNED before behavioral diagnosis  `[REQ-INSTALL-11]`

- **Source:** v0.13.0 P1c (operator-ruled root-fix before ship; doyle root-cause via instrumented repro 2026-06-20; design corrected across two gate rounds — fix #1 reverted, then the kitsubito RACEDIAG pinned the residual to the consumer-side operator-stream boundary, fixed by the `subscribe_with` cursor reset on `attach_as` re-subscribe). The last v0.13.0 ship-blocker.

### 7.22 An idle delivery with no working translation binary must SPOOL, never raw-inject a pseudo-delivery reported as delivered  `[REQ-HAZARD-IDLE-SILENT-NONDELIVERY]`
- **Failure (F-019 post-mortem):** the v0.11.0 path raw-injected `payload+\r` into the spt-hosted PTY whenever no working translation binary handled an inbound message — none declared, spawn-failed, FAULTED (commit-deadline miss / binary death), or its inject-worker channel dropped — AND acked `delivered=true`. But a bare `payload+\r` does NOT submit on a modern TUI (Claude Code): the message was TYPED into the harness but never sent, a silent pseudo-delivery reported as success. That silent degrade-to-raw-inject is precisely what MASKED F-019 (`[REQ-INSTALL-11]`, above) through a multi-hour black-box hunt — every behavioral hypothesis was moot because the "delivery" never delivered.
- **Invariant:** spt-hosted idle delivery is translation-binary-ONLY (ADR-0022 amendment). `dispatch_endpoint_input` with no working binary replies `endpoint_injected_envelope(ep, delivered=false)` and writes NOTHING to the PTY; the caller (`try_broker_inject` → `cmd_send`) reads `delivered=false` and falls through to `deliver::send` = SPOOL (poll-fed, never lost), reporting an honest `QUEUED`, never a confident-but-false `SENT`. The failure is LOUD (`ENDPOINT_INJECT:<ep>: no working translation binary … -> SPOOLED, not injected`). The raw-inject fallback (`input.enqueue`) is removed from the no-binary, worker-dropped, AND post-fault paths; the operator-keystroke floor-flush on FAULT is UNCHANGED (operator input is never stranded). Out of scope (follow-up): broker-side auto-redrive of already-spooled inbound when a live-update binary spawns (ordering/exactly-once hazards; the poll substrate + subsequent sends cover re-delivery).
- **spt-core mapping:** impl `broker.rs` `dispatch_endpoint_input` (no-working-binary → `delivered=false` + loud log, no `input.enqueue`; the `input` writer is no longer resolved from the sessions table) + `build_translation`/`fault_translation` (`None`/fault now MEAN spool); unit `msg.rs` `endpoint_injected_envelope_carries_delivered_both_ways` (the `delivered=false` spool signal round-trips); int (real broker+PTY) `tests/broker.rs` `endpoint_keyed_inject_without_binary_spools_not_pty` + `tests/inject_control_wedge.rs` `large_endpoint_inject_to_a_no_binary_session_spools_promptly_without_wedging` + `g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input` (post-FAULT inbound spools, marker NOT on PTY).
- **Source:** v0.14.3 (ADR-0022 amendment @1eaeef1, operator-ruled; doyle-scoped, the F-019 follow-up). CHANGE-5 (`cli.rs` honest report) verified a NO-OP — the spool fall-through already reported `QUEUED`, never `SENT`.
<!-- [doc->REQ-HAZARD-IDLE-SILENT-NONDELIVERY] -->

---

### F-019 diagnosis lesson — confirm an adapter binary actually SPAWNED before behavioral diagnosis  `[REQ-INSTALL-11]`


The file C:\Users\decid\Documents\projects\spt-core\docs\KNOWN-HAZARDS.md has been updated successfully. (file state is current in your context — no need to Read it back)

- **Source:** v0.13.0 P1c (operator-ruled root-fix before ship; doyle root-cause via instrumented repro 2026-06-20; design corrected across two gate rounds — fix #1 reverted, then the kitsubito RACEDIAG pinned the residual to the consumer-side operator-stream boundary, fixed by the `subscribe_with` cursor reset on `attach_as` re-subscribe). The last v0.13.0 ship-blocker.

---

### F-019 diagnosis lesson — confirm an adapter binary actually SPAWNED before behavioral diagnosis  `[REQ-INSTALL-11]`

- **Source:** v0.13.0 P1c (operator-ruled root-fix before ship; doyle root-cause via instrumented repro 2026-06-20; design corrected across two gate rounds — fix #1 reverted, then the kitsubito RACEDIAG pinned the residual to the consumer-side operator-stream boundary, fixed by the `subscribe_with` cursor reset on `attach_as` re-subscribe). The last v0.13.0 ship-blocker.

### 7.22 An idle delivery with no working translation binary must SPOOL, never raw-inject a pseudo-delivery reported as delivered  `[REQ-HAZARD-IDLE-SILENT-NONDELIVERY]`
- **Failure (F-019 post-mortem):** the v0.11.0 path raw-injected `payload+\r` into the spt-hosted PTY whenever no working translation binary handled an inbound message — none declared, spawn-failed, FAULTED (commit-deadline miss / binary death), or its inject-worker channel dropped — AND acked `delivered=true`. But a bare `payload+\r` does NOT submit on a modern TUI (Claude Code): the message was TYPED into the harness but never sent, a silent pseudo-delivery reported as success. That silent degrade-to-raw-inject is precisely what MASKED F-019 (`[REQ-INSTALL-11]`, above) through a multi-hour black-box hunt — every behavioral hypothesis was moot because the "delivery" never delivered.
- **Invariant:** spt-hosted idle delivery is translation-binary-ONLY (ADR-0022 amendment). `dispatch_endpoint_input` with no working binary replies `endpoint_injected_envelope(ep, delivered=false)` and writes NOTHING to the PTY; the caller (`try_broker_inject` → `cmd_send`) reads `delivered=false` and falls through to `deliver::send` = SPOOL (poll-fed, never lost), reporting an honest `QUEUED`, never a confident-but-false `SENT`. The failure is LOUD (`ENDPOINT_INJECT:<ep>: no working translation binary … -> SPOOLED, not injected`). The raw-inject fallback (`input.enqueue`) is removed from the no-binary, worker-dropped, AND post-fault paths; the operator-keystroke floor-flush on FAULT is UNCHANGED (operator input is never stranded). Out of scope (follow-up): broker-side auto-redrive of already-spooled inbound when a live-update binary spawns (ordering/exactly-once hazards; the poll substrate + subsequent sends cover re-delivery).
- **spt-core mapping:** impl `broker.rs` `dispatch_endpoint_input` (no-working-binary → `delivered=false` + loud log, no `input.enqueue`; the `input` writer is no longer resolved from the sessions table) + `build_translation`/`fault_translation` (`None`/fault now MEAN spool); unit `msg.rs` `endpoint_injected_envelope_carries_delivered_both_ways` (the `delivered=false` spool signal round-trips); int (real broker+PTY) `tests/broker.rs` `endpoint_keyed_inject_without_binary_spools_not_pty` + `tests/inject_control_wedge.rs` `large_endpoint_inject_to_a_no_binary_session_spools_promptly_without_wedging` + `g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input` (post-FAULT inbound spools, marker NOT on PTY).
- **Source:** v0.14.3 (ADR-0022 amendment @1eaeef1, operator-ruled; doyle-scoped, the F-019 follow-up). CHANGE-5 (`cli.rs` honest report) verified a NO-OP — the spool fall-through already reported `QUEUED`, never `SENT`.
<!-- [doc->REQ-HAZARD-IDLE-SILENT-NONDELIVERY] -->

---

### F-019 diagnosis lesson — confirm an adapter binary actually SPAWNED before behavioral diagnosis  `[REQ-INSTALL-11]`

- **spt-core mapping:** impl `brain.rs` `Brain::handoff` (KEEP the eager `subscribe`; seed `session_cursors`) + `Brain::subscribe_with` (resume-mode dedup-cursor reset to `from_seq`, shared by `attach`/`attach_as` — the operator-stream boundary fix) + `broker.rs` `OutputLog.controller_epoch: Arc<AtomicU64>` / `become_controller` (atomic `fetch_add`, passes the new epoch + `Arc::clone` into the writer) / `controller_writer` (epoch gate read UNDER the lock on both loops) / `mark_controller_gone` + the `ControllerJob` epoch read; unit (white-box, `src/broker.rs`) the epoch-gated writer (a superseded writer flushes nothing — only the latest writer's monotonic stream reaches the wire) + `handoff` seeds `session_cursors`/resubscribes; int (keystone, `tests/broker.rs` + `tests/attach.rs`) deterministically force two `become_controller`-on-one-connection on a real broker+brain (no mocks) — PRE-FIX reorders/gaps, POST-FIX monotonic + byte-exact + session live, PLUS `attach_survives_target_brain_restart_exactly_once` green (doyle: 20× isolated single-threaded timeout-wrapped on Linux/kitsubito — the deterministic RED-on-revert carrier).

+### 7.22 An idle delivery with no working translation binary must SPOOL, never raw-inject a pseudo-delivery reported as delivered  `[REQ-HAZARD-IDLE-SILENT-NONDELIVERY]`

+- **Failure (F-019 post-mortem):** the v0.11.0 path raw-injected `payload+\r` into the spt-hosted PTY whenever no working translation binary handled an inbound message — none declared, spawn-failed, FAULTED (commit-deadline miss / binary death), or its inject-worker channel dropped — AND acked `delivered=true`. But a bare `payload+\r` does NOT submit on a modern TUI (Claude Code): the message was TYPED into the harness but never sent, a silent pseudo-delivery reported as success. That silent degrade-to-raw-inject is precisely what MASKED F-019 (`[REQ-INSTALL-11]`, above) through a multi-hour black-box hunt — every behavioral hypothesis was moot because the "delivery" never delivered.

+- **Invariant:** spt-hosted idle delivery is translation-binary-ONLY (ADR-0022 amendment). `dispatch_endpoint_input` with no working binary replies `endpoint_injected_envelope(ep, delivered=false)` and writes NOTHING to the PTY; the caller (`try_broker_inject` → `cmd_send`) reads `delivered=false` and falls through to `deliver::send` = SPOOL (poll-fed, never lost), reporting an honest `QUEUED`, never a confident-but-false `SENT`. The failure is LOUD (`ENDPOINT_INJECT:<ep>: no working translation binary … -> SPOOLED, not injected`). The raw-inject fallback (`input.enqueue`) is removed from the no-binary, worker-dropped, AND post-fault paths; the operator-keystroke floor-flush on FAULT is UNCHANGED (operator input is never stranded). Out of scope (follow-up): broker-side auto-redrive of already-spooled inbound when a live-update binary spawns (ordering/exactly-once hazards; the poll substrate + subsequent sends cover re-delivery).

+- **spt-core mapping:** impl `broker.rs` `dispatch_endpoint_input` (no-working-binary → `delivered=false` + loud log, no `input.enqueue`; the `input` writer is no longer resolved from the sessions table) + `build_translation`/`fault_translation` (`None`/fault now MEAN spool); unit `msg.rs` `endpoint_injected_envelope_carries_delivered_both_ways` (the `delivered=false` spool signal round-trips); int (real broker+PTY) `tests/broker.rs` `endpoint_keyed_inject_without_binary_spools_not_pty` + `tests/inject_control_wedge.rs` `large_endpoint_inject_to_a_no_binary_session_spools_promptly_without_wedging` + `g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input` (post-FAULT inbound spools, marker NOT on PTY).

+- **Source:** v0.14.3 (ADR-0022 amendment @1eaeef1, operator-ruled; doyle-scoped, the F-019 follow-up). CHANGE-5 (`cli.rs` honest report) verified a NO-OP — the spool fall-through already reported `QUEUED`, never `SENT`.


All four stages of the new REQ now have evidence (doc/impl/unit/int). Compile + run the new msg unit, then re-verify traceable:


Bash

cargo test -p spt-daemon --lib endpoint_injected_envelope_carries_delivered_both_ways 2>&1 | tail -8

Run new msg delivered unit


LNK1104 — the linker can't write the test exe because the full lib suite (b5advywec) still holds it locked. Contention, not a code error. Check if that suite finished:


tail -8 /tmp/daemonlib.txt 2>/dev/null; echo "=== exit marker? ==="; grep "LIB_EXIT" /tmp/daemonlib.txt 2>/dev/null; echo "=== bg output ==="; tail -3 "C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\22d5a4f6-16ae-4552-82b1-0f69cfb556b0\tasks\b5advywec.output" 2>/dev/null

Check if full lib suite completed


test shellwake::tests::watcher_crash_exits_respawn_then_give_up ... ok
test sync::tests::reconcile_driver_structurally_excludes_live_role ... ok
test sync::tests::select_refs_scopes_two_tiers ... ok
test seedmap::tests::request_stop_barrier_holds_until_no_listener has been running for over 60 seconds
test seedmap::tests::stop_op_acks_then_serve_returns has been running for over 60 seconds
test seedmap::tests::take_absent_is_none has been running for over 60 seconds
test serveprobe::tests::is_serving_subnet_honors_membership_and_detach has been running for over 60 seconds
test shellwake::tests::resolve_wake_revives_a_suspended_owner_without_double_launch has been running for over 60 seconds
=== exit marker? ===
=== bg output ===


The suite is hung on `seedmap` stop-barrier tests (untouched by me, holding the binary lock — pre-existing env hang, [[delegate-build-preflight-hang]] pattern). Stop it to free the lock:


Successfully stopped task: b5advywec (cargo test -p spt-daemon --lib > /tmp/daemonlib.txt 2>&1; echo "LIB_EXIT=$?"; tail -4 /tmp/daemonlib.txt)


Run the new msg unit (lock freed):


cargo test -p spt-daemon --lib endpoint_injected_envelope_carries_delivered_both_ways 2>&1 | tail -8

Run new msg delivered unit