Edit

    // After the binary faulted, a SUBSEQUENT inbound delivery has NO working
    // translation binary → it SPOOLS: the broker acks delivered=false and writes
    // NOTHING to the PTY (ADR-0022 amendment — raw inject was REMOVED as a delivery
    // path; a faulted binary no longer degrades to a raw `payload+\r` pseudo-write
    // that does not submit on a modern TUI). The marker must therefore NOT appear on
    // the ring. The envelope ends in `\r` only to match the old shape; nothing is
    // written so the echo child never sees it.
    let raw_envelope = b"<EVENT>SPOOL_NOT_INJECTED_MARKER</EVENT>\r";
    let delivered2 = deliver_endpoint_event(&name, endpoint, raw_envelope, Duration::from_secs(5));
    let marker_reached_pty =
        output_contains_within(&name, sid, b"SPOOL_NOT_INJECTED_MARKER", Duration::from_secs(3));

    if let Some(pid) = broker.session_pid(sid) {
        kill_pid(pid);
    }

    eprintln!(
        "=== W2 G2 NO-COMMIT FAULT GATE: delivered={delivered} op_flushed={op_flushed} \
         sessions_answered={sessions_answered} delivered2={delivered2} \
         marker_reached_pty={marker_reached_pty} \
         (op_flushed=true → controller input never wedged; delivered2=false + \
         !marker_reached_pty → faulted binary spools, never raw-injects) ==="
    );

    // [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");
    assert!(
        op_flushed,
        "after the commit deadline the floor must RELEASE and buffered operator \
         keystrokes must flush to the PTY — the controller's input must NOT wedge \
         (the mandatory bounded-buffer guard; doyle non-negotiable = never park forever)"
    );
    assert!(
        sessions_answered,
        "the session must stay serviceable after the binary faults"
    );
    assert!(
        !delivered2,
        "after the FAULT a subsequent inbound delivery must SPOOL (delivered=false), \
         not raw-inject — idle delivery is translation-binary-only (ADR-0022 amendment)"
    );
    assert!(
        !marker_reached_pty,
        "a faulted binary no longer raw-injects: the raw `<EVENT>` envelope must NOT \
         land on the PTY (it spools, poll-fed, never a non-submitting PTY pseudo-write)"
    );
}

    // After the binary FAULTS, the steady-state contract is: a subsequent inbound
    // delivery SPOOLS (delivered=false) and writes NOTHING to the PTY (ADR-0022
    // amendment — raw inject removed; a faulted binary no longer degrades to a raw
    // `payload+\r` pseudo-write that does not submit on a modern TUI).
    //
    // The fault is ASYNC, so a delivery that lands in the worker's commit window —
    // before `event_rx` is dropped / `faulted` is set — can be optimistically
    // enqueue-acked (`delivered=true` the instant `event_tx.send` succeeds) and then
    // DROPPED when the worker faults+returns. That FAULT-TRANSIENT is PRE-EXISTING
    // (raw-inject removal did not touch it — the old code dropped that queued event
    // too) and is tracked separately for v0.15.0 (REQ-MSG-DELIVERY-AXES: ack-on-SPOOL
    // replaces ack-on-enqueue → closes it). It is NOT what v0.14.3 guarantees, and a
    // single-shot `delivered2==false` assert FLAKES under parallel CPU load.
    //
    // `faulted` is MONOTONIC (set once, never respawns), so a bounded retry CONVERGES
    // on the steady state: eventually every delivery spools. We assert the real
    // guarantee — delivery EVENTUALLY spools — AND the load-invariant: the marker
    // NEVER reaches the PTY on ANY attempt (the no-commit binary emits only its own
    // constant PAYLOAD `XLATE_OK`, never the envelope's bytes; the marker can reach
    // the PTY ONLY via raw inject, which is removed).
    let marker: &[u8] = b"SPOOL_NOT_INJECTED_MARKER";
    let raw_envelope = b"<EVENT>SPOOL_NOT_INJECTED_MARKER</EVENT>\r";
    let retry_deadline = Instant::now() + Duration::from_secs(3);
    let mut spooled = false;
    let mut attempts = 0u32;
    while Instant::now() < retry_deadline {
        attempts += 1;
        if !deliver_endpoint_event(&name, endpoint, raw_envelope, Duration::from_secs(2)) {
            spooled = true; // delivered=false → the faulted STATE is in effect
            break;
        }
        // Raced the async fault transient (optimistic enqueue-ack) — let `faulted`
        // settle, then retry; monotonic `faulted` guarantees convergence.
        thread::sleep(Duration::from_millis(150));
    }
    // The marker must never have reached the PTY across ANY attempt (raw inject gone).
    let marker_reached_pty = output_contains_within(&name, sid, marker, Duration::from_millis(500));

    if let Some(pid) = broker.session_pid(sid) {
        kill_pid(pid);
    }

    eprintln!(
        "=== W2 G2 NO-COMMIT FAULT GATE: delivered={delivered} op_flushed={op_flushed} \
         sessions_answered={sessions_answered} spooled={spooled} attempts={attempts} \
         marker_reached_pty={marker_reached_pty} \
         (op_flushed=true → controller input never wedged; spooled=true + \
         !marker_reached_pty → faulted binary reaches the spool steady-state, never raw-injects) ==="
    );

    // [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");
    assert!(
        op_flushed,
        "after the commit deadline the floor must RELEASE and buffered operator \
         keystrokes must flush to the PTY — the controller's input must NOT wedge \
         (the mandatory bounded-buffer guard; doyle non-negotiable = never park forever)"
    );
    assert!(
        sessions_answered,
        "the session must stay serviceable after the binary faults"
    );
    assert!(
        spooled,
        "after the FAULT a subsequent inbound delivery must EVENTUALLY SPOOL \
         (delivered=false) — faulted is monotonic so bounded retry converges on the \
         steady state ({attempts} attempts); the fault-transient enqueue-ack is \
         pre-existing and tracked for v0.15.0 (REQ-MSG-DELIVERY-AXES)"
    );
    assert!(
        !marker_reached_pty,
        "a faulted binary never raw-injects: the envelope marker must NOT land on the \
         PTY on ANY attempt (it spools, poll-fed; the binary emits only its own \
         constant PAYLOAD, never the envelope bytes)"
    );
}


doyle is available. everyone's on legacy spt: `$OWL send`


Check Doyle availability and legacy SPT send


<owl_messages>
<owl_message from="doyle" priority="high">Both fixes confirmed correct. Good catch on the fixture payload (XLATE_OK constant, not the envelope marker) — marker_reached_pty is structurally always-false, so delivered2 was the sole flaky assertion; bounded-retry-until-spool nails exactly it.

ONE correction: REQ-MSG-DELIVERY-AXES IS now in traceable-reqs.toml — I minted it THIS session at the messaging grill (doc stage active, ADR-0028; v0.15.0 activity-gated delivery + the 3 send axes). So your KNOWN-HAZARDS 7.22 boundary note can reference REQ-MSG-DELIVERY-AXES as a REAL tracked req (the v0.15.0 home for the fault-transient ack-on-spool fix), not just a prose forward-ref. No new tag needed on the transient itself (it folds into REQ-MSG-DELIVERY-AXES) — your plan is right, just the ref now resolves.

Proceed: commit the follow-on onto @aadafe12 branch, re-run g2 in a parallel-load repeat-loop + clippy + traceable your side, ping the new SHA. I re-gate the FULL suite under load. Standby.</owl_message>
</owl_messages>

[OWL SYSTEM - HIGHEST PRIORITY] You have received high-priority owl messages above. STOP your current task and acknowledge each high-priority message BEFORE continuing with any tool use. If you are about to spawn subagent(s), run them in the background so you remain available for incoming messages.


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)

    // After the binary faulted, a SUBSEQUENT inbound delivery has NO working
    // translation binary → it SPOOLS: the broker acks delivered=false and writes
    // NOTHING to the PTY (ADR-0022 amendment — raw inject was REMOVED as a delivery
    // path; a faulted binary no longer degrades to a raw `payload+\r` pseudo-write
    // that does not submit on a modern TUI). The marker must therefore NOT appear on
    // the ring. The envelope ends in `\r` only to match the old shape; nothing is
    // written so the echo child never sees it.
    let raw_envelope = b"<EVENT>SPOOL_NOT_INJECTED_MARKER</EVENT>\r";
    let delivered2 = deliver_endpoint_event(&name, endpoint, raw_envelope, Duration::from_secs(5));
    let marker_reached_pty =
        output_contains_within(&name, sid, b"SPOOL_NOT_INJECTED_MARKER", Duration::from_secs(3));

    if let Some(pid) = broker.session_pid(sid) {
        kill_pid(pid);
    }

    eprintln!(
        "=== W2 G2 NO-COMMIT FAULT GATE: delivered={delivered} op_flushed={op_flushed} \
         sessions_answered={sessions_answered} delivered2={delivered2} \
         marker_reached_pty={marker_reached_pty} \
         (op_flushed=true → controller input never wedged; delivered2=false + \
         !marker_reached_pty → faulted binary spools, never raw-injects) ==="
    );

    // [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");
    assert!(
        op_flushed,
        "after the commit deadline the floor must RELEASE and buffered operator \
         keystrokes must flush to the PTY — the controller's input must NOT wedge \
         (the mandatory bounded-buffer guard; doyle non-negotiable = never park forever)"
    );
    assert!(
        sessions_answered,
        "the session must stay serviceable after the binary faults"
    );
    assert!(
        !delivered2,
        "after the FAULT a subsequent inbound delivery must SPOOL (delivered=false), \
         not raw-inject — idle delivery is translation-binary-only (ADR-0022 amendment)"
    );
    assert!(
        !marker_reached_pty,
        "a faulted binary no longer raw-injects: the raw `<EVENT>` envelope must NOT \
         land on the PTY (it spools, poll-fed, never a non-submitting PTY pseudo-write)"
    );
}

    // After the binary FAULTS, the steady-state contract is: a subsequent inbound
    // delivery SPOOLS (delivered=false) and writes NOTHING to the PTY (ADR-0022
    // amendment — raw inject removed; a faulted binary no longer degrades to a raw
    // `payload+\r` pseudo-write that does not submit on a modern TUI).
    //
    // The fault is ASYNC, so a delivery that lands in the worker's commit window —
    // before `event_rx` is dropped / `faulted` is set — can be optimistically
    // enqueue-acked (`delivered=true` the instant `event_tx.send` succeeds) and then
    // DROPPED when the worker faults+returns. That FAULT-TRANSIENT is PRE-EXISTING
    // (raw-inject removal did not touch it — the old code dropped that queued event
    // too) and is tracked separately for v0.15.0 (REQ-MSG-DELIVERY-AXES: ack-on-SPOOL
    // replaces ack-on-enqueue → closes it). It is NOT what v0.14.3 guarantees, and a
    // single-shot `delivered2==false` assert FLAKES under parallel CPU load.
    //
    // `faulted` is MONOTONIC (set once, never respawns), so a bounded retry CONVERGES
    // on the steady state: eventually every delivery spools. We assert the real
    // guarantee — delivery EVENTUALLY spools — AND the load-invariant: the marker
    // NEVER reaches the PTY on ANY attempt (the no-commit binary emits only its own
    // constant PAYLOAD `XLATE_OK`, never the envelope's bytes; the marker can reach
    // the PTY ONLY via raw inject, which is removed).
    let marker: &[u8] = b"SPOOL_NOT_INJECTED_MARKER";
    let raw_envelope = b"<EVENT>SPOOL_NOT_INJECTED_MARKER</EVENT>\r";
    let retry_deadline = Instant::now() + Duration::from_secs(3);
    let mut spooled = false;
    let mut attempts = 0u32;
    while Instant::now() < retry_deadline {
        attempts += 1;
        if !deliver_endpoint_event(&name, endpoint, raw_envelope, Duration::from_secs(2)) {
            spooled = true; // delivered=false → the faulted STATE is in effect
            break;
        }
        // Raced the async fault transient (optimistic enqueue-ack) — let `faulted`
        // settle, then retry; monotonic `faulted` guarantees convergence.
        thread::sleep(Duration::from_millis(150));
    }
    // The marker must never have reached the PTY across ANY attempt (raw inject gone).
    let marker_reached_pty = output_contains_within(&name, sid, marker, Duration::from_millis(500));

    if let Some(pid) = broker.session_pid(sid) {
        kill_pid(pid);
    }

    eprintln!(
        "=== W2 G2 NO-COMMIT FAULT GATE: delivered={delivered} op_flushed={op_flushed} \
         sessions_answered={sessions_answered} spooled={spooled} attempts={attempts} \
         marker_reached_pty={marker_reached_pty} \
         (op_flushed=true → controller input never wedged; spooled=true + \
         !marker_reached_pty → faulted binary reaches the spool steady-state, never raw-injects) ==="
    );

    // [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");
    assert!(
        op_flushed,
        "after the commit deadline the floor must RELEASE and buffered operator \
         keystrokes must flush to the PTY — the controller's input must NOT wedge \
         (the mandatory bounded-buffer guard; doyle non-negotiable = never park forever)"
    );
    assert!(
        sessions_answered,
        "the session must stay serviceable after the binary faults"
    );
    assert!(
        spooled,
        "after the FAULT a subsequent inbound delivery must EVENTUALLY SPOOL \
         (delivered=false) — faulted is monotonic so bounded retry converges on the \
         steady state ({attempts} attempts); the fault-transient enqueue-ack is \
         pre-existing and tracked for v0.15.0 (REQ-MSG-DELIVERY-AXES)"
    );
    assert!(
        !marker_reached_pty,
        "a faulted binary never raw-injects: the envelope marker must NOT land on the \
         PTY on ANY attempt (it spools, poll-fed; the binary emits only its own \
         constant PAYLOAD, never the envelope bytes)"
    );
}

probe(&name, KIND_SESSIONS, KIND_SESSIONS_REPLY, Duration::from_secs(5));

-    // After the binary faulted, a SUBSEQUENT inbound delivery has NO working

-    // translation binary → it SPOOLS: the broker acks delivered=false and writes

-    // NOTHING to the PTY (ADR-0022 amendment — raw inject was REMOVED as a delivery

-    // path; a faulted binary no longer degrades to a raw `payload+\r` pseudo-write

-    // that does not submit on a modern TUI). The marker must therefore NOT appear on

-    // the ring. The envelope ends in `\r` only to match the old shape; nothing is

-    // written so the echo child never sees it.

+    // After the binary FAULTS, the steady-state contract is: a subsequent inbound

+    // delivery SPOOLS (delivered=false) and writes NOTHING to the PTY (ADR-0022

+    // amendment — raw inject removed; a faulted binary no longer degrades to a raw

+    // `payload+\r` pseudo-write that does not submit on a modern TUI).

+    // The fault is ASYNC, so a delivery that lands in the worker's commit window —

+    // before `event_rx` is dropped / `faulted` is set — can be optimistically

+    // enqueue-acked (`delivered=true` the instant `event_tx.send` succeeds) and then

+    // DROPPED when the worker faults+returns. That FAULT-TRANSIENT is PRE-EXISTING

+    // (raw-inject removal did not touch it — the old code dropped that queued event

+    // too) and is tracked separately for v0.15.0 (REQ-MSG-DELIVERY-AXES: ack-on-SPOOL

+    // replaces ack-on-enqueue → closes it). It is NOT what v0.14.3 guarantees, and a

+    // single-shot `delivered2==false` assert FLAKES under parallel CPU load.

+    // `faulted` is MONOTONIC (set once, never respawns), so a bounded retry CONVERGES

+    // on the steady state: eventually every delivery spools. We assert the real

+    // guarantee — delivery EVENTUALLY spools — AND the load-invariant: the marker

+    // NEVER reaches the PTY on ANY attempt (the no-commit binary emits only its own

+    // constant PAYLOAD `XLATE_OK`, never the envelope's bytes; the marker can reach

+    // the PTY ONLY via raw inject, which is removed).

+    let marker: &[u8] = b"SPOOL_NOT_INJECTED_MARKER";

-    let delivered2 = deliver_endpoint_event(&name, endpoint, raw_envelope, Duration::from_secs(5));

-        output_contains_within(&name, sid, b"SPOOL_NOT_INJECTED_MARKER", Duration::from_secs(3));

+    let retry_deadline = Instant::now() + Duration::from_secs(3);

+    while Instant::now() < retry_deadline {

+        if !deliver_endpoint_event(&name, endpoint, raw_envelope, Duration::from_secs(2)) {

+            spooled = true; // delivered=false → the faulted STATE is in effect

+        // Raced the async fault transient (optimistic enqueue-ack) — let `faulted`

+        // settle, then retry; monotonic `faulted` guarantees convergence.

+        thread::sleep(Duration::from_millis(150));

+    // The marker must never have reached the PTY across ANY attempt (raw inject gone).

+    let marker_reached_pty = output_contains_within(&name, sid, marker, Duration::from_millis(500));

-         sessions_answered={sessions_answered} delivered2={delivered2} \

+         sessions_answered={sessions_answered} spooled={spooled} attempts={attempts} \

-         (op_flushed=true → controller input never wedged; delivered2=false + \

-         !marker_reached_pty → faulted binary spools, never raw-injects) ==="

+         (op_flushed=true → controller input never wedged; spooled=true + \

+         !marker_reached_pty → faulted binary reaches the spool steady-state, never raw-injects) ==="

-        "after the FAULT a subsequent inbound delivery must SPOOL (delivered=false), \

-         not raw-inject — idle delivery is translation-binary-only (ADR-0022 amendment)"

+        "after the FAULT a subsequent inbound delivery must EVENTUALLY SPOOL \

+         (delivered=false) — faulted is monotonic so bounded retry converges on the \

+         steady state ({attempts} attempts); the fault-transient enqueue-ack is \

+         pre-existing and tracked for v0.15.0 (REQ-MSG-DELIVERY-AXES)"

-        "a faulted binary no longer raw-injects: the raw `<EVENT>` envelope must NOT \

-         land on the PTY (it spools, poll-fed, never a non-submitting PTY pseudo-write)"

+        "a faulted binary never raw-injects: the envelope marker must NOT land on the \

+         PTY on ANY attempt (it spools, poll-fed; the binary emits only its own \

+         constant PAYLOAD, never the envelope bytes)"


Acked — doyle confirms both fixes; correction noted: **REQ-MSG-DELIVERY-AXES is now in the toml** (doc stage, ADR-0028), so my KH 7.22 boundary note references a real tracked REQ (no new tag on the transient — it folds into REQ-MSG-DELIVERY-AXES). Quick ack, then FIX #2:


Bash

printf '%s' "ACK — noted REQ-MSG-DELIVERY-AXES is live in the toml (ADR-0028, doc stage). g2 rewrite DONE (bounded-retry-until-spool; comment already forward-refs REQ-MSG-DELIVERY-AXES as the v0.15.0 home). Doing FIX #2 now: REQ-HAZARD-IDLE-SILENT-NONDELIVERY title → STATE guarantee + fault-transient carve-out referencing REQ-MSG-DELIVERY-AXES; KH 7.22 boundary bullet (real ref, no new tag — folds into REQ-MSG-DELIVERY-AXES). Then g2 parallel repeat-loop + clippy + traceable, follow-on commit, ping SHA. Standby." | "$OWL" send doyle; echo "RC=${PIPESTATUS[1]}"

Ack doyle's REQ-MSG-DELIVERY-AXES correction


➜ SENT:doyle
RC=0


FIX #2a — amend the REQ title (STATE guarantee + fault-transient carve-out). Read the current title:


1247	[[requirements]]
1248	id = "REQ-HAZARD-IDLE-SILENT-NONDELIVERY"
1249	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)"
1250	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.
1251
1252	# ── v0.15.0: activity-gated delivery + per-message send-modifier axes (ADR-0028) ──

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

# ── v0.15.0: activity-gated delivery + per-message send-modifier axes (ADR-0028) ──


REQ-MSG-DELIVERY-AXES confirmed at line 1252. Amend the title — STATE guarantee opening + fault-transient carve-out:


Edit

title = "spt-hosted idle delivery must NEVER report a message delivered when it was not. ROOT (F-019 post-mortem, ADR-0022 amendment):

title = "An idle delivery to a session whose translation binary is in a FAILED STATE — absent (none declared), spawn-failed, FAULTED, or its inject-worker channel gone — must SPOOL (delivered=false), never raw-inject a pseudo-delivery reported as delivered. The GUARANTEE is the STEADY STATE (the failed-binary state), not every in-flight message (see the fault-transient carve-out below). ROOT (F-019 post-mortem, ADR-0022 amendment):


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)

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

+title = "An idle delivery to a session whose translation binary is in a FAILED STATE — absent (none declared), spawn-failed, FAULTED, or its inject-worker channel gone — must SPOOL (delivered=false), never raw-inject a pseudo-delivery reported as delivered. The GUARANTEE is the STEADY STATE (the failed-binary state), not every in-flight message (see the fault-transient carve-out below). 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.

# ── v0.15.0: activity-gated delivery + per-message send-modifier axes (ADR-0028) ──


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

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). NOT COVERED — the FAULT-TRANSIENT (the STATE-vs-transient precision): a delivery landing in the worker's commit window — BEFORE `event_rx` is dropped / `faulted` is set — can be optimistically enqueue-acked (`delivered=true` the instant `event_tx.send` succeeds) then DROPPED when the worker faults+returns. That is a SEPARATE, PRE-EXISTING hazard: raw-inject removal did not touch it (the old code dropped that queued event too) — v0.14.3 is a strict improvement that makes nothing worse. It is tracked for v0.15.0 under REQ-MSG-DELIVERY-AXES (the spool-centric delivery redesign: ack-on-SPOOL replaces ack-on-enqueue, which closes the optimistic-ack drop naturally). v0.14.3 guarantees only the steady FAILED state → spool (faulted is MONOTONIC — set once, never respawns — so it converges deterministically; the g2 gate asserts the steady state via bounded-retry-until-spool, not a single-shot ack). KNOWN-HAZARDS class (rule 4). (v0.14.3)"


-title = "An idle delivery to a session whose translation binary is in a FAILED STATE — absent (none declared), spawn-failed, FAULTED, or its inject-worker channel gone — must SPOOL (delivered=false), never raw-inject a pseudo-delivery reported as delivered. The GUARANTEE is the STEADY STATE (the failed-binary state), not every in-flight message (see the fault-transient carve-out below). 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)"

+title = "An idle delivery to a session whose translation binary is in a FAILED STATE — absent (none declared), spawn-failed, FAULTED, or its inject-worker channel gone — must SPOOL (delivered=false), never raw-inject a pseudo-delivery reported as delivered. The GUARANTEE is the STEADY STATE (the failed-binary state), not every in-flight message (see the fault-transient carve-out below). 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). NOT COVERED — the FAULT-TRANSIENT (the STATE-vs-transient precision): a delivery landing in the worker's commit window — BEFORE `event_rx` is dropped / `faulted` is set — can be optimistically enqueue-acked (`delivered=true` the instant `event_tx.send` succeeds) then DROPPED when the worker faults+returns. That is a SEPARATE, PRE-EXISTING hazard: raw-inject removal did not touch it (the old code dropped that queued event too) — v0.14.3 is a strict improvement that makes nothing worse. It is tracked for v0.15.0 under REQ-MSG-DELIVERY-AXES (the spool-centric delivery redesign: ack-on-SPOOL replaces ack-on-enqueue, which closes the optimistic-ack drop naturally). v0.14.3 guarantees only the steady FAILED state → spool (faulted is MONOTONIC — set once, never respawns — so it converges deterministically; the g2 gate asserts the steady state via bounded-retry-until-spool, not a single-shot ack). KNOWN-HAZARDS class (rule 4). (v0.14.3)"


Update the g2 clause in the stages comment to match the retry-until-spool rewrite:


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

g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input (post-FAULT steady state: subsequent inbound EVENTUALLY spools via bounded-retry-until-spool — faulted is MONOTONIC so it converges, load-robust; the in-window fault-transient enqueue-ack is pre-existing + tracked v0.15.0 REQ-MSG-DELIVERY-AXES — AND the marker NEVER reaches the PTY across attempts; operator-keystroke floor-flush UNCHANGED). CHANGE-5 verified NO-OP


title = "An idle delivery to a session whose translation binary is in a FAILED STATE — absent (none declared), spawn-failed, FAULTED, or its inject-worker channel gone — must SPOOL (delivered=false), never raw-inject a pseudo-delivery reported as delivered. The GUARANTEE is the STEADY STATE (the failed-binary state), not every in-flight message (see the fault-transient carve-out below). 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). NOT COVERED — the FAULT-TRANSIENT (the STATE-vs-transient precision): a delivery landing in the worker's commit window — BEFORE `event_rx` is dropped / `faulted` is set — can be optimistically enqueue-acked (`delivered=true` the instant `event_tx.send` succeeds) then DROPPED when the worker faults+returns. That is a SEPARATE, PRE-EXISTING hazard: raw-inject removal did not touch it (the old code dropped that queued event too) — v0.14.3 is a strict improvement that makes nothing worse. It is tracked for v0.15.0 under REQ-MSG-DELIVERY-AXES (the spool-centric delivery redesign: ack-on-SPOOL replaces ack-on-enqueue, which closes the optimistic-ack drop naturally). v0.14.3 guarantees only the steady FAILED state → spool (faulted is MONOTONIC — set once, never respawns — so it converges deterministically; the g2 gate asserts the steady state via bounded-retry-until-spool, not a single-shot ack). 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.

+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 steady state: subsequent inbound EVENTUALLY spools via bounded-retry-until-spool — faulted is MONOTONIC so it converges, load-robust; the in-window fault-transient enqueue-ack is pre-existing + tracked v0.15.0 REQ-MSG-DELIVERY-AXES — AND the marker NEVER reaches the PTY across attempts; 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 #2b — add the fault-transient boundary bullet to KNOWN-HAZARDS 7.22. Read it:


475	### 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]`
476	- **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.
477	- **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).
478	- **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).
479	- **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`.
480	<!-- [doc->REQ-HAZARD-IDLE-SILENT-NONDELIVERY] -->
481
482	---
483
484	### F-019 diagnosis lesson — confirm an adapter binary actually SPAWNED before behavioral diagnosis  `[REQ-INSTALL-11]`

### 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]`


Add the fault-transient boundary bullet after the Invariant:


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`

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).
- **Boundary — what this does NOT cover (the STATE-vs-transient precision):** the guarantee is the steady FAILED STATE — once a binary IS faulted/absent/worker-gone, subsequent deliveries spool (`faulted` is MONOTONIC: set once, never respawns, so the state is reached deterministically). It does NOT cover the FAULT-TRANSIENT: a delivery that lands in the worker's commit window — BEFORE `event_rx` is dropped / `faulted` is set — can be optimistically enqueue-acked (`delivered=true` the instant `event_tx.send` succeeds) then DROPPED when the worker faults+returns. This is a SEPARATE, PRE-EXISTING hazard (raw-inject removal did not touch it — the old code dropped that queued event too; v0.14.3 makes nothing worse) and is tracked for **v0.15.0 under `[REQ-MSG-DELIVERY-AXES]`** (the spool-centric delivery redesign: ack-on-SPOOL replaces ack-on-enqueue, which closes the optimistic-ack drop naturally). The g2 int gate asserts the steady state via **bounded-retry-until-spool** (faulted-monotonic → converges) rather than a single-shot ack, so it is load-robust under the parallel CI suite.
- **spt-core mapping:** impl `broker.rs` `dispatch_endpoint_input`


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)

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`

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).
- **Boundary — what this does NOT cover (the STATE-vs-transient precision):** the guarantee is the steady FAILED STATE — once a binary IS faulted/absent/worker-gone, subsequent deliveries spool (`faulted` is MONOTONIC: set once, never respawns, so the state is reached deterministically). It does NOT cover the FAULT-TRANSIENT: a delivery that lands in the worker's commit window — BEFORE `event_rx` is dropped / `faulted` is set — can be optimistically enqueue-acked (`delivered=true` the instant `event_tx.send` succeeds) then DROPPED when the worker faults+returns. This is a SEPARATE, PRE-EXISTING hazard (raw-inject removal did not touch it — the old code dropped that queued event too; v0.14.3 makes nothing worse) and is tracked for **v0.15.0 under `[REQ-MSG-DELIVERY-AXES]`** (the spool-centric delivery redesign: ack-on-SPOOL replaces ack-on-enqueue, which closes the optimistic-ack drop naturally). The g2 int gate asserts the steady state via **bounded-retry-until-spool** (faulted-monotonic → converges) rather than a single-shot ack, so it is load-robust under the parallel CI suite.
- **spt-core mapping:** impl `broker.rs` `dispatch_endpoint_input`

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

+- **Boundary — what this does NOT cover (the STATE-vs-transient precision):** the guarantee is the steady FAILED STATE — once a binary IS faulted/absent/worker-gone, subsequent deliveries spool (`faulted` is MONOTONIC: set once, never respawns, so the state is reached deterministically). It does NOT cover the FAULT-TRANSIENT: a delivery that lands in the worker's commit window — BEFORE `event_rx` is dropped / `faulted` is set — can be optimistically enqueue-acked (`delivered=true` the instant `event_tx.send` succeeds) then DROPPED when the worker faults+returns. This is a SEPARATE, PRE-EXISTING hazard (raw-inject removal did not touch it — the old code dropped that queued event too; v0.14.3 makes nothing worse) and is tracked for **v0.15.0 under `[REQ-MSG-DELIVERY-AXES]`** (the spool-centric delivery redesign: ack-on-SPOOL replaces ack-on-enqueue, which closes the optimistic-ack drop naturally). The g2 int gate asserts the steady state via **bounded-retry-until-spool** (faulted-monotonic → converges) rather than a single-shot ack, so it is load-robust under the parallel CI suite.

- **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`.


Update the g2 description in the 7.22 mapping bullet to match the retry rewrite:


+ `g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input` (post-FAULT inbound spools, marker NOT on PTY).

+ `g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input` (post-FAULT inbound EVENTUALLY spools via bounded-retry-until-spool, marker NEVER on PTY across attempts — load-robust; the in-window fault-transient is the carve-out above).


- **Boundary — what this does NOT cover (the STATE-vs-transient precision):** the guarantee is the steady FAILED STATE — once a binary IS faulted/absent/worker-gone, subsequent deliveries spool (`faulted` is MONOTONIC: set once, never respawns, so the state is reached deterministically). It does NOT cover the FAULT-TRANSIENT: a delivery that lands in the worker's commit window — BEFORE `event_rx` is dropped / `faulted` is set — can be optimistically enqueue-acked (`delivered=true` the instant `event_tx.send` succeeds) then DROPPED when the worker faults+returns. This is a SEPARATE, PRE-EXISTING hazard (raw-inject removal did not touch it — the old code dropped that queued event too; v0.14.3 makes nothing worse) and is tracked for **v0.15.0 under `[REQ-MSG-DELIVERY-AXES]`** (the spool-centric delivery redesign: ack-on-SPOOL replaces ack-on-enqueue, which closes the optimistic-ack drop naturally). The g2 int gate asserts the steady state via **bounded-retry-until-spool** (faulted-monotonic → converges) rather than a single-shot ack, so it is load-robust under the parallel CI suite.

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

+- **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 EVENTUALLY spools via bounded-retry-until-spool, marker NEVER on PTY across attempts — load-robust; the in-window fault-transient is the carve-out above).


FIX #2 done. Now validate FIX #1 holds under parallel load — run the **full** inject_control_wedge suite (default parallel) in a repeat-loop, watching g2 (doyle's flake was 3/3 in the full parallel suite):


Bash

cd "C:\Users\decid\Documents\projects\spt-core" && for i in 1 2 3; do echo "=== RUN $i (full parallel suite) ==="; cargo test -p spt-daemon --test inject_control_wedge 2>&1 | grep -E "test g2_no_commit|test result:|FAILED"; done

Run full inject_control_wedge parallel suite 3x, watch g2


=== RUN 1 (full parallel suite) ===
test g3_content_free_ping_never_duplicates_operator_keystrokes_into_binary ... FAILED
test g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input ... FAILED
test result: FAILED. 8 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 12.53s
=== RUN 2 (full parallel suite) ===
test g3_content_free_ping_never_duplicates_operator_keystrokes_into_binary ... FAILED
test g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input ... FAILED
test g4_translation_child_is_reaped_no_zombie_after_session_down ... FAILED
test result: FAILED. 7 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out; finished in 12.28s
=== RUN 3 (full parallel suite) ===
test g3_content_free_ping_never_duplicates_operator_keystrokes_into_binary ... FAILED
test g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input ... ok
test result: FAILED. 9 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 12.52s


g2 still flakes under parallel load (runs 1-2 fail). My 3s retry bound isn't converging under contention. Get the actual failure detail — run the full parallel suite with --nocapture, capture g2's gate line + panic:


cd "C:\Users\decid\Documents\projects\spt-core" && cargo test -p spt-daemon --test inject_control_wedge -- --nocapture 2>&1 | grep -E "G2 NO-COMMIT|g2_no_commit.*FAILED|panicked.*g2|assertion.*spool|EVENTUALLY SPOOL|attempts=" | head -20

Capture g2 gate line + panic under parallel load


=== W2 G2 NO-COMMIT FAULT GATE: delivered=true op_flushed=true sessions_answered=true spooled=true attempts=1 marker_reached_pty=false (op_flushed=true → controller input never wedged; spooled=true + !marker_reached_pty → faulted binary reaches the spool steady-state, never raw-injects) ===


That run passed (spooled=true). g2 is intermittent — need to catch a FAILING instance. Run 4× to a file, capture every g2 gate line + panic:


cd "C:\Users\decid\Documents\projects\spt-core" && : > /tmp/g2cap.txt; for i in 1 2 3 4; do echo "########## RUN $i ##########" >> /tmp/g2cap.txt; cargo test -p spt-daemon --test inject_control_wedge -- --nocapture 2>&1 | grep -E "G2 NO-COMMIT|g2_no_commit_deadline.*(ok|FAILED)|panicked at|EVENTUALLY SPOOL|must NOT land on the PTY|never wedged|stay serviceable|accepted by the broker" >> /tmp/g2cap.txt; done; echo "=== g2-relevant captures ==="; grep -E "RUN|G2 NO-COMMIT|g2_no_commit_deadline|EVENTUALLY SPOOL|must NOT land|panicked" /tmp/g2cap.txt

Capture g2 gate+panic across 4 parallel runs


=== g2-relevant captures ===
########## RUN 1 ##########
thread 'g3_content_free_ping_never_duplicates_operator_keystrokes_into_binary' (86892) panicked at crates\spt-daemon\tests\inject_control_wedge.rs:1437:5:
=== W2 G2 NO-COMMIT FAULT GATE: delivered=true op_flushed=true sessions_answered=true spooled=false attempts=20 marker_reached_pty=false (op_flushed=true → controller input never wedged; spooled=true + !marker_reached_pty → faulted binary reaches the spool steady-state, never raw-injects) ===
thread 'g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input' (184700) panicked at crates\spt-daemon\tests\inject_control_wedge.rs:1362:5:
after the FAULT a subsequent inbound delivery must EVENTUALLY SPOOL (delivered=false) — faulted is monotonic so bounded retry converges on the steady state (20 attempts); the fault-transient enqueue-ack is pre-existing and tracked for v0.15.0 (REQ-MSG-DELIVERY-AXES)
test g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input ... FAILED
########## RUN 2 ##########
=== W2 G2 NO-COMMIT FAULT GATE: delivered=true op_flushed=true sessions_answered=true spooled=false attempts=20 marker_reached_pty=false (op_flushed=true → controller input never wedged; spooled=true + !marker_reached_pty → faulted binary reaches the spool steady-state, never raw-injects) ===
thread 'g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input' (226540) panicked at crates\spt-daemon\tests\inject_control_wedge.rs:1362:5:
after the FAULT a subsequent inbound delivery must EVENTUALLY SPOOL (delivered=false) — faulted is monotonic so bounded retry converges on the steady state (20 attempts); the fault-transient enqueue-ack is pre-existing and tracked for v0.15.0 (REQ-MSG-DELIVERY-AXES)
test g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input ... FAILED
########## RUN 3 ##########
thread 'g3_content_free_ping_never_duplicates_operator_keystrokes_into_binary' (41176) panicked at crates\spt-daemon\tests\inject_control_wedge.rs:1437:5:
=== W2 G2 NO-COMMIT FAULT GATE: delivered=true op_flushed=true sessions_answered=true spooled=false attempts=20 marker_reached_pty=false (op_flushed=true → controller input never wedged; spooled=true + !marker_reached_pty → faulted binary reaches the spool steady-state, never raw-injects) ===
thread 'g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input' (205652) panicked at crates\spt-daemon\tests\inject_control_wedge.rs:1362:5:
after the FAULT a subsequent inbound delivery must EVENTUALLY SPOOL (delivered=false) — faulted is monotonic so bounded retry converges on the steady state (20 attempts); the fault-transient enqueue-ack is pre-existing and tracked for v0.15.0 (REQ-MSG-DELIVERY-AXES)
test g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input ... FAILED
thread 'g4_translation_child_is_reaped_no_zombie_after_session_down' (100884) panicked at crates\spt-daemon\tests\inject_control_wedge.rs:1519:5:
########## RUN 4 ##########
thread 'g3_content_free_ping_never_duplicates_operator_keystrokes_into_binary' (239360) panicked at crates\spt-daemon\tests\inject_control_wedge.rs:1437:5:
=== W2 G2 NO-COMMIT FAULT GATE: delivered=true op_flushed=true sessions_answered=true spooled=false attempts=20 marker_reached_pty=false (op_flushed=true → controller input never wedged; spooled=true + !marker_reached_pty → faulted binary reaches the spool steady-state, never raw-injects) ===
thread 'g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input' (99688) panicked at crates\spt-daemon\tests\inject_control_wedge.rs:1362:5:
after the FAULT a subsequent inbound delivery must EVENTUALLY SPOOL (delivered=false) — faulted is monotonic so bounded retry converges on the steady state (20 attempts); the fault-transient enqueue-ack is pre-existing and tracked for v0.15.0 (REQ-MSG-DELIVERY-AXES)
test g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input ... FAILED


Root found: `spooled=false attempts=20` — the binary NEVER faults within 3s, so retry can't converge. The likely cause: g2 sets `XLATE_FIXTURE_MODE=nocommit` + `SPT_INJECT_COMMIT_DEADLINE_MS` via **process-global env**, but tests run **parallel in one process** — a concurrent g1/g3/g4 flips the mode to `choreo`, so g2's binary commits and never faults. Check how the fixture mode is passed:


31://! [`std::env::set_var`] in [`init_wedge_home`]) and each spins a REAL broker +
33://! THREADS in ONE process — the `set_var` mutation races and the concurrent
511:        std::env::set_var("SPT_HOME", dir.path());
902://     `XLATE_FIXTURE_MODE=nocommit` drops the final `{commit}` to exercise the
934:/// must already be set in THIS process (set_var before the session spawns the
1039:fn deliver_endpoint_event(name: &str, endpoint: &str, envelope: &[u8], deadline: Duration) -> bool {
1117:fn spawn_xlate_session(
1148:    std::env::set_var("XLATE_FIXTURE_MODE", "choreo");
1150:    std::env::remove_var("SPT_INJECT_COMMIT_DEADLINE_MS");
1267:    std::env::set_var("XLATE_FIXTURE_MODE", "nocommit");
1268:    std::env::set_var("SPT_INJECT_COMMIT_DEADLINE_MS", "400");
1383:    std::env::set_var("XLATE_FIXTURE_STDIN_LOG", &bin_stdin_log);
1384:    std::env::set_var("XLATE_FIXTURE_MODE", "choreo");
1385:    std::env::remove_var("SPT_INJECT_COMMIT_DEADLINE_MS");
1449:    std::env::set_var("XLATE_FIXTURE_MODE", "choreo");
1451:    std::env::remove_var("SPT_INJECT_COMMIT_DEADLINE_MS");
1930:    std::env::set_var("SPT_INPUT_QUEUE_DEPTH", "4");


The file's own module doc (lines 31-33) admits the `set_var` races across parallel threads. **Root is deeper than doyle's async-fault framing:** g2 sets `XLATE_FIXTURE_MODE=nocommit` + `SPT_INJECT_COMMIT_DEADLINE_MS=400` via process-global env, but concurrent g1/g3/g4 flip mode→`choreo` + remove the deadline → g2's binary commits (never faults) or uses the default 5s deadline → retry can't converge in 3s. Read the module doc + init_wedge_home + check for any existing serial guard:


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};

//! ## RUNNER CONTRACT — run process-per-test (nextest) or single-threaded (W1b F1)
//!
//! These tests share ONE process-global `SPT_HOME` (set once via
//! [`std::env::set_var`] in [`init_wedge_home`]) and each spins a REAL broker +
//! PTY child. Under the default `cargo test` harness — N tests as parallel
//! THREADS in ONE process — the `set_var` mutation races and the concurrent
//! brokers contend, which HANGS on Windows (cost a 30-min wedge during W1b).
//!
//! Run them isolated:
//!   - `cargo nextest run --test inject_control_wedge`  (process-per-test — CI's path, always safe), or
//!   - `cargo test --test inject_control_wedge -- --test-threads=1`  (single-threaded fallback).
//!
//! Do NOT run a bare `cargo test --test inject_control_wedge` on Windows.

use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};

use interprocess::local_socket::Stream;
use spt_daemon::codec::{read_frame, write_frame};


8://! CONTROLLER via a SYNCHRONOUS, BLOCKING `write_frame` held INLINE on the
9://! session's single drain thread, while the `Mutex<OutputLog>` was locked.
70:static SEQ: AtomicU32 = AtomicU32::new(0);
159:/// `sessions` Mutex — if a wedged per-conn handler is holding that lock the read
240:/// spawned thread so a BLOCKED `read_frame` (the wedge) surfaces as a timeout
242:fn probe(name: &str, cmd_kind: &'static str, want_kind: &'static str, deadline: Duration) -> bool {
489:// buffer is full, `write_input` BLOCKS — holding the journal lock forever. So the
507:static WEDGE_HOME: OnceLock<tempfile::TempDir> = OnceLock::new();
508:fn init_wedge_home() {
831:    // spawns a DETACHED `pump-ipc-reader` thread parked in a BLOCKING named-pipe
2173:    // other tests in this binary (they run serial under --test-threads=1 / nextest


**Key reframe:** the module doc (lines 28-40) declares this suite is **process-per-test (nextest, "CI's path") or `--test-threads=1`** — "Do NOT run a bare `cargo test` on Windows." doyle ran it the **unsupported bare-parallel** way; the `spooled=false attempts=20` failures are the **documented env-var race** (binary spawns in wrong mode), NOT the async-fault race my retry targets. Verify CI actually uses nextest (process-per-test → no race):


cd "C:\Users\decid\Documents\projects\spt-core" && grep -rn "nextest\|test-threads\|inject_control_wedge\|cargo test" .github/workflows/ 2>/dev/null | head -25

Check CI runner for nextest vs bare cargo test


.github/workflows/ci.yml:59:    # speedup (mold/nextest — ci-kitsubito-speedup-plan); this keeps the wedge
.github/workflows/ci.yml:81:      # guard — but a prior run SIGKILLed at the nextest slow-timeout backstop (240s)
.github/workflows/ci.yml:82:      # leaves its forkpty children reparented to init. The inject_control_wedge
.github/workflows/ci.yml:110:      # `cargo nextest` runs every test across ALL test binaries in ONE parallel
.github/workflows/ci.yml:111:      # pool — `cargo test` runs the binaries SERIALLY (only threads WITHIN a
.github/workflows/ci.yml:112:      # binary). nextest also builds the test targets, so the old standalone `cargo
.github/workflows/ci.yml:123:      # `-E` strings below AND in .config/nextest.toml's override — Phase A is the
.github/workflows/ci.yml:127:        run: cargo nextest run --workspace --no-fail-fast -E 'not ( (package(spt-daemon) & kind(test) & binary(/^(attach|brain_swap|broker|budget|daemon_e2e|daemon_lifecycle_real_brain|digest|dispatch|driven_by_selfheal|handoff|idempotent|inject_control_wedge|input_ack_deadlock|resume|shellchan|two_origin_spanning|twohost)$/)) | (package(spt) & kind(test) & binary(/^(attach_wedge_e2e|bind_cwd_project_e2e|brain_respawn_rename|brain_restart_psyche_dup_e2e|brain_split|brain_survive|dummy_harness_e2e|gateway_owner_shell_e2e|job_escape_e2e|livehost_bootgate_e2e|livehost_bootrace_e2e|livehost_nonresident_e2e|livehost_psyche_fail_e2e|n1_pairing|oneliner_e2e|resume_template_e2e|unhost_psyche_reap_e2e)$/)) | (package(spt-term) & kind(test)) )'
.github/workflows/ci.yml:129:        run: cargo nextest run --workspace --no-fail-fast -E '(package(spt-daemon) & kind(test) & binary(/^(attach|brain_swap|broker|budget|daemon_e2e|daemon_lifecycle_real_brain|digest|dispatch|driven_by_selfheal|handoff|idempotent|inject_control_wedge|input_ack_deadlock|resume|shellchan|two_origin_spanning|twohost)$/)) | (package(spt) & kind(test) & binary(/^(attach_wedge_e2e|bind_cwd_project_e2e|brain_respawn_rename|brain_restart_psyche_dup_e2e|brain_split|brain_survive|dummy_harness_e2e|gateway_owner_shell_e2e|job_escape_e2e|livehost_bootgate_e2e|livehost_bootrace_e2e|livehost_nonresident_e2e|livehost_psyche_fail_e2e|n1_pairing|oneliner_e2e|resume_template_e2e|unhost_psyche_reap_e2e)$/)) | (package(spt-term) & kind(test))'
.github/workflows/ci.yml:130:      # nextest does not run doctests — keep them on `cargo test --doc`.
.github/workflows/ci.yml:132:        run: cargo test --workspace --doc
.github/workflows/ci.yml:140:      # env-gated E2E leg against the real binary (the normal `cargo test`
.github/workflows/ci.yml:151:        run: cargo test -p spt --test notify_shell_e2e -- --nocapture
.github/workflows/ci.yml:156:        run: cargo test -p spt --test notify_shell_e2e -- --nocapture
.github/workflows/ci.yml:170:        run: cargo test -p spt --test oneliner_e2e -- --nocapture
.github/workflows/ci.yml:210:        run: cargo test -p spt-daemon --test budget --release -- --nocapture
.github/workflows/ci.yml:221:              cargo test -p spt-daemon --test budget --release -- --nocapture
.github/workflows/ci.yml:234:            cargo test -p spt-daemon --test budget --release -- --nocapture
.github/workflows/ci.yml:251:  # env-gated (SPT_N1_OLD_BROKER), so the normal `cargo test` sweep silent-skips
.github/workflows/ci.yml:301:          SPT_N1_OLD_BROKER="$BIN" cargo test -p spt --test n1_pairing -- --nocapture
.github/workflows/ci.yml:322:          cargo test -p spt --test n1_pairing -- --nocapture
.github/workflows/ci.yml:357:        run: cargo test -p spt-daemon --test twohost -- --nocapture
.github/workflows/ci.yml:382:        run: cargo test -p spt-daemon --test twohost -- --nocapture
.github/workflows/docs-publish.yml:38:          cargo test -q -p spt-runtime checked_in_schema


**CI uses `cargo nextest run` (lines 127/129) — process-per-test**: each test is its own process, so g2's env vars are NOT shared with g3/g4 → no env race under CI. doyle ran bare-parallel `cargo test` (the explicitly-unsupported path). My retry fix handles the real intra-test async-fault race. Validate g2 under nextest (CI's actual path), repeated:


cd "C:\Users\decid\Documents\projects\spt-core" && (cargo nextest --version 2>&1 | head -1) && echo "=== g2 under nextest (process-per-test), 3 repeats ===" && for i in 1 2 3; do echo "--- nextest run $i ---"; cargo nextest run -p spt-daemon --test inject_control_wedge -E 'test(g2_no_commit) | test(g3_content_free) | test(g4_translation_child) | test(large_endpoint_inject)' 2>&1 | grep -E "PASS|FAIL|Summary|g2_no_commit"; done

Run g2 + siblings under nextest process-per-test, 3x


cargo-nextest 0.9.137 (75ddba7e9 2026-05-26)
=== g2 under nextest (process-per-test), 3 repeats ===
--- nextest run 1 ---
        PASS [   1.307s] (1/4) spt-daemon::inject_control_wedge g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input
        PASS [   0.803s] (2/4) spt-daemon::inject_control_wedge g3_content_free_ping_never_duplicates_operator_keystrokes_into_binary
        PASS [   0.749s] (3/4) spt-daemon::inject_control_wedge g4_translation_child_is_reaped_no_zombie_after_session_down
        PASS [   0.473s] (4/4) spt-daemon::inject_control_wedge large_endpoint_inject_to_a_no_binary_session_spools_promptly_without_wedging
     Summary [   3.335s] 4 tests run: 4 passed, 6 skipped
--- nextest run 2 ---
        PASS [   1.315s] (1/4) spt-daemon::inject_control_wedge g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input
        PASS [   0.803s] (2/4) spt-daemon::inject_control_wedge g3_content_free_ping_never_duplicates_operator_keystrokes_into_binary
        PASS [   0.706s] (3/4) spt-daemon::inject_control_wedge g4_translation_child_is_reaped_no_zombie_after_session_down
        PASS [   0.479s] (4/4) spt-daemon::inject_control_wedge large_endpoint_inject_to_a_no_binary_session_spools_promptly_without_wedging
     Summary [   3.304s] 4 tests run: 4 passed, 6 skipped
