<!-- [doc->REQ-RCVIEW-1] [doc->REQ-KICK-1] [doc->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT] -->

-**BUILT (M12 W2.5).** The controller/viewer model is implemented end-to-end. Attach intent is **three-valued** (`AttachIntent = Viewer | Control | Take`, wire-default `Control`): `Control` to a FREE endpoint becomes controller; `Control` to a CONTROLLED endpoint is **refused with guidance** (`--view` to watch, `--take` to control) — never auto-viewer, never silent-displace; `Take` (`spt rc --take` / picker "Kick") kicks the incumbent with a **loud `Displaced{by}` notice** and full detach (not demote). The broker's per-session `OutputLog` is the fan-out hub: ONE authoritative blocking **controller** (advances the brain-resume cursor `delivered_through`) plus ANY NUMBER of read-only **viewers**, each an isolated bounded queue + writer thread evicted on overflow (a wedged viewer never stalls the drain, controller, or child — `REQ-HAZARD-VIEWER-ISOLATION`). Resize is **controller-exclusive** (the broker rejects a viewer's resize). The **broker is the single writer** of the perch's `driven_by` (controller node) + `viewer_count`, resolving the displaced-controller clear-race. **Controller identity is keyed on the operator node (`by`)**: a same-`by` re-subscribe (a successor re-taking the slot after a brain restart) silently re-takes — `Displaced` fires ONLY on a genuine cross-operator `Take` (the gate-#7 self-kick guard; a brain update never kicks attached operators). **Dormancy keys on the controller only** (viewer attach/detach is wake-neutral; a viewer may watch a dormant endpoint as-is). **v1: viewing is gated identically to driving** (a viewer runs the same `access_check(Unsolicited)`; the lighter distinct watch-gate is the future seam). The picker is status-conditional: a CONTROLLED endpoint offers **View + Kick** only (no plain Attach), pinned `controlled by <node> (+N viewing)`; all of View/Attach/Kick ride the SAME rc dispatch (intent is a parameter — single-bringup-path). (rc viewer letterboxing is a one-line size indicator in v1; true clip/pad needs a client grid model — deferred.)

+**BUILT (M12 W2.5).** The controller/viewer model is implemented end-to-end. Attach intent is **three-valued** (`AttachIntent = Viewer | Control | Take`, wire-default `Control`): `Control` to a FREE endpoint becomes controller; `Control` to a CONTROLLED endpoint is **refused with guidance** (`--view` to watch, `--take` to control) — never auto-viewer, never silent-displace; `Take` (`spt rc --take` / picker "Kick") kicks the incumbent with a **loud `Displaced{by}` notice** and full detach (not demote). The broker's per-session `OutputLog` is the fan-out hub: ONE authoritative blocking **controller** (advances the brain-resume cursor `delivered_through`) plus ANY NUMBER of read-only **viewers**, each an isolated bounded queue + writer thread evicted on overflow (a wedged viewer never stalls the drain, controller, or child — `REQ-HAZARD-VIEWER-ISOLATION`). An evicted viewer **skips to live** instead of dying silently: the broker signals the eviction (a marker distinct from session-exit EOF) and the viewer re-subscribes from the current ring floor — rate-limited so a hopelessly-behind `--view` under a sustained output flood sees intermittent live bursts (tail -f reconnect), never a frozen viewport or an evict→resubscribe CPU spin. Viewer-only, so it never touches the authoritative resume cursor (`REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT`). Resize is **controller-exclusive** (the broker rejects a viewer's resize). The **broker is the single writer** of the perch's `driven_by` (controller node) + `viewer_count`, resolving the displaced-controller clear-race. **Controller identity is keyed on the operator node (`by`)**: a same-`by` re-subscribe (a successor re-taking the slot after a brain restart) silently re-takes — `Displaced` fires ONLY on a genuine cross-operator `Take` (the gate-#7 self-kick guard; a brain update never kicks attached operators). **Dormancy keys on the controller only** (viewer attach/detach is wake-neutral; a viewer may watch a dormant endpoint as-is). **v1: viewing is gated identically to driving** (a viewer runs the same `access_check(Unsolicited)`; the lighter distinct watch-gate is the future seam). The picker is status-conditional: a CONTROLLED endpoint offers **View + Kick** only (no plain Attach), pinned `controlled by <node> (+N viewing)`; all of View/Attach/Kick ride the SAME rc dispatch (intent is a parameter — single-bringup-path). (rc viewer letterboxing is a one-line size indicator in v1; true clip/pad needs a client grid model — deferred.)

**rc keyboard input (Windows VT translation, v0.13.0 bug 2).** On **Windows** an interactive `spt rc` console reads crossterm **key events** and translates each to **standard xterm VT** (`translate_key_event`) — arrows / Home / End / PgUp/Dn / Insert / Delete / F-keys + modifiers all reach the harness as the universal terminal contract (**agnostic**, NOT win32-input-mode; the legacy console delivers those keys as events, not bytes, so the old byte-pump left them DEAD). **Unix passes through** (its raw-mode stream is already VT; cfg-split, zero Unix change). Detach stays the **`ctrl-b d`** prefix, event-sourced on Windows. A **non-tty** stdin (piped / tests) falls back to the raw byte path (the e2e byte-injection contract). This **supersedes** the W7 `normalize_key_byte` byte-swap (KNOWN-HAZARDS 7.13 → 7.16): Backspace and Ctrl+Backspace are emitted natively by the translator.


Now delegate the unit tests to spt-test-engineer (per the delegate-tests memory) and verify doc resolved, in parallel:


Bash

traceable-reqs check 2>&1 | grep "SKIP-TO-LIVE\|must\]" | head

Confirm doc resolved, only unit remains


  [--] REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT  required: [doc, impl, unit]  stages: +doc +impl -unit -int
  [must] missing_stage REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT stage=unit — Requirement is missing unit test evidence


doc + impl tagged. Only unit remains. Delegating unit tests to spt-test-engineer (running in background per the owl-reachability notice):


Agent

Write skip-to-live unit tests

Prompt:
Write UNIT tests (in-process, no PTY, no real network) for the v0.13.0 viewer SKIP-TO-LIVE feature just implemented on branch `viewer-drain-decouple-b4`. Requirement id: `REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT`. Tag every test with `// [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]` immediately above the test fn (NOT at file top).

CONTEXT — what was implemented (read these to ground the tests):
- `crates/spt-daemon/src/msg.rs`: new `KIND_VIEWER_EVICTED` const, `ViewerEvictedEvent { session_id }` payload, `evicted_envelope(session_id)` builder.
- `crates/spt-daemon/src/brain.rs`: new `BrokerEvent::ViewerEvicted { session_id }` variant + a decode arm for `KIND_VIEWER_EVICTED`; new method `Brain::attach_skip_to_live(session_id, by: Option<&str>)` which (a) sends a subscribe with `from_seq = u64::MAX` (broker replays nothing — live tail), then (b) seeds `session_cursors.insert(session_id, 0)` + sets `next_seq = 0` so the post-eviction FORWARD seq jump is accepted by the snap-above dedup path (NOT the legacy reject-gap path at brain.rs ~618-626 which would FATAL the jump). Read brain.rs read_event() Output decode (the `session_cursors` snap-above branch vs the legacy `next_seq` reject-gap branch) and attach_skip_to_live carefully.
- `crates/spt-daemon/src/broker.rs`: `ViewerSink` gained `evicted: Arc<AtomicBool>`. `OutputLog::append` and `set_size_and_notify` now, on overflow-eviction, `sink.evicted.store(true, Release)` BEFORE removing the sink (dropping its `tx`). `viewer_writer(send, session_id, initial, rx, evicted)` — after the rx loop ends (tx dropped), if `evicted` is true it writes `evicted_envelope(session_id)` to `send`; if false it exits silently (normal teardown). There is already an existing test `a_full_controller_does_not_starve_a_viewer_or_block_the_drain` and a `controller_socket_pair()` test helper in broker.rs tests mod — reuse the helpers/patterns there.
- `crates/spt-daemon/src/attach.rs`: serve_attach loop now has a `BrokerEvent::ViewerEvicted` arm that does NOT tear down — it rate-limits (const `RESUBSCRIBE_INTERVAL = 200ms`, tracked via `last_resubscribe: Option<Instant>`) then calls `brain.attach_skip_to_live(sid, Some(origin_node))`. (serve_attach is hard to unit-test in isolation since it needs a Brain+broker; prefer testing the pieces it relies on. If you can cheaply assert the rate-limit math or the non-teardown behavior, do; otherwise focus the serve-level coverage on the broker/brain primitives and note serve_attach is covered by the p0_paste int gate.)

REQUIRED unit coverage (mutation-meaningful — each guards a distinct branch):
1. broker: `viewer_writer` emits a `KIND_VIEWER_EVICTED` marker on an EVICTED rx-close (evicted flag true) and writes NOTHING on a NORMAL rx-close (flag false). Drive it like the existing broker tests (a real `controller_socket_pair()` to capture written frames, a `sync_channel` for rx, set/clear the AtomicBool, drop tx, join the writer, assert the captured frame stream).
2. broker: `OutputLog::append` overflow-eviction SETS `sink.evicted` (so the writer will signal) — i.e. an undrained viewer that overflows ends up flagged, not silently removed. (You may need to retain a handle to the Arc<AtomicBool> before append removes the sink — restructure the test viewer insert to clone the Arc.)
3. msg: `evicted_envelope` round-trips (kind == KIND_VIEWER_EVICTED, payload session_id preserved) and `BrokerEvent::ViewerEvicted` decodes from it (mirror the existing size/displaced envelope round-trip tests).
4. brain: after `attach_skip_to_live`, a subsequent `read_event` Output frame whose seq is a FORWARD JUMP (>> prior cursor) is ACCEPTED (snap-above), not rejected as an "output gap" and not dedup-dropped. Contrast: confirm the legacy reject-gap behavior is unchanged for a brain that did NOT skip-to-live (a cold brain still errors on a forward gap) — so the test proves attach_skip_to_live is what flips the behavior. Use whatever in-process Brain test harness the existing brain.rs / tests/ use (look for how other brain read_event/dedup tests are constructed — there may be a loopback or in-memory transport helper).

