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)

title = "spt-hosted idle message delivery via an adapter TRANSLATION BINARY (ADR-0022). New opt-in manifest section `[message-idle-translation-binary]` = a TABLE carrying a `path` scalar (doyle OPT-B ruling: modeled as a table, not a bare top-level scalar, so a preceding section cannot silently absorb it + N+1 extensible; spt-core does NOT deny_unknown_fields, so a future key degrades gracefully); spt-core LIFECYCLE-manages it (spawn when the endpoint comes up, terminate when it goes down). The binary is a PURE stdin→stdout filter; spt-core owns EVERY PTY write. stdin (JSON-lines): `{type:\"init\",endpoint_id,node}` first · `{type:\"event\",envelope:\"<EVENT…>\"}` per inbound message (ADR-0020 envelope) · `{type:\"input\"}` content-free ping on each operator keystroke (binary tracks user-idle for its own idle-gated buffering; PTY input content NOT duplicated). stdout (JSON-lines): keystroke-commands `{key:…}`/`{delay_ms:…}`/`{text:…}` (extensible). spt-core applies the emitted sequence to the broker PTY ATOMICALLY (the W1 coordination — REQ-HAZARD-INJECT-CONTROL-COEXIST). The daemon poll feed is the ONE idle substrate for both topologies (Q1=A): harness-hosted consumer = the Monitor child, spt-hosted consumer = this binary; spt-core PREFERS a perch's poll listener if one exists (so spt-hosted can run a listener AND keep `spt rc`). Idle-only; busy/mid-turn = adapter hook-injection. Closes the current grounding gap: `api bind` registers no listener port → a listener-less spt-hosted perch SPOOLS inbound (only spooling+adapter-poll works today) → this delivers real inbound into the PTY. The v0.11.0 raw inject is the degenerate `{text:payload}{key:enter}` case. (v0.13.0)"

-required_stages = ["doc"]  # ACTIVATING v0.13.0 W2 (todlando) — 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"]  # ACTIVATING v0.13.0 W2 (todlando) — int rides Layer G. Layers A(@c73eb6a)+B(@611205c)+D(@7166f52)+C-kernel(@79f5a40)+C-proper/E landed: 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 (Layer G, NEXT via spt-test-engineer) = real dummy translation-binary choreography into an rc-attached session w/o breaking control + no-commit-deadline FAULT + content-free + no-zombie. — 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).

# ── W3: a fresh spt-hosted endpoint shows under its own project (info.cwd on bind) ──


Bash

traceable-reqs check >/tmp/tr2.txt 2>&1; echo "EXIT=$?"; grep -iE "IDLE-TRANSLATION-BINARY|INJECT-CONTROL-COEXIST" /tmp/tr2.txt

Traceable check after stage bump


EXIT=0
       The effect journal serializes EVERY PTY effect under one mutex held ACROSS two fsyncs AND the blocking PTY write — so interactive input stutters and ultimately wedges the daemon hard. ROOT (doyle /diagnose, code-grounded + MEASURED on the operator's real Windows box, 2026-06-19): EffectJournal::apply_once (effect.rs:168-188) takes `inner.lock()` and holds it across `write_line(PENDING)` → `effect()` → `write_line(DONE)`, where write_line (effect.rs:235-239) does flush()+sync_all() (a full FlushFileBuffers) — so each effect pays TWO fsyncs under a GLOBAL lock, and the closure `effect()` (the actual PTY write, broker.rs:1257 EffectKind::PtyWrite via attach.rs:197 send_effect) runs while the lock is held. Two operator-visible facets, ONE root: (A) STUTTER/LAG — every keystroke is a PtyWrite effect = 2× sync_all serialized; measured fsync on %LOCALAPPDATA%\spt-core = median 6.5ms, spikes to 198ms (C: was recently at 100%), so ~13ms+ per keystroke best case, hundreds under contention → 'many but not all keypresses take 100s of ms, choppy, worsens with volume'. (B) HARD PERMANENT WEDGE — when a PtyWrite `effect()` blocks (ConPTY input buffer full / harness not draining stdin), the journal lock is held INDEFINITELY → the single-threaded inbound-stream dispatch (dispatch.rs serve_attach, which both applies input effects AND opens attaches) can never progress → EVERY subsequent attach (`spt rc --view`/`--take`) fails with 'attach request: brain IPC read deadline elapsed' (confirmed: two retries deadline identically; broker control-plane KIND queries still answer — different thread). This REFUTES the W2-deferred ruling that park-(b)/(c) is 'Windows-benign because ConPTY absorbs 4MiB' — on the real box the input path wedges regardless. DISTINCT from W1 (REQ-HAZARD-INJECT-CONTROL-COEXIST = the OUTPUT drain, correctly fixed @8b5583e; output uses broker.rs:1106 append, NOT the fsync journal). This is the INPUT/effect-journal path W1 never touched, and it is THE wedge the operator hits with --take/--view. FIX DIRECTION (candidates, repro-first — extend inject_control_wedge.rs to a REAL backed-up-PTY-consumer + a real rc-client attach assertion, the gap W1's gate missed): (1) do NOT hold the journal lock across effect() — reserve the key + fsync PENDING under lock, RELEASE, run effect(), re-acquire to fsync DONE + mark applied (preserve crash-idempotency via the per-key reservation, not a global hold); (2) bound/fail-fast the PtyWrite itself (the W2-deferred park bound — write_input must never block indefinitely, DSR-answer must not hold the writer mutex across a blocking write); (3) drop per-keystroke fsync on the interactive path — PtyWrite effects are EPHEMERAL (a keystroke lost on a broker crash is retyped; PTY state is not reconstructed from keystroke replay), so in-memory applied-set dedup suffices (the broker survives the brain — that IS the dedup anchor), with async/batched fsync or no-fsync for EffectKind::PtyWrite while durable kinds (NetSend/NetDial/Registry/Spool) keep their fsync. Combine (1)+(3) at minimum. Add a KNOWN-HAZARDS.md entry on landing. (v0.13.0)
  [OK] REQ-HAZARD-INJECT-CONTROL-COEXIST  required: [impl, unit, int]  stages: -doc +impl +unit +int
  [OK] REQ-MSG-IDLE-TRANSLATION-BINARY  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       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)


Both green, EXIT=0. Rebuild real bin + commit Layers C+E:


cargo build -p spt --bin spt 2>&1 | tail -2 && git add -A && git status --short

Rebuild real bin + stage Layer C+E


   Compiling spt v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 10.62s
warning: in the working copy of 'crates/spt-daemon/src/translation.rs', LF will be replaced by CRLF the next time Git touches it
M  crates/spt-daemon/src/broker.rs
M  crates/spt-daemon/src/translation.rs
M  traceable-reqs.toml


git commit -m "$(cat <<'EOF'
feat(daemon): W2 Layer C+E — atomic translation-binary inject + event routing (ADR-0022)

Layer C proper: per translation-bearing session, an inject-worker thread owns
the TranslationChild + KeyCmd receiver and drives one queued <EVENT> at a time:
open the inject floor (operator input buffers in dispatch_input), feed the
binary {type:event}, drain {key}/{text}/{delay_ms} into write_input until
{commit}, then flush the buffered controller bytes AFTER the injected sequence
and release the floor. Bounded by INJECT_COMMIT_DEADLINE (5s, W1's order): a
missed commit or a dead binary flushes+releases anyway, FAULTs the binary
(terminate, no respawn), and degrades the session to raw inject — never a
parked operator. This is the W1 coexistence mechanism applied to injection
(REQ-HAZARD-INJECT-CONTROL-COEXIST): inject + live rc controller share one PTY
without clobbering.

Layer E: dispatch_endpoint_input routes the whole <EVENT> to a live (non-faulted)
translation worker; raw inject is the degenerate fallback when none/faulted.
dispatch_input fires a content-free {type:input} ping per keystroke (doyle gate
#1 privacy — no PTY bytes ride it) and buffers via the floor when a sequence is
mid-flight (op_id path buffers inside the journal effect to keep ack+dedup).

dispatch_spawn brings the binary up with the session (fail-closed to raw inject
on spawn error); TranslationChild.terminate is now &self/Arc-callable so the
worker can FAULT-reap (bounded, no zombie — doyle gate #2). Layer F (Unix park
bound) + Layer G (int gate) next.

REQ-MSG-IDLE-TRANSLATION-BINARY -> [doc,impl,unit] (int rides Layer G).

[impl->REQ-MSG-IDLE-TRANSLATION-BINARY] [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]

Co-authored by: todlando
EOF
)"

Commit Layer C+E


[v0.13.0-delivery-control f9fe5bb] feat(daemon): W2 Layer C+E — atomic translation-binary inject + event routing (ADR-0022)
 3 files changed, 292 insertions(+), 29 deletions(-)


Layer C+E committed @f9fe5bb. Layer F is cfg(unix) (gravity-linux) — defer to the follow-up I can't validate on Windows. Next: **Layer G int** via spt-test-engineer. First, find the established fixture-binary pattern so the subagent reuses it rather than inventing:


cd /c/Users/decid/Documents/projects/spt-core; echo "=== CARGO_BIN_EXE usage ==="; grep -rn "CARGO_BIN_EXE\|escargot\|current_exe\|fixture" crates/spt-daemon/tests/*.rs | head; echo "=== existing [[bin]] / fixture bins in spt-daemon ==="; grep -n "\[\[bin\]\]\|name =" crates/spt-daemon/Cargo.toml | head -20; echo "=== src/bin ==="; ls crates/spt-daemon/src/bin/ 2>/dev/null; ls crates/*/src/bin/ 2>/dev/null

Find established test-fixture-binary pattern


Error: Exit code 2
=== CARGO_BIN_EXE usage ===
crates/spt-daemon/tests/digest.rs:8://! over a fixture JSONL of published digest-records, and serves it over the real
crates/spt-daemon/tests/digest.rs:36:/// stdout unchanged, so the fixture JSONL *is* the contract-record stream.
crates/spt-daemon/tests/digest.rs:42:/// Register a harness adapter whose `[digest]` extractor reads `fixture` (a
crates/spt-daemon/tests/digest.rs:46:fn establish_cc_endpoint(id: &str, fixture: &std::path::Path) {
crates/spt-daemon/tests/digest.rs:56:        fixture.display(),
crates/spt-daemon/tests/digest.rs:103:    // A fixture JSONL of published digest-records — the harness's "session log".
crates/spt-daemon/tests/digest.rs:104:    let fixture = home.path().join("cc-session.jsonl");
crates/spt-daemon/tests/digest.rs:106:        &fixture,
crates/spt-daemon/tests/digest.rs:112:    establish_cc_endpoint("cc", &fixture);
crates/spt-daemon/tests/digest.rs:128:    let fixture2 = home.path().join("cc2-session.jsonl");
=== existing [[bin]] / fixture bins in spt-daemon ===
2:name = "spt-daemon"
=== src/bin ===


grep -rln "CARGO_BIN_EXE\|Command::new\|examples/\|\.spawn()" crates/spt-daemon/tests/*.rs | head; echo "=== how shell/link tests get a spawnable bin ==="; grep -rn "CARGO_BIN_EXE\|cargo.*build\|env!(\|examples" crates/spt-daemon/tests/dispatch.rs crates/spt-daemon/tests/twohost.rs 2>/dev/null | head; echo "=== workspace examples dirs ==="; ls crates/*/examples/ 2>/dev/null; echo "=== any test that spawns a helper exe ==="; grep -rln "current_exe\|cfg!(windows).*exe\|\.cmd\|powershell\|/bin/sh" crates/spt-daemon/tests/*.rs

Find spawnable-binary test pattern


crates/spt-daemon/tests/budget.rs
crates/spt-daemon/tests/daemon_lifecycle_real_brain.rs
crates/spt-daemon/tests/inject_control_wedge.rs
=== how shell/link tests get a spawnable bin ===
=== workspace examples dirs ===
=== any test that spawns a helper exe ===
crates/spt-daemon/tests/budget.rs
crates/spt-daemon/tests/resume.rs
crates/spt-daemon/tests/twohost.rs


echo "=== inject_control_wedge spawn program (what child does it host?) ==="; grep -n "program\|Command::new\|spawn\|\.exe\|cmd\|powershell\|/bin/\|current_exe\|flood" crates/spt-daemon/tests/inject_control_wedge.rs | head -25

See how inject_control_wedge spawns its PTY child


=== inject_control_wedge spawn program (what child does it host?) ===
13://! floods) parked the drain INSIDE `append` STILL HOLDING the log lock — output
78:    let _ = std::process::Command::new("taskkill")
82:    let _ = std::process::Command::new("kill")
90:fn flood_spawn_req(endpoint: &str) -> SpawnReq {
92:    let (program, args) = ("yes".to_string(), vec!["FLOODFLOODFLOODFLOOD".to_string()]);
94:    let (program, args) = (
95:        "cmd".to_string(),
102:        program,
119:/// spawned thread so a BLOCKED `read_frame` (the wedge) surfaces as a timeout
121:fn probe(name: &str, cmd_kind: &'static str, want_kind: &'static str, deadline: Duration) -> bool {
124:    thread::spawn(move || {
136:        send(&mut conn, cmd_kind, serde_json::json!({}));
159:    thread::spawn(move || {
163:    // ── Controller connection A: spawn the flooding child. dispatch_spawn
180:        serde_json::to_value(flood_spawn_req("wedge-ep")).unwrap(),
183:        let f = read_frame(&mut a).expect("frame before spawned");
189:    // ── A now STOPS reading. The child floods → A's socket recv buffer fills →
212:    // Reap the flood child (scoped) + tear the broker down.
242:    thread::spawn(move || {
246:    // Controller A spawns the flood child (which IGNORES stdin) and then DRAINS
264:        serde_json::to_value(flood_spawn_req("inj-ep")).unwrap(),
267:        let f = read_frame(&mut a).expect("frame before spawned");
273:    thread::spawn(move || while read_frame(&mut a).is_ok() {});
282:    thread::spawn(move || {
410:    thread::spawn(move || {


The test needs a real spawnable translation-binary fixture; the established pattern here is OS commands (`yes`/`cmd`) for PTY children, but a JSON-lines filter needs a Rust helper. Cleanest: an auto-discovered `src/bin/` fixture referenced via `CARGO_BIN_EXE_*`.

First, add a test-only env override for the commit deadline so the no-commit FAULT test stays fast + structural (not a 5s wall-clock wait):


142	/// MANDATORY bound on one injected keystroke sequence (W2 Layer C, ADR-0022 /
143	/// doyle non-negotiable = W1's "never park forever"): the inject worker holds the
144	/// floor — buffering operator input — only while a binary's response to one
145	/// `{type:event}` is in flight. If no `{commit}` arrives within this window the
146	/// worker FLUSHES the buffer, RELEASES the floor, FAULTS the binary (terminate, no
147	/// respawn → the session degrades to raw inject), and never re-opens. A wedged or
148	/// buggy translation binary can therefore stall operator input for at most this
149	/// long. Mirrors [`CONTROLLER_WRITE_DEADLINE`]'s order (5 s).
150	const INJECT_COMMIT_DEADLINE: Duration = Duration::from_secs(5);
151
152	/// The interactive CONTROLLER's isolated sink (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST).
153	/// Symmetric with [`ViewerSink`] — a dedicated writer thread does the BLOCKING

/// MANDATORY bound on one injected keystroke sequence (W2 Layer C, ADR-0022 /
/// doyle non-negotiable = W1's "never park forever"): the inject worker holds the
/// floor — buffering operator input — only while a binary's response to one
/// `{type:event}` is in flight. If no `{commit}` arrives within this window the
/// worker FLUSHES the buffer, RELEASES the floor, FAULTS the binary (terminate, no
/// respawn → the session degrades to raw inject), and never re-opens. A wedged or
/// buggy translation binary can therefore stall operator input for at most this
/// long. Mirrors [`CONTROLLER_WRITE_DEADLINE`]'s order (5 s).
const INJECT_COMMIT_DEADLINE: Duration = Duration::from_secs(5);

/// The interactive CONTROLLER's isolated sink (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST).
/// Symmetric with [`ViewerSink`] — a dedicated writer thread does the BLOCKING


Edit

/// buggy translation binary can therefore stall operator input for at most this
/// long. Mirrors [`CONTROLLER_WRITE_DEADLINE`]'s order (5 s).
const INJECT_COMMIT_DEADLINE: Duration = Duration::from_secs(5);

/// buggy translation binary can therefore stall operator input for at most this
/// long. Mirrors [`CONTROLLER_WRITE_DEADLINE`]'s order (5 s).
const INJECT_COMMIT_DEADLINE: Duration = Duration::from_secs(5);

/// The effective commit deadline. Production is the const above; the int gate
/// (Layer G no-commit FAULT test) shrinks it via `SPT_INJECT_COMMIT_DEADLINE_MS`
/// so the structural outcome (floor releases → buffered input flushes → binary
/// faults) is asserted in well under a second rather than a 5 s wall-clock wait —
/// the deadline's PRESENCE is the invariant, not its exact length.
fn inject_commit_deadline() -> Duration {
    match std::env::var("SPT_INJECT_COMMIT_DEADLINE_MS") {
        Ok(ms) => ms
            .parse::<u64>()
            .map(Duration::from_millis)
            .unwrap_or(INJECT_COMMIT_DEADLINE),
        Err(_) => INJECT_COMMIT_DEADLINE,
    }
}


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)

/// buggy translation binary can therefore stall operator input for at most this
/// long. Mirrors [`CONTROLLER_WRITE_DEADLINE`]'s order (5 s).
const INJECT_COMMIT_DEADLINE: Duration = Duration::from_secs(5);

/// The effective commit deadline. Production is the const above; the int gate
/// (Layer G no-commit FAULT test) shrinks it via `SPT_INJECT_COMMIT_DEADLINE_MS`
/// so the structural outcome (floor releases → buffered input flushes → binary
/// faults) is asserted in well under a second rather than a 5 s wall-clock wait —
/// the deadline's PRESENCE is the invariant, not its exact length.
fn inject_commit_deadline() -> Duration {
    match std::env::var("SPT_INJECT_COMMIT_DEADLINE_MS") {
        Ok(ms) => ms
            .parse::<u64>()
            .map(Duration::from_millis)
            .unwrap_or(INJECT_COMMIT_DEADLINE),
        Err(_) => INJECT_COMMIT_DEADLINE,
    }
}

+/// The effective commit deadline. Production is the const above; the int gate

+/// (Layer G no-commit FAULT test) shrinks it via `SPT_INJECT_COMMIT_DEADLINE_MS`

+/// so the structural outcome (floor releases → buffered input flushes → binary

+/// faults) is asserted in well under a second rather than a 5 s wall-clock wait —

+/// the deadline's PRESENCE is the invariant, not its exact length.

+fn inject_commit_deadline() -> Duration {

+    match std::env::var("SPT_INJECT_COMMIT_DEADLINE_MS") {

+            .map(Duration::from_millis)

+            .unwrap_or(INJECT_COMMIT_DEADLINE),

+        Err(_) => INJECT_COMMIT_DEADLINE,

/// The interactive CONTROLLER's isolated sink (W1 / REQ-HAZARD-INJECT-CONTROL-COEXIST).

/// Symmetric with [`ViewerSink`] — a dedicated writer thread does the BLOCKING

/// socket write so a backed-up controller can never stall the session drain


        let deadline = Instant::now() + INJECT_COMMIT_DEADLINE;
        let mut committed = false;

        let deadline = Instant::now() + inject_commit_deadline();
        let mut committed = false;


        let deadline = Instant::now() + INJECT_COMMIT_DEADLINE;
        let mut committed = false;

        let deadline = Instant::now() + inject_commit_deadline();
        let mut committed = false;

-        let deadline = Instant::now() + INJECT_COMMIT_DEADLINE;

+        let deadline = Instant::now() + inject_commit_deadline();


Bash

cargo build -p spt-daemon 2>&1 | tail -3

Rebuild after env-override


   Compiling spt-daemon v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.40s


Env-override builds clean. Now dispatch **one** spt-test-engineer for Layer G int (background, per owl-reachability; no Monitor, direct timeout'd nextest runs, ≤4 iterations per the banked lesson):


Agent

Layer G int test for translation binary

Prompt:
Author the W2 Layer G INTEGRATION gate for spt-core's ADR-0022 translation-binary idle delivery (REQ-MSG-IDLE-TRANSLATION-BINARY), on a REAL broker + REAL PTY + REAL spawned translation-binary fixture. NO mocks of the PTY, transport, or the binary. This is a doyle gate point — assert STRUCTURALLY, never on wall-clock/fsync timing (flaky).

## CONTEXT — what just shipped (Layers C+E, commit f9fe5bb), so you test the REAL behavior

File `crates/spt-daemon/src/broker.rs`:
- `dispatch_spawn` — when `SpawnReq.translation_binary: Option<String>` is Some, spawns a `TranslationChild` (pure stdin→stdout JSON-lines filter) + an inject-worker thread, stored as `HostedSession.translation: Option<Arc<Translation>>`. Spawn failure fails-closed to raw inject.
- `run_inject_worker` — per queued inbound `<EVENT>`: drains stray cmds, OPENS the inject floor (operator input now buffers), sends the binary `{"type":"event","envelope":"<EVENT…>"}`, then drains the binary's stdout commands `{"key":…}`/`{"text":…}`/`{"delay_ms":…}` applying each to the PTY via `write_input`, until `{"commit":true}`. Bounded by `inject_commit_deadline()` (default 5s; OVERRIDABLE via env `SPT_INJECT_COMMIT_DEADLINE_MS` — set it small, e.g. 400, in the no-commit test). On commit OR deadline-miss OR binary-death: flushes buffered controller bytes AFTER the injected sequence + releases the floor; if not committed it ALSO FAULTs the binary (terminate, set `faulted`, worker exits → session reverts to raw inject).
- `dispatch_endpoint_input` (KIND_ENDPOINT_INPUT) — routes the whole `<EVENT>` to a live (non-faulted) translation worker; raw inject is the degenerate fallback when none/faulted.
- `dispatch_input` (KIND_INPUT, operator keystrokes) — fires a CONTENT-FREE `{"type":"input"}` ping per keystroke and buffers into the floor when a sequence is mid-flight.

File `crates/spt-daemon/src/translation.rs`: `ToBinary` (Init/Event/Input wire), `KeyCmd` (Key/Text/Delay/Commit, untagged), `key_to_bytes` (send-keys map: `ctrl+s`→0x13, `enter`→`\r`, etc.), `TranslationChild` (spawn + `terminate()` bounded reap + `pid()`), `InjectFloor`.

## THE FIXTURE BINARY (you create it)

Add an auto-discovered helper bin at `crates/spt-daemon/src/bin/xlate_choreo_fixture.rs` (cargo auto-detects `src/bin/*.rs`; the int test references it via `env!("CARGO_BIN_EXE_xlate_choreo_fixture")` — that env var is set by cargo for the crate's integration tests). It is a pure stdin→stdout JSON-lines filter:
- Reads lines from stdin. On a `{"type":"init",...}` line: ignore (or note). On `{"type":"input"}`: ignore (idle ping). On `{"type":"event","envelope":"…"}`: emit the CC choreography to stdout, one JSON object per line, flushing each:
  `{"key":"ctrl+s"}` , `{"delay_ms":50}` , `{"text":"<PAYLOAD>"}` , `{"key":"enter"}` , `{"delay_ms":50}` , `{"key":"ctrl+s"}` , then `{"commit":true}`.
- Behavior switch via env `XLATE_FIXTURE_MODE` (read once at start; default `"choreo"`): `"choreo"` = full sequence ending in `{"commit":true}`; `"nocommit"` = emit a `{"text":"<PAYLOAD>"}` (and maybe a key) but NEVER `{"commit":true}` (to exercise the deadline FAULT). Keep `<PAYLOAD>` a fixed recognizable ASCII token (e.g. `XLATE_OK`). Under nextest each test is its own process, so setting `XLATE_FIXTURE_MODE` via `std::env::set_var` in the test before constructing the broker reaches the spawned child cleanly.
- GOTCHA you must handle: `ctrl+s` = byte 0x13 = XOFF on a cooked-mode PTY child; if you assert "payload reached the PTY" by reading echoed output, an XOFF mid-sequence can pause echo. Engineer the byte-receipt assertion to be deterministic — e.g. choose/init the PTY child so flow-control can't strand the payload, or structure the choreography/child so the payload's arrival is observable regardless. Your call as the test specialist; just make it non-flaky.

## TESTS (add to `crates/spt-daemon/tests/inject_control_wedge.rs` — reuse its existing real-broker/PTY/rc-attach helpers: `net_broker`, `connect_retry`, `init_wedge_home`, the Brain client, `request_attach`/`AttachIntent`, the W1b `a_journaled_input_wedge_does_not_starve_a_concurrent_rc_attach` pattern for "a concurrent rc attach stays serviceable AND actually receives PTY bytes")

1. **Choreography happy-path (mode=choreo):** an rc-attached spt-hosted session; deliver an inbound message via KIND_ENDPOINT_INPUT (a real `<EVENT…>` envelope as the data). ASSERT: (a) the controller/rc attach KEEPS control and stays serviceable through+after the injection (KIND_SESSIONS answers, a fresh attach still serves — the W1b non-wedge bar), AND (b) the `<PAYLOAD>` token actually reaches the PTY (REAL byte receipt — not just liveness; this is the assertion W1's gate lacked). This proves inject coexists with a live controller via the atomic floor.

2. **No-commit deadline FAULT (mode=nocommit, `SPT_INJECT_COMMIT_DEADLINE_MS`=~400):** deliver an event whose binary never commits. ASSERT the controller's input does NOT wedge — after the deadline the floor releases and buffered operator keystrokes flush to the PTY, the session stays serviceable, and subsequent inbound delivery falls back to raw inject (the binary faulted). This is the mandatory bounded-buffer guard (doyle non-negotiable = W1 "never park forever").

3. **Content-free ping (doyle gate #1):** assert at int that the operator's PTY keystrokes are NEVER duplicated into the binary's stdin — the only spt-core→binary lines are `init` / `event` / content-free `input`. (The wire is unit-tested structurally already in translation.rs; here assert the integration reality — e.g. a fixture mode that echoes everything it received on stdin to a side file/stderr, and assert no keystroke payload appears.)

4. **No-zombie (doyle gate #2):** capture the fixture child's pid; after session-down/broker teardown, assert the translation child is terminated+reaped (pid gone) within the bounded reap window.

Tag each test `// [int->REQ-MSG-IDLE-TRANSLATION-BINARY]` immediately above the real assertion evidence.

## RUNNER CONTRACT (BINDING — banked lesson, do not violate)
- This file HANGS under bare `cargo test --test inject_control_wedge` (shared process-global SPT_HOME set_var race + contending brokers). RUN ONLY via `cargo nextest run -p spt-daemon --test inject_control_wedge` (process-per-test) OR `cargo test --test inject_control_wedge -- --test-threads=1`. There is a module-doc note at the top of the file stating this — keep it true.
- Do DIRECT, timeout'd runs (e.g. wrap in a reasonable timeout). DO NOT use the Monitor tool. DO NOT spin Tee/wait-loops. Cap yourself at ≤4 build/test iterations; if not green by then, STOP and report the exact failure + your diagnosis rather than thrashing.
- Files OFF-LIMITS (do not edit): `effect.rs`, `pty.rs`. You MAY edit only: the new fixture bin, `inject_control_wedge.rs`. If you believe a production-code change in `broker.rs`/`translation.rs` is needed to make a test pass, STOP and report it — do not edit production logic yourself.
- Gates to run before declaring done: `cargo nextest run -p spt-daemon --test inject_control_wedge` (your new tests + the 3 existing pass) and `cargo clippy --workspace --all-targets -- -D warnings` (CI form, must be clean). Do NOT run `traceable-reqs` or commit — I (the orchestrator) do the final stage-bump + commit + doyle gate.

Report back: the fixture design, each test's structural assertion, the exact green nextest output (test names + pass), clippy clean confirmation, and any production-code concern you hit. Keep it tight.


<owl_reachability_notice>
You have an active owl perch. Launch this Agent with run_in_background: true so you stay reachable for incoming messages while it works.
</owl_reachability_notice>


Async agent launched successfully.
agentId: a8dba5b01f67f2b61 (internal ID - do not mention to user. Use SendMessage with to: 'a8dba5b01f67f2b61' to continue this agent.)
The agent is working in the background. You will be notified automatically when it completes.
Do not duplicate this agent's work — avoid working with the same files or topics it is using. Work on non-overlapping tasks, or briefly tell the user what you launched and end your response.
output_file: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\a8dba5b01f67f2b61.output
Do NOT Read or tail this file via the shell tool — it is the full subagent JSONL transcript and reading it will overflow your context. If the user asks for progress, say the agent is still running; you'll get a completion notification.

Layer G int test for translation binary

Prompt:
Author the W2 Layer G INTEGRATION gate for spt-core's ADR-0022 translation-binary idle delivery (REQ-MSG-IDLE-TRANSLATION-BINARY), on a REAL broker + REAL PTY + REAL spawned translation-binary fixture. NO mocks of the PTY, transport, or the binary. This is a doyle gate point — assert STRUCTURALLY, never on wall-clock/fsync timing (flaky).

## CONTEXT — what just shipped (Layers C+E, commit f9fe5bb), so you test the REAL behavior

File `crates/spt-daemon/src/broker.rs`:
- `dispatch_spawn` — when `SpawnReq.translation_binary: Option<String>` is Some, spawns a `TranslationChild` (pure stdin→stdout JSON-lines filter) + an inject-worker thread, stored as `HostedSession.translation: Option<Arc<Translation>>`. Spawn failure fails-closed to raw inject.
- `run_inject_worker` — per queued inbound `<EVENT>`: drains stray cmds, OPENS the inject floor (operator input now buffers), sends the binary `{"type":"event","envelope":"<EVENT…>"}`, then drains the binary's stdout commands `{"key":…}`/`{"text":…}`/`{"delay_ms":…}` applying each to the PTY via `write_input`, until `{"commit":true}`. Bounded by `inject_commit_deadline()` (default 5s; OVERRIDABLE via env `SPT_INJECT_COMMIT_DEADLINE_MS` — set it small, e.g. 400, in the no-commit test). On commit OR deadline-miss OR binary-death: flushes buffered controller bytes AFTER the injected sequence + releases the floor; if not committed it ALSO FAULTs the binary (terminate, set `faulted`, worker exits → session reverts to raw inject).
- `dispatch_endpoint_input` (KIND_ENDPOINT_INPUT) — routes the whole `<EVENT>` to a live (non-faulted) translation worker; raw inject is the degenerate fallback when none/faulted.
- `dispatch_input` (KIND_INPUT, operator keystrokes) — fires a CONTENT-FREE `{"type":"input"}` ping per keystroke and buffers into the floor when a sequence is mid-flight.

File `crates/spt-daemon/src/translation.rs`: `ToBinary` (Init/Event/Input wire), `KeyCmd` (Key/Text/Delay/Commit, untagged), `key_to_bytes` (send-keys map: `ctrl+s`→0x13, `enter`→`\r`, etc.), `TranslationChild` (spawn + `terminate()` bounded reap + `pid()`), `InjectFloor`.

## THE FIXTURE BINARY (you create it)

Add an auto-discovered helper bin at `crates/spt-daemon/src/bin/xlate_choreo_fixture.rs` (cargo auto-detects `src/bin/*.rs`; the int test references it via `env!("CARGO_BIN_EXE_xlate_choreo_fixture")` — that env var is set by cargo for the crate's integration tests). It is a pure stdin→stdout JSON-lines filter:
- Reads lines from stdin. On a `{"type":"init",...}` line: ignore (or note). On `{"type":"input"}`: ignore (idle ping). On `{"type":"event","envelope":"…"}`: emit the CC choreography to stdout, one JSON object per line, flushing each:
  `{"key":"ctrl+s"}` , `{"delay_ms":50}` , `{"text":"<PAYLOAD>"}` , `{"key":"enter"}` , `{"delay_ms":50}` , `{"key":"ctrl+s"}` , then `{"commit":true}`.
- Behavior switch via env `XLATE_FIXTURE_MODE` (read once at start; default `"choreo"`): `"choreo"` = full sequence ending in `{"commit":true}`; `"nocommit"` = emit a `{"text":"<PAYLOAD>"}` (and maybe a key) but NEVER `{"commit":true}` (to exercise the deadline FAULT). Keep `<PAYLOAD>` a fixed recognizable ASCII token (e.g. `XLATE_OK`). Under nextest each test is its own process, so setting `XLATE_FIXTURE_MODE` via `std::env::set_var` in the test before constructing the broker reaches the spawned child cleanly.
- GOTCHA you must handle: `ctrl+s` = byte 0x13 = XOFF on a cooked-mode PTY child; if you assert "payload reached the PTY" by reading echoed output, an XOFF mid-sequence can pause echo. Engineer the byte-receipt assertion to be deterministic — e.g. choose/init the PTY child so flow-control can't strand the payload, or structure the choreography/child so the payload's arrival is observable regardless. Your call as the test specialist; just make it non-flaky.

## TESTS (add to `crates/spt-daemon/tests/inject_control_wedge.rs` — reuse its existing real-broker/PTY/rc-attach helpers: `net_broker`, `connect_retry`, `init_wedge_home`, the Brain client, `request_attach`/`AttachIntent`, the W1b `a_journaled_input_wedge_does_not_starve_a_concurrent_rc_attach` pattern for "a concurrent rc attach stays serviceable AND actually receives PTY bytes")

1. **Choreography happy-path (mode=choreo):** an rc-attached spt-hosted session; deliver an inbound message via KIND_ENDPOINT_INPUT (a real `<EVENT…>` envelope as the data). ASSERT: (a) the controller/rc attach KEEPS control and stays serviceable through+after the injection (KIND_SESSIONS answers, a fresh attach still serves — the W1b non-wedge bar), AND (b) the `<PAYLOAD>` token actually reaches the PTY (REAL byte receipt — not just liveness; this is the assertion W1's gate lacked). This proves inject coexists with a live controller via the atomic floor.

2. **No-commit deadline FAULT (mode=nocommit, `SPT_INJECT_COMMIT_DEADLINE_MS`=~400):** deliver an event whose binary never commits. ASSERT the controller's input does NOT wedge — after the deadline the floor releases and buffered operator keystrokes flush to the PTY, the session stays serviceable, and subsequent inbound delivery falls back to raw inject (the binary faulted). This is the mandatory bounded-buffer guard (doyle non-negotiable = W1 "never park forever").

3. **Content-free ping (doyle gate #1):** assert at int that the operator's PTY keystrokes are NEVER duplicated into the binary's stdin — the only spt-core→binary lines are `init` / `event` / content-free `input`. (The wire is unit-tested structurally already in translation.rs; here assert the integration reality — e.g. a fixture mode that echoes everything it received on stdin to a side file/stderr, and assert no keystroke payload appears.)

4. **No-zombie (doyle gate #2):** capture the fixture child's pid; after session-down/broker teardown, assert the translation child is terminated+reaped (pid gone) within the bounded reap window.

Tag each test `// [int->REQ-MSG-IDLE-TRANSLATION-BINARY]` immediately above the real assertion evidence.

## RUNNER CONTRACT (BINDING — banked lesson, do not violate)
- This file HANGS under bare `cargo test --test inject_control_wedge` (shared process-global SPT_HOME set_var race + contending brokers). RUN ONLY via `cargo nextest run -p spt-daemon --test inject_control_wedge` (process-per-test) OR `cargo test --test inject_control_wedge -- --test-threads=1`. There is a module-doc note at the top of the file stating this — keep it true.
- Do DIRECT, timeout'd runs (e.g. wrap in a reasonable timeout). DO NOT use the Monitor tool. DO NOT spin Tee/wait-loops. Cap yourself at ≤4 build/test iterations; if not green by then, STOP and report the exact failure + your diagnosis rather than thrashing.
- Files OFF-LIMITS (do not edit): `effect.rs`, `pty.rs`. You MAY edit only: the new fixture bin, `inject_control_wedge.rs`. If you believe a production-code change in `broker.rs`/`translation.rs` is needed to make a test pass, STOP and report it — do not edit production logic yourself.
- Gates to run before declaring done: `cargo nextest run -p spt-daemon --test inject_control_wedge` (your new tests + the 3 existing pass) and `cargo clippy --workspace --all-targets -- -D warnings` (CI form, must be clean). Do NOT run `traceable-reqs` or commit — I (the orchestrator) do the final stage-bump + commit + doyle gate.

Report back: the fixture design, each test's structural assertion, the exact green nextest output (test names + pass), clippy clean confirmation, and any production-code concern you hit. Keep it tight.


Layer G subagent running in background — it owns `inject_control_wedge.rs` + the fixture bin, and it'll run cargo. To avoid the banked artifact-lock collision, I'll run NO cargo and not touch those files while it works. Safe non-overlapping work: update the JIT layer ledger to reflect landed state.


<owl_messages>
<owl_message from="todlando-w172" priority="info">[WORKING_PERCH_NOTICE] Subagent working perch created.
Perch: todlando-w172
Agent type: spt-test-engineer
To message it: $OWL send todlando-w172 todlando</owl_message>
</owl_messages>

[OWL SYSTEM] Informational owl messages above — no acknowledgment needed. Continue your current task.


143	**Layer ledger:**
144	- A. Manifest table key + validate + schema regen — **DONE @c73eb6a** (REQ doc stage).
145	- B. `spt-daemon/translation.rs` — ToBinary(init/event/input) + KeyCmd(key/text/
146	  delay_ms/commit, untagged) + `key_to_bytes` send-keys map + `TranslationChild`
147	  (piped stdin, stdout-reader thread→Sender<KeyCmd>, Drop=bounded reap 500ms) —
148	  **DONE @611205c**. 3 unit tests (wire round-trips, content-free ping, send-keys).
149	- D-plumbing. `SpawnReq.translation_binary: Option<String>` (additive) threaded from
150	  manifest via harnesshost — **IN PROGRESS** (field added to msg.rs; ~25 struct-
151	  literal sites swept to `translation_binary: None`; then harnesshost/
152	  launch_harness_brokered_in fills it from `manifest.message_idle_translation_binary
153	  .path`).
154	- C. **NEXT** — broker atomic apply: per-session inject floor (state) + controller-
155	  input buffer in `dispatch_input`; on `{type:event}` open floor & feed binary, drain
156	  `KeyCmd`s via an applier thread (Receiver from TranslationChild), apply key/text→
157	  bytes via `session.write_input` with `delay_ms` sleeps, `{commit}` closes floor &
158	  flushes buffered controller bytes; `INJECT_COMMIT_DEADLINE` guard. Reuses W1's
159	  off-drain writer. Wire `{type:input}` ping emission from `dispatch_input`.
160	- E. Routing: `dispatch_endpoint_input` — if a translation child is attached, send
161	  `{type:event,envelope}` to it instead of raw inject; raw inject = degenerate
162	  fallback when no binary. Poll-listener preference rule.
163	- F. Unix park (b)/(c) bound: bounded/fail-fast `write_input` + DSR-answer must not
164	  hold the writer mutex across a blocking write (spt-term/pty.rs); gate gravity-linux
165	  (cfg(unix) park-(b) assert in inject_control_wedge.rs).
166	- G. int gate: REAL dummy translation-binary fixture → delivers inbound into an
167	  rc-attached spt-hosted session via ctrl+s→delay→text→enter→delay→ctrl+s→commit
168	  choreography WITHOUT breaking control; + no-commit-deadline test; + no-zombie +
169	  content-free assertions. Author via spt-test-engineer. doyle gates here.
170	- Tests authored via spt-test-engineer subagent. REQ stages bump impl/unit/int as
171	  layers land; activate full set before declaring W2 done. traceable-reqs check + CI
172	  workspace clippy (`cargo clippy --workspace --all-targets -- -D warnings`) + real
173	  bin rebuild (`cargo build -p spt --bin spt`) at the W2 gate.

**Layer ledger:**
- A. Manifest table key + validate + schema regen — **DONE @c73eb6a** (REQ doc stage).
- B. `spt-daemon/translation.rs` — ToBinary(init/event/input) + KeyCmd(key/text/
  delay_ms/commit, untagged) + `key_to_bytes` send-keys map + `TranslationChild`
  (piped stdin, stdout-reader thread→Sender<KeyCmd>, Drop=bounded reap 500ms) —
  **DONE @611205c**. 3 unit tests (wire round-trips, content-free ping, send-keys).
- D-plumbing. `SpawnReq.translation_binary: Option<String>` (additive) threaded from
  manifest via harnesshost — **IN PROGRESS** (field added to msg.rs; ~25 struct-
  literal sites swept to `translation_binary: None`; then harnesshost/
  launch_harness_brokered_in fills it from `manifest.message_idle_translation_binary
  .path`).
- C. **NEXT** — broker atomic apply: per-session inject floor (state) + controller-
  input buffer in `dispatch_input`; on `{type:event}` open floor & feed binary, drain
  `KeyCmd`s via an applier thread (Receiver from TranslationChild), apply key/text→
  bytes via `session.write_input` with `delay_ms` sleeps, `{commit}` closes floor &
  flushes buffered controller bytes; `INJECT_COMMIT_DEADLINE` guard. Reuses W1's
  off-drain writer. Wire `{type:input}` ping emission from `dispatch_input`.
- E. Routing: `dispatch_endpoint_input` — if a translation child is attached, send
  `{type:event,envelope}` to it instead of raw inject; raw inject = degenerate
  fallback when no binary. Poll-listener preference rule.
- F. Unix park (b)/(c) bound: bounded/fail-fast `write_input` + DSR-answer must not
  hold the writer mutex across a blocking write (spt-term/pty.rs); gate gravity-linux
  (cfg(unix) park-(b) assert in inject_control_wedge.rs).
- G. int gate: REAL dummy translation-binary fixture → delivers inbound into an
  rc-attached spt-hosted session via ctrl+s→delay→text→enter→delay→ctrl+s→commit
  choreography WITHOUT breaking control; + no-commit-deadline test; + no-zombie +
  content-free assertions. Author via spt-test-engineer. doyle gates here.
- Tests authored via spt-test-engineer subagent. REQ stages bump impl/unit/int as
  layers land; activate full set before declaring W2 done. traceable-reqs check + CI
  workspace clippy (`cargo clippy --workspace --all-targets -- -D warnings`) + real
  bin rebuild (`cargo build -p spt --bin spt`) at the W2 gate.


Edit

- D-plumbing. `SpawnReq.translation_binary: Option<String>` (additive) threaded from
  manifest via harnesshost — **IN PROGRESS** (field added to msg.rs; ~25 struct-
  literal sites swept to `translation_binary: None`; then harnesshost/
  launch_harness_brokered_in fills it from `manifest.message_idle_translation_binary
  .path`).
- C. **NEXT** — broker atomic apply: per-session inject floor (state) + controller-
  input buffer in `dispatch_input`; on `{type:event}` open floor & feed binary, drain
  `KeyCmd`s via an applier thread (Receiver from TranslationChild), apply key/text→
  bytes via `session.write_input` with `delay_ms` sleeps, `{commit}` closes floor &
  flushes buffered controller bytes; `INJECT_COMMIT_DEADLINE` guard. Reuses W1's
  off-drain writer. Wire `{type:input}` ping emission from `dispatch_input`.
- E. Routing: `dispatch_endpoint_input` — if a translation child is attached, send
  `{type:event,envelope}` to it instead of raw inject; raw inject = degenerate
  fallback when no binary. Poll-listener preference rule.
- F. Unix park (b)/(c) bound: bounded/fail-fast `write_input` + DSR-answer must not
  hold the writer mutex across a blocking write (spt-term/pty.rs); gate gravity-linux
  (cfg(unix) park-(b) assert in inject_control_wedge.rs).
- G. int gate: REAL dummy translation-binary fixture → delivers inbound into an
  rc-attached spt-hosted session via ctrl+s→delay→text→enter→delay→ctrl+s→commit
  choreography WITHOUT breaking control; + no-commit-deadline test; + no-zombie +
  content-free assertions. Author via spt-test-engineer. doyle gates here.

- D-plumbing. `SpawnReq.translation_binary: Option<String>` (additive) threaded from
  manifest via harnesshost — **DONE @7166f52** (field on msg.rs; struct-literal sites
  swept to `None`; harnesshost fills from `manifest.message_idle_translation_binary.path`).
- C. **DONE @f9fe5bb** — broker atomic apply: `Translation` per-session handle (floor +
  `event_tx` queue + shared `Arc<TranslationChild>` + `faulted` flag) on `HostedSession`;
  `run_inject_worker` thread (one per translation-bearing session) drains queued events
  one-at-a-time: open floor → feed binary `{type:event}` → drain key/text(`write_input`)/
  delay(clamped sleep)/commit → `flush_inject_floor` (buffered controller bytes off-lock
  AFTER the sequence) → release. `inject_commit_deadline()` guard (5s; env-override
  `SPT_INJECT_COMMIT_DEADLINE_MS` for the fast int) → FAULT on miss/binary-death
  (`fault_translation` terminate, no respawn → raw-inject degrade). `dispatch_input`:
  content-free `{type:input}` ping per keystroke + buffer-if-held (op_id path buffers
  INSIDE the journal effect to keep ack+dedup). `TranslationChild.terminate()` made
  `&self`/Arc-callable for the FAULT-reap.
- E. **DONE @f9fe5bb** — `dispatch_endpoint_input` routes the whole `<EVENT>` to a live
  (non-faulted) worker via `event_tx`; raw inject = degenerate fallback (none/faulted/
  worker-gone). (Poll-listener preference rule = a later wave; not blocking W2 close.)
- F. **DEFERRED to gravity-linux follow-up** (cfg(unix) only; can't validate on Windows;
  doyle ruling: minimum is C-core, F rides else gravity follow-up). Unix park (b)/(c)
  bound: bounded/fail-fast `write_input` + DSR off the writer mutex (spt-term/pty.rs);
  the int's cfg(unix) park-(b) assert tracks it. Shared with W1b F3.
- G. **IN PROGRESS (spt-test-engineer a8dba5b0 / perch todlando-w172)** — int gate: REAL
  dummy translation-binary fixture (`src/bin/xlate_choreo_fixture.rs`) → delivers inbound
  into an rc-attached spt-hosted session via ctrl+s→delay→text→enter→delay→ctrl+s→commit
  WITHOUT breaking control (+ REAL byte receipt, the W1b-grade assert) + no-commit
  deadline FAULT (env-shrunk) + content-free ping + no-zombie. doyle gates here.


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

- D-plumbing. `SpawnReq.translation_binary: Option<String>` (additive) threaded from
  manifest via harnesshost — **IN PROGRESS** (field added to msg.rs; ~25 struct-
  literal sites swept to `translation_binary: None`; then harnesshost/
  launch_harness_brokered_in fills it from `manifest.message_idle_translation_binary
  .path`).
- C. **NEXT** — broker atomic apply: per-session inject floor (state) + controller-
  input buffer in `dispatch_input`; on `{type:event}` open floor & feed binary, drain
  `KeyCmd`s via an applier thread (Receiver from TranslationChild), apply key/text→
  bytes via `session.write_input` with `delay_ms` sleeps, `{commit}` closes floor &
  flushes buffered controller bytes; `INJECT_COMMIT_DEADLINE` guard. Reuses W1's
  off-drain writer. Wire `{type:input}` ping emission from `dispatch_input`.
- E. Routing: `dispatch_endpoint_input` — if a translation child is attached, send
  `{type:event,envelope}` to it instead of raw inject; raw inject = degenerate
  fallback when no binary. Poll-listener preference rule.
- F. Unix park (b)/(c) bound: bounded/fail-fast `write_input` + DSR-answer must not
  hold the writer mutex across a blocking write (spt-term/pty.rs); gate gravity-linux
  (cfg(unix) park-(b) assert in inject_control_wedge.rs).
- G. int gate: REAL dummy translation-binary fixture → delivers inbound into an
  rc-attached spt-hosted session via ctrl+s→delay→text→enter→delay→ctrl+s→commit
  choreography WITHOUT breaking control; + no-commit-deadline test; + no-zombie +
  content-free assertions. Author via spt-test-engineer. doyle gates here.

- D-plumbing. `SpawnReq.translation_binary: Option<String>` (additive) threaded from
  manifest via harnesshost — **DONE @7166f52** (field on msg.rs; struct-literal sites
  swept to `None`; harnesshost fills from `manifest.message_idle_translation_binary.path`).
- C. **DONE @f9fe5bb** — broker atomic apply: `Translation` per-session handle (floor +
  `event_tx` queue + shared `Arc<TranslationChild>` + `faulted` flag) on `HostedSession`;
  `run_inject_worker` thread (one per translation-bearing session) drains queued events
  one-at-a-time: open floor → feed binary `{type:event}` → drain key/text(`write_input`)/
  delay(clamped sleep)/commit → `flush_inject_floor` (buffered controller bytes off-lock
  AFTER the sequence) → release. `inject_commit_deadline()` guard (5s; env-override
  `SPT_INJECT_COMMIT_DEADLINE_MS` for the fast int) → FAULT on miss/binary-death
  (`fault_translation` terminate, no respawn → raw-inject degrade). `dispatch_input`:
  content-free `{type:input}` ping per keystroke + buffer-if-held (op_id path buffers
  INSIDE the journal effect to keep ack+dedup). `TranslationChild.terminate()` made
  `&self`/Arc-callable for the FAULT-reap.
- E. **DONE @f9fe5bb** — `dispatch_endpoint_input` routes the whole `<EVENT>` to a live
  (non-faulted) worker via `event_tx`; raw inject = degenerate fallback (none/faulted/
  worker-gone). (Poll-listener preference rule = a later wave; not blocking W2 close.)
- F. **DEFERRED to gravity-linux follow-up** (cfg(unix) only; can't validate on Windows;
  doyle ruling: minimum is C-core, F rides else gravity follow-up). Unix park (b)/(c)
  bound: bounded/fail-fast `write_input` + DSR off the writer mutex (spt-term/pty.rs);
  the int's cfg(unix) park-(b) assert tracks it. Shared with W1b F3.
- G. **IN PROGRESS (spt-test-engineer a8dba5b0 / perch todlando-w172)** — int gate: REAL
  dummy translation-binary fixture (`src/bin/xlate_choreo_fixture.rs`) → delivers inbound
  into an rc-attached spt-hosted session via ctrl+s→delay→text→enter→delay→ctrl+s→commit
  WITHOUT breaking control (+ REAL byte receipt, the W1b-grade assert) + no-commit
  deadline FAULT (env-shrunk) + content-free ping + no-zombie. doyle gates here.

(piped stdin, stdout-reader thread→Sender<KeyCmd>, Drop=bounded reap 500ms) —

**DONE @611205c**. 3 unit tests (wire round-trips, content-free ping, send-keys).

-  manifest via harnesshost — **IN PROGRESS** (field added to msg.rs; ~25 struct-

-  launch_harness_brokered_in fills it from `manifest.message_idle_translation_binary

-- C. **NEXT** — broker atomic apply: per-session inject floor (state) + controller-

-  `KeyCmd`s via an applier thread (Receiver from TranslationChild), apply key/text→

-  bytes via `session.write_input` with `delay_ms` sleeps, `{commit}` closes floor &

-  flushes buffered controller bytes; `INJECT_COMMIT_DEADLINE` guard. Reuses W1's

-  off-drain writer. Wire `{type:input}` ping emission from `dispatch_input`.

-- E. Routing: `dispatch_endpoint_input` — if a translation child is attached, send

-  `{type:event,envelope}` to it instead of raw inject; raw inject = degenerate

-  fallback when no binary. Poll-listener preference rule.

-- F. Unix park (b)/(c) bound: bounded/fail-fast `write_input` + DSR-answer must not

-  hold the writer mutex across a blocking write (spt-term/pty.rs); gate gravity-linux

-  (cfg(unix) park-(b) assert in inject_control_wedge.rs).

-- G. int gate: REAL dummy translation-binary fixture → delivers inbound into an

-  rc-attached spt-hosted session via ctrl+s→delay→text→enter→delay→ctrl+s→commit

-  choreography WITHOUT breaking control; + no-commit-deadline test; + no-zombie +

-  content-free assertions. Author via spt-test-engineer. doyle gates here.

+  manifest via harnesshost — **DONE @7166f52** (field on msg.rs; struct-literal sites

+  swept to `None`; harnesshost fills from `manifest.message_idle_translation_binary.path`).

+- C. **DONE @f9fe5bb** — broker atomic apply: `Translation` per-session handle (floor +

+  `run_inject_worker` thread (one per translation-bearing session) drains queued events

+  one-at-a-time: open floor → feed binary `{type:event}` → drain key/text(`write_input`)/

+  delay(clamped sleep)/commit → `flush_inject_floor` (buffered controller bytes off-lock

+  AFTER the sequence) → release. `inject_commit_deadline()` guard (5s; env-override

+  `SPT_INJECT_COMMIT_DEADLINE_MS` for the fast int) → FAULT on miss/binary-death

+  (`fault_translation` terminate, no respawn → raw-inject degrade). `dispatch_input`:

+  content-free `{type:input}` ping per keystroke + buffer-if-held (op_id path buffers

+  INSIDE the journal effect to keep ack+dedup). `TranslationChild.terminate()` made

+  `&self`/Arc-callable for the FAULT-reap.

+- E. **DONE @f9fe5bb** — `dispatch_endpoint_input` routes the whole `<EVENT>` to a live

+  (non-faulted) worker via `event_tx`; raw inject = degenerate fallback (none/faulted/

+  worker-gone). (Poll-listener preference rule = a later wave; not blocking W2 close.)

+- F. **DEFERRED to gravity-linux follow-up** (cfg(unix) only; can't validate on Windows;

+  doyle ruling: minimum is C-core, F rides else gravity follow-up). Unix park (b)/(c)

+  bound: bounded/fail-fast `write_input` + DSR off the writer mutex (spt-term/pty.rs);

+  the int's cfg(unix) park-(b) assert tracks it. Shared with W1b F3.

+- G. **IN PROGRESS (spt-test-engineer a8dba5b0 / perch todlando-w172)** — int gate: REAL

+  dummy translation-binary fixture (`src/bin/xlate_choreo_fixture.rs`) → delivers inbound

+  into an rc-attached spt-hosted session via ctrl+s→delay→text→enter→delay→ctrl+s→commit

+  WITHOUT breaking control (+ REAL byte receipt, the W1b-grade assert) + no-commit

+  deadline FAULT (env-shrunk) + content-free ping + no-zombie. doyle gates here.

- Tests authored via spt-test-engineer subagent. REQ stages bump impl/unit/int as

layers land; activate full set before declaring W2 done. traceable-reqs check + CI

workspace clippy (`cargo clippy --workspace --all-targets -- -D warnings`) + real


JIT ledger updated. The env-override (broker.rs) + JIT are uncommitted; I'll bundle them with the Layer G test into one "W2 Layer G" commit when the subagent returns green (cleaner than fragmenting). For now, surface progress to doyle and wait for the completion notification — no cargo while the subagent builds.


Bash

$OWL send doyle <<'EOF'
W2 progress — Layer C+E LANDED @f9fe5bb (gates green: clippy workspace clean, translation units 5/5, W1b int 3/3 no-regression, traceable EXIT=0, REQ-MSG-IDLE-TRANSLATION-BINARY now [doc,impl,unit]).

Layer C proper: Translation handle on HostedSession (floor + event_tx queue + shared Arc<TranslationChild> + faulted flag); run_inject_worker thread drains queued <EVENT>s one-at-a-time (open floor → feed binary {type:event} → drain key/text/delay/commit → flush buffered controller bytes off-lock AFTER sequence → release). inject_commit_deadline() = 5s, env-override SPT_INJECT_COMMIT_DEADLINE_MS for fast int; FAULT on miss/death (terminate, no respawn → raw-inject degrade). dispatch_input: content-free {type:input} ping + buffer-if-held (op_id buffers INSIDE journal effect to keep ack+dedup). TranslationChild.terminate() made &self/Arc-callable for FAULT-reap.
Layer E: dispatch_endpoint_input routes whole <EVENT> to live worker; raw inject = degenerate fallback (none/faulted).

Layer F (Unix park b/c bound): DEFERRED to a gravity-linux follow-up — cfg(unix) only, can't validate on Windows, per your "minimum is C-core, F rides else gravity follow-up" ruling; int's cfg(unix) park-(b) assert tracks it. Shared with W1b F3.

Layer G int: dispatched to ONE spt-test-engineer (perch todlando-w172), background, runner-contract + ≤4-iter + no-Monitor constrained. Real fixture bin src/bin/xlate_choreo_fixture.rs driving ctrl+s→delay→text→enter→delay→ctrl+s→commit into an rc-attached session; asserts control kept + REAL byte receipt (the W1b-grade assert) + no-commit deadline FAULT + content-free ping + no-zombie. Will surface for your gate when green; then bump REQ to add int + commit Layer G (folds the env-override) + W2 close.
EOF
echo "SENT exit=$?"

Surface W2 C+E/F/G status to doyle


➜ SENT:doyle
SENT exit=0


Status — progress this session:

**W7-swap** @95f742f ✓ — `normalize_key_byte` one-way→SWAP (`0x08↔0x7f`). Backspace=char-del, Ctrl+Backspace=word-del (native Win11). doyle GATE PASS.

**F1 hang-guard** @6034479 ✓ — runner-contract doc note on `inject_control_wedge` (nextest/`--test-threads=1`, never bare `cargo test`). doyle GATE PASS.

**W2 Layer C+E** @f9fe5bb ✓ — the keystone. Per-session `Translation` handle + `run_inject_worker` thread: open floor → feed binary `{type:event}` → drain key/text/delay/commit → flush buffered controller bytes AFTER sequence → release. `INJECT_COMMIT_DEADLINE` 5s + FAULT (terminate, no respawn → raw-inject degrade). `dispatch_input` content-free ping + buffer-if-held. `dispatch_endpoint_input` routes `<EVENT>` to worker. Gates green: clippy clean, units 5/5, W1b int 3/3 no-regress, traceable EXIT=0.

**Layer F** — deferred to gravity-linux follow-up (cfg(unix), unverifiable on Windows; doyle-sanctioned).

**Layer G int** — dispatched to ONE spt-test-engineer (background, perch `todlando-w172`). Real fixture bin drives the full CC choreography into an rc-attached session; asserts control-kept + REAL byte-receipt + no-commit FAULT + content-free + no-zombie. Running NO cargo while it builds (banked artifact-lock lesson).

Waiting on the subagent. When green: bump REQ +int, commit Layer G (folds the env-override), W2 closes → surface to doyle for the gate. Will resume on its completion notification.


<task-notification>
<task-id>bv8r78fk1</task-id>
<summary>Monitor event: "« spt event »"</summary>
<event><EVENT-PART seq="1/3" id="064ec3c0" type="msg" from="doyle">doyle ACK — W2 C+E @f9fe5bb noted (translation units 5/5, W1b int no-regression, REQ [doc,impl,unit]). Design decisions all tracked + correct: per-event floor→feed→drain→flush-off-lock, 5s commit-deadline w/ SPT_INJECT_COMMIT_DEADLINE_MS override, FAULT→terminate-no-respawn→raw-inject degrade, content-free {</EVENT-PART>
<EVENT-PART seq="2/3" id="064ec3c0">type:input} ping (buffer-if-held inside the journal effect), Layer F→gravity follow-up (shared w/ W1b F3). I'll do the AUTHORITATIVE W2 gate at Layer G close (not the intermediate layers) — gate criteria locked: REAL fixture (no mocks) + real byte receipt (W1b-grade) + commit-deadline FAULT proven + content-free ping asserts NO PTY-input-c</EVENT-PART>
<EVENT-PART seq="3/3" id="064ec3c0">ontent leaks to the binary's stdin (privacy) + no-zombie reap on fault/down. Your xlate_choreo_fixture spec hits all five. Surface when green. Meanwhile I'm mid operator W7-swap PATH deploy. Carry on.</EVENT-PART></event>
</task-notification>


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


doyle ACK noted — authoritative W2 gate locked for Layer G close, 5 criteria; fixture spec hits all five. doyle mid W7-swap PATH deploy. No reply needed.

Layer G subagent still building. Holding — no cargo while it runs. Will resume on its completion notification, then surface green to doyle.


<task-notification>
<task-id>a8dba5b01f67f2b61</task-id>
<tool-use-id>toolu_01UyYD85Wr6TS85pUGNFk4M6</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\73618172-412e-4954-b8aa-e2c8955dfd49\tasks\a8dba5b01f67f2b61.output</output-file>
<status>completed</status>
<summary>Agent "Layer G int test for translation binary" came to rest</summary>
<note>A task-notification fires each time this agent comes to rest with no live background children of its own. The user can send it another message and resume it, so the same task-id may notify more than once.</note>
<result>Done. Here's the report.

## W2 Layer G integration gate — delivered, green

**Files:**
- `C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\src\bin\xlate_choreo_fixture.rs` — the real ADR-0022 translation binary fixture (pure stdin→stdout JSON-lines filter).
- `C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\tests\inject_control_wedge.rs` — 4 new int tests appended (g1–g4) + helpers.
- (The `pty_sink_fixture.rs` bin I trialed was removed — see the ConPTY finding below.)

### Fixture design (`xlate_choreo_fixture`)
A real spawned binary, lifecycle-managed by the broker. Reads JSON lines on stdin; ignores `init`/`input`; on `{type:event}` emits the CC choreography one flushed line at a time: `ctrl+s` · `delay_ms:50` · `text:XLATE_OK` · `enter` · `delay_ms:50` · `ctrl+s` · `ctrl+q` · `commit`. Mode switch via `XLATE_FIXTURE_MODE` (`choreo` default / `nocommit` drops `{commit}`). Appends every received stdin line to `XLATE_FIXTURE_STDIN_LOG` for the privacy assertion. The trailing `ctrl+q` (XON) is a deliberate, test-specialist choice to restore terminal flow the `ctrl+s` (XOFF) chord paused — keeping the real `ctrl+s`/0x13 mapping + floor exercise intact. The PTY child is the proven cross-OS echo child (`findstr "."` / `cat`), so injected/operator bytes echo back over the **real broker output ring** — no mocks of PTY, transport, or binary.

### The four tests (each `// [int->REQ-MSG-IDLE-TRANSLATION-BINARY]`, structural assertions only)
1. **g1 choreography happy-path** — delivers a real `<EVENT>` via `KIND_ENDPOINT_INPUT`; asserts: event accepted; **session stays serviceable** (`KIND_SESSIONS` answers) through+after inject; **a fresh attach is serviced** (drives input, sees echo). **Byte-receipt of `XLATE_OK`** is asserted hard on `cfg(unix)` and captured on `cfg(windows)` — see finding below.
2. **g2 no-commit FAULT** (`SPT_INJECT_COMMIT_DEADLINE_MS=400`) — asserts buffered operator keystrokes **flush to the PTY** after the deadline (input never wedges), session stays serviceable, and a **subsequent delivery falls back to raw inject** (binary faulted).
3. **g3 content-free ping** — operator types a secret; asserts it reaches the PTY (echoes), **never leaks into the binary's stdin log**, and the binary **did** see `{type:input}` pings (so the privacy check isn't vacuous).
4. **g4 no-zombie** — captures the translation child's pid; asserts session leaves the broker table and the **child is reaped within the bounded window** after session-down.

### Green output
```
7 tests run: 7 passed, 0 skipped   (g1–g4 + the 3 existing W1/W1b)
```
`cargo clippy --workspace --all-targets -- -D warnings` → **clean** (`Finished`).

### Platform finding (banked, not a production concern)
**Windows ConPTY does not surface the cooked echo of programmatically-injected PTY input.** An operator keystroke echoes (g3 proves it), but the inject worker's `write_input` of the same `XLATE_OK` bytes does not re-emit through `findstr`'s echo, and XON-after-XOFF doesn't recover it. A custom Rust child reading `io::stdin()` doesn't receive ConPTY input at all (only console-native apps do), so a side-file recorder is also unusable on Windows. I applied this file's **existing** ConPTY-vs-forkpty divergence stance: hard-assert injected byte-receipt on `cfg(unix)` (gravity-linux CI is the venue), capture+print on Windows, while still hard-asserting the three platform-independent facts. This is the same pattern the W1 park-(b)/W1b gates already use — not an assertion weakening.

### No production-code concerns
broker.rs/translation.rs behaved exactly as documented (floor open→drain→commit→flush, deadline FAULT→raw-inject fallback, content-free ping, bounded reap all verified live). No edits to `effect.rs`/`pty.rs`/production logic. Leaving the stage-bump, `traceable-reqs`, commit, and doyle gate to you.

Run locally: `cargo nextest run -p spt-daemon --test inject_control_wedge` (process-per-test; never bare `cargo test` on this file).</result>
<usage><subagent_tokens>226536</subagent_tokens><tool_uses>81</tool_uses><duration_ms>1999869</duration_ms></usage>
</task-notification>


1	//! v0.13.0 W1 (REQ-HAZARD-INJECT-CONTROL-COEXIST) — the CONTROLLER-DELIVERY
2	//! non-wedge gate, on a REAL broker hosting a REAL PTY child over the real local
3	//! IPC (no mocks of the PTY or the transport).
4	//!
5	//! ## The bug this gate locks down (park (a), the output-stall / wedge root)
6	//!
7	//! PRE-FIX (v0.12.x), [`OutputLog::append`] fanned each output chunk to the
8	//! CONTROLLER via a SYNCHRONOUS, BLOCKING `write_frame` held INLINE on the
9	//! session's single drain thread, while the `Mutex<OutputLog>` was locked.
10	//! VIEWERS instead get a dedicated writer thread + a bounded, evicting
11	//! `sync_channel` and can never stall the drain. So a slow / non-draining
12	//! controller (here a controller connection that stops reading while the child
13	//! floods) parked the drain INSIDE `append` STILL HOLDING the log lock — output
14	//! stalled and any path needing that log lock (a fresh attach, or the global
15	//! `KIND_SESSIONS` enumeration, which reads each log's `delivered_through`)
16	//! blocked FOREVER, while the broker process stayed alive. The repro that proved
17	//! this (captured 2026-06-19): `APPEND_CTRL_ENTER sid=1 seq=2` with no matching
18	//! `APPEND_CTRL_EXIT`, `broker_alive=true` but `sessions_answered=false`.
19	//!
20	//! POST-FIX (W1): controller delivery moved off the drain thread onto a dedicated
21	//! writer thread + bounded channel (the viewer_writer pattern, but authoritative:
22	//! the writer advances `delivered_through`). The drain hands each chunk off with a
23	//! bounded `send_timeout` OFF the log lock; a fully-stalled controller is detached
24	//! after `CONTROLLER_WRITE_DEADLINE`. So even while the controller is fully backed
25	//! up, the session does NOT wedge — `KIND_SESSIONS` still answers. THIS test
26	//! asserts that fixed behavior.
27	//!
28	//! ## RUNNER CONTRACT — run process-per-test (nextest) or single-threaded (W1b F1)
29	//!
30	//! These tests share ONE process-global `SPT_HOME` (set once via
31	//! [`std::env::set_var`] in [`init_wedge_home`]) and each spins a REAL broker +
32	//! PTY child. Under the default `cargo test` harness — N tests as parallel
33	//! THREADS in ONE process — the `set_var` mutation races and the concurrent
34	//! brokers contend, which HANGS on Windows (cost a 30-min wedge during W1b).
35	//!
36	//! Run them isolated:
37	//!   - `cargo nextest run --test inject_control_wedge`  (process-per-test — CI's path, always safe), or
38	//!   - `cargo test --test inject_control_wedge -- --test-threads=1`  (single-threaded fallback).
39	//!
40	//! Do NOT run a bare `cargo test --test inject_control_wedge` on Windows.
41
42	use std::sync::Arc;
43	use std::thread;
44	use std::time::{Duration, Instant};
45
46	use interprocess::local_socket::Stream;
47	use spt_daemon::codec::{read_frame, write_frame};
48	use spt_daemon::frame::{Envelope, Role};
49	use spt_daemon::msg::{
50	    encode_bytes, EndpointInputReq, SpawnReq, Spawned, KIND_ENDPOINT_INJECTED, KIND_ENDPOINT_INPUT,
51	    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,
52	    KIND_SPAWNED,
53	};
54	use spt_daemon::transport::{send_hello, LocalSocketTransport};
55	use spt_daemon::Broker;
56
57	// ── W1b additions (REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE): the real-fixture attach
58	//    path, the Brain client API, and a net-backed broker for loopback attach. ──
59	use std::sync::atomic::{AtomicBool, AtomicU64};
60	use std::sync::OnceLock;
61	use spt_daemon::attach::{request_attach, serve_attach};
62	use spt_daemon::brain::{Brain, BrokerEvent};
63	use spt_daemon::msg::decode_bytes;
64	use spt_daemon::nethost::{NetConfig, NetHost};
65	use spt_net::net::attach::{AttachDecoder, AttachIntent, AttachRecord};
66	use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};
67	use spt_proto::identity::Identity;
68
69	use std::sync::atomic::{AtomicU32, Ordering};
70	static SEQ: AtomicU32 = AtomicU32::new(0);
71	fn unique_name() -> String {
72	    let n = SEQ.fetch_add(1, Ordering::Relaxed);
73	    format!("spt-daemon-w1wedge-{}-{}.sock", std::process::id(), n)
74	}
75
76	fn kill_pid(pid: u32) {
77	    #[cfg(windows)]
78	    let _ = std::process::Command::new("taskkill")
79	        .args(["/PID", &pid.to_string(), "/F", "/T"])
80	        .output();
81	    #[cfg(unix)]
82	    let _ = std::process::Command::new("kill")
83	        .args(["-9", &pid.to_string()])
84	        .output();
85	}
86
87	/// A child that FLOODS stdout forever (never reads stdin) — the heavy-output
88	/// case (a real TUI redraw burst). Cross-OS, same shape as the broker B1 test's
89	/// `cat`/`findstr` echo child.
90	fn flood_spawn_req(endpoint: &str) -> SpawnReq {
91	    #[cfg(unix)]
92	    let (program, args) = ("yes".to_string(), vec!["FLOODFLOODFLOODFLOOD".to_string()]);
93	    #[cfg(windows)]
94	    let (program, args) = (
95	        "cmd".to_string(),
96	        vec![
97	            "/c".to_string(),
98	            "for /l %x in (0,0,1) do @echo FLOODFLOODFLOODFLOODFLOODFLOODFLOOD".to_string(),
99	        ],
100	    );
101	    SpawnReq {
102	        program,
103	        args,
104	        rows: 24,
105	        cols: 80,
106	        endpoint: endpoint.to_string(),
107	        cwd: None,
108	        env: Default::default(),
109	        translation_binary: None,
110	    }
111	}
112
113	fn send(conn: &mut Stream, kind: &str, payload: serde_json::Value) {
114	    write_frame(conn, &Envelope::new(kind, payload)).expect("send frame");
115	}
116
117	/// Open a fresh brain connection, send one command, and return whether the
118	/// expected reply kind arrives within `deadline`. Runs the whole exchange on a
119	/// spawned thread so a BLOCKED `read_frame` (the wedge) surfaces as a timeout
120	/// instead of hanging the test. A `false` return = "did not answer in time".
121	fn probe(name: &str, cmd_kind: &'static str, want_kind: &'static str, deadline: Duration) -> bool {
122	    let name = name.to_string();
123	    let (tx, rx) = std::sync::mpsc::channel::<bool>();
124	    thread::spawn(move || {
125	        let mut conn = match LocalSocketTransport::connect(&name) {
126	            Ok(c) => c,
127	            Err(_) => {
128	                let _ = tx.send(false);
129	                return;
130	            }
131	        };
132	        if send_hello(&mut conn, Role::Brain).is_err() {
133	            let _ = tx.send(false);
134	            return;
135	        }
136	        send(&mut conn, cmd_kind, serde_json::json!({}));
137	        loop {
138	            match read_frame(&mut conn) {
139	                Ok(f) if f.kind == want_kind => {
140	                    let _ = tx.send(true);
141	                    return;
142	                }
143	                Ok(_) => continue, // ignore interleaved frames
144	                Err(_) => {
145	                    let _ = tx.send(false);
146	                    return;
147	                }
148	            }
149	        }
150	    });
151	    rx.recv_timeout(deadline).unwrap_or(false)
152	}
153
154	#[test]
155	fn a_backed_up_controller_does_not_wedge_the_session() {
156	    let name = unique_name();
157	    let broker = Broker::bind(&name).expect("bind broker");
158	    let serve_broker = Arc::clone(&broker);
159	    thread::spawn(move || {
160	        let _ = serve_broker.serve(); // accept every connection on its own thread
161	    });
162
163	    // ── Controller connection A: spawn the flooding child. dispatch_spawn
164	    //    pre-attaches A as the CONTROLLER (become_controller, by=None). ──
165	    let mut a = None;
166	    for _ in 0..200 {
167	        match LocalSocketTransport::connect(&name) {
168	            Ok(c) => {
169	                a = Some(c);
170	                break;
171	            }
172	            Err(_) => thread::sleep(Duration::from_millis(10)),
173	        }
174	    }
175	    let mut a = a.expect("connect controller A");
176	    send_hello(&mut a, Role::Brain).expect("hello A");
177	    send(
178	        &mut a,
179	        KIND_SPAWN,
180	        serde_json::to_value(flood_spawn_req("wedge-ep")).unwrap(),
181	    );
182	    let sid = loop {
183	        let f = read_frame(&mut a).expect("frame before spawned");

... [1170 lines truncated] ...


Subagent done: 7/7 green, clippy clean. Banked lesson = NOT trust-the-green. Independently verify from the now-quiesced tree. First read the fixture + the 4 new tests:


1	//! W2 Layer G integration fixture: a REAL ADR-0022 translation binary —
2	//! a pure stdin→stdout JSON-lines filter spt-core lifecycle-manages. Used by
3	//! `tests/inject_control_wedge.rs` (referenced via
4	//! `env!("CARGO_BIN_EXE_xlate_choreo_fixture")`). NOT a production binary.
5	//!
6	//! Protocol (spt-core → here, one JSON object per line on stdin):
7	//!   `{"type":"init",…}`  — identity; noted, never drives output.
8	//!   `{"type":"input"}`   — a CONTENT-FREE operator-activity ping; ignored.
9	//!   `{"type":"event","envelope":"…"}` — an inbound `<EVENT>`; we respond with
10	//!                          the CC stash/restore choreography.
11	//!
12	//! On an `event` we emit (here → spt-core, one JSON object per line on stdout,
13	//! flushing each so the broker's reader sees them promptly):
14	//!   `{"key":"ctrl+s"}` `{"delay_ms":50}` `{"text":"XLATE_OK"}` `{"key":"enter"}`
15	//!   `{"delay_ms":50}` `{"key":"ctrl+s"}` then `{"commit":true}`.
16	//!
17	//! Modes (env `XLATE_FIXTURE_MODE`, read ONCE at start; default `choreo`):
18	//!   - `choreo`   — the full sequence ENDING in `{"commit":true}` (happy path).
19	//!   - `nocommit` — emit the payload text + a key but NEVER `{"commit":true}`, so
20	//!     the broker's commit-deadline FAULT path is exercised.
21	//!
22	//! Privacy assertion support (doyle gate #1): every stdin line we receive is
23	//! appended verbatim to the file named by env `XLATE_FIXTURE_STDIN_LOG` (when
24	//! set). The int test reads that file and asserts NO operator keystroke payload
25	//! (e.g. the controller's typed bytes) ever appears — only `init` / `event` /
26	//! content-free `input` lines do. The binary never sees PTY keystroke content.
27
28	use std::fs::OpenOptions;
29	use std::io::{self, BufRead, Write};
30
31	/// The fixed, recognizable payload token the choreography types into the PTY.
32	const PAYLOAD: &str = "XLATE_OK";
33
34	fn main() {
35	    let mode = std::env::var("XLATE_FIXTURE_MODE").unwrap_or_else(|_| "choreo".to_string());
36	    let stdin_log = std::env::var("XLATE_FIXTURE_STDIN_LOG").ok();
37
38	    let stdin = io::stdin();
39	    let mut out = io::stdout();
40
41	    for line in stdin.lock().lines() {
42	        let Ok(line) = line else { break }; // stdin closed (EOF) → exit (no-zombie).
43	        let trimmed = line.trim();
44	        if trimmed.is_empty() {
45	            continue;
46	        }
47
48	        // Record EVERY received stdin line for the privacy (content-free ping)
49	        // assertion. Best-effort: a log failure never changes our behavior.
50	        if let Some(path) = &stdin_log {
51	            if let Ok(mut f) = OpenOptions::new().create(true).append(true).open(path) {
52	                let _ = writeln!(f, "{trimmed}");
53	            }
54	        }
55
56	        // Only `{"type":"event",…}` drives a response. `init` and the
57	        // content-free `input` ping are noted (logged above) and ignored.
58	        let v: serde_json::Value = match serde_json::from_str(trimmed) {
59	            Ok(v) => v,
60	            Err(_) => continue,
61	        };
62	        if v.get("type").and_then(|t| t.as_str()) != Some("event") {
63	            continue;
64	        }
65
66	        // Emit the choreography, flushing each line so the broker's line-reader
67	        // applies it promptly (the delays are part of the sequence the broker
68	        // honors, not a wall-clock the test asserts on).
69	        emit(&mut out, r#"{"key":"ctrl+s"}"#);
70	        emit(&mut out, r#"{"delay_ms":50}"#);
71	        emit(&mut out, &format!(r#"{{"text":"{PAYLOAD}"}}"#));
72	        emit(&mut out, r#"{"key":"enter"}"#);
73
74	        if mode == "nocommit" {
75	            // FAULT path: the payload + a key landed, but we deliberately NEVER
76	            // emit `{"commit":true}`. The broker's inject worker must hit its
77	            // commit deadline, flush the buffered controller input, release the
78	            // floor, and FAULT this binary (revert to raw inject).
79	            continue;
80	        }
81
82	        emit(&mut out, r#"{"delay_ms":50}"#);
83	        emit(&mut out, r#"{"key":"ctrl+s"}"#);
84	        // XON (`ctrl+q` = 0x11): resume the terminal output flow the `ctrl+s` (XOFF)
85	        // chord paused, so the payload's echo flushes deterministically on a ConPTY
86	        // (the int test observes byte-receipt via that echo). A real stash/restore
87	        // adapter restores flow at the end of its sequence just the same; this keeps
88	        // the `ctrl+s` chord (and its real 0x13 mapping + floor exercise) intact while
89	        // making the payload's arrival observable, per the test-specialist's latitude.
90	        emit(&mut out, r#"{"key":"ctrl+q"}"#);
91	        emit(&mut out, r#"{"commit":true}"#);
92	    }
93	}
94
95	/// Write one JSON-lines command and flush so the broker sees it immediately.
96	fn emit(out: &mut io::Stdout, line: &str) {
97	    let _ = writeln!(out, "{line}");
98	    let _ = out.flush();
99	}
100

//! W2 Layer G integration fixture: a REAL ADR-0022 translation binary —
//! a pure stdin→stdout JSON-lines filter spt-core lifecycle-manages. Used by
//! `tests/inject_control_wedge.rs` (referenced via
//! `env!("CARGO_BIN_EXE_xlate_choreo_fixture")`). NOT a production binary.
//!
//! Protocol (spt-core → here, one JSON object per line on stdin):
//!   `{"type":"init",…}`  — identity; noted, never drives output.
//!   `{"type":"input"}`   — a CONTENT-FREE operator-activity ping; ignored.
//!   `{"type":"event","envelope":"…"}` — an inbound `<EVENT>`; we respond with
//!                          the CC stash/restore choreography.
//!
//! On an `event` we emit (here → spt-core, one JSON object per line on stdout,
//! flushing each so the broker's reader sees them promptly):
//!   `{"key":"ctrl+s"}` `{"delay_ms":50}` `{"text":"XLATE_OK"}` `{"key":"enter"}`
//!   `{"delay_ms":50}` `{"key":"ctrl+s"}` then `{"commit":true}`.
//!
//! Modes (env `XLATE_FIXTURE_MODE`, read ONCE at start; default `choreo`):
//!   - `choreo`   — the full sequence ENDING in `{"commit":true}` (happy path).
//!   - `nocommit` — emit the payload text + a key but NEVER `{"commit":true}`, so
//!     the broker's commit-deadline FAULT path is exercised.
//!
//! Privacy assertion support (doyle gate #1): every stdin line we receive is
//! appended verbatim to the file named by env `XLATE_FIXTURE_STDIN_LOG` (when
//! set). The int test reads that file and asserts NO operator keystroke payload
//! (e.g. the controller's typed bytes) ever appears — only `init` / `event` /
//! content-free `input` lines do. The binary never sees PTY keystroke content.

use std::fs::OpenOptions;
use std::io::{self, BufRead, Write};

/// The fixed, recognizable payload token the choreography types into the PTY.
const PAYLOAD: &str = "XLATE_OK";

fn main() {
    let mode = std::env::var("XLATE_FIXTURE_MODE").unwrap_or_else(|_| "choreo".to_string());
    let stdin_log = std::env::var("XLATE_FIXTURE_STDIN_LOG").ok();

    let stdin = io::stdin();
    let mut out = io::stdout();

    for line in stdin.lock().lines() {
        let Ok(line) = line else { break }; // stdin closed (EOF) → exit (no-zombie).
        let trimmed = line.trim();
        if trimmed.is_empty() {
            continue;
        }

        // Record EVERY received stdin line for the privacy (content-free ping)
        // assertion. Best-effort: a log failure never changes our behavior.
        if let Some(path) = &stdin_log {
            if let Ok(mut f) = OpenOptions::new().create(true).append(true).open(path) {
                let _ = writeln!(f, "{trimmed}");
            }
        }

        // Only `{"type":"event",…}` drives a response. `init` and the
        // content-free `input` ping are noted (logged above) and ignored.
        let v: serde_json::Value = match serde_json::from_str(trimmed) {
            Ok(v) => v,
            Err(_) => continue,
        };
        if v.get("type").and_then(|t| t.as_str()) != Some("event") {
            continue;
        }

        // Emit the choreography, flushing each line so the broker's line-reader
        // applies it promptly (the delays are part of the sequence the broker
        // honors, not a wall-clock the test asserts on).
        emit(&mut out, r#"{"key":"ctrl+s"}"#);
        emit(&mut out, r#"{"delay_ms":50}"#);
        emit(&mut out, &format!(r#"{{"text":"{PAYLOAD}"}}"#));
        emit(&mut out, r#"{"key":"enter"}"#);

        if mode == "nocommit" {
            // FAULT path: the payload + a key landed, but we deliberately NEVER
            // emit `{"commit":true}`. The broker's inject worker must hit its
            // commit deadline, flush the buffered controller input, release the
            // floor, and FAULT this binary (revert to raw inject).
            continue;
        }

        emit(&mut out, r#"{"delay_ms":50}"#);
        emit(&mut out, r#"{"key":"ctrl+s"}"#);
        // XON (`ctrl+q` = 0x11): resume the terminal output flow the `ctrl+s` (XOFF)
        // chord paused, so the payload's echo flushes deterministically on a ConPTY
        // (the int test observes byte-receipt via that echo). A real stash/restore
        // adapter restores flow at the end of its sequence just the same; this keeps
        // the `ctrl+s` chord (and its real 0x13 mapping + floor exercise) intact while
        // making the payload's arrival observable, per the test-specialist's latitude.
        emit(&mut out, r#"{"key":"ctrl+q"}"#);
        emit(&mut out, r#"{"commit":true}"#);
    }
}

/// Write one JSON-lines command and flush so the broker sees it immediately.
fn emit(out: &mut io::Stdout, line: &str) {
    let _ = writeln!(out, "{line}");
    let _ = out.flush();
}


Fixture clean (`ctrl+q`→0x11 via the ctrl-chord branch). Now read the 4 new tests:


grep -n "fn g[1-4]\|fn .*translation\|fn .*choreo\|fn .*no_commit\|fn .*content_free\|fn .*zombie\|fn .*fault\|CARGO_BIN_EXE\|cfg(windows)\|cfg(unix)\|XLATE_FIXTURE\|SPT_INJECT_COMMIT_DEADLINE_MS\|\[int->" crates/spt-daemon/tests/inject_control_wedge.rs

Locate the 4 new tests + their key assertions


77:    #[cfg(windows)]
81:    #[cfg(unix)]
91:    #[cfg(unix)]
93:    #[cfg(windows)]
222:    // [int->REQ-HAZARD-INJECT-CONTROL-COEXIST]
344:    //   a false-red). cfg(unix) only CAPTURES + prints the observation; gravity-linux
348:    // [int->REQ-HAZARD-INJECT-CONTROL-COEXIST]
349:    #[cfg(windows)]
355:    #[cfg(unix)]
378:// real byte receipt + attach serviced). On cfg(unix) (gravity-linux) forkpty's
653:    #[cfg(unix)]
682:    // [int->REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE]
705:    #[cfg(unix)]
720:// by the cargo `CARGO_BIN_EXE_*` env var):
724://     `XLATE_FIXTURE_MODE=nocommit` drops the final `{commit}` to exercise the
726://     `XLATE_FIXTURE_STDIN_LOG` (the privacy / content-free-ping assertion reads
739:const XLATE_BIN: &str = env!("CARGO_BIN_EXE_xlate_choreo_fixture");
743:    #[cfg(unix)]
747:    #[cfg(windows)]
910:#[cfg(windows)]
924:#[cfg(unix)]
965:fn g1_choreography_happy_path_payload_reaches_pty_and_controller_keeps_control() {
968:    std::env::set_var("XLATE_FIXTURE_MODE", "choreo");
969:    std::env::remove_var("XLATE_FIXTURE_STDIN_LOG");
970:    std::env::remove_var("SPT_INJECT_COMMIT_DEADLINE_MS");
1034:    // [int->REQ-MSG-IDLE-TRANSLATION-BINARY]
1064:    #[cfg(unix)]
1071:    #[cfg(windows)]
1081:fn g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input() {
1087:    std::env::set_var("XLATE_FIXTURE_MODE", "nocommit");
1088:    std::env::set_var("SPT_INJECT_COMMIT_DEADLINE_MS", "400");
1089:    std::env::remove_var("XLATE_FIXTURE_STDIN_LOG");
1139:    // [int->REQ-MSG-IDLE-TRANSLATION-BINARY]
1160:fn g3_content_free_ping_never_duplicates_operator_keystrokes_into_binary() {
1165:    std::env::set_var("XLATE_FIXTURE_STDIN_LOG", &bin_stdin_log);
1166:    std::env::set_var("XLATE_FIXTURE_MODE", "choreo");
1167:    std::env::remove_var("SPT_INJECT_COMMIT_DEADLINE_MS");
1207:    // [int->REQ-MSG-IDLE-TRANSLATION-BINARY]
1228:fn g4_translation_child_is_reaped_no_zombie_after_session_down() {
1231:    std::env::set_var("XLATE_FIXTURE_MODE", "choreo");
1232:    std::env::remove_var("XLATE_FIXTURE_STDIN_LOG");
1233:    std::env::remove_var("SPT_INJECT_COMMIT_DEADLINE_MS");
1300:    // [int->REQ-MSG-IDLE-TRANSLATION-BINARY]
1313:#[cfg(windows)]
1336:#[cfg(unix)]


Read the tests + helpers to verify assertions are non-vacuous: