{
  "summary": "Strongest pinned root candidate: a pre-v0.30.6 broker connection write could block indefinitely after a controller/brain consumer stopped reading. The physical mechanism is pinned, reproduced, and source-localized; only the historical identity/reason of the field consumer that stopped draining is not recoverable from code alone. Before round 4, `SharedSend` was `Arc<Mutex<SendHalf>>`; controller/viewer/reply writers acquired that mutex and retained it across synchronous length-prefixed `write_frame` (`write_all` prefix, body, then `flush`). On Windows/interprocess 2.4.2 the underlying named-pipe write waits through `WriteFileEx` + `SleepEx(INFINITE)` and exposes no usable write timeout. Logical `stall_evict_controller` removed the controller role but did not cancel the in-flight write, close the physical connection, release the writer's stack-owned mutex guard, or join the writer. A real black-hole reproduction observed four writers acquire immediately (`wait_us=0`) and then remain inside `write_frame` for ~127.953 s, returning only when a later restart closed their connections; this kills the competing mutex-convoy and scheduler-starvation explanations. Exact evidence: `.worktrees/rca-capture/docs/UPDATE-WEDGE-ROOT-CAUSE.md:1-97`; old implementation `.worktrees/rca-capture/crates/spt-daemon/src/broker.rs:73-77,1048-1108,1664-1760`; framing `crates/spt-daemon/src/codec.rs:21-35`; current regression narrative/test `crates/spt-daemon/tests/brain_decouple.rs:384-474`.\n\nVersion/commit chain: v0.30.2 tag ref `b0d77197ab07bcb30d8bbf24d5e01f2ce0a0f1fa`; v0.30.3 `0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047`; v0.30.4 `ade41be9d865b43f92a9c9017507734d8a9edd55`; v0.30.5 `8e5ff889e0a4fa9a907801dc861904608cbc8e65`; v0.30.6 `d73e19e12f7192a08d6ee502d21d03809a57a921` (`.git/refs/tags/v0.30.2` through `v0.30.6`). The v0.30.3 window includes `ce053dc930104afda280504b7e489f27462d47bb`, “brain self-drives the controller reap”; it added a second serialized request/reply round trip, `brain.sessions()`, every 500 ms (`brainproc.rs:253-287`). It did not itself block on peers, but made heartbeat progress depend on the already-contended brain connection. v0.30.4 fix `fb499dbfc29521daab72cb49e1768aeb5f3c74bc` changed cold resume from local Controller to Viewer, preventing control steal but retaining an output subscription on the same non-draining request/reply connection. v0.30.5 fix `03c71093ca70e76a86d42087e753e3cca0793095` made the supervised daemon resume cursor-only, eliminating that specific undrained-subscription self-deadlock (`brainproc.rs:181-208`; `brain.rs:1029-1085`). v0.30.6 round-4 fix `d634b67b2962ab919d0a03a280d63695e48214b3` replaced every raw physical broker-connection send with bounded, cancelable, poison-on-failure `BrokerConn` writes; branch log explicitly names the change, and `conn.rs:1-59,304-465` shows the absolute deadline covering gate wait plus OS completion, out-of-band CancelIoEx/shutdown, connection poison/sever, and no reuse.\n\nCode-path operation map for v0.30.3-v0.30.6: (1) PTY output drain itself appends/rings and performs only `try_send` fan-out under `Mutex<OutputLog>` in the modern/pre-round-4 architecture (`broker.rs:715-858`); this is non-blocking and therefore not the terminal freeze root. (2) Per-controller and per-viewer dedicated writer threads block on `rx.recv()` while idle and blocked synchronously on physical socket writes while active (`broker.rs:1330-1357,1438-1500`); before v0.30.6 the latter held the connection mutex indefinitely. (3) All dispatch replies on the same connection serialized behind the same send mutex; thus an undrained resumed viewer could hold the mutex while output-writing and starve `KIND_NET_STATUS_REPLY`/`KIND_SESSIONS_REPLY`, preventing the brain from reading, which perpetuated the writer block—a closed causal cycle documented at `docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md:31-85` and tested at `tests/brain_resume_conn_deadlock.rs:4-24`. (4) Resume in v0.30.4 called `resume_sessions`: query sessions, seed cursor, subscribe each as Viewer (`brain.rs:1029-1058`). The supervised brain does not continuously drain PTY output, so this multiplexed unsolicited output and request/reply onto one connection. v0.30.5 uses `resume_session_cursors` and sends no subscribe (`brain.rs:1061-1085`). (5) Inject delivery is serialized per translation-bearing session: one `event_rx` loop, up to two attempts, a 400 ms settle probe cadence, 1.2 s echo-verify window when declared, and a 5 s translation commit deadline (`broker.rs:353-445,1960-2145,2160-2285`). `SPT_INJECT_VERIFY_ECHO` is non-default and enabled on the live daemon, so it can increase PTY traffic and worker occupancy and may help satisfy the load trigger, but it sleeps on the dedicated inject worker with no output-log/floor lock held; output-log reads are short snapshots, PTY writes enqueue through bounded non-blocking `InputWriter::enqueue` (`broker.rs:1600-1715`), and the sole blocking PTY write is isolated on its per-session input-writer thread (`broker.rs:1717-1730`). It is therefore a confounder/load amplifier, not a code-supported global broker-freeze mechanism.\n\nFalsifiable hypotheses:\n1. PHYSICAL NON-DRAINING-CONSUMER WRITE (strongest/pinned, supported): real `seq>0` output plus a connected consumer that stops reading causes a controller/viewer writer to enter `write_frame`; pre-v0.30.6 it remains alive after logical eviction. Falsifier: reproduce with an unread controller while showing the writer/handler physically retires within the configured deadline without client drop on pre-v0.30.6. Existing capture shows the opposite pre-fix, and `brain_decouple.rs:471+` is the post-fix gate.\n2. v0.30.4 UNDRAINED RESUME SELF-DEADLOCK (supported as a concrete trigger, closed in v0.30.5): resume-as-Viewer replays active sessions onto the daemon brain's request/reply connection; a viewer writer blocks holding SharedSend; heartbeat reply needs SharedSend; heartbeat cannot return/drain. Falsifier: run `resume_sessions` on N flooding sessions while heartbeat round trips continue at the healthy floor, or prove zero viewer subscriptions after daemon resume. `brain_resume_conn_deadlock.rs:264-340` now asserts cursor-only leaves zero viewers and its seam-sensitivity arm proves `resume_sessions` does create them.\n3. v0.30.3 REAP-DRIVE ITSELF BLOCKS ON DEAD PEER (killed): `sessions()` snapshots only session locks; `net_status()` is a local status read; `OutputLog::append` uses non-blocking `try_send`; no dead-peer operation is on this tick. `docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md:5-30` pins the negative code trace. Falsifier for the kill would be an exact call from either heartbeat request into a peer dial/wait or a held lock shared with it; none exists.\n4. BROKER NET-RUNTIME STARVATION BY DEAD-PEER DIALS (killed): a 12-deep proof-hang/unreachable dial rig kept the net-runtime liveness canary at 32-33 ms versus the 15 s stall threshold and left 12/24 permits free; async awaits yield. Evidence `docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md:1-54`. Falsifier for the kill: canary age approaching the stall deadline under the same dial shapes.\n5. INJECT SETTLE/ECHO RE-DRIVE GLOBALLY SERIALIZES PTY DATA (killed as root, retained as confounder): declared echo verification may cause settle probes, sleeps, a second typed attempt, and additional output, but operations are confined to a per-session worker; sleeps happen outside locks and PTY writes are non-blocking queue handoffs. Falsifier: thread evidence showing the inject worker sleeps while holding `OutputLog`, the inject floor, global broker dispatch, or a physical connection gate. Current code shows none. It can still increase output pressure and should be disabled in comparative field trials because the daemon has non-default `SPT_INJECT_VERIFY_ECHO` enabled.\n\nConclusion: code plus the saved physical reproduction pins the durable broker-side root to unbounded/cancel-incapable physical connection writes, not merely to the v0.30.3/v0.30.4 resume policy. v0.30.3 and v0.30.4 supplied/widened reproducible triggers; v0.30.5 removed the daemon-resume trigger; only v0.30.6 closes the general failure class for any stuck controller/viewer/reply consumer. Code alone cannot name why the original field client stopped reading; the root report explicitly records that historical process identity as unproven.",
  "files": [
    {
      "path": "spt-core/.worktrees/rca-capture/docs/UPDATE-WEDGE-ROOT-CAUSE.md",
      "description": "Authoritative physical RCA and Windows reproduction: four immediate-lock writes parked inside frame I/O ~127.953 s; logical eviction did not cancel/retire them; exact old-code anchors and rejected causes."
    },
    {
      "path": "spt-core/.worktrees/rca-capture/crates/spt-daemon/src/broker.rs",
      "description": "Instrumented pre-v0.30.6 broker. Lines 73-77 define raw SharedSend; 1048-1108 install controller writer; 1664-1760 retain SendHalf mutex across write_frame on initial/live paths."
    },
    {
      "path": "spt-core/crates/spt-daemon/src/codec.rs",
      "description": "Lines 21-35 implement synchronous length-prefixed frame output using write_all(prefix), write_all(body), flush."
    },
    {
      "path": "spt-core/docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md",
      "description": "Exact v0.30.4 self-deadlock causal chain and code proof that v0.30.3 reap-drive/dead-peer tick is not itself blocking; explains Viewer fix did not remove subscription."
    },
    {
      "path": "spt-core/docs/UPDATE-WEDGE-2-ROUND3-RIG-VERDICT.md",
      "description": "Empirical kill of net-runtime starvation: 12 in-flight dead-peer dials, 32-33 ms canary age, permits available."
    },
    {
      "path": "spt-core/crates/spt-daemon/src/brainproc.rs",
      "description": "Lines 181-208 current v0.30.5 cursor-only resume; lines 253-287 v0.30.3 heartbeat sessions reap-drive serialized after net_status."
    },
    {
      "path": "spt-core/crates/spt-daemon/src/brain.rs",
      "description": "Lines 1029-1058 draining-only subscribe-as-Viewer resume and deadlock warning; 1061-1085 cursor-only safe resume; 1585-1610 explains local Control resume steals/freezes."
    },
    {
      "path": "spt-core/crates/spt-daemon/tests/brain_resume_conn_deadlock.rs",
      "description": "Dead-peer-independent round-3 regression proof and deterministic zero-viewer/seam-sensitivity guard."
    },
    {
      "path": "spt-core/crates/spt-daemon/src/conn.rs",
      "description": "v0.30.6 bounded connection primitive. Lines 1-59 state failure class; 304-465 implement absolute bounded gate+write, watchdog cancellation, poisoning, severing, and retirement."
    },
    {
      "path": "spt-core/crates/spt-daemon/tests/brain_decouple.rs",
      "description": "Lines 384-474 document exact pre-fix Windows physical-retirement failure and post-fix non-draining-controller gate."
    },
    {
      "path": "spt-core/crates/spt-daemon/src/broker.rs",
      "description": "Current active paths: output non-blocking fan-out 715-858; writers 1330-1500; InputWriter isolation 1600-1730; settle/echo/re-drive 1960-2285."
    },
    {
      "path": "spt-core/.git/logs/refs/heads/update-wedge-drain-drive",
      "description": "Commit evidence for v0.30.3-era ce053dc reap-drive change."
    },
    {
      "path": "spt-core/.git/logs/refs/heads/uwedge2-resume-viewer",
      "description": "Commit evidence for fb499db resume-as-Viewer and 03c7109 cursor-only round-3 fix, plus diagnostic verdict commits."
    },
    {
      "path": "spt-core/.git/logs/refs/heads/fix/update-wedge-bounded-conn-write",
      "description": "Commit evidence for d634b67 v0.30.6 bounded+cancelable+poison-on-failure writes and its RED-first physical gate."
    },
    {
      "path": "spt-core/.git/refs/tags/v0.30.2",
      "description": "Tag ref b0d77197ab07bcb30d8bbf24d5e01f2ce0a0f1fa."
    },
    {
      "path": "spt-core/.git/refs/tags/v0.30.3",
      "description": "Tag ref 0a306c9a86d9c7bedf96bf106cb9e1df3a6c9047."
    },
    {
      "path": "spt-core/.git/refs/tags/v0.30.4",
      "description": "Tag ref ade41be9d865b43f92a9c9017507734d8a9edd55."
    },
    {
      "path": "spt-core/.git/refs/tags/v0.30.5",
      "description": "Tag ref 8e5ff889e0a4fa9a907801dc861904608cbc8e65."
    },
    {
      "path": "spt-core/.git/refs/tags/v0.30.6",
      "description": "Tag ref d73e19e12f7192a08d6ee502d21d03809a57a921."
    }
  ],
  "architecture": "Active terminal output flows PTY child → one per-session drain thread → short `Mutex<OutputLog>` append/ring update → non-blocking bounded queue handoffs to isolated controller/viewer writers → serialized physical broker connection → attach server/net stream → `spt rc`. Input flows controller/inject → per-session `InjectFloor` ordering → bounded non-blocking `InputWriter` queue → sole blocking PTY input writer. A brain swap preserves broker/PTYs; the replacement brain queries broker-held session cursors. In v0.30.4 it subscribed those sessions as Viewers on the same connection used for heartbeat request/reply, although that brain did not continuously drain output. The resulting unsolicited-output writer and dispatch reply writer shared one `Arc<Mutex<SendHalf>>`; if output filled the pipe, the former held the mutex inside OS I/O and permanently prevented the reply that would let the brain drain—self-deadlock. v0.30.5 separates cursor reconstruction from actual subscription for the non-draining supervised brain. v0.30.6 changes the lower connection layer itself: every writer acquires a deadline-bounded gate, writes outside locks with a watchdog, and on timeout poisons/aborts/severs the whole physical connection so existing EOF cleanup removes roles. This is the general fix because it also covers a stuck human controller unrelated to resume."
}