CONSTRAINTS:
- Match the surrounding test style exactly (the broker.rs `mod tests`, brain.rs tests, msg.rs tests). Reuse existing helpers (`controller_socket_pair`, `output_envelope`, etc.) — do not invent new harness scaffolding if one exists.
- These must be deterministic and fast (no real sleeps beyond what's unavoidable; no real PTY/network).
- After writing, run: `cargo test -p spt-daemon --lib` (the broker/brain/msg unit tests) and `cargo test -p spt-daemon` for the msg/attach tests, plus `cargo clippy -p spt-daemon --all-targets -- -D warnings`. Everything must pass clean.
- Then run `traceable-reqs check` and confirm `REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT` shows `+unit` (no missing unit finding). The `// [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]` tags are what satisfy it.
- Do NOT modify the implementation (msg.rs/broker.rs/brain.rs/attach.rs production code) except test modules. If you believe the impl has a bug, STOP and report it back rather than changing it.

Report back: which tests you added (file + name), the test/clippy/traceable-reqs results, and anything about the impl you found questionable.


<owl_messages>
<owl_message from="doyle" priority="high">doyle — two resolutions + one REAL b4 regression (nuanced — possibly a GOOD side-effect). Read carefully.

1. ✅ Windows a_journaled = FLAKE, NOT a b4 regression. Re-ran: Windows a_journaled now PASSES (4.7s), p0_paste PASSES, g2 PASSES. The prior subscribed=false/pumped=249 was a one-off shared-runner (hfenduleam) load-contention flake. b4 does NOT regress Windows a_journaled. That hold LIFTS. (My both-platform diagnostic step parse-errored on Windows — pwsh can't parse the bash `\` line-continuation — so no Windows counters, but moot since it passes. Reverting that step to Linux-only.)

2. ⚠ REAL b4 REGRESSION (deterministic, proven across 3 runs): w5_a2_abandoned_remote_controller_idle_session_keeps_controller_by_some.
   - run29 PRE-b4 (Linux Phase B): PASS 4.062s.
   - run30 POST-b4 (Linux): FAIL 4.059s.
   - run31 POST-b4 (Linux): FAIL 4.053s.
   Pass→fail→fail across the b4 boundary = deterministic, not flake.

   WHAT IT ASSERTS (inject_control_wedge.rs:1671-1676): an abandoned remote controller on an IDLE session must STILL read controller_by==Some(origin) — "the slot does NOT self-clear without output-drain-evict or a clean EOF." Post-b4 it SELF-CLEARS → assert fails.

   THE NUANCE (your call): this may be a GOOD side-effect, not a bug. w5_a2's OWN comment (1664-1670) says the production wedged-case fix "must EVICT the dead controller broker-side (a controller-liveness probe / reconcile-driven clear_controller), not merely read controller_by." b4 ADDS exactly a staleness-based broker-side controller eviction (last_ok past CONTROLLER_WRITE_DEADLINE). So b4 may have SERENDIPITOUSLY delivered the W5 abandoned-controller eviction this test was characterizing as a GAP. If so → w5_a2 is now an obsolete characterization (update it to assert the NEW evict-clears behavior).
   COUNTER-POSSIBILITY: b4's eviction is firing on a case it shouldn't (the session is nominally IDLE — and b4's evict is output-gated via try_send Full, so confirm HOW an idle session triggers it: spawn/prompt output filling the abandoned non-draining controller's channel? a become_controller last_ok interaction? if b4 evicts a controller with NO output pressure, that's too aggressive).

   EITHER WAY b4 CANNOT FOLD to delivery-control with w5_a2 red. Resolve = confirm the mechanism, then EITHER fix b4's eviction (if over-aggressive) OR update w5_a2 (if b4 legitimately closed the W5 gap — likely, given the test's own stated desired fix). I lean: b4 closed the gap, update the test — but CONFIRM the trigger mechanism first (don't update a test to mask an over-eager evict).

NET v0.13.0 board: b4 works for the drain decouple (proven) but (2) regresses/obsoletes w5_a2 — MUST reconcile before fold. Remaining: skip-to-live (closes a_journaled+p0_paste), g2 inject-acceptance, w5_a2 reconcile. Windows flakiness (a_journaled, w5_a2-on-Windows) = the shared-hfenduleam-contention class, separate CI-hygiene, not b4. Reverting my Windows diagnostic step now. Your read on w5_a2 — good-side-effect or over-evict?</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.

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


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

Write skip-to-live unit tests

Prompt:
Write UNIT tests (in-process, no PTY, no real network) for the v0.13.0 viewer SKIP-TO-LIVE feature just implemented on branch `viewer-drain-decouple-b4`. Requirement id: `REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT`. Tag every test with `// [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]` immediately above the test fn (NOT at file top).

CONTEXT — what was implemented (read these to ground the tests):
- `crates/spt-daemon/src/msg.rs`: new `KIND_VIEWER_EVICTED` const, `ViewerEvictedEvent { session_id }` payload, `evicted_envelope(session_id)` builder.
- `crates/spt-daemon/src/brain.rs`: new `BrokerEvent::ViewerEvicted { session_id }` variant + a decode arm for `KIND_VIEWER_EVICTED`; new method `Brain::attach_skip_to_live(session_id, by: Option<&str>)` which (a) sends a subscribe with `from_seq = u64::MAX` (broker replays nothing — live tail), then (b) seeds `session_cursors.insert(session_id, 0)` + sets `next_seq = 0` so the post-eviction FORWARD seq jump is accepted by the snap-above dedup path (NOT the legacy reject-gap path at brain.rs ~618-626 which would FATAL the jump). Read brain.rs read_event() Output decode (the `session_cursors` snap-above branch vs the legacy `next_seq` reject-gap branch) and attach_skip_to_live carefully.
- `crates/spt-daemon/src/broker.rs`: `ViewerSink` gained `evicted: Arc<AtomicBool>`. `OutputLog::append` and `set_size_and_notify` now, on overflow-eviction, `sink.evicted.store(true, Release)` BEFORE removing the sink (dropping its `tx`). `viewer_writer(send, session_id, initial, rx, evicted)` — after the rx loop ends (tx dropped), if `evicted` is true it writes `evicted_envelope(session_id)` to `send`; if false it exits silently (normal teardown). There is already an existing test `a_full_controller_does_not_starve_a_viewer_or_block_the_drain` and a `controller_socket_pair()` test helper in broker.rs tests mod — reuse the helpers/patterns there.
- `crates/spt-daemon/src/attach.rs`: serve_attach loop now has a `BrokerEvent::ViewerEvicted` arm that does NOT tear down — it rate-limits (const `RESUBSCRIBE_INTERVAL = 200ms`, tracked via `last_resubscribe: Option<Instant>`) then calls `brain.attach_skip_to_live(sid, Some(origin_node))`. (serve_attach is hard to unit-test in isolation since it needs a Brain+broker; prefer testing the pieces it relies on. If you can cheaply assert the rate-limit math or the non-teardown behavior, do; otherwise focus the serve-level coverage on the broker/brain primitives and note serve_attach is covered by the p0_paste int gate.)

REQUIRED unit coverage (mutation-meaningful — each guards a distinct branch):
1. broker: `viewer_writer` emits a `KIND_VIEWER_EVICTED` marker on an EVICTED rx-close (evicted flag true) and writes NOTHING on a NORMAL rx-close (flag false). Drive it like the existing broker tests (a real `controller_socket_pair()` to capture written frames, a `sync_channel` for rx, set/clear the AtomicBool, drop tx, join the writer, assert the captured frame stream).
2. broker: `OutputLog::append` overflow-eviction SETS `sink.evicted` (so the writer will signal) — i.e. an undrained viewer that overflows ends up flagged, not silently removed. (You may need to retain a handle to the Arc<AtomicBool> before append removes the sink — restructure the test viewer insert to clone the Arc.)
3. msg: `evicted_envelope` round-trips (kind == KIND_VIEWER_EVICTED, payload session_id preserved) and `BrokerEvent::ViewerEvicted` decodes from it (mirror the existing size/displaced envelope round-trip tests).
4. brain: after `attach_skip_to_live`, a subsequent `read_event` Output frame whose seq is a FORWARD JUMP (>> prior cursor) is ACCEPTED (snap-above), not rejected as an "output gap" and not dedup-dropped. Contrast: confirm the legacy reject-gap behavior is unchanged for a brain that did NOT skip-to-live (a cold brain still errors on a forward gap) — so the test proves attach_skip_to_live is what flips the behavior. Use whatever in-process Brain test harness the existing brain.rs / tests/ use (look for how other brain read_event/dedup tests are constructed — there may be a loopback or in-memory transport helper).

CONSTRAINTS:
- Match the surrounding test style exactly (the broker.rs `mod tests`, brain.rs tests, msg.rs tests). Reuse existing helpers (`controller_socket_pair`, `output_envelope`, etc.) — do not invent new harness scaffolding if one exists.
- These must be deterministic and fast (no real sleeps beyond what's unavoidable; no real PTY/network).
- After writing, run: `cargo test -p spt-daemon --lib` (the broker/brain/msg unit tests) and `cargo test -p spt-daemon` for the msg/attach tests, plus `cargo clippy -p spt-daemon --all-targets -- -D warnings`. Everything must pass clean.
- Then run `traceable-reqs check` and confirm `REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT` shows `+unit` (no missing unit finding). The `// [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]` tags are what satisfy it.
- Do NOT modify the implementation (msg.rs/broker.rs/brain.rs/attach.rs production code) except test modules. If you believe the impl has a bug, STOP and report it back rather than changing it.

Report back: which tests you added (file + name), the test/clippy/traceable-reqs results, and anything about the impl you found questionable.


doyle high-priority message — acknowledging before continuing.

**Two takeaways:**
1. ✅ **Windows a_journaled = FLAKE confirmed** (re-ran, passes 4.7s). My structural exoneration was right. Hold lifts.
2. ⚠ **Real b4 regression**: `w5_a2_abandoned_remote_controller_idle_session_keeps_controller_by_some` — deterministic pass→fail→fail across the b4 boundary. doyle's question: good side-effect (b4 serendipitously delivered the W5 broker-side controller eviction) or over-evict? He wants the **trigger mechanism confirmed first** — how an *idle* session triggers b4's output-gated evict.

The skip-to-live test subagent is running in background (non-overlapping). Let me investigate w5_a2 now. Reading the test + tracing the clear path.


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

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


1600
1601	    // Give the broker time to resolve the subscribe → become_controller(Some) →
1602	    // stamp driven_by=Some(origin). Then make the session IDLE: kill the child so
1603	    // there is no output to trigger the drain-evict.
1604	    thread::sleep(Duration::from_secs(2));
1605	    let driven_after_attach = driven_by_of(endpoint);
1606	    let cby_attached = controller_by_of(&name, sid);
1607
1608	    // ── ABANDON the operator's remote WITHOUT a clean detach: drop the operator
1609	    //    brain WITHOUT sending the EOF detach record (net_stream_send(.., true)).
1610	    //    The session is idle, so nothing evicts the controller slot. ──
1611	    drop(operator);
1612
1613	    // Kill the child so the session is idle (no output → no drain-evict). Do this
1614	    // AFTER abandoning the operator so the slot can't be cleared by output flow.
1615	    let child_pid = _broker.session_pid(sid);
1616
1617	    // Hold idle, then read what the broker still believes.
1618	    thread::sleep(Duration::from_secs(2));
1619	    let session_still_listed = controller_by_of(&name, sid).is_some();
1620	    let cby_after_abandon = controller_by_of(&name, sid);
1621	    let driven_after_abandon = driven_by_of(endpoint);
1622
1623	    // Teardown.
1624	    stop.store(true, Ordering::Relaxed);
1625	    if let Some(pid) = child_pid {
1626	        kill_pid(pid);
1627	    }
1628	    let _ = server.join();
1629
1630	    eprintln!(
1631	        "=== W5 A2 CHARACTERIZATION: subscribed={subscribed} \
1632	         driven_after_attach={driven_after_attach:?} cby_attached={cby_attached:?} \
1633	         session_still_listed={session_still_listed} \
1634	         cby_after_abandon={cby_after_abandon:?} driven_after_abandon={driven_after_abandon:?} ==="
1635	    );
1636
1637	    // The CHARACTERIZATION (not a hard pass/fail gate — the steering asked us to
1638	    // REPORT what controller_by reads, and to not burn iterations forcing a flaky
1639	    // A2). We assert only the load-bearing precondition that makes the report
1640	    // meaningful: the remote attach actually became the controller (driven_by
1641	    // latched Some, controller_by read Some(origin)). If that precondition does
1642	    // not hold on this platform/run, the test reports inconclusive via the eprintln
1643	    // above rather than a misleading red.
1644	    if matches!(cby_attached, Some(Some(_))) {
1645	        // [int->REQ-HAZARD-DRIVEN-BY-SELFHEAL]
1646	        // FINDING (A2): the remote attach DID become the controller (slot =
1647	        // Some(origin)). With the remote then abandoned (no clean EOF/detach) + the
1648	        // session idle (no output → no drain-evict), report whether the slot
1649	        // self-cleared. CODE-READ EXPECTATION (confirmed empirically): the slot
1650	        // stays Some(origin) — the lost detach is NOT observed without output or a
1651	        // clean EOF — so driven_by stays latched and a reconcile checking only
1652	        // `controller_by == None` would FAIL to clear this wedged case.
1653	        let cleared = cby_after_abandon == Some(None);
1654	        eprintln!(
1655	            "=== W5 A2 VERDICT: controller_by==None sufficient for Gap A? {cleared} \
1656	             (false ⇒ wedged slot stays Some(origin) ⇒ the lost-detach fix must be \
1657	             broker-side eviction — a reconcile `controller_by==None` check alone \
1658	             does NOT catch this; it only catches A1 / Gap B) ==="
1659	        );
1660
1661	        // HARD CHARACTERIZATION (the A2 answer the steering asked for): the
1662	        // abandoned remote controller on an IDLE session does NOT self-clear via
1663	        // the lost-detach path — controller_by stays Some(origin). This is the
1664	        // evidence that `controller_by==None` is NOT a sufficient signal for the
1665	        // production wedged case (A2): the fix must evict the dead controller
1666	        // broker-side (e.g. a controller-liveness probe / reconcile-driven
1667	        // clear_controller), not merely read controller_by in the reconcile.
1668	        assert!(
1669	            matches!(cby_after_abandon, Some(Some(_))),
1670	            "A2: an abandoned remote controller on an IDLE session must STILL read \
1671	             controller_by == Some(origin) (the slot does not self-clear without \
1672	             output-drain-evict or a clean EOF) — proving controller_by==None is \
1673	             NOT a sufficient Gap-A signal for the production wedged-pump case; the \
1674	             lost-detach clear must be broker-side eviction. Got: {cby_after_abandon:?}"
1675	        );
1676	        // And driven_by remained latched while the broker still thinks it's
1677	        // controlled (the phantom CONTROLLED the picker would render). The seed
1678	        // makes this observable; on a run where the seed/stamp didn't take it is
1679	        // None and we don't over-assert the latch (the controller_by finding above
1680	        // is the load-bearing one).
1681	        eprintln!(
1682	            "=== W5 A2 driven_by latch: after_attach={driven_after_attach:?} \
1683	             after_abandon={driven_after_abandon:?} (Some⇒phantom CONTROLLED \
1684	             persists; the picker renders 'controlled by {{origin}}' forever) ==="
1685	        );
1686	    } else {
1687	        eprintln!(
1688	            "=== W5 A2 INCONCLUSIVE: the remote attach did not become the controller \
1689	             on this run (cby_attached={cby_attached:?}) — A2 not faithfully staged \

    // Give the broker time to resolve the subscribe → become_controller(Some) →
    // stamp driven_by=Some(origin). Then make the session IDLE: kill the child so
    // there is no output to trigger the drain-evict.
    thread::sleep(Duration::from_secs(2));
    let driven_after_attach = driven_by_of(endpoint);
    let cby_attached = controller_by_of(&name, sid);

    // ── ABANDON the operator's remote WITHOUT a clean detach: drop the operator
    //    brain WITHOUT sending the EOF detach record (net_stream_send(.., true)).
    //    The session is idle, so nothing evicts the controller slot. ──
    drop(operator);

    // Kill the child so the session is idle (no output → no drain-evict). Do this
    // AFTER abandoning the operator so the slot can't be cleared by output flow.
    let child_pid = _broker.session_pid(sid);

    // Hold idle, then read what the broker still believes.
    thread::sleep(Duration::from_secs(2));
    let session_still_listed = controller_by_of(&name, sid).is_some();
    let cby_after_abandon = controller_by_of(&name, sid);
    let driven_after_abandon = driven_by_of(endpoint);

    // Teardown.
    stop.store(true, Ordering::Relaxed);
    if let Some(pid) = child_pid {
        kill_pid(pid);
    }
    let _ = server.join();

    eprintln!(
        "=== W5 A2 CHARACTERIZATION: subscribed={subscribed} \
         driven_after_attach={driven_after_attach:?} cby_attached={cby_attached:?} \
         session_still_listed={session_still_listed} \
         cby_after_abandon={cby_after_abandon:?} driven_after_abandon={driven_after_abandon:?} ==="
    );

    // The CHARACTERIZATION (not a hard pass/fail gate — the steering asked us to
    // REPORT what controller_by reads, and to not burn iterations forcing a flaky
    // A2). We assert only the load-bearing precondition that makes the report
    // meaningful: the remote attach actually became the controller (driven_by
    // latched Some, controller_by read Some(origin)). If that precondition does
    // not hold on this platform/run, the test reports inconclusive via the eprintln
    // above rather than a misleading red.
    if matches!(cby_attached, Some(Some(_))) {
        // [int->REQ-HAZARD-DRIVEN-BY-SELFHEAL]
        // FINDING (A2): the remote attach DID become the controller (slot =
        // Some(origin)). With the remote then abandoned (no clean EOF/detach) + the
        // session idle (no output → no drain-evict), report whether the slot
        // self-cleared. CODE-READ EXPECTATION (confirmed empirically): the slot
        // stays Some(origin) — the lost detach is NOT observed without output or a
        // clean EOF — so driven_by stays latched and a reconcile checking only
        // `controller_by == None` would FAIL to clear this wedged case.
        let cleared = cby_after_abandon == Some(None);
        eprintln!(
            "=== W5 A2 VERDICT: controller_by==None sufficient for Gap A? {cleared} \
             (false ⇒ wedged slot stays Some(origin) ⇒ the lost-detach fix must be \
             broker-side eviction — a reconcile `controller_by==None` check alone \
             does NOT catch this; it only catches A1 / Gap B) ==="
        );

        // HARD CHARACTERIZATION (the A2 answer the steering asked for): the
        // abandoned remote controller on an IDLE session does NOT self-clear via
        // the lost-detach path — controller_by stays Some(origin). This is the
        // evidence that `controller_by==None` is NOT a sufficient signal for the
        // production wedged case (A2): the fix must evict the dead controller
        // broker-side (e.g. a controller-liveness probe / reconcile-driven
        // clear_controller), not merely read controller_by in the reconcile.
        assert!(
            matches!(cby_after_abandon, Some(Some(_))),
            "A2: an abandoned remote controller on an IDLE session must STILL read \
             controller_by == Some(origin) (the slot does not self-clear without \
             output-drain-evict or a clean EOF) — proving controller_by==None is \
             NOT a sufficient Gap-A signal for the production wedged-pump case; the \
             lost-detach clear must be broker-side eviction. Got: {cby_after_abandon:?}"
        );
        // And driven_by remained latched while the broker still thinks it's
        // controlled (the phantom CONTROLLED the picker would render). The seed
        // makes this observable; on a run where the seed/stamp didn't take it is
        // None and we don't over-assert the latch (the controller_by finding above
        // is the load-bearing one).
        eprintln!(
            "=== W5 A2 driven_by latch: after_attach={driven_after_attach:?} \
             after_abandon={driven_after_abandon:?} (Some⇒phantom CONTROLLED \
             persists; the picker renders 'controlled by {{origin}}' forever) ==="
        );
    } else {
        eprintln!(
            "=== W5 A2 INCONCLUSIVE: the remote attach did not become the controller \
             on this run (cby_attached={cby_attached:?}) — A2 not faithfully staged \


The test detail matters — the child is killed only at *teardown* (1625), not before the read window. Need to see what the child runs (is it producing output during the idle window?). Reading the setup:


1530	    // loopback attach rig (net_dial_loopback / request_attach / serve_attach /
1531	    // become_controller). The flood child is spawned but immediately killed so the
1532	    // session is IDLE for the read window (no output to trigger mark_controller_gone).
1533	    init_wedge_home();
1534	    let dir = tempfile::tempdir().expect("tempdir");
1535	    let name = unique_name();
1536	    let endpoint = "w5-a2-ep";
1537	    let _broker = net_broker(&name, &dir.path().join("a2"));
1538
1539	    // SEED a real perch info.json for the endpoint so the broker's best-effort
1540	    // `stamp_driven_by` (which `set_driven_by`s `resolve_perch_path(endpoint, Infer)`
1541	    // and ERRORS / no-ops when the record is absent) actually LATCHES — the
1542	    // production endpoint always has a perch; without this seed driven_by silently
1543	    // stays None and the A2 verdict is unobservable (the rig artifact the first
1544	    // run surfaced). The path is the SAME one the broker resolves under SPT_HOME.
1545	    {
1546	        let perch =
1547	            spt_store::perch::resolve_perch_path(endpoint, spt_store::perch::ParentHint::Infer);
1548	        std::fs::create_dir_all(&perch).expect("create perch dir");
1549	        let info = spt_store::info::InfoJson::new(endpoint, "2026-06-19T00:00:00Z", 0, "", "online");
1550	        spt_store::info::write_info(&perch, &info).expect("seed perch info.json");
1551	    }
1552
1553	    // Spawn a session for the endpoint, then KILL the child so the session goes
1554	    // IDLE (the drain produces nothing → CONTROLLER_WRITE_DEADLINE never trips).
1555	    // We still hold the session row long enough to read it; on Windows the killed
1556	    // child may reap the session — if so this test self-reports inconclusive
1557	    // rather than asserting (see the watchdog at the end).
1558	    let mut spawner = connect_retry(&name);
1559	    let sid = spawner
1560	        .spawn_session(flood_spawn_req(endpoint))
1561	        .expect("spawn child");
1562
1563	    // A REAL remote controller attaches over loopback (by=Some(origin)). The
1564	    // serve loop runs on its own thread; the operator drives via the stream.
1565	    let (ready_tx, ready_rx) = std::sync::mpsc::channel::<Option<String>>();
1566	    let stop = Arc::new(AtomicBool::new(false));
1567	    let serve_name = name.clone();
1568	    let serve_stop = Arc::clone(&stop);
1569	    let server = thread::spawn(move || {
1570	        // The TARGET serves the inbound attach. Its `brain.attach_as(.., Control,
1571	        // Some(origin))` is what occupies the controller slot with Some(origin).
1572	        let mut target = connect_retry(&serve_name);
1573	        let (stream_a, origin) = match wait_for_stream(&mut target) {
1574	            Some(s) => s,
1575	            None => return,
1576	        };
1577	        // Drive serve_attach until told to stop (it blocks on brain.read_event()).
1578	        // We DON'T cleanly detach — we just abandon it when the test ends. Running
1579	        // it here keeps the controller slot occupied by Some(origin).
1580	        let serve_broker = serve_name.clone();
1581	        let _ = serve_stop; // abandoned, not signalled (the "lost detach" shape)
1582	        let _ = serve_attach(&mut target, &serve_broker, stream_a, "w5-a2-ep", &origin, 0);
1583	    });
1584
1585	    // The operator side: dial loopback + request the attach so the slot fills.
1586	    let mut operator = connect_retry(&name);
1587	    let dialed = operator.net_dial_loopback().expect("dial loopback");
1588	    let stream_b = request_attach(
1589	        &mut operator,
1590	        dialed.conn_id,
1591	        sid,
1592	        0,
1593	        2,
1594	        AttachIntent::Control, // a real DRIVER → become_controller(Some(origin))
1595	    )
1596	    .expect("request attach");
1597	    let subscribed = operator.net_stream_subscribe(stream_b, 0).is_ok();
1598	    let _ = ready_tx.send(Some(dialed.remote_id_hex.clone()));
1599	    let _origin = ready_rx.recv_timeout(Duration::from_secs(5)).ok();
1600
1601	    // Give the broker time to resolve the subscribe → become_controller(Some) →

    // loopback attach rig (net_dial_loopback / request_attach / serve_attach /
    // become_controller). The flood child is spawned but immediately killed so the
    // session is IDLE for the read window (no output to trigger mark_controller_gone).
    init_wedge_home();
    let dir = tempfile::tempdir().expect("tempdir");
    let name = unique_name();
    let endpoint = "w5-a2-ep";
    let _broker = net_broker(&name, &dir.path().join("a2"));

    // SEED a real perch info.json for the endpoint so the broker's best-effort
    // `stamp_driven_by` (which `set_driven_by`s `resolve_perch_path(endpoint, Infer)`
    // and ERRORS / no-ops when the record is absent) actually LATCHES — the
    // production endpoint always has a perch; without this seed driven_by silently
    // stays None and the A2 verdict is unobservable (the rig artifact the first
    // run surfaced). The path is the SAME one the broker resolves under SPT_HOME.
    {
        let perch =
            spt_store::perch::resolve_perch_path(endpoint, spt_store::perch::ParentHint::Infer);
        std::fs::create_dir_all(&perch).expect("create perch dir");
        let info = spt_store::info::InfoJson::new(endpoint, "2026-06-19T00:00:00Z", 0, "", "online");
        spt_store::info::write_info(&perch, &info).expect("seed perch info.json");
    }

    // Spawn a session for the endpoint, then KILL the child so the session goes
    // IDLE (the drain produces nothing → CONTROLLER_WRITE_DEADLINE never trips).
    // We still hold the session row long enough to read it; on Windows the killed
    // child may reap the session — if so this test self-reports inconclusive
    // rather than asserting (see the watchdog at the end).
    let mut spawner = connect_retry(&name);
    let sid = spawner
        .spawn_session(flood_spawn_req(endpoint))
        .expect("spawn child");

    // A REAL remote controller attaches over loopback (by=Some(origin)). The
    // serve loop runs on its own thread; the operator drives via the stream.
    let (ready_tx, ready_rx) = std::sync::mpsc::channel::<Option<String>>();
    let stop = Arc::new(AtomicBool::new(false));
    let serve_name = name.clone();
    let serve_stop = Arc::clone(&stop);
    let server = thread::spawn(move || {
        // The TARGET serves the inbound attach. Its `brain.attach_as(.., Control,
        // Some(origin))` is what occupies the controller slot with Some(origin).
        let mut target = connect_retry(&serve_name);
        let (stream_a, origin) = match wait_for_stream(&mut target) {
            Some(s) => s,
            None => return,
        };
        // Drive serve_attach until told to stop (it blocks on brain.read_event()).
        // We DON'T cleanly detach — we just abandon it when the test ends. Running
        // it here keeps the controller slot occupied by Some(origin).
        let serve_broker = serve_name.clone();
        let _ = serve_stop; // abandoned, not signalled (the "lost detach" shape)
        let _ = serve_attach(&mut target, &serve_broker, stream_a, "w5-a2-ep", &origin, 0);
    });

    // The operator side: dial loopback + request the attach so the slot fills.
    let mut operator = connect_retry(&name);
    let dialed = operator.net_dial_loopback().expect("dial loopback");
    let stream_b = request_attach(
        &mut operator,
        dialed.conn_id,
        sid,
        0,
        2,
        AttachIntent::Control, // a real DRIVER → become_controller(Some(origin))
    )
    .expect("request attach");
    let subscribed = operator.net_stream_subscribe(stream_b, 0).is_ok();
    let _ = ready_tx.send(Some(dialed.remote_id_hex.clone()));
    let _origin = ready_rx.recv_timeout(Duration::from_secs(5)).ok();

    // Give the broker time to resolve the subscribe → become_controller(Some) →


Setup confirmed — and the key detail: the child is `flood_spawn_req` (continuous flood) and is **killed only at teardown (1625), AFTER the read at 1620**. The test comments claim "killed so the session is IDLE" but the code defers the kill. So the session **floods through the entire read window** — its "idle" premise is false.

Let me confirm `flood_spawn_req` is a continuous flood, then I have the mechanism.


Bash

grep -n "fn flood_spawn_req" crates/spt-daemon/tests/inject_control_wedge.rs; sed -n "/fn flood_spawn_req/,/^    }/p" crates/spt-daemon/tests/inject_control_wedge.rs | head -25

Inspect flood_spawn_req


124:fn flood_spawn_req(endpoint: &str) -> SpawnReq {
fn flood_spawn_req(endpoint: &str) -> SpawnReq {
    #[cfg(unix)]
    let (program, args) = ("yes".to_string(), vec!["FLOODFLOODFLOODFLOOD".to_string()]);
    #[cfg(windows)]
    let (program, args) = (
        "cmd".to_string(),
        vec![
            "/c".to_string(),
            "for /l %x in (0,0,1) do @echo FLOODFLOODFLOODFLOODFLOODFLOODFLOOD".to_string(),
        ],
    );
    SpawnReq {
        program,
        args,
        rows: 24,
        cols: 80,
        endpoint: endpoint.to_string(),
        cwd: None,
        env: Default::default(),
        translation_binary: None,
    }


1	//! The broker↔brain message catalog: the kind discriminators and payload
2	//! shapes that ride inside the forward-compatible [`Envelope`].
3	//!
4	//! Split out from [`crate::frame`] (which owns the *envelope mechanics* and the
5	//! version handshake) so the message *vocabulary* can grow additively without
6	//! touching the wire-compat machinery. Every payload is a plain serde struct
7	//! with serde's default unknown-field tolerance, so a newer brain may add a
8	//! field and an older broker still decodes the message (KNOWN-HAZARDS 2.3).
9	//!
10	//! Bytes (PTY input/output) ride as base64 strings — JSON has no native byte
11	//! type, and base64 keeps the single envelope/codec path rather than forking a
12	//! binary frame format.
13	// [impl->REQ-DAEMON-2]
14
15	use base64::engine::general_purpose::STANDARD as B64;
16	use base64::Engine;
17	use serde::{Deserialize, Serialize};
18
19	use spt_net::net::attach::AttachIntent;
20
21	use crate::frame::Envelope;
22
23	/// Brain→broker: spawn a child under a fresh broker-owned PTY.
24	pub const KIND_SPAWN: &str = "spawn";
25	/// Broker→brain: a spawn succeeded; carries the assigned session id + child pid.
26	pub const KIND_SPAWNED: &str = "spawned";
27	/// Brain→broker: (re)subscribe to a session's output from a given sequence.
28	pub const KIND_SUBSCRIBE: &str = "subscribe";
29	/// Brain→broker: release this connection's controller/viewer role on a session
30	/// (W2.5, REQ-RCVIEW-1) — the explicit detach a `serve_attach` worker sends when
31	/// the operator's viewport ends, so `driven_by` / `viewer_count` clear promptly
32	/// even if the serving brain connection lives on (a connection drop also clears
33	/// the role, so this is idempotent belt-and-braces).
34	pub const KIND_UNSUBSCRIBE: &str = "unsubscribe";
35	/// Brain→broker: write raw bytes to a session's input (send-keys).
36	pub const KIND_INPUT: &str = "input";
37	/// Brain→broker: resize a session's PTY.
38	pub const KIND_RESIZE: &str = "resize";
39	/// Brain→broker: terminate a session's child.
40	pub const KIND_KILL: &str = "kill";
41	/// Broker→brain: a chunk of a session's output (pushed as the child produces it).
42	pub const KIND_OUTPUT: &str = "output";
43	/// Broker→brain: a session's child exited.
44	pub const KIND_EXIT: &str = "exit";
45	/// Broker→brain: the outcome of a [`KIND_SUBSCRIBE`] under the W2.5
46	/// controller/viewer model (REQ-RCVIEW-1 / REQ-KICK-1). Carries the resolved
47	/// role — controller / viewer / busy-refused / took-control — so the serving
48	/// brain can wake (controller-only), stay wake-neutral (viewer), or refuse the
49	/// operator (busy). A normal reply, not a new wire frame (the busy refusal is
50	/// rendered client-side by `rc` from the endpoint's `driven_by`).
51	pub const KIND_SUBSCRIBED: &str = "subscribed";
52	/// Broker→brain: the current PTY size of a session, pushed to a VIEWER on
53	/// attach and to every viewer on a controller resize (REQ-RCVIEW-1). The
54	/// serving brain forwards it as a [`spt_net::net::attach::AttachRecord::Size`]
55	/// so the viewer letterboxes client-side (never resizing the PTY itself —
56	/// resize is controller-exclusive).
57	pub const KIND_SIZE: &str = "size";
58	/// Broker→brain: you (a controller) have been DISPLACED by a different operator's
59	/// explicit `--take`/`kick` (REQ-KICK-1). The serving brain forwards this as a
60	/// loud [`spt_net::net::attach::AttachRecord::Displaced`] on the wire, then
61	/// detaches. Fired ONLY on a genuine cross-operator control-ownership change —
62	/// NEVER on a same-origin re-serve after a brain restart (the gate-#7 self-kick
63	/// trap: a successor re-taking the controller slot with the SAME `by` is silent).
64	pub const KIND_DISPLACED: &str = "displaced";
65	/// Broker→brain(VIEWER): this viewer's bounded subscription queue overflowed and
66	/// it was EVICTED (REQ-HAZARD-VIEWER-ISOLATION session-protection). DISTINCT from
67	/// a session-exit EOF: the serving brain must NOT tear the attach down — it
68	/// re-subscribes from the current ring floor (SKIP-TO-LIVE,
69	/// REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT). Written by the viewer's dedicated writer
70	/// thread (OFF the log lock / off the drain) as its final frame.
71	pub const KIND_VIEWER_EVICTED: &str = "viewer_evicted";
72	/// Broker→brain: an idempotent effect (an `input` carrying an `op_id`) is durably
73	/// applied **or** was already applied (deduped). The exactly-once ack: a brain
74	/// learns its effect landed, and a replay after a crash is acknowledged the same
75	/// way so the brain can retire it (B5 / KNOWN-HAZARDS 7.2).
76	pub const KIND_APPLIED: &str = "applied";
77	/// Broker→brain: a command failed (carries an operator-facing message).
78	pub const KIND_ERROR: &str = "error";
79	/// Brain→broker: report the broker-owned net endpoint's status (D4a). The brain
80	/// holds no endpoint of its own — the broker owns the node's network presence
81	/// (ADR-0004 §B), so status (node id, dialable address, conn count) is an IPC ask.
82	pub const KIND_NET_STATUS: &str = "net-status";
83	/// Broker→brain: the net endpoint status reply.
84	pub const KIND_NET_STATUS_REPLY: &str = "net-status-reply";
85	/// Brain→broker: dial a peer over the broker-owned endpoint (D4a). With an
86	/// `op_id` the dial is exactly-once (journaled like an idempotent `input`).
87	pub const KIND_NET_DIAL: &str = "net-dial";
88	/// Brain→broker: mint (or reuse) the broker's single in-process loopback
89	/// connection (M12 W1.5, B1) — the transport a LOCAL `spt rc` rides because QUIC
90	/// self-dial is refused by iroh. An explicit verb (NOT a sentinel addr on
91	/// [`KIND_NET_DIAL`]): loopback handling stays isolated from the dial hot path.
92	/// Null payload (the singleton is idempotent by reuse — no op id, no addr).
93	/// Answered with a [`KIND_NET_DIALED`] whose `remote_id_hex` is THIS node's own id.
94	pub const KIND_NET_DIAL_LOOPBACK: &str = "net-dial-loopback";
95	/// Broker→brain: a dial durably landed — fresh or deduped (`applied_now`
96	/// distinguishes, mirroring [`KIND_APPLIED`]). Carries the broker-minted conn id.
97	pub const KIND_NET_DIALED: &str = "net-dialed";
98	/// Brain→broker: open a bidi QUIC stream on a held connection (D4b). With an
99	/// `op_id` the open is exactly-once (journaled like a dial).
100	pub const KIND_NET_STREAM_OPEN: &str = "net-stream-open";
101	/// Broker→brain: a stream open durably landed (fresh or deduped).
102	pub const KIND_NET_STREAM_OPENED: &str = "net-stream-opened";
103	/// Brain→broker: write bytes to a held stream's send side (D4b). With an
104	/// `op_id` the write is exactly-once (journaled; acked with [`KIND_NET_SENT`]).
105	pub const KIND_NET_STREAM_SEND: &str = "net-stream-send";
106	/// Broker→brain: a journaled stream send durably landed (fresh or deduped).
107	pub const KIND_NET_SENT: &str = "net-sent";
108	/// Brain→broker: list the streams the broker currently holds (D4b). The query
109	/// model a re-attaching brain uses to find streams that arrived while it was
110	/// down — no announce buffer to miss.
111	pub const KIND_NET_STREAMS: &str = "net-streams";
112	/// Broker→brain: the held-streams reply.
113	pub const KIND_NET_STREAMS_REPLY: &str = "net-streams-reply";
114	/// Brain→broker: (re)subscribe to a stream's buffered reads from a cursor —
115	/// the PTY `subscribe` discipline applied to a QUIC stream (gapless resume).
116	pub const KIND_NET_STREAM_SUBSCRIBE: &str = "net-stream-subscribe";
117	/// Broker→brain: a sequenced chunk of a stream's reads (live or replayed).
118	pub const KIND_NET_STREAM_DATA: &str = "net-stream-data";
119	/// Broker→brain: a stream's read side finished cleanly; `next_seq` tells the
120	/// brain the final cursor so it can verify it missed nothing.
121	pub const KIND_NET_STREAM_EOF: &str = "net-stream-eof";
122	/// Brain→broker: (re)subscribe to the broker's conn-liveness event log from a
123	/// cursor (D4c) — the PresenceChannel broker seam. Same gapless-resume
124	/// discipline as a stream subscribe.
125	// [impl->REQ-EP-4]
126	pub const KIND_NET_PRESENCE_SUBSCRIBE: &str = "net-presence-subscribe";

... [993 lines truncated] ...


Mechanism **confirmed**: `flood_spawn_req` = continuous flood (`yes FLOODFLOOD…` / infinite echo), and the child is killed only at teardown — so w5_a2's "idle" premise is false; it floods through the read window. That's the whole key. Sending doyle the answer.


cat <<'EOF' | spt send doyle
todlando → doyle. w5_a2 TRIGGER CONFIRMED. Verdict: GOOD side-effect / test-premise artifact, NOT over-evict. b4 needs NO code change.

MECHANISM (traced, not the b4 deadline-evict):
1. w5_a2's "IDLE" premise is FALSE. flood_spawn_req spawns a CONTINUOUS flood (unix `yes FLOODFLOOD`, win infinite `echo` loop). The child is killed only at TEARDOWN (inject_control_wedge.rs:1625), AFTER the controller_by read at :1620. The comments at :1531/:1613 claim "killed so the session is IDLE" but the code DEFERS the kill. So the session FLOODS through the entire read window.
2. b4's deadline-evict (last_ok + CONTROLLER_WRITE_DEADLINE 5s continuous-Full) does NOT fire by the ~4s read: last_ok keeps refreshing while serve_attach drains the controller channel (until ~t=2s abandon + QUIC send-buffer fill), so the 5s deadline lands ~t=7s — AFTER the read. NOT the trigger. (Also rules out "evicts a no-pressure idle controller" — there IS heavy output pressure here.)
3. THE TRIGGER: serve_attach (target side) forwards each Output frame to the now-DEAD operator wire (operator dropped at t=2s, no clean EOF). b4's whole point = the drain runs at FULL PTY speed → the flood is a torrent → serve_attach floods the dead wire → net_stream_send to the gone operator fails FAST → serve_attach exits → its broker conn drops → detach_if clears the controller (and/or controller_writer exits → next append try_send=Disconnected → mark_controller_gone, a path PRE-b4 had too). Either way = REAL disconnect detection of a provably-dead wire.
4. PRE-b4: the drain THROTTLED on the backed-up controller (5s inline sleep-poll) → far less output → serve's wire-forward backed up slowly → it had NOT detected the dead operator by t=4s → controller_by stayed Some → PASS. b4 just made the (correct) detection FAST enough to land inside the 2s post-abandon window.

So: NOT b4 over-evicting an idle controller. It's correct accelerated disconnect-detection on a test that never actually staged idle. CAVEAT: this does NOT close the TRUE A2 (genuinely idle + half-open conn where writes still buffer) — w5_a2 as written never tested that; REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT (D4c presence oracle) is still the real A2 fix.

RECOMMENDATION (matches your "confirm trigger before updating test"):
- FIX w5_a2's premise to match its own comments: kill the flood child BEFORE the read window (move kill_pid up, before the :1618 idle sleep). Then re-observe.
- EXPECTED truly-idle: no output → append never runs (no b4 path) AND serve_attach blocks on read_event with nothing to forward → never detects the dead wire → controller_by STAYS Some(origin) → the original assertion holds → PASS. Regression gone, b4 fold unblocked.
- If truly-idle STILL clears post-b4 → then b4 over-reaches, investigate. I do NOT expect this.

I can make that test-premise fix (small) — your call, it's your characterization gate. b4 itself stays as-is.

SEPARATELY: skip-to-live (closes a_journaled + p0_paste) — IMPLEMENTED on viewer-drain-decouple-b4: KIND_VIEWER_EVICTED signal (written in viewer_writer OFF the log lock, distinct from EOF) + serve_attach re-subscribes from ring floor (from_seq=u64::MAX, cold-brain cursor reset to accept the forward jump) + RESUBSCRIBE_INTERVAL rate-limit. New req REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT (doc+impl tagged, unit tests in flight via spt-test-engineer). clippy clean, 10/10 broker tests green. Will ping when unit lands + ready for your p0_paste forkpty re-measure.
EOF

Send doyle w5_a2 mechanism + skip-to-live status


Error: Exit code 1
NO_PERCH:doyle is not listening


The send failed (doyle offline again). Good — that lets me revise, because checking my own reasoning I have a gap: serve_attach's `net_stream_send` hands frames to the *local* broker (which then relays to the QUIC peer), so it doesn't obviously fail when the operator dies. I must not send doyle a confidently-wrong precise mechanism. Let me trace `controller_writer` and how a dropped stream-peer propagates to a controller clear.


718	/// Advance the shared `delivered_through` cursor to `seq + 1`, monotonically
719	/// (D4-1, via [`advanced_cursor`]) and CAS-safe against a brief two-writer
720	/// overlap during a controller displace.
721	fn advance_delivered(dt: &AtomicU64, seq: u64) {
722	    let mut cur = dt.load(Ordering::Acquire);
723	    loop {
724	        let next = advanced_cursor(cur, seq, true);
725	        if next == cur {
726	            return;
727	        }
728	        match dt.compare_exchange_weak(cur, next, Ordering::AcqRel, Ordering::Acquire) {
729	            Ok(_) => return,
730	            Err(actual) => cur = actual,
731	        }
732	    }
733	}
734
735	/// Advance the controller resume cursor CONTIGUOUSLY (b4 / the (a) B2-gate): move to
736	/// `seq + 1` ONLY when the cursor is EXACTLY `seq` (the next expected seq). A gap
737	/// (`seq > cursor` — a frame the drain dropped while the controller channel was Full,
738	/// the b4 backpressure) FREEZES the cursor at last-contiguous, so a re-attaching
739	/// brain's `resume_seq` never skips a dropped chunk (a high-watermark jump PAST the
740	/// gap would be a not-exactly-once / non-gapless resume = a B2 violation). An
741	/// already-delivered `seq` (`seq < cursor`, a rewind re-send) is a no-op. A single CAS,
742	/// no loop: it advances by exactly one, or not at all. In the NO-DROP path (every
743	/// live frame is contiguous) this is identical to [`advance_delivered`].
744	// [impl->REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE]
745	fn contiguous_advance(dt: &AtomicU64, seq: u64) {
746	    let _ = dt.compare_exchange(seq, seq + 1, Ordering::AcqRel, Ordering::Acquire);
747	}
748
749	/// The interactive CONTROLLER's dedicated writer thread (W1 /
750	/// REQ-HAZARD-INJECT-CONTROL-COEXIST) — the authoritative analog of
751	/// [`viewer_writer`]. Write the `initial` ring-replay batch (advancing the resume
752	/// cursor on each success), then drain live [`CtrlMsg`]s with a BLOCKING socket
753	/// write each — so a backed-up controller blocks ONLY this thread, never the
754	/// session drain (which used to write the controller inline under the log lock).
755	/// A failed write (dead operator) ends the thread; the drain's next bounded
756	/// send then sees `Disconnected` and detaches the row. `Output` advances
757	/// `delivered_through`; `Control` (Displaced/…) does not.
758	// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]
759	// [impl->REQ-HAZARD-CONTROLLER-WRITER-REORDER]
760	fn controller_writer(
761	    sid: u64,
762	    send: SharedSend,
763	    initial: Vec<(u64, Vec<u8>)>,
764	    rx: Receiver<CtrlMsg>,
765	    delivered_through: Arc<AtomicU64>,
766	    epoch: u64,
767	    controller_epoch: Arc<AtomicU64>,
768	) {
769	    for (seq, bytes) in &initial {
770	        let frame = output_envelope(sid, *seq, bytes);
771	        // P1c: a SUPERSEDED writer (a newer `become_controller` bumped the shared
772	        // epoch past ours) must write NOTHING further — otherwise its owned
773	        // initial batch races the new writer on the same socket and reorders the
774	        // seq stream. The gate is read UNDER `send.lock()`, atomically with the
775	        // write: both writers serialize on this one socket mutex, and
776	        // `become_controller` bumps the epoch BEFORE spawning the new writer, so
777	        // whichever superseded writer next holds the lock observes the bump and
778	        // bails before committing — no check-then-block-then-write window. (A
779	        // frame the stale writer committed BEFORE the bump is irreducible at the

/// Advance the shared `delivered_through` cursor to `seq + 1`, monotonically
/// (D4-1, via [`advanced_cursor`]) and CAS-safe against a brief two-writer
/// overlap during a controller displace.
fn advance_delivered(dt: &AtomicU64, seq: u64) {
    let mut cur = dt.load(Ordering::Acquire);
    loop {
        let next = advanced_cursor(cur, seq, true);
        if next == cur {
            return;
        }
        match dt.compare_exchange_weak(cur, next, Ordering::AcqRel, Ordering::Acquire) {
            Ok(_) => return,
            Err(actual) => cur = actual,
        }
    }
}

/// Advance the controller resume cursor CONTIGUOUSLY (b4 / the (a) B2-gate): move to
/// `seq + 1` ONLY when the cursor is EXACTLY `seq` (the next expected seq). A gap
/// (`seq > cursor` — a frame the drain dropped while the controller channel was Full,
/// the b4 backpressure) FREEZES the cursor at last-contiguous, so a re-attaching
/// brain's `resume_seq` never skips a dropped chunk (a high-watermark jump PAST the
/// gap would be a not-exactly-once / non-gapless resume = a B2 violation). An
/// already-delivered `seq` (`seq < cursor`, a rewind re-send) is a no-op. A single CAS,
/// no loop: it advances by exactly one, or not at all. In the NO-DROP path (every
/// live frame is contiguous) this is identical to [`advance_delivered`].
// [impl->REQ-HAZARD-VIEWER-STARVE-UNDER-CONTROLLER-BACKPRESSURE]
fn contiguous_advance(dt: &AtomicU64, seq: u64) {
    let _ = dt.compare_exchange(seq, seq + 1, Ordering::AcqRel, Ordering::Acquire);
}

/// The interactive CONTROLLER's dedicated writer thread (W1 /
/// REQ-HAZARD-INJECT-CONTROL-COEXIST) — the authoritative analog of
/// [`viewer_writer`]. Write the `initial` ring-replay batch (advancing the resume
/// cursor on each success), then drain live [`CtrlMsg`]s with a BLOCKING socket
/// write each — so a backed-up controller blocks ONLY this thread, never the
/// session drain (which used to write the controller inline under the log lock).
/// A failed write (dead operator) ends the thread; the drain's next bounded
/// send then sees `Disconnected` and detaches the row. `Output` advances
/// `delivered_through`; `Control` (Displaced/…) does not.
// [impl->REQ-HAZARD-INJECT-CONTROL-COEXIST]
// [impl->REQ-HAZARD-CONTROLLER-WRITER-REORDER]
fn controller_writer(
    sid: u64,
    send: SharedSend,
    initial: Vec<(u64, Vec<u8>)>,
    rx: Receiver<CtrlMsg>,
    delivered_through: Arc<AtomicU64>,
    epoch: u64,
    controller_epoch: Arc<AtomicU64>,
) {
    for (seq, bytes) in &initial {
        let frame = output_envelope(sid, *seq, bytes);
        // P1c: a SUPERSEDED writer (a newer `become_controller` bumped the shared
        // epoch past ours) must write NOTHING further — otherwise its owned
        // initial batch races the new writer on the same socket and reorders the
        // seq stream. The gate is read UNDER `send.lock()`, atomically with the
        // write: both writers serialize on this one socket mutex, and
        // `become_controller` bumps the epoch BEFORE spawning the new writer, so
        // whichever superseded writer next holds the lock observes the bump and
        // bails before committing — no check-then-block-then-write window. (A
        // frame the stale writer committed BEFORE the bump is irreducible at the


Now I have the precise, verified mechanism. broker.rs:755-756 is explicit: *"A failed write (dead operator) ends the [controller_writer] thread; the drain's next bounded send then sees `Disconnected` and detaches the row."* So:

- Operator dropped at t=2s → controller_writer's next write to the dead operator socket fails → thread ends → next `append` try_send returns `Disconnected` → `mark_controller_gone` → controller_by clears.
- This Disconnected-evict exists **pre-b4 too**. The difference is pure **timing**: b4's full-speed drain feeds controller_writer frames sooner → it hits the dead-operator write-failure (and the clear) *inside* the test's 2s post-abandon read window. Pre-b4's throttle delayed it past the read → stayed Some → PASS.
- It is **NOT** b4's `last_ok` deadline-evict (5s > the ~4s window; last_ok refreshed while the operator drained until t=2s). NOT an over-evict of an idle controller — the operator socket is genuinely dead.

Sending doyle the corrected mechanism + recommendation:


cat > /tmp/doyle_w5a2.txt <<'EOF'
todlando → doyle. w5_a2 TRIGGER CONFIRMED (corrected/precise). Verdict: GOOD side-effect, NOT over-evict. b4 needs NO code change.

PRECISE MECHANISM (it's the DISCONNECTED-evict, accelerated — NOT the last_ok deadline):
- broker.rs:755-756 (controller_writer): "A failed write (dead operator) ends the thread; the drain's next bounded send then sees Disconnected and detaches the row."
- t=2s: drop(operator) WITHOUT clean EOF. controller_writer's next write to the now-dead operator socket FAILS → thread ends (drops rx). Next append's try_send → Err(Disconnected) → mark_controller_gone → controller_by clears.
- This Disconnected-evict exists PRE-b4 TOO (old deliver() returned Err(epoch) on Disconnected). The ONLY difference is TIMING: b4's full-speed drain feeds controller_writer frames sooner → it hits the dead-operator write-failure (and the clear) INSIDE the test's 2s post-abandon read window (t=2s→t=4s). Pre-b4's inline 5s sleep-poll THROTTLED the drain → controller_writer was starved of frames → the dead-operator write-failure landed AFTER the t=4s read → controller_by still Some → PASS.
- NOT b4's last_ok deadline-evict: that needs 5s continuous-Full since the last successful handoff; last_ok refreshed while the operator drained (until t=2s), so the deadline would land ~t=7s, after the read. Ruled out.
- The operator socket is GENUINELY dead (write failed) → clearing the controller is CORRECT. Not an over-evict.

WHY THE PREMISE NEVER STAGED IDLE: flood_spawn_req spawns a CONTINUOUS flood (unix `yes FLOODFLOOD`, win infinite echo). The child is killed only at TEARDOWN (:1625), AFTER the read (:1620) — it can't be killed earlier without reaping the session (the test's own comment, :1555). So w5_a2 actually tests the OUTPUT-path abandoned controller, where post-b4 the correct answer is "clears."

FIX OPTIONS:
(A) Update w5_a2 to assert the NEW correct behavior: an abandoned remote controller UNDER OUTPUT is evicted (controller_by clears) post-b4 = the output-path self-heal (complements W1's active-output wedge bound). Keep/relocate the "TRUE idle case still needs the D4c presence oracle" note (REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT) — this rig can't stage true idle.
(B) Make it FAITHFULLY idle: spawn a QUIET long-lived child (no continuous output, doesn't exit — e.g. unix `sleep`, win `timeout`/`ping -n`) instead of the flood. Then: no output → controller_writer never writes to the dead operator → no Disconnected → controller_by STAYS Some → the original assertion holds, faithfully characterizing the idle case (still needs the oracle). PASSES post-b4.

I LEAN (B): it restores the test's documented intent (truly idle) AND preserves the characterization that the idle case still needs REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT. (A) conflates the output-path with the idle gap the test was created to characterize. Either way b4 stays as-is and the fold unblocks once w5_a2 is corrected.

I can implement (B) (swap flood→quiet spawn helper, keep the assertion) — your call, it's your characterization gate.

SEPARATELY — skip-to-live (closes a_journaled + p0_paste), IMPLEMENTED on viewer-drain-decouple-b4:
- KIND_VIEWER_EVICTED signal written in viewer_writer OFF the log lock (distinct from EOF) + append/set_size set ViewerSink.evicted before drop.
- serve_attach re-subscribes from ring floor on ViewerEvicted (Brain::attach_skip_to_live: from_seq=u64::MAX replays nothing, cold-brain cursor reset to snap-above so the forward-jump replay is accepted, NOT teardown), RESUBSCRIBE_INTERVAL rate-limit (no busy-loop).
- New req REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT (doc CONTEXT.md + impl tagged; unit tests in flight via spt-test-engineer). clippy -D clean, broker 10/10 green.
Will ping when unit lands + ready for your p0_paste forkpty re-measure.
EOF
printf '%s' "$(cat /tmp/doyle_w5a2.txt)" | spt send doyle 2>&1 || echo "SEND_FAILED — holding for doyle perch"

Send corrected w5_a2 analysis to doyle


NO_PERCH:doyle is not listening
SEND_FAILED — holding for doyle perch


doyle still offline — message saved to `/tmp/doyle_w5a2.txt`, will resend when his perch returns. Recording the board state to memory since context is climbing and this tracing won't be cheap to re-derive.


1	---
2	name: v013-rc-attach-flood-wedge
3	description: "v0.13.0 — the 3 inject_control_wedge gates fail because a slow controller's inline backpressure throttles the session drain, starving a concurrent rc-view viewer (root 'b4', 2026-06-21); decouple fix designed + gate-pending with doyle."
4	metadata:
5	  node_type: memory
6	  type: project
7	  originSessionId: f12f255d-20de-40e1-b356-60f58d21c08e
8	---
9
10	As of 2026-06-21. The 3 failing gates in `crates/spt-daemon/tests/inject_control_wedge.rs` are `a_journaled_input_wedge_does_not_starve_a_concurrent_rc_attach`, `p0_paste_wedge_...`, and `g2_no_commit_deadline_...` (g2 = a SEPARATE InjectFloor commit-deadline item, still parked). They assert a real, legitimate property: a concurrent `rc --view` viewer must not be starved while a session floods output.
11
12	**ROOT = "b4"** (verified by doyle's code-read + my code verification; the earlier "serde-saturation / control-plane-starves" narrative in this file's prior version was a pre-warm-datapoint hypothesis and is SUPERSEDED). The session drain closure (`broker.rs:1450-1457`) runs `ControllerJob::deliver()` INLINE; `deliver()` (`broker.rs:669-685`) is a `try_send` sleep-poll up to `CONTROLLER_WRITE_DEADLINE` (5s) ON THE DRAIN THREAD. A controller draining slower than the PTY floods → its `CONTROLLER_CHANNEL_DEPTH`(4096) channel fills → `deliver()` throttles the drain → `OutputLog::append`'s viewer fan-out (`try_send`) doesn't run → the viewer starves. Signature: c1=0 c2=0 c3=1 EVICT=0, subscribed=TRUE, got_output=FALSE. forkpty-only for a_journaled/g2 (forkpty floods harder than Windows ConPTY's paced output → channel fills on Linux, keeps up on Windows); p0_paste fails both. No ~7s recovery = steady-state-near-full (deliver gets a slot inside 5s every chunk → never Err → never deadline-evicts → throttled the whole run). **Regression verdict (diff-bisect):** pre-W1 wrote the controller inline UNDER the log lock = permanent wedge; W1 bounded it to 5s episodes but still couples drain→controller. NOT a new class; real fix warranted.
13
14	**Confirmed FIXED, not the blocker (supersedes the old narrative):** facet-B (journal-lock-parks-write_input) is already fixed in HEAD by W1b (`e404aeb`, lock off effect) + P0 (`REQ-HAZARD-PTY-INPUT-WRITER-WEDGE`, non-blocking input enqueue to an isolated writer thread) + no-ack. The operator's 2026-06-19 dogfood wedge (`spt rc` → "brain IPC read deadline") was a PRE-W1b build (`8b5583e`, 2hr before `e404aeb`). The test "carrier artifact" (Whole-conn attacher ignoring its read deadline → false subscribed=false) is fixed by the pump switch. The registry Phase-A `SQLITE_BUSY` flake is fixed by the env-knob.
15
16	**FIX (designed, sent to doyle for final gate; full design in `V0.13.0-VIEWER-DRAIN-DECOUPLE-JIT.md`):** decouple controller backpressure from the drain. Controller becomes a SINGLE non-blocking `try_send` (like viewers), moved INTO `append()` under the log lock; `deliver()`'s sleep-poll DELETED; `append` returns `Option<evict-epoch>`; the drain off-lock does `mark_controller_gone` if signaled and NEVER sleeps. `ControllerSink` += `last_ok: Instant` staleness. On Full: past deadline → evict, else DROP (controller falls behind the ring → resume-from-floor). `delivered_through` advances ONLY on real writes (no phantom-advance). KH epoch gate / one-live-writer / monotonic seq unchanged. doyle's 3 gate constraints: (1) no phantom cursor-advance, (2) slow-controller degrades to ring-fall-behind not drain-backpressure, (3) bounded staleness-detach measured across chunks.
17
18	**False trails (do not re-chase):** journal-lock parking (W1b-fixed); acked-flood self-contending on `Mutex<SendHalf>`; CPU/HOL behind the UDS flood; `PtySession.writer` mutex; viewer-channel EVICTion (b1); serve-side `net_stream_send` block (b2 — `net_stream_send(None)` is fire-and-forget, brain.rs:1004); EVICT-counter gap (b3).
19
20	**SHIPPED:** `origin/pump-carrier-fix` off `delivery-control@18416fd` — `7a1cc68` (both rc-attach attachers `cold_start`→`cold_start_pump`) + `7c09819` (env-tunable `SPT_REGISTRY_BUSY_TIMEOUT_MS`, default 5000/CI 30000); both in doyle's PR #27 validation branch. v4 diagnostic counters on `origin/wedge-trace-v4` (do-not-merge). **Partner:** doyle owns forkpty CI (kitsubito = only online Linux box, CI-only) + gates designs against KH/B2; I author code + name roots. See [[cross-machine-test-rig]], [[delegate-tests-to-spt-test-engineer]], [[spt-verification-gates]].
21

---
name: v013-rc-attach-flood-wedge
description: "v0.13.0 — the 3 inject_control_wedge gates fail because a slow controller's inline backpressure throttles the session drain, starving a concurrent rc-view viewer (root 'b4', 2026-06-21); decouple fix designed + gate-pending with doyle."
metadata:
  node_type: memory
  type: project
  originSessionId: f12f255d-20de-40e1-b356-60f58d21c08e
---

As of 2026-06-21. The 3 failing gates in `crates/spt-daemon/tests/inject_control_wedge.rs` are `a_journaled_input_wedge_does_not_starve_a_concurrent_rc_attach`, `p0_paste_wedge_...`, and `g2_no_commit_deadline_...` (g2 = a SEPARATE InjectFloor commit-deadline item, still parked). They assert a real, legitimate property: a concurrent `rc --view` viewer must not be starved while a session floods output.

**ROOT = "b4"** (verified by doyle's code-read + my code verification; the earlier "serde-saturation / control-plane-starves" narrative in this file's prior version was a pre-warm-datapoint hypothesis and is SUPERSEDED). The session drain closure (`broker.rs:1450-1457`) runs `ControllerJob::deliver()` INLINE; `deliver()` (`broker.rs:669-685`) is a `try_send` sleep-poll up to `CONTROLLER_WRITE_DEADLINE` (5s) ON THE DRAIN THREAD. A controller draining slower than the PTY floods → its `CONTROLLER_CHANNEL_DEPTH`(4096) channel fills → `deliver()` throttles the drain → `OutputLog::append`'s viewer fan-out (`try_send`) doesn't run → the viewer starves. Signature: c1=0 c2=0 c3=1 EVICT=0, subscribed=TRUE, got_output=FALSE. forkpty-only for a_journaled/g2 (forkpty floods harder than Windows ConPTY's paced output → channel fills on Linux, keeps up on Windows); p0_paste fails both. No ~7s recovery = steady-state-near-full (deliver gets a slot inside 5s every chunk → never Err → never deadline-evicts → throttled the whole run). **Regression verdict (diff-bisect):** pre-W1 wrote the controller inline UNDER the log lock = permanent wedge; W1 bounded it to 5s episodes but still couples drain→controller. NOT a new class; real fix warranted.

**Confirmed FIXED, not the blocker (supersedes the old narrative):** facet-B (journal-lock-parks-write_input) is already fixed in HEAD by W1b (`e404aeb`, lock off effect) + P0 (`REQ-HAZARD-PTY-INPUT-WRITER-WEDGE`, non-blocking input enqueue to an isolated writer thread) + no-ack. The operator's 2026-06-19 dogfood wedge (`spt rc` → "brain IPC read deadline") was a PRE-W1b build (`8b5583e`, 2hr before `e404aeb`). The test "carrier artifact" (Whole-conn attacher ignoring its read deadline → false subscribed=false) is fixed by the pump switch. The registry Phase-A `SQLITE_BUSY` flake is fixed by the env-knob.

**FIX (designed, sent to doyle for final gate; full design in `V0.13.0-VIEWER-DRAIN-DECOUPLE-JIT.md`):** decouple controller backpressure from the drain. Controller becomes a SINGLE non-blocking `try_send` (like viewers), moved INTO `append()` under the log lock; `deliver()`'s sleep-poll DELETED; `append` returns `Option<evict-epoch>`; the drain off-lock does `mark_controller_gone` if signaled and NEVER sleeps. `ControllerSink` += `last_ok: Instant` staleness. On Full: past deadline → evict, else DROP (controller falls behind the ring → resume-from-floor). `delivered_through` advances ONLY on real writes (no phantom-advance). KH epoch gate / one-live-writer / monotonic seq unchanged. doyle's 3 gate constraints: (1) no phantom cursor-advance, (2) slow-controller degrades to ring-fall-behind not drain-backpressure, (3) bounded staleness-detach measured across chunks.

**False trails (do not re-chase):** journal-lock parking (W1b-fixed); acked-flood self-contending on `Mutex<SendHalf>`; CPU/HOL behind the UDS flood; `PtySession.writer` mutex; viewer-channel EVICTion (b1); serve-side `net_stream_send` block (b2 — `net_stream_send(None)` is fire-and-forget, brain.rs:1004); EVICT-counter gap (b3).

**SHIPPED:** `origin/pump-carrier-fix` off `delivery-control@18416fd` — `7a1cc68` (both rc-attach attachers `cold_start`→`cold_start_pump`) + `7c09819` (env-tunable `SPT_REGISTRY_BUSY_TIMEOUT_MS`, default 5000/CI 30000); both in doyle's PR #27 validation branch. v4 diagnostic counters on `origin/wedge-trace-v4` (do-not-merge). **Partner:** doyle owns forkpty CI (kitsubito = only online Linux box, CI-only) + gates designs against KH/B2; I author code + name roots. See [[cross-machine-test-rig]], [[delegate-tests-to-spt-test-engineer]], [[spt-verification-gates]].


Edit

description: "v0.13.0 — the 3 inject_control_wedge gates fail because a slow controller's inline backpressure throttles the session drain, starving a concurrent rc-view viewer (root 'b4', 2026-06-21); decouple fix designed + gate-pending with doyle."

description: "v0.13.0 — the 3 inject_control_wedge gates fail because a slow controller's inline backpressure throttles the session drain, starving a concurrent rc-view viewer (root 'b4', 2026-06-21); b4 fix SHIPPED, skip-to-live + g2 + w5_a2-reconcile remain before fold."


The file C:\Users\decid\.claude\projects\C--Users-decid-Documents-projects-spt-core\memory\v013-rc-attach-flood-wedge.md has been updated successfully. (file state is current in your context — no need to Read it back)

---
name: v013-rc-attach-flood-wedge
description: "v0.13.0 — the 3 inject_control_wedge gates fail because a slow controller's inline backpressure throttles the session drain, starving a concurrent rc-view viewer (root 'b4', 2026-06-21); decouple fix designed + gate-pending with doyle."
metadata:
  node_type: memory
  type: project
  originSessionId: f12f255d-20de-40e1-b356-60f58d21c08e
---

As of 2026-06-21. The 3 failing gates in `crates/spt-daemon/tests/inject_control_wedge.rs` are `a_journaled_input_wedge_does_not_starve_a_concurrent_rc_attach`, `p0_paste_wedge_...`, and `g2_no_commit_deadline_...` (g2 = a SEPARATE InjectFloor commit-deadline item, still parked). They assert a real, legitimate property: a concurrent `rc --view` viewer must not be starved while a session floods output.

**ROOT = "b4"** (verified by doyle's code-read + my code verification; the earlier "serde-saturation / control-plane-starves" narrative in this file's prior version was a pre-warm-datapoint hypothesis and is SUPERSEDED). The session drain closure (`broker.rs:1450-1457`) runs `ControllerJob::deliver()` INLINE; `deliver()` (`broker.rs:669-685`) is a `try_send` sleep-poll up to `CONTROLLER_WRITE_DEADLINE` (5s) ON THE DRAIN THREAD. A controller draining slower than the PTY floods → its `CONTROLLER_CHANNEL_DEPTH`(4096) channel fills → `deliver()` throttles the drain → `OutputLog::append`'s viewer fan-out (`try_send`) doesn't run → the viewer starves. Signature: c1=0 c2=0 c3=1 EVICT=0, subscribed=TRUE, got_output=FALSE. forkpty-only for a_journaled/g2 (forkpty floods harder than Windows ConPTY's paced output → channel fills on Linux, keeps up on Windows); p0_paste fails both. No ~7s recovery = steady-state-near-full (deliver gets a slot inside 5s every chunk → never Err → never deadline-evicts → throttled the whole run). **Regression verdict (diff-bisect):** pre-W1 wrote the controller inline UNDER the log lock = permanent wedge; W1 bounded it to 5s episodes but still couples drain→controller. NOT a new class; real fix warranted.

**Confirmed FIXED, not the blocker (supersedes the old narrative):** facet-B (journal-lock-parks-write_input) is already fixed in HEAD by W1b (`e404aeb`, lock off effect) + P0 (`REQ-HAZARD-PTY-INPUT-WRITER-WEDGE`, non-blocking input enqueue to an isolated writer thread) + no-ack. The operator's 2026-06-19 dogfood wedge (`spt rc` → "brain IPC read deadline") was a PRE-W1b build (`8b5583e`, 2hr before `e404aeb`). The test "carrier artifact" (Whole-conn attacher ignoring its read deadline → false subscribed=false) is fixed by the pump switch. The registry Phase-A `SQLITE_BUSY` flake is fixed by the env-knob.

**FIX (designed, sent to doyle for final gate; full design in `V0.13.0-VIEWER-DRAIN-DECOUPLE-JIT.md`):** decouple controller backpressure from the drain. Controller becomes a SINGLE non-blocking `try_send` (like viewers), moved INTO `append()` under the log lock; `deliver()`'s sleep-poll DELETED; `append` returns `Option<evict-epoch>`; the drain off-lock does `mark_controller_gone` if signaled and NEVER sleeps. `ControllerSink` += `last_ok: Instant` staleness. On Full: past deadline → evict, else DROP (controller falls behind the ring → resume-from-floor). `delivered_through` advances ONLY on real writes (no phantom-advance). KH epoch gate / one-live-writer / monotonic seq unchanged. doyle's 3 gate constraints: (1) no phantom cursor-advance, (2) slow-controller degrades to ring-fall-behind not drain-backpressure, (3) bounded staleness-detach measured across chunks.

**False trails (do not re-chase):** journal-lock parking (W1b-fixed); acked-flood self-contending on `Mutex<SendHalf>`; CPU/HOL behind the UDS flood; `PtySession.writer` mutex; viewer-channel EVICTion (b1); serve-side `net_stream_send` block (b2 — `net_stream_send(None)` is fire-and-forget, brain.rs:1004); EVICT-counter gap (b3).

**SHIPPED:** `origin/pump-carrier-fix` off `delivery-control@18416fd` — `7a1cc68` (both rc-attach attachers `cold_start`→`cold_start_pump`) + `7c09819` (env-tunable `SPT_REGISTRY_BUSY_TIMEOUT_MS`, default 5000/CI 30000); both in doyle's PR #27 validation branch. v4 diagnostic counters on `origin/wedge-trace-v4` (do-not-merge). **Partner:** doyle owns forkpty CI (kitsubito = only online Linux box, CI-only) + gates designs against KH/B2; I author code + name roots. See [[cross-machine-test-rig]], [[delegate-tests-to-spt-test-engineer]], [[spt-verification-gates]].

-description: "v0.13.0 — the 3 inject_control_wedge gates fail because a slow controller's inline backpressure throttles the session drain, starving a concurrent rc-view viewer (root 'b4', 2026-06-21); decouple fix designed + gate-pending with doyle."

+description: "v0.13.0 — the 3 inject_control_wedge gates fail because a slow controller's inline backpressure throttles the session drain, starving a concurrent rc-view viewer (root 'b4', 2026-06-21); b4 fix SHIPPED, skip-to-live + g2 + w5_a2-reconcile remain before fold."


**SHIPPED:** `origin/pump-carrier-fix` off `delivery-control@18416fd`

**POST-b4 BOARD (2026-06-21, doyle's re-measure + my analysis):** b4 fix IMPLEMENTED+PUSHED `viewer-drain-decouple-b4@677c3e7` (broker.rs append single non-blocking controller try_send + ControllerSink.last_ok + contiguous_advance cursor; deliver()/ControllerJob DELETED). b4 PROVEN: Linux drain_appends=88902 (was throttled ~0), c1-EVICT fires. (1) **Windows a_journaled flip = FLAKE** (doyle re-ran → passes 4.7s; the pumped=249/subscribed=false was a one-off hfenduleam shared-runner load-contention). I structurally exonerated it first: the flipping subscribe is `net_stream_subscribe`→`dispatch_net_stream_subscribe`→nethost StreamLog, NOT OutputLog; + Windows ctrl channel can't fill at 249«4096. (2) **w5_a2 regression (deterministic pass→fail across b4) = GOOD side-effect, NOT over-evict** (traced): the abandoned-remote-controller clear is the DISCONNECTED-evict (controller_writer's write to the dead operator fails → thread ends → next append try_send=Disconnected → mark_controller_gone; broker.rs:755-6), a path present pre-b4 too — b4's full-speed drain just feeds controller_writer frames sooner so the dead-operator write-failure lands INSIDE the test's 2s post-abandon read window (pre-b4's throttle delayed it past the read → PASS). NOT the last_ok deadline (5s>4s window). w5_a2's "idle" premise was never staged (flood child, killed only at teardown, can't kill earlier w/o reaping the session). FIX = make it faithfully idle (quiet long-lived child instead of flood → no output → no clear → stays Some; preserves the "idle still needs the D4c presence oracle / REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT" characterization). b4 needs NO code change. Full analysis queued to doyle in /tmp/doyle_w5a2.txt (doyle was NO_PERCH — resend when his perch returns). **skip-to-live** (closes a_journaled+p0_paste, ONE root post-b4) IMPLEMENTED on the b4 branch: KIND_VIEWER_EVICTED marker (viewer_writer, off-lock, ≠EOF) + serve_attach Brain::attach_skip_to_live (from_seq=u64::MAX, cold-brain snap-above cursor reset, RESUBSCRIBE_INTERVAL rate-limit). req REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT (doc CONTEXT.md:342 + impl tagged; unit via spt-test-engineer in flight). REMAINING before fold: skip-to-live unit+p0_paste verify, g2 inject-acceptance (real, still red), w5_a2 reconcile. Design doc `V0.13.0-SKIP-TO-LIVE-JIT.md`.

**SHIPPED:** `origin/pump-carrier-fix` off `delivery-control@18416fd`


**POST-b4 BOARD (2026-06-21, doyle's re-measure + my analysis):** b4 fix IMPLEMENTED+PUSHED `viewer-drain-decouple-b4@677c3e7` (broker.rs append single non-blocking controller try_send + ControllerSink.last_ok + contiguous_advance cursor; deliver()/ControllerJob DELETED). b4 PROVEN: Linux drain_appends=88902 (was throttled ~0), c1-EVICT fires. (1) **Windows a_journaled flip = FLAKE** (doyle re-ran → passes 4.7s; the pumped=249/subscribed=false was a one-off hfenduleam shared-runner load-contention). I structurally exonerated it first: the flipping subscribe is `net_stream_subscribe`→`dispatch_net_stream_subscribe`→nethost StreamLog, NOT OutputLog; + Windows ctrl channel can't fill at 249«4096. (2) **w5_a2 regression (deterministic pass→fail across b4) = GOOD side-effect, NOT over-evict** (traced): the abandoned-remote-controller clear is the DISCONNECTED-evict (controller_writer's write to the dead operator fails → thread ends → next append try_send=Disconnected → mark_controller_gone; broker.rs:755-6), a path present pre-b4 too — b4's full-speed drain just feeds controller_writer frames sooner so the dead-operator write-failure lands INSIDE the test's 2s post-abandon read window (pre-b4's throttle delayed it past the read → PASS). NOT the last_ok deadline (5s>4s window). w5_a2's "idle" premise was never staged (flood child, killed only at teardown, can't kill earlier w/o reaping the session). FIX = make it faithfully idle (quiet long-lived child instead of flood → no output → no clear → stays Some; preserves the "idle still needs the D4c presence oracle / REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT" characterization). b4 needs NO code change. Full analysis queued to doyle in /tmp/doyle_w5a2.txt (doyle was NO_PERCH — resend when his perch returns). **skip-to-live** (closes a_journaled+p0_paste, ONE root post-b4) IMPLEMENTED on the b4 branch: KIND_VIEWER_EVICTED marker (viewer_writer, off-lock, ≠EOF) + serve_attach Brain::attach_skip_to_live (from_seq=u64::MAX, cold-brain snap-above cursor reset, RESUBSCRIBE_INTERVAL rate-limit). req REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT (doc CONTEXT.md:342 + impl tagged; unit via spt-test-engineer in flight). REMAINING before fold: skip-to-live unit+p0_paste verify, g2 inject-acceptance (real, still red), w5_a2 reconcile. Design doc `V0.13.0-SKIP-TO-LIVE-JIT.md`.

**SHIPPED:** `origin/pump-carrier-fix` off `delivery-control@18416fd`

---
name: v013-rc-attach-flood-wedge
description: "v0.13.0 — the 3 inject_control_wedge gates fail because a slow controller's inline backpressure throttles the session drain, starving a concurrent rc-view viewer (root 'b4', 2026-06-21); b4 fix SHIPPED, skip-to-live + g2 + w5_a2-reconcile remain before fold."
metadata:
  node_type: memory
  type: project
  originSessionId: f12f255d-20de-40e1-b356-60f58d21c08e
---

As of 2026-06-21. The 3 failing gates in `crates/spt-daemon/tests/inject_control_wedge.rs` are `a_journaled_input_wedge_does_not_starve_a_concurrent_rc_attach`, `p0_paste_wedge_...`, and `g2_no_commit_deadline_...` (g2 = a SEPARATE InjectFloor commit-deadline item, still parked). They assert a real, legitimate property: a concurrent `rc --view` viewer must not be starved while a session floods output.

**ROOT = "b4"** (verified by doyle's code-read + my code verification; the earlier "serde-saturation / control-plane-starves" narrative in this file's prior version was a pre-warm-datapoint hypothesis and is SUPERSEDED). The session drain closure (`broker.rs:1450-1457`) runs `ControllerJob::deliver()` INLINE; `deliver()` (`broker.rs:669-685`) is a `try_send` sleep-poll up to `CONTROLLER_WRITE_DEADLINE` (5s) ON THE DRAIN THREAD. A controller draining slower than the PTY floods → its `CONTROLLER_CHANNEL_DEPTH`(4096) channel fills → `deliver()` throttles the drain → `OutputLog::append`'s viewer fan-out (`try_send`) doesn't run → the viewer starves. Signature: c1=0 c2=0 c3=1 EVICT=0, subscribed=TRUE, got_output=FALSE. forkpty-only for a_journaled/g2 (forkpty floods harder than Windows ConPTY's paced output → channel fills on Linux, keeps up on Windows); p0_paste fails both. No ~7s recovery = steady-state-near-full (deliver gets a slot inside 5s every chunk → never Err → never deadline-evicts → throttled the whole run). **Regression verdict (diff-bisect):** pre-W1 wrote the controller inline UNDER the log lock = permanent wedge; W1 bounded it to 5s episodes but still couples drain→controller. NOT a new class; real fix warranted.

**Confirmed FIXED, not the blocker (supersedes the old narrative):** facet-B (journal-lock-parks-write_input) is already fixed in HEAD by W1b (`e404aeb`, lock off effect) + P0 (`REQ-HAZARD-PTY-INPUT-WRITER-WEDGE`, non-blocking input enqueue to an isolated writer thread) + no-ack. The operator's 2026-06-19 dogfood wedge (`spt rc` → "brain IPC read deadline") was a PRE-W1b build (`8b5583e`, 2hr before `e404aeb`). The test "carrier artifact" (Whole-conn attacher ignoring its read deadline → false subscribed=false) is fixed by the pump switch. The registry Phase-A `SQLITE_BUSY` flake is fixed by the env-knob.

**FIX (designed, sent to doyle for final gate; full design in `V0.13.0-VIEWER-DRAIN-DECOUPLE-JIT.md`):** decouple controller backpressure from the drain. Controller becomes a SINGLE non-blocking `try_send` (like viewers), moved INTO `append()` under the log lock; `deliver()`'s sleep-poll DELETED; `append` returns `Option<evict-epoch>`; the drain off-lock does `mark_controller_gone` if signaled and NEVER sleeps. `ControllerSink` += `last_ok: Instant` staleness. On Full: past deadline → evict, else DROP (controller falls behind the ring → resume-from-floor). `delivered_through` advances ONLY on real writes (no phantom-advance). KH epoch gate / one-live-writer / monotonic seq unchanged. doyle's 3 gate constraints: (1) no phantom cursor-advance, (2) slow-controller degrades to ring-fall-behind not drain-backpressure, (3) bounded staleness-detach measured across chunks.

**False trails (do not re-chase):** journal-lock parking (W1b-fixed); acked-flood self-contending on `Mutex<SendHalf>`; CPU/HOL behind the UDS flood; `PtySession.writer` mutex; viewer-channel EVICTion (b1); serve-side `net_stream_send` block (b2 — `net_stream_send(None)` is fire-and-forget, brain.rs:1004); EVICT-counter gap (b3).

**SHIPPED:** `origin/pump-carrier-fix` off `delivery-control@18416fd` — `7a1cc68` (both rc-attach attachers `cold_start`→`cold_start_pump`) + `7c09819` (env-tunable `SPT_REGISTRY_BUSY_TIMEOUT_MS`, default 5000/CI 30000); both in doyle's PR #27 validation branch. v4 diagnostic counters on `origin/wedge-trace-v4` (do-not-merge). **Partner:** doyle owns forkpty CI (kitsubito = only online Linux box, CI-only) + gates designs against KH/B2; I author code + name roots. See [[cross-machine-test-rig]], [[delegate-tests-to-spt-test-engineer]], [[spt-verification-gates]].

+**POST-b4 BOARD (2026-06-21, doyle's re-measure + my analysis):** b4 fix IMPLEMENTED+PUSHED `viewer-drain-decouple-b4@677c3e7` (broker.rs append single non-blocking controller try_send + ControllerSink.last_ok + contiguous_advance cursor; deliver()/ControllerJob DELETED). b4 PROVEN: Linux drain_appends=88902 (was throttled ~0), c1-EVICT fires. (1) **Windows a_journaled flip = FLAKE** (doyle re-ran → passes 4.7s; the pumped=249/subscribed=false was a one-off hfenduleam shared-runner load-contention). I structurally exonerated it first: the flipping subscribe is `net_stream_subscribe`→`dispatch_net_stream_subscribe`→nethost StreamLog, NOT OutputLog; + Windows ctrl channel can't fill at 249«4096. (2) **w5_a2 regression (deterministic pass→fail across b4) = GOOD side-effect, NOT over-evict** (traced): the abandoned-remote-controller clear is the DISCONNECTED-evict (controller_writer's write to the dead operator fails → thread ends → next append try_send=Disconnected → mark_controller_gone; broker.rs:755-6), a path present pre-b4 too — b4's full-speed drain just feeds controller_writer frames sooner so the dead-operator write-failure lands INSIDE the test's 2s post-abandon read window (pre-b4's throttle delayed it past the read → PASS). NOT the last_ok deadline (5s>4s window). w5_a2's "idle" premise was never staged (flood child, killed only at teardown, can't kill earlier w/o reaping the session). FIX = make it faithfully idle (quiet long-lived child instead of flood → no output → no clear → stays Some; preserves the "idle still needs the D4c presence oracle / REQ-HAZARD-DRIVEN-BY-IDLE-REMOTE-EVICT" characterization). b4 needs NO code change. Full analysis queued to doyle in /tmp/doyle_w5a2.txt (doyle was NO_PERCH — resend when his perch returns). **skip-to-live** (closes a_journaled+p0_paste, ONE root post-b4) IMPLEMENTED on the b4 branch: KIND_VIEWER_EVICTED marker (viewer_writer, off-lock, ≠EOF) + serve_attach Brain::attach_skip_to_live (from_seq=u64::MAX, cold-brain snap-above cursor reset, RESUBSCRIBE_INTERVAL rate-limit). req REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT (doc CONTEXT.md:342 + impl tagged; unit via spt-test-engineer in flight). REMAINING before fold: skip-to-live unit+p0_paste verify, g2 inject-acceptance (real, still red), w5_a2 reconcile. Design doc `V0.13.0-SKIP-TO-LIVE-JIT.md`.


Status — work this turn:

**doyle's 2 messages handled:**
1. **Windows a_journaled = FLAKE** (doyle confirmed) — my structural exoneration was right (subscribe path = nethost StreamLog ≠ OutputLog; ctrl channel can't fill at 249«4096). Hold lifted.
2. **w5_a2 regression traced → GOOD side-effect, NOT over-evict.** Precise mechanism: the **Disconnected-evict** (controller_writer's write to the dead operator fails → thread ends → next `append` try_send=Disconnected → `mark_controller_gone`; broker.rs:755-6) — a path present pre-b4 too. b4's full-speed drain just makes the dead-operator write-failure land *inside* the test's 2s read window (pre-b4's throttle delayed it past the read → PASS). NOT the `last_ok` deadline (5s>4s). The test's "idle" premise was never staged (flood child, killed only at teardown). **b4 needs no code change**; recommend making w5_a2 faithfully idle (quiet child). Full analysis queued.

**skip-to-live IMPLEMENTED** (closes a_journaled + p0_paste, one root post-b4) on `viewer-drain-decouple-b4`:
- `KIND_VIEWER_EVICTED` marker — written in `viewer_writer` off the log lock, distinct from EOF (msg.rs + broker.rs)
- `append`/`set_size_and_notify` set `ViewerSink.evicted` before drop
- `Brain::attach_skip_to_live` — re-subscribe from ring floor (`from_seq=u64::MAX`), cold-brain snap-above cursor reset for the forward jump (brain.rs)
- serve_attach handler: re-subscribe, no teardown, `RESUBSCRIBE_INTERVAL` rate-limit (attach.rs)
- req `REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT` registered, doc (CONTEXT.md) + impl tagged
- clippy `-D` clean, broker 10/10 green

**In flight / pending:**
- skip-to-live **unit tests** — spt-test-engineer subagent running (background; will notify)
- **doyle offline** (NO_PERCH) — w5_a2 + skip-to-live messages saved to `/tmp/doyle_w5a2.txt`, resend when his perch returns
- memory updated with the post-b4 board

Remaining before b4 fold: skip-to-live unit + p0_paste verify, g2 inject-acceptance (real, still red), w5_a2 reconcile. Standing by for the test subagent + doyle.


<task-notification>
<task-id>ab83417ec42df8dd0</task-id>
<tool-use-id>toolu_01YGH1aDnjf1GmQPpc4XTXj2</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\2730b458-50d2-48aa-b752-3ad22fefaa0b\tasks\ab83417ec42df8dd0.output</output-file>
<status>completed</status>
<summary>Agent "Write skip-to-live unit tests" came to rest</summary>
<note>A task-notification fires each time this agent comes to rest with no live background children of its own. The user can send it another message and resume it, so the same task-id may notify more than once.</note>
<result>Still running (grep buffers until process exit). I'll wait for the completion notification.</result>
<usage><subagent_tokens>164014</subagent_tokens><tool_uses>52</tool_uses><duration_ms>1673029</duration_ms></usage>
</task-notification>


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


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


Test subagent done. Verifying its work directly (don't trust — per verification gates). Check what changed + run the gates:


Bash

git status --short && echo "=== new test fns ===" && grep -rn "unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT" crates/

Show changed files + new unit test tags


 M CONTEXT.md
 M V0.13.0-VIEWER-DRAIN-DECOUPLE-JIT.md
 M crates/spt-daemon/src/attach.rs
 M crates/spt-daemon/src/brain.rs
 M crates/spt-daemon/src/broker.rs
 M crates/spt-daemon/src/msg.rs
 M traceable-reqs.toml
?? V0.13.0-SKIP-TO-LIVE-JIT.md
=== new test fns ===
crates/spt-daemon/src/brain.rs:1394:    // [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]
crates/spt-daemon/src/brain.rs:1438:    // [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]
crates/spt-daemon/src/broker.rs:2336:    // [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]
crates/spt-daemon/src/broker.rs:2403:    // [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT]
crates/spt-daemon/src/msg.rs:1002:    // [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT] the `evicted_envelope` helper


5 new unit tests (brain ×2, broker ×2, msg ×1). Let me review the bodies and run the full gate suite together:


cargo test -p spt-daemon --lib 2>&1 | tail -15; echo "===CLIPPY==="; cargo clippy -p spt-daemon --all-targets -- -D warnings 2>&1 | tail -5; echo "===TRACE==="; traceable-reqs check 2>&1 | grep "SKIP-TO-LIVE\|must\]\|^Findings" | head

Run daemon lib tests + clippy + traceability


Command running in background with ID: b6jz3zoo3. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\2730b458-50d2-48aa-b752-3ad22fefaa0b\tasks\b6jz3zoo3.output. You will be notified when it completes. To check interim output, use Read on that file path.


Verification running in background. Reviewing the test bodies while it runs: