{
  "summary": "Diagnosis: the parent hypothesis is directionally correct about which process death interrupts controllers, but incomplete about intended restoration. `spt daemon refresh` and normal in-place `spt update` kill only the supervised brain process. That kill destroys every brain-owned inbound-dispatch worker and its target-side `serve_attach` pump, while the broker retains the PTY child, PTY master, NetHost connection, both stream rows, stream rings, and the operator-side `spt rc` broker connection. However, the successor brain is intended to restore active controllers separately from `run_brain`'s cursor-only PTY-session resume: after its first 500 ms heartbeat, `spawn_net_consumers` starts a fresh dispatcher, which enumerates broker-held peer-initiated streams and re-runs `serve_attach`. Therefore `BRAIN_RESUMED` only mentioning session cursors is not by itself proof that rc streams are abandoned.\n\nThe strongest code-proven causal candidate is the dispatcher's non-durable, one-shot reconstruction protocol. `dispatch::run_dispatch_loop` creates a fresh per-brain `claimed: HashSet<u64>` and inserts each stream ID before spawning its worker (`dispatch.rs:205-225`). A worker reconstructs the protocol family by subscribing from sequence 0 and replaying the first NDJSON record (`dispatch.rs:245-301`). Attach stream openers are not durable metadata: the original `AttachRecord::Request` is merely sequence 0 in an ordinary bounded-overwrite `StreamLog`. `DEFAULT_STREAM_RING_CHUNKS` is 4096 (`nethost.rs:108-111`); `StreamLog::append` evicts the oldest entries forever once the ring exceeds the cap (`nethost.rs:206-225`). Ordinary attach streams are not made retentive. After the opener rolls out, `peek_first_line` calls `Brain::net_stream_subscribe(stream_id, 0)`, which explicitly seeds cursor 0 (`brain.rs:1527-1542`); the first retained frame has sequence >0, so `Brain::read_event` rejects it as `stream … gap: got seq … want 0` (`brain.rs:807-830`). The worker returns `DispatchOutcome::Failed`; the dispatcher logs `DISPATCH:<id>:Failed(…)` but never removes the ID from `claimed`, so it will never retry during that brain generation. Detach plus `spt rc` opens a fresh stream with a retained Request and a new, unclaimed stream ID, exactly matching the observed repair.\n\n[INFERENCE] The 4-of-5 partial observation is naturally explained if four older/busier controller request-side streams had advanced beyond their retained opener while one newer/quieter stream still retained sequence 0. The cap is measured in transport read chunks, not logical input records, and QUIC/loopback may coalesce or split writes, so the exact age/input threshold varies per stream. A second compatible explanation is independent worker failure: because each stream gets its own IPC connections but every claim is one-shot, transient replay/connect/subscribe failure can strand any subset while another succeeds. Logs can discriminate: look for `DISPATCH:<stream>:Failed(\"stream <stream> gap: got seq … want 0\")`; other `DISPATCH:*:Failed`, `CONN_WRITE_RETIRED`, or `CONN_WRITE_POISONED` records would support the transient-worker arm.\n\nRanked falsifiable candidates:\n1. HIGH — opener evicted from the bounded broker stream ring, producing a replay gap on successor-dispatcher classification, followed by permanent one-shot claim. This is fully established as a reachable source-level failure; field causality remains [INFERENCE] until the generation logs show the gap or the deterministic rig reproduces it.\n2. MEDIUM-HIGH — any transient successor worker failure is made permanent by inserting into `claimed` before work and never clearing/retrying Failed/Unknown. Falsify by showing all affected stream workers reached `serve_attach` and emitted successful stream/controller subscription lifecycle events after the new generation.\n3. MEDIUM — the refresh acknowledgment/readiness ordering creates a real pause and increases replay races: the CLI returns after the broker acknowledges only that the restart signal was accepted; the supervisor consumes it on a 200 ms tick, kills the old child, and the successor writes `brain.ready` before net consumers are spawned. Dispatcher startup waits for the first 500 ms heartbeat/net-status. This explains a short interruption, not a persistent until-detach stall by itself.\n4. LOW — bounded broker-connection write retirement during kill/replay can independently fail some workers. Current `BrokerConn` bounds and poisons physical IPC writes, so this should self-clean rather than globally wedge, but because dispatcher claims are one-shot even an ordinary retired worker connection can become persistent at the stream level.\n5. LOW / no direct evidence — inherited client pipe handles could delay old brain-connection EOF cleanup if a descendant inherited one. The source contains no transfer of ConPTY/forkpty handles into the brain: those remain broker-owned. `spawn_brain_child` is a normal managed child with `CREATE_NO_WINDOW` on Windows, not a detached broker/PTY owner. The designated long-lived detached daemon/shell paths use `bInheritHandles=FALSE`; ordinary short role spawns are not the resource boundary here. Delayed EOF also does not block a new subscriber: both stream and controller cleanup use pointer-identity guards, so late old-connection cleanup should not detach the newer subscriber. This candidate does not explain 4/5 as cleanly as the per-stream claim/replay fault.\n6. REFUTED for the reported refresh/default-update shape — service manager or broker lifecycle. `spt daemon refresh` never calls service start/stop. Normal bare `spt update` and `update fetch --apply` use brain-only `apply_staged`; the service manager is involved only in `update apply --finish` / `spt update --restart`, which deliberately stops and restarts the whole daemon. That full-cycle path kills broker PTYs and re-runs endpoints, unlike the reported live-PTY/stalled-view symptom.\n\nExisting coverage proves adjacent invariants but misses this contract. `daemon_refresh_e2e.rs` proves broker PID, harness PID, and ONLINE perch survival, but has no active `spt rc` stream/controller. `brain_survive.rs` proves PTY + QUIC resources survive a process swap, but not that a long-lived production-dispatched attach continues. `attach_survives_target_brain_restart_exactly_once` manually constructs life 2 and calls `serve_attach` from 0 while the opener is still retained; it bypasses production dispatcher claim/classification and ring-floor aging. Dispatcher tests cover normal production serving and surviving an unrelated Unknown stream, not restarting the dispatcher over an existing stream or retrying a failed claim. `resume_no_control_steal_e2e` and `brain_resume_conn_deadlock` test direct PTY subscribers/session resume, not the broker-held network stream and target-side attach worker. `rc` reconnect tests exercise broker/transport EOF; a brain-only restart leaves the broker and operator stream alive, so the frontend receives neither broker EOF nor stream EOF when the target serve worker vanishes and therefore does not enter reconnect.\n\nProposed safe deterministic feedback loop (not executed because this assignment is read-only): add a test-only NetHost constructor/ring-cap seam so the attach stream ring can be 2–4 chunks rather than relying on 4096 timing-sensitive OS reads. Start the production dispatcher and a real echo PTY under an isolated broker; establish an rc-equivalent loopback Control attach; prove output/input first. Advance the peer/request-side stream enough to evict the Request, then terminate dispatcher generation 1 and start generation 2 while keeping broker, PTY PID, operator connection, and stream IDs fixed. Require the SAME operator stream—without detach, redial, or a new stream ID—to accept new input and render its echo within a short deadline. Current code should fail with the stream-gap/one-shot-claim signature. Add two companion arms: (a) opener retained, which must already pass, preventing an overbroad restart fix; (b) injected fail-once worker/connect outcome, which must be retried and then pass, proving claim lifecycle rather than only opener persistence. The end-to-end production gate should then extend `daemon_refresh_e2e`: active controller before real CLI `daemon refresh`; brain PID/generation changes; broker and PTY PID stay fixed; same controller stream receives post-refresh echo; no detach/reattach. Capture brain/broker logs and assert a new-generation stream-subscribe plus controller silent re-take, not merely `brain.ready` or perch ONLINE.",
  "files": [
    {
      "path": "crates/spt/src/cli.rs",
      "description": "CLI dispatch and update shapes. `DaemonCmd::Refresh` routes at 1471-1477 to `cmd_daemon_refresh` at 4064-4090. Update dispatch is 1443-1456. `cmd_update_apply` at 4890-4955 chooses live `apply_staged` vs daemonless apply. `plan_update_legs`/`cmd_update_composite` at 4958-5044 make normal bare update FetchApply and reserve whole-daemon Finish for `--restart`. `cmd_update_apply_finish` at 5140-5229 explicitly stop/wait/start cycles the broker. `cmd_update_fetch` at 6110-6333 routes `--apply` to `cmd_update_apply(false)`."
    },
    {
      "path": "crates/spt-daemon/src/applyhost.rs",
      "description": "Shared refresh/update handoff. `apply_staged` at 299-348 connects before swap, swaps/records pending, then calls `Brain::request_brain_restart`. `refresh_brain` at 354-368 performs the same restart request without swap/staging. The returned boolean is only the broker's honored acknowledgment, not restart completion."
    },
    {
      "path": "crates/spt-daemon/src/brain.rs",
      "description": "`Brain::request_brain_restart` at 1459-1467 sends `KIND_BRAIN_RESTART` and waits only for `BrainRestarted{honored}`. `resume_sessions` vs `resume_session_cursors` at 1028-1090 distinguishes subscribing/draining callers from the supervised non-draining brain. `net_stream_subscribe` at 1527-1542 seeds the requested cursor. Stream data at 807-830 rejects a first retained sequence above that cursor as a gap."
    },
    {
      "path": "crates/spt-daemon/src/broker.rs",
      "description": "Broker owns PTY sessions and client-role cleanup. `handle_conn` at 2970-3220 labels every IPC client generically `role=brain`, serves commands, and on connection end detaches only that connection's PTY/stream roles while leaving sessions/rings alive. Thus `role=brain stream-subscriber` logs can be per-attach dispatcher workers, not the main supervised heartbeat brain. `dispatch_brain_restart` at 3877-3898 raises the supervisor atomic and acks. `dispatch_subscribe` at 3449-3482 resolves a target serve worker to controller/viewer. `OutputLog::resolve_subscribe` at 1147-1210 silently re-takes controller for the same remote identity, enabling intended restart re-serve. `any_local_controller_wedged` at 2910-2940 is promotion gating only, not active rc stream reconstruction."
    },
    {
      "path": "crates/spt-daemon/src/brainproc.rs",
      "description": "Exact process transition. `BrainRestart` at 106-140 is an atomic coalescing signal. `supervise_brain` consumes it at 887-891, hard-kills/waits the current brain, increments generation, and respawns with `StartReason::Update` at 913-922. `run_brain` at 155-315 cursor-resumes PTY session metadata, writes ready, starts brain-owned hosts, then on the first 500 ms heartbeat starts net consumers. `spawn_net_consumers` at 321-335 starts the dispatcher. `spawn_brain_child` at 1018-1069 is a managed plain child (`CREATE_NO_WINDOW` on Windows; own process group on Unix), not owner of broker PTY handles. `spawn_brain_supervisor` at 1095-1131 wires the production broker truth/reaper."
    },
    {
      "path": "crates/spt-daemon/src/dispatch.rs",
      "description": "Primary defect locus. `run_dispatch_loop` at 198-232 creates an in-memory per-generation `claimed` set, inserts before spawning, and never consumes worker completion to retry/remove Failed/Unknown. `peek_first_line` at 245-279 always subscribes from 0. `worker` at 283-302 classifies that replayed opener and returns permanent Failed/Unknown on replay/connect errors. Attach arm at 333-394 resolves endpoint/session and calls `serve_attach`. `spawn_dispatcher` at 690-697 logs only loop-level termination; per-stream failures are `DISPATCH:<id>:…`."
    },
    {
      "path": "crates/spt-daemon/src/nethost.rs",
      "description": "Broker-held network data plane and opener-loss mechanism. `DEFAULT_STREAM_RING_CHUNKS=4096` at 108-111. `StreamLog` at 150-291 stores the opener only as ordinary ring data; `append` cap-evicts oldest frames at 206-225; `attach` replays only retained frames >= requested cursor at 256-281. `register_stream` at 640-700 keeps read pumps and rings broker-side across brain death. `open_stream` at 1260-1370 creates paired loopback stream rows for local rc. `stream_infos` at 1420-1450 is what successor dispatch enumerates. `subscribe_stream`/`detach_stream_sub` at 1500-1533 use subscriber replacement and pointer-guarded late cleanup."
    },
    {
      "path": "crates/spt-daemon/src/attach.rs",
      "description": "Target-side control pump owned by dispatcher worker inside the brain. `serve_attach` at 235-540 subscribes the peer stream, creates a separate forwarding broker connection, replays `AttachRecord::Request`, and at 333-416 attaches to the PTY as remote Control/Viewer. When the brain is killed this worker dies without finishing the broker-owned network stream. A successful successor worker can silently re-take the same identity and continue; without it, PTY and operator stream remain alive but no process forwards between them."
    },
    {
      "path": "crates/spt/src/rc.rs",
      "description": "Operator-side topology and symptom. Module docs at 1-24 establish rc as a broker-net-stream pump; `establish_attach` around 1330-1510 opens/subscribes the operator stream. `attach_viewport` at 1519-1628 reconnects only after `BrokerGone` or `Severed`. `pump` at 1820-2000 retries read timeouts forever after any prior event; it enters reconnect on broker IPC EOF or target stream EOF. Brain-only refresh preserves both broker and stream, while the dead target worker sends no EOF, so an established rc can remain silently blank until operator detach/new attach."
    },
    {
      "path": "crates/spt-daemon/src/conn.rs",
      "description": "Physical broker IPC write boundary. `BrokerConn` provides bounded/cancelable/poison-on-failure writes, with lifecycle attribution. This prevents one dead consumer from globally pinning broker writes, but a per-stream worker connection retirement can still feed the dispatch one-shot-claim defect. Relevant log discriminators are `CONN_WRITE_POISONED`, `CONN_WRITE_RETIRED`, and `CONN_LIFECYCLE`."
    },
    {
      "path": "crates/spt-daemon/src/daemon.rs",
      "description": "Broker lifecycle and handle boundary. `Daemon::run` binds/serves Broker/NetHost and starts the brain supervisor at 165-367; graceful whole-daemon stop raises brain_stop and reaps the brain subtree. `start_daemon`/`stop_daemon` at 554-596 invoke service routing only for explicit whole-daemon lifecycle. `spawn_detached` at 610-720 and Windows `detached_no_inherit` at 738-858 concern initial long-lived broker startup and captured-pipe safety, not routine brain refresh."
    },
    {
      "path": "crates/spt-daemon/src/service.rs",
      "description": "Service-manager boundary. `plan_start`/`plan_stop` at 57-78; Linux registered default-home daemon uses `systemctl --user start/stop` at 149-180. Windows logon task is boot-only and `detected=false` at 245-265. Refresh and normal brain-only update do not traverse this module; finish/restart does."
    },
    {
      "path": "crates/spt/tests/daemon_refresh_e2e.rs",
      "description": "Existing real CLI refresh gate. It verifies brain PID/generation cycle, unchanged broker PID, live harness PID, and ONLINE perch, but never opens an active rc/controller stream or checks same-stream post-refresh input/output. This is the central missing acceptance seam."
    },
    {
      "path": "crates/spt-daemon/tests/attach.rs",
      "description": "`attach_survives_target_brain_restart_exactly_once` at 772-918 manually drops life 1 and explicitly calls `serve_attach` for life 2 from sequence 0. It validates retained-ring replay and journal dedup but bypasses production dispatcher classification/claim and does not age the Request beyond the ring floor."
    },
    {
      "path": "crates/spt-daemon/tests/dispatch.rs",
      "description": "Production dispatcher tests cover normal families and `dispatcher_survives_an_unknown_stream` at 1092-1155, which proves one bad stream does not kill processing of a later stream. They do not require retry of the bad stream, restart a dispatcher over existing attach streams, or test opener eviction."
    },
    {
      "path": "crates/spt/tests/brain_survive.rs",
      "description": "Process-level PTY + QUIC survival across swapped brain binaries. It proves the broker resource plane survives but not active controller forwarding through a successor production dispatcher."
    },
    {
      "path": "crates/spt/tests/resume_no_control_steal_e2e.rs",
      "description": "Proves brain respawn does not seize direct PTY controller connections and promotion still succeeds. It does not exercise broker-held rc network streams or dispatcher reclassification."
    },
    {
      "path": "crates/spt-daemon/tests/brain_resume_conn_deadlock.rs",
      "description": "Guards the former undrained session-subscription self-deadlock and justifies cursor-only main-brain resume. It is orthogonal to per-stream dispatcher workers, which intentionally subscribe and drain."
    },
    {
      "path": "docs/KNOWN-HAZARDS.md",
      "description": "Domain invariants: §6.7 broker/brain process isolation, §7.36 subscriber/control decoupling, and §7.38 bounded physical broker writes. These support keeping PTY and network resource ownership broker-side while treating brain-owned view pumps as reconstructible control-plane state."
    },
    {
      "path": "docs/adr/0018-broker-brain-process-isolation-restoration.md",
      "description": "Architecture ruling: broker process owns PTY masters, children, sockets, NetHost/QUIC and cursor truth; brain is freely restartable and must reconstruct logic. It explicitly requires process-level survival but leaves active protocol-worker reconstruction dependent on broker stream replay."
    },
    {
      "path": "docs/DEBUG-ROLLOUT.md",
      "description": "Operational update rules: normal signed apply, brain-only vs broker-touching classification, and refusal of broker-touching updates while broker-held runtime is live. Confirms default refresh/apply should not cycle broker resources."
    }
  ],
  "architecture": "Four distinct layers must not be conflated:\n\n1. PTY data plane / resource ownership. `spt daemon run` is the long-lived broker process. `Broker` owns the PTY master/ConPTY/forkpty session, child process, `OutputLog`, input journal, NetHost, live connection table, and per-stream `StreamLog`. None of those OS PTY handles are handed to the brain. This is why terminal children and rings remain alive during refresh.\n\n2. Operator/viewer connection. The `spt rc` process has its own local broker IPC `Brain` connection. It opens an operator-side network stream row and subscribes to output records. Its physical broker IPC connection and broker-held stream survive target brain refresh. It only reconnects on broker IPC EOF or network stream EOF; target pump death alone produces neither.\n\n3. Target-side controller/viewer bridge. The brain-owned inbound dispatcher enumerates peer-initiated stream rows. Each row gets a worker with a fresh broker IPC connection. `serve_attach` subscribes that peer stream, interprets the replayed Request, and subscribes the PTY OutputLog as Control/Viewer; a second connection forwards PTY output onto the operator stream. This worker is the actual controller bridge. Its physical broker connection is logged as `role=brain`, then gains `stream-subscriber` and controller/session attribution; `role=brain` is the protocol handshake class for all IPC clients, not proof that it is the singleton heartbeat brain.\n\n4. Daemon brain and broker lifecycle. Refresh/in-place apply sends `KIND_BRAIN_RESTART` over a transient CLI broker connection. The broker acks acceptance, its supervisor hard-kills the whole brain process, and respawns it. All dispatcher workers die with that process. Broker connection cleanup removes their logical subscriber/controller roles but not PTYs or streams. The new brain's `resume_session_cursors` restores only main-brain cursor knowledge without subscribing. Independently, first-heartbeat `spawn_net_consumers` starts a new dispatcher that is supposed to reconstruct active stream workers from broker truth.\n\nThe reconstruction boundary is not currently durable enough. Broker stream truth records direction, IDs, connection, origin, sequence/ring, and finished state, but not protocol family or attach opener metadata. The successor therefore depends on sequence-0 payload replay. Because that payload can be cap-evicted and claim failure is permanent for the generation, a resource-surviving refresh can leave an operator stream and PTY alive with no target bridge—precisely the detach/reattach-heals shape. The safe architectural direction is to make protocol/attach claim identity broker-durable (or otherwise recoverable without ancient payload replay) and make dispatcher claims transactional/retryable: in-flight only while a worker is starting, committed while a worker is serving, released/retried on recoverable failure. Readiness should also not imply controller continuity until restartable protocol workers have been reconciled, or the CLI must explicitly say it acknowledged rather than completed the refresh."
}