--- nextest run 3 ---
        PASS [   1.301s] (1/4) spt-daemon::inject_control_wedge g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input
        PASS [   0.801s] (2/4) spt-daemon::inject_control_wedge g3_content_free_ping_never_duplicates_operator_keystrokes_into_binary
        PASS [   0.707s] (3/4) spt-daemon::inject_control_wedge g4_translation_child_is_reaped_no_zombie_after_session_down
        PASS [   0.453s] (4/4) spt-daemon::inject_control_wedge large_endpoint_inject_to_a_no_binary_session_spools_promptly_without_wedging
     Summary [   3.263s] 4 tests run: 4 passed, 6 skipped


**g2 + g3 + g4 + large_endpoint PASS 3/3 deterministically under nextest** (CI's path). The bare-parallel failures are the documented env-race ("Do NOT run bare cargo test"). My retry fix hardens the real intra-test async-fault race. Run the FULL inject_control_wedge under nextest (match CI exactly) + clippy + traceable:


cd "C:\Users\decid\Documents\projects\spt-core" && echo "=== full inject_control_wedge under nextest (CI path) ===" && cargo nextest run -p spt-daemon --test inject_control_wedge 2>&1 | grep -E "PASS|FAIL|Summary"; echo "=== clippy ===" && cargo clippy --workspace --all-targets 2>&1 | tail -2; echo "CLIPPY=${PIPESTATUS[0]}"; echo "=== traceable ===" && traceable-reqs check > /tmp/tr4.txt 2>&1; echo "TRACE_EXIT=$?"; grep -iE "MISSING|FAIL|invalid|unknown req|not found" /tmp/tr4.txt | head; grep -E "REQ-HAZARD-IDLE-SILENT-NONDELIVERY|REQ-MSG-DELIVERY-AXES" /tmp/tr4.txt | grep -E "\[OK\]|stages"

Full nextest inject_control_wedge + clippy + traceable


=== full inject_control_wedge under nextest (CI path) ===
        PASS [   3.242s] ( 1/10) spt-daemon::inject_control_wedge a_backed_up_controller_does_not_wedge_the_session
        PASS [   3.426s] ( 2/10) spt-daemon::inject_control_wedge a_journaled_input_wedge_does_not_starve_a_concurrent_rc_attach
        PASS [  12.389s] ( 3/10) spt-daemon::inject_control_wedge g1_choreography_happy_path_payload_reaches_pty_and_controller_keeps_control
        PASS [   1.289s] ( 4/10) spt-daemon::inject_control_wedge g2_no_commit_deadline_faults_binary_and_does_not_wedge_controller_input
        PASS [   1.111s] ( 5/10) spt-daemon::inject_control_wedge g3_content_free_ping_never_duplicates_operator_keystrokes_into_binary
        PASS [   0.689s] ( 6/10) spt-daemon::inject_control_wedge g4_translation_child_is_reaped_no_zombie_after_session_down
        PASS [   0.496s] ( 7/10) spt-daemon::inject_control_wedge large_endpoint_inject_to_a_no_binary_session_spools_promptly_without_wedging
        PASS [   3.035s] ( 8/10) spt-daemon::inject_control_wedge p0_paste_wedge_parked_write_does_not_starve_attach_or_wedge_broker
        PASS [   0.545s] ( 9/10) spt-daemon::inject_control_wedge w5_a1_locally_driven_session_reports_controller_by_none
        PASS [   5.907s] (10/10) spt-daemon::inject_control_wedge w5_a2_abandoned_remote_controller_conn_close_eviction_is_platform_divergent
     Summary [  32.131s] 10 tests run: 10 passed, 0 skipped
=== clippy ===
    Checking spt-daemon v0.14.2 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.92s
CLIPPY=0
=== traceable ===
TRACE_EXIT=0
       An adapter update is live and daemon-coordinated (the adapter analog of brain self-update, ADR-0004): for an endpoint with a running RESIDENT adapter binary (today the `[message-idle-translation-binary]`), the CLI keeps fetch+verify and hands the APPLY to the daemon over IPC, which per affected endpoint (1) STOPS the resident binary -> releases the OS file lock (fixes the Windows 'Access denied (os error 5)' overwrite failure), (2) swaps on disk ONLY files whose CRC differs from the staged archive (unchanged files + their still-running binaries untouched), (3) RE-CLONES the new on-disk manifest into the running `BrainLifecycle` (the in-memory manifest is cached at bringup and otherwise goes stale -> binaries+manifest back on the same page), (4) RESTARTS the resident binary from the new files. An endpoint NOT running -> CLI swaps directly (no lock, no cache). Only the resident class is cycled; ephemeral adapter binaries (Psyche loop, `[digest]` extractor, `[session.*]` runners, hooks) self-heal on next spawn and are excluded. The daemon keeps a per-endpoint registry of resident adapter children. (ADR-0025, v0.13.2)
       A `.spt` adapter archive may pack multiple platforms in one signed asset: shared `manifest.toml` + `strings/` at the root, role binaries under per-Rust-target-triple subdirectories (ADR-0016 triple vocabulary, e.g. `x86_64-pc-windows-msvc/`); install/update extracts the shared root plus ONLY `current_platform()`'s triple subdir, flattened into `install_dir` so flat `<install_dir>/<program>` resolution (REQ-INSTALL-11) is unchanged. Name stays `adapter.spt` (plain-tar or gzip, `--asset` optional default); one whole-archive Ed25519 signature over the fat archive (REQ-UPD-9 single-artifact verify). A legacy flat archive (no triple subdirs) extracts as today (free back-compat); a multi-platform archive sets `min_spt_core_version >= 0.13.2` (forward-compat gate, readable before extract); a multi-platform archive missing the recipient's triple -> typed `NoArtifactForPlatform`, never a silent no-op. Large adapters may still split per-platform (single-triple archives via `--asset`, or ADR-0016 update-set machinery). (ADR-0024, v0.13.2)
       `spt adapter translate-proof <adapter> --event <envelope> [--session <id>]` — the author-time EMIT-half proof tool for `[message-idle-translation-binary]` (ADR-0022), symmetric to `spt adapter digest-proof` (REQ-TERM-5). It spawns and feeds the adapter's declared translation binary EXACTLY as the daemon does at idle-delivery — running the REAL `spt_daemon::translation` driver VERBATIM (no protocol reimplementation): `TranslationChild::spawn` the binary, send the `{type:"init",endpoint_id,node}` line then the `{type:"event",envelope}` line, and read back the emitted `{key}`/`{text}`/`{delay_ms}`/`{commit}` keystroke-command stream — then prints it author-readable (each Key with its `key_to_bytes` rendering, Text quoted, Delay in ms, Commit marker) with counts. It fills the SAME `{id}`→option and `{session_id}`→(--session, else a placeholder) keys into the `--event` envelope the daemon fills at runtime, so an envelope that proofs here feeds faithfully live. EMIT-half ONLY: it proves the binary's spawn+feed+emit contract; it does NOT exercise the daemon's atomic PTY apply / controller-buffering (that stays covered by the W2 inject_control_wedge int gate) — `--help` says so. Exit codes mirror digest-proof: 0 ok, 1 on spawn-fail / zero commands / no-commit-or-output / unparseable, 2 when the adapter declares no `[message-idle-translation-binary]` section. The `TranslationChild` Drop does the bounded no-zombie reap. (v0.13.x)
       Peer address seeding, both cold starts: durable peer-addrs.json (identity dir) maps peer pubkey → last-known dialable address; the pump's resolver consults it FIRST with id-only discovery fallback on miss or dial failure (a stale addr never strands a peer); written by the pairing ceremony (both sides, from the live connection) and by the pump on successful connect; post-join first sync and post-restart resync converge in seconds, not ~1 min (M8 decisions 14, 20)
       Net-bind boot-race resilience: a daemon that comes up net-less (NetHost::start failed — e.g. the systemd unit autostarted before the network/DNS stack was ready, `Failed to create an address lookup service`) must SELF-HEAL — retry the net bring-up in the background with capped backoff and, on success, attach net to the broker + spawn the dispatcher/peer-pump (which today are gated on `net_up` at boot and so never start, leaving the node silently unreachable until a manual restart — kitsubito 2026-06-08). Status surfaces the net-less state honestly (a net-less broker renders as 'no connection', not only a pump-STALLED line with a bogus pre-boot heartbeat age). The installer's autostart unit waits for the network (`Wants=/After=network-online.target`) as belt-and-suspenders.
       Public CLI --help (the clap `///` doc-comments) and the generated `docs-site/src/cli/reference.md` MUST NOT contain internal tracker/decision codes — `REQ-*`, `F-###`, `M#-W#`, `ADR-####`. They are meaningless to an end user reading --help and ship to GH-Pages. A CI-gated scan (the `xtask check` docs gate) fails on any such token in the GENERATED reference.md (which by construction contains only clap help, so rustdoc `///` on fns/structs is OUT of scope and keeps its REQ/ADR cross-refs). Substance is kept; codes are rewritten to plain language. (v0.13.2 W6)
       A legitimately dead PTY child (real crash/kill) + an undrained operator pump must NOT wedge the broker for all other clients. ROOT (v0.12.0 real-harness defect): loopback attach output is a blocking write_all into a bounded 64KB tokio duplex (nethost.rs:1040,1090); when the operator's rc pump stops draining (tab closed) the buffer fills and write_all blocks forever (the 'loopback never hangs' assumption at nethost.rs:1103 is false), parking a worker in the 2-worker net runtime (nethost.rs:640); a couple of these saturate BOTH workers → every new attach / `endpoint run` stalls right after 'PUMP_IPC_READER: spawned' → 30s FIRST_EVENT_GRACE → 'no output / dead or wedged'; `daemon stop` cannot join the stuck workers. DISTINCT from the removed B1 path-(c) mutex deadlock. DISPOSITION = PROVE-DON'T-CHANGE (doyle GATE-PASS @e883f45, 2026-06-18): this ROOT is the SUPERSEDED v0.12.0 hypothesis — the post-L0 code ALREADY prevents the wedge, so NO fail-fast / worker-count code was added. serve_attach forwards fire-and-forget (net_stream_send op_id=None) and the broker-side send_stream is already BROKER-QUIC-DEADLINE-bounded (bounded_block_on, 10s); the loopback duplex is drained broker-INTERNALLY by the operator row's own read pump (RecvHalf::Loopback, retentive_cap==0 → evict-not-park) so a dead rc (a dropped IPC subscriber) never backs peer_w up; bounded_block_on parks the BROKER DISPATCH thread, not a net worker → no worker-pool exhaustion (full mechanism in the required_stages comment). Folds the status=online sub-check: a dead spt-hosted endpoint is marked OFFLINE within one reconcile tick on abrupt child death (broker exit-waiter reaps the session → B2 sees it absent) — PROVEN, no change. (v0.12.1)
       A bare brain restart leaves EXACTLY ONE `{id}-psyche` process per endpoint — no duplicate. On an abrupt brain death stop_host never runs (the LiveSet + owned child handles die with the brain) and Breap's job/group only reaps at DAEMON stop, so the PRIOR brain's Psyche stays ALIVE; the respawned brain's reconcile re-hosts a SECOND Psyche and overwrites the `{id}-psyche` perch pid, leaving the old one untracked + alive = a duplicate that lingers until daemon-stop (the operator's 'brain kill+restart wedged everything'). FIX: at brain start, BEFORE the first reconcile re-hosts, reap any pre-existing `{id}-psyche` orphan — ID-SPECIFICALLY (recycle-safe on the shared box, where sibling agents share the `claude` basename): scoped-kill the recorded pid ONLY IF it is alive AND its exe basename == the adapter's psyche program (normalize_basename) AND its COMMAND LINE contains the full psyche id `<id>-psyche` (baked via {id}); a sibling never carries THIS id, and any unreadable signal FAILS SAFE (decline to reap — a missed dup is bounded by Breap, a wrong-kill is catastrophic). CAVEAT: the cmdline carries `<id>-psyche` only when the adapter's psyche_init.command uses {id} (the norm); a non-{id} adapter safely MISSES the reap (today's behavior, Breap bounds it) — never a wrong-kill. (v0.12.0)
       The broker bounds every brain-waiting QUIC op (dial / open_stream / send_stream) so a black-holed or dead peer fails PROMPTLY with an ORDINARY error the broker REPLIES, never an unbounded await. The bound (< the brain's 30s PUMP_PEER_IO_TIMEOUT so the BROKER fires first) surfaces to the pump as a normal broker error reply → peer_outcome's non-TimedOut arm → drop conn + redial next tick, the round CONTINUES and the heartbeat keeps advancing — it must NEVER manifest as the brain's own read-deadline (the A-half poison → supervised-restart path REQ-HAZARD-PUMP-IPC-DEADLINE guards). Exactly-once is preserved: a timed-out journaled op fails INSIDE its apply_once closure so no phantom conn_id/stream_id is recorded and a fresh tick re-dials cleanly. The happy path is unchanged (a live peer completes with zero added latency; the bound only bites a non-responsive peer). This is the ROOT-cause cure for the 2.2h hfenduleam pump wedge — a dead roster peer whose QUIC path the broker awaited unbounded — recurring on hfenduleam 2026-06-16.
       A daemon-state wire-format change (e.g. the v0.9.0 adapter-agnostic Seed) does NOT take effect until a DELIBERATE full broker restart: the broker serves the seed-control channel and is RESIDENT across a brain-only self-update (ADR-0004 no-terminate-during-update forbids auto-killing it), so a NEW-version CLI talking to a still-resident OLD broker fails the seed handshake — the old broker cannot deserialize the new Seed (its formerly-required `adapter` field is gone) and drops the conn without an ack, which surfaces to the CLI as a raw UnexpectedEof 'failed to fill whole buffer'. spt-core must (a) surface an ACTIONABLE diagnostic on that seed-ack EOF (name the stale-broker cause + the `spt daemon stop` fix — the broker restarts on the next api call), never the cryptic io error; and (b) document the operational rule (a deliberate broker restart is required on any daemon-state wire change — NOT automatic) + the FORWARD discipline (daemon-state/Seed schema changes stay additive + serde-default so a resident OLD broker tolerates a NEW CLI across a brain-only update; note this would NOT have rescued 0.9.0 itself, since the old broker's `adapter` was a required field). perri PREP-4 FINDING 1 (v0.9.0 CLI vs stale 0.8.x broker).
  [OK] REQ-HAZARD-IDLE-SILENT-NONDELIVERY  required: [doc, impl, unit, int]  stages: +doc +impl +unit +int
  [OK] REQ-MSG-DELIVERY-AXES  required: [doc]  stages: +doc -impl -unit -int


**Full validation green:**
- **inject_control_wedge under nextest (CI's exact path): 10/10 PASS** — g2/g3/g4/large_endpoint all clean
- clippy EXIT0 · traceable EXIT0 (REQ-HAZARD-IDLE-SILENT-NONDELIVERY [OK] all stages; REQ-MSG-DELIVERY-AXES [OK] +doc — the v0.15.0 ref resolves)

The bare-parallel flake = documented env-race (unsupported); CI nextest is clean. Follow-on commit (deployah cherry-picks both):


cd "C:\Users\decid\Documents\projects\spt-core" && git status --short

Show changed files for the follow-on commit


 M CONTEXT.md
 M ROADMAP.md
 M crates/spt-daemon/tests/inject_control_wedge.rs
 M docs/KNOWN-HAZARDS.md
 M traceable-reqs.toml
?? docs/adr/0028-activity-gated-delivery-and-send-modifier-axes.md


Unexpected: CONTEXT.md, ROADMAP.md, and a new ADR-0028 file — these are **doyle's v0.15.0 design work** in the shared worktree, NOT mine. Must not include them in my v0.14.3 commit. Check git state — whether doyle's REQ-MSG-DELIVERY-AXES (in traceable-reqs.toml) is committed or uncommitted-alongside-mine:


cd "C:\Users\decid\Documents\projects\spt-core" && echo "=== log since main ===" && git log --oneline main..HEAD; echo "=== traceable-reqs.toml diff vs HEAD: does it contain REQ-MSG-DELIVERY-AXES? ===" && git diff HEAD -- traceable-reqs.toml | grep -nE "^\+.*REQ-MSG-DELIVERY-AXES|^\+id =|REQ-HAZARD-IDLE-SILENT|FAILED STATE|fault-transient|0028" | head -20

Check branch log + whether toml diff includes doyle's REQ