{
  "summary": "The supervised daemon’s cursor-only `BRAIN_RESUMED` path is not, by itself, the missing reattach: after a brain refresh the new brain starts a fresh inbound dispatcher, whose empty per-generation `claimed` set enumerates all broker-held peer streams and attempts to re-serve each attach. The strongest code-grounded causal candidate is instead stale finished attach-stream replay: broker `NetShared.streams` retains finished rows indefinitely, the new dispatcher reclaims them alongside the live stream, and a stale same-origin `Control` Request can silently replace the live controller before its replayed EOF clears the slot. The original live worker gets no `Displaced`, remains connected, and receives no more PTY output; the existing `spt rc` therefore looks frozen. Intentional detach plus a new `spt rc` creates a fresh stream that takes the now-free controller slot after the stale replay storm. A second, independently code-grounded failure is opener eviction: the ordinary stream ring retains only 4096 transport chunks, so a long-lived active stream can lose its seq-0 Request; restart classification from cursor 0 then fails with a forward stream gap and the one-shot claim is never retried. Either mechanism naturally acts per stream, not globally; the reported 4-of-5 split is therefore plausible [INFERENCE], depending on stale-stream history, retained opener, and concurrent reclaim ordering.",
  "files": [
    {
      "path": "crates/spt/src/rc.rs",
      "description": "`run_attach_inner`, `establish_attach`, `attach_viewport`, and `pump` define the operator side. Local control uses a broker loopback connection; remote control uses QUIC, but both converge on `request_attach_endpoint` and `net_stream_subscribe`. `pump` sends Input/Resize and renders AttachRecord Output. Ctrl-B D finishes the operator stream. Only `BrokerGone`/`Severed` trigger automatic full re-establishment; a previously-active but subsequently silent stream emits neither. `FIRST_EVENT_GRACE` only fires when no event has ever arrived, so an established viewport that freezes after earlier output can wait indefinitely."
    },
    {
      "path": "crates/spt-daemon/src/attach.rs",
      "description": "`serve_attach` is the target-side worker. Its primary `Brain` connection first becomes the attach stream subscriber and, after replaying `AttachRecord::Request`, becomes the PTY controller/viewer via `attach_as`; therefore one physical broker connection can be labeled both `stream-subscriber stream=X` and `controller session=Y`. A second `wire` Brain connection forwards PTY Output/Size/Exit onto the network stream. Replayed EOF calls `detach_session`, explicitly releasing the worker’s current PTY role. `read_controller_event_resuming` handles PTY-output gaps but cannot notice that another same-origin worker silently superseded its controller slot."
    },
    {
      "path": "crates/spt-daemon/src/dispatch.rs",
      "description": "`run_dispatch_loop` owns a process-local `HashSet<u64> claimed`, initialized empty on every brain generation. It enumerates every `NetStreamInfo` with `!initiated_locally && next_seq > 0`, inserts the stream id before starting a worker, and never retries that id during the generation even if the worker fails. `worker` calls `peek_first_line(stream_id, 0)`, opens a fresh Brain connection, classifies the replayed opener, and invokes `serve_attach(..., net_from_seq=0)`. This refutes the theory that a new brain intentionally ignores all active remote-control streams, but exposes stale-row replay and permanent one-shot failure."
    },
    {
      "path": "crates/spt-daemon/src/nethost.rs",
      "description": "`NetShared` is broker-resident and owns `conns`, `streams`, monotonic connection/stream ids, and each `StreamEntry`. `StreamLog` has one replaceable subscriber, a bounded 4096-transport-chunk ordinary ring, and `finished`. `finish` sets `finished=true` and sends EOF but does not remove the `StreamEntry`; no stream-table removal path exists. `StreamLog::attach` replaces the subscriber and replays retained seqs plus EOF. `detach_if` uses `Arc::ptr_eq`, so late cleanup cannot detach a newer subscriber. These are the central persistent data-plane facts."
    },
    {
      "path": "crates/spt-daemon/src/broker.rs",
      "description": "`OutputLog::resolve_subscribe` implements controller/viewer ownership. A `Control` subscribe with the same `by` identity silently calls `become_controller`, replacing the incumbent with no `Displaced`; only a different identity’s explicit `Take` is loud. `become_controller` bumps `controller_epoch`, replaces the sink, and spawns a new writer. `detach_if` clears only the role currently owned by that physical connection. `handle_conn` tracks session and net-stream subscriptions and runs pointer-conditional cleanup on EOF. `dispatch_spawn` owns real per-session PTY drain/exit tasks. The writer epoch protects against superseded writer bytes, but does not prevent a stale replay worker from legitimately becoming the newest controller and then explicitly detaching itself."
    },
    {
      "path": "crates/spt-daemon/src/conn.rs",
      "description": "`BrokerConn` is the bounded, cancelable, poison-on-failure physical broker connection. `CONN_ID_SEQ`, role attribution, `CONN_LIFECYCLE`, `CONN_WRITE_POISONED`, and `CONN_WRITE_RETIRED` provide the observability needed to distinguish a logical stale-stream takeover from a half-open/black-holed IPC carrier. Its watchdog aborts a write that exceeds the production brain-write deadline and physically retires the connection."
    },
    {
      "path": "crates/spt-daemon/src/brain.rs",
      "description": "`resume_session_cursors` seeds PTY-session cursors without subscribing the daemon heartbeat connection; `resume_sessions` is the dangerous subscribing variant reserved for draining callers. `net_stream_subscribe` seeds an exact stream cursor, and `read_event` rejects a forward `NetStreamData` gap. Consequently, if seq 0 has fallen out of a stream ring, `peek_first_line(...,0)` receives the retained floor and errors `stream X gap: got seq N want 0` rather than classifying the stream."
    },
    {
      "path": "crates/spt-daemon/src/brainproc.rs",
      "description": "`run_brain` uses `resume_session_cursors`, writes readiness, and after the first successful 500 ms `net_status` starts `spawn_net_consumers`, including a fresh dispatcher. Thus `BRAIN_RESUMED` refers to broker PTY-session cursors, not restoration of attach protocol workers. The stable broker process remains the owner of PTYs, NetHost connections, and stream rows while these brain workers die and respawn."
    },
    {
      "path": "crates/spt-term/src/pty.rs",
      "description": "`PtySession` owns one real child PTY’s master reader, serialized master writer, child, killer, and single-take reader. This confirms that terminal compute and the PTY data plane remain in the broker process; an `spt rc` viewport or daemon brain worker owns no PTY handle."
    },
    {
      "path": "crates/spt/tests/daemon_refresh_e2e.rs",
      "description": "Production CLI refresh coverage proves brain PID/generation changes while the broker PID, harness PID, and ONLINE perch survive. It does not keep an `spt rc` process attached, create stale finished attach streams, or assert pre/post-refresh output progress on the same viewport, leaving the reported interaction boundary uncovered."
    },
    {
      "path": "crates/spt-daemon/tests/attach.rs",
      "description": "Relevant tests include `attach_survives_target_brain_restart_exactly_once`, `controller_viewer_matrix_and_loud_take`, `same_origin_re_subscribe_does_not_displace`, `controller_restart_with_viewer_no_displace_and_viewer_survives`, `wedged_viewer_does_not_stall_controller`, and detach/stamp cases. The restart test manually re-serves one known stream before ring rollover; it does not restart the production dispatcher over a table containing both stale finished and active streams. The same-origin test directly confirms the silent replacement behavior needed by the primary hypothesis."
    },
    {
      "path": "crates/spt/src/rc.rs#tests",
      "description": "`attach_viewport_reconnects_across_a_broker_bounce` validates frontend reconnection when the entire broker dies and the rc carrier gets EOF. It creates a replacement broker/session, unlike brain-only refresh where the broker stream remains open. `reconnect_class_only_severed_transport_redrives` explicitly makes Detached, Displaced, Stalled, and NoLiveSession final. This explains why a silent logical controller loss does not self-heal in the existing rc process."
    },
    {
      "path": "crates/spt-daemon/tests/brain_resume_conn_deadlock.rs",
      "description": "`daemon_resume_leaves_zero_brain_subscribers` structurally proves that the supervised heartbeat connection uses cursor-only PTY resume and attaches zero session viewers; a separate hazard arm proves the old subscribing seam is observable. This test addresses the earlier SharedSend self-deadlock, not re-serving broker-held attach streams."
    },
    {
      "path": "crates/spt/tests/resume_no_control_steal_e2e.rs",
      "description": "Process-level coverage proves daemon brain respawn does not bulk-steal active PTY controller slots through `resume_sessions`. It does not exercise per-stream dispatcher reclaim and therefore does not rule out stale attach workers taking the same controllers."
    },
    {
      "path": "crates/spt-daemon/tests/brain_decouple.rs",
      "description": "`suspended_brain_controller_is_stall_evicted_take_completes_viewer_ticks` and the Windows physical-retirement leg reproduce a non-reading controller and assert bounded logical/physical release plus fresh attach. They cover black-holed physical connections, not stale finished stream replay after a normal brain generation rollover."
    },
    {
      "path": "crates/spt-daemon/tests/conn_blackhole_lifecycle.rs",
      "description": "Five-invariant Windows harness verifies unrelated-session progress, physical connection cancellation, writer/handler exit, fresh viewer attach, and no retained lock/task after a black-holed controller. It is the best diagnostic comparator for hypothesis 4."
    },
    {
      "path": "crates/spt/tests/attach_wedge_e2e.rs",
      "description": "Real-daemon coverage kills an rc pump and PTY child, then proves a different fresh endpoint/attach still works and daemon stop remains bounded. It does not assert continuity of the original active rc across a brain refresh."
    },
    {
      "path": "crates/spt-daemon/tests/control_stamp_lifetime.rs",
      "description": "Verifies controller/viewer stamps clear on PTY reap and converge on broker truth. Useful for observing the primary mechanism: after a stale replay worker detaches, broker truth should show no current controller even though the original rc stream and serve loop remain open."
    },
    {
      "path": "docs/KNOWN-HAZARDS.md",
      "description": "Binding related hazards include 6.7 broker/brain process isolation; 7.2 restart idempotency; 7.7 viewer isolation; 7.11 dropped attach pump; 7.12 off-drain controller writes; 7.19 no-ack input flood; and 7.21 one controller writer/epoch ordering. None currently states the stale-finished-stream replay invariant."
    },
    {
      "path": "docs/adr/0018-broker-brain-process-isolation-restoration.md",
      "description": "Defines the stable broker as PTY/socket owner beneath a freely restartable brain. This is why refresh/update can preserve the terminal data plane while losing and reconstructing per-stream controller workers."
    },
    {
      "path": "docs/adr/0031-server-side-screen-grid-render-repaint.md",
      "description": "Defines cold PTY attach as a synthesized current-screen repaint rather than raw output-ring replay. It affects PTY OutputLog replay after control is reacquired, but does not preserve or classify the separate attach protocol’s seq-0 Request."
    },
    {
      "path": "docs/UPDATE-WEDGE-2-ROUND3-CODEREAD.md",
      "description": "Historical RCA for the old undrained brain-connection SharedSend self-deadlock. The current cursor-only daemon resume fixes that mechanism; the present investigation distinguishes it from production attach-worker reconstruction."
    },
    {
      "path": "docs/DEBUG-ROLLOUT.md",
      "description": "Reiterates that a broker-touching candidate is refused while broker-held endpoints are live; safe field work should use brain-only refresh/update boundaries or an isolated test home, never force a broker-breaking apply over live PTYs."
    }
  ],
  "architecture": "Ownership and call chain\n\n1. PTY data plane: `spt_term::PtySession` owns the real master reader/writer and child. `Broker::dispatch_spawn` registers a `HostedSession`, starts one PTY drain and one exit waiter, and stores `OutputLog`. This process and state survive a brain-only refresh/update.\n2. Operator/viewer connection: `spt rc` creates its own pump-mode `Brain`. For a local endpoint `establish_attach` resolves the broker session, mints a loopback connection and paired streams; remotely it dials QUIC. It writes `AttachRecord::Request`, subscribes its locally-initiated stream, forwards terminal Input/Resize, and renders Output records.\n3. Target controller/viewer connection: the daemon dispatcher claims the peer-initiated paired stream. `worker` peeks the opener, then `serve_attach` uses a fresh primary Brain connection to subscribe that stream and to subscribe the PTY session as Controller/Viewer. That one physical broker IPC connection therefore owns two logical roles. A separate `wire` Brain connection forwards session output to the stream, avoiding mutual IPC backpressure.\n4. Daemon brain lifecycle: refresh/update ends all target-side dispatcher threads and their broker IPC connections, but not the broker’s PTY sessions, NetHost connections, or `StreamEntry` rows. The replacement brain’s heartbeat starts a fresh dispatcher. Its empty local `claimed` set attempts to re-serve all peer-initiated stream rows from cursor 0.\n5. Broker lifecycle: physical Brain-IPC disconnect cleanup is pointer-conditional; old cleanup cannot unseat a genuinely newer controller/subscriber. Controller writers also carry an epoch so a superseded writer cannot continue an initial replay. These protections handle late physical cleanup and byte ordering, but deliberately permit a new same-origin worker to silently retake the controller.\n\nPrimary causal candidate — stale finished-stream takeover\n\n`StreamLog::finish` retains the stream row forever. On brain replacement, all historical finished peer attach streams are indistinguishable from active rows to `run_dispatch_loop`: each has `initiated_locally=false` and `next_seq>0`. Their buffered Request and EOF are replayed. For a stale control stream with the same stable origin identity as the current local operator, `resolve_subscribe` takes the `same_identity` branch and calls `become_controller`; this silently replaces the newly restored active worker and intentionally emits no `Displaced`. The stale worker then reads its buffered EOF and calls `detach_session`; because it is now the current controller, the unsubscribe clears the slot. The legitimate active worker remains alive, retains `role=Controller` in its local state, and its operator stream remains unfinished, but the broker no longer routes PTY output to it. No EOF, error, or kick reaches the existing rc frontend. Since that rc already rendered earlier output, its first-event stall guard is permanently disarmed. A fresh rc creates a new stream and a new worker that takes the free slot, matching the observed recovery.\n\nWhy only 4 of 5 [INFERENCE]: stream reclaim is per row and concurrent. Endpoints differ in historical finished-stream count, whether old Request records remain retained, and which worker performs the final same-origin take. A session with no usable stale stream, or whose active worker attaches after all stale EOF workers finish, remains live; one with a stale worker attaching last is left controllerless. This mechanism predicts correlation with attach/detach history and nondeterministic HashMap/thread ordering, not with PTY survival.\n\nAdjacent causal candidate — opener eviction plus one-shot claim\n\nOrdinary `StreamLog` retains 4096 transport read chunks, not logical Attach records. A sufficiently old/input-active control stream can evict its seq-0 Request. The fresh dispatcher still calls `peek_first_line(stream,0)`. `Brain::net_stream_subscribe` seeds cursor 0, the broker replays only from its surviving ring floor, and `Brain::read_event` rejects the first seq as a forward gap. `worker` returns `Failed(\"stream X gap: got seq N want 0\")`. Because `claimed.insert(stream_id)` happened before spawning the worker, that stream is never retried in the generation. Detach/reattach creates a new stream with a retained opener and therefore succeeds. This predicts failure by accumulated operator-side transport chunks even without historical detached streams.\n\nFive falsifiable hypotheses, ordered\n\nH1 — stale finished attach rows replay and logically steal/clear control. Confirm with logs showing, after `BRAIN_NET_CONSUMERS_UP`, multiple `stream-sub-attach` records for old finished stream ids, then `controller-replaced` from the active controller to a stale worker, followed by `session-detach was_controller=true`; the final session snapshot has no controller while its PTY output high-water advances and the active operator stream is unfinished. Falsify if finished stream rows are absent or never process a Request/Control after refresh.\n\nH2 — the active attach opener rolled out of the 4096-chunk stream ring. Confirm `NetStreamInfo.next_seq > 4096` and dispatcher `DISPATCH:<id>:Failed(\"stream <id> gap: got seq <floor> want 0\")`, with no subsequent worker attempt for that id. Falsify if seq 0/Request is retained and classification/serve completes.\n\nH3 — another transient per-stream worker failure becomes permanent because claim is one-shot. Any temporary broker-connect, access, session-resolution, or replay error after `claimed.insert` abandons the stream until the next brain generation. Confirm one `DISPATCH:<id>:Failed(...)`, no retry despite the row remaining active, and immediate success when the same operation is manually retried/new stream opened. Falsify if no worker failure occurs or a later retry is observed.\n\nH4 — a half-open old physical Brain connection black-holes controller writes. This is lower probability in the current tree because `BrokerConn` has a bounded watchdog, physical poison/retire, stall eviction, pointer-conditional detach, and same-origin takeover. Confirm with `CONN_WRITE_POISONED`, writer deadline, delayed `transport-close`, and controller slot held by that same old conn until eviction. Falsify with prompt old-conn close plus the stale logical replace/detach sequence. This mechanism is already substantially covered by `brain_decouple` and `conn_blackhole_lifecycle`.\n\nH5 — the target controller falls irrecoverably behind the PTY OutputLog during the brain gap. Confirm the explicit `ControllerIrrecoverablyBehind` truncation path and a finished target send side; the operator should then see EOF and enter its severed reconnect class, so a truly silent indefinitely-open freeze would falsify it. This is less consistent with the reported symptom than H1/H2.\n\nSafe deterministic feedback loop proposal\n\nA. Minimal deterministic stale-take test, real broker/NetHost/PTY but no machine daemon: use a temp `SPT_HOME`, loopback-only NetHost, and a ticker/echo PTY. Create a finished same-origin attach stream containing Request+EOF and keep its broker row. Establish a separate active same-origin controller stream, wait until its operator sees a marker, and barrier on broker controller ownership. Then deliberately re-serve the finished stream after the active barrier. Assert, before any teardown: (1) the stale worker resolves Controller without Displaced; (2) its EOF path returns Detached; (3) broker controller truth becomes empty; (4) the active operator stream remains unfinished/open; (5) PTY/ring high-water continues; (6) the active operator receives no post-event marker within a bounded window; and (7) a newly-created attach stream receives that marker and becomes controller. This ordering makes the red state deterministic rather than relying on thread scheduling.\n\nB. Deterministic opener-eviction arm: give a test NetHost a tiny ordinary stream-ring cap or send more than the real 4096 independently-read chunks after the Request. Restart only the dispatcher generation and assert the first replay is a forward gap, the worker fails once, the row remains, and no retry occurs. The desired future contract should make the attach opener/classification durable independently of the evicting payload ring or otherwise expose an explicit retained floor/retry outcome; the test should assert behavior, not source text.\n\nC. Production-topology capstone: extend `daemon_refresh_e2e.rs` under its isolated temp home. Bring up five mock ticker endpoints. For four, intentionally create and cleanly finish one or more preliminary rc streams; leave the fifth without stale history. Start five persistent `spt rc` children with piped stdin held open, prove each sees a pre-refresh tick, run the real `spt daemon refresh` with a deadline, verify the broker and PTY PIDs remain unchanged, then require every same rc process to see a post-generation tick and round-trip a unique input marker without detach/reattach. On failure, capture brain stderr plus stream table (`stream_id`, `finished`, `initiated_locally`, `next_seq`) and session controller identity/connection id. Reap only test-scoped children. Do not invoke production `spt update`, mutate real endpoints, or use a shared `SPT_HOME`.\n\nD. Diagnostics that discriminate without guesswork: correlate `BRAIN_UP`/`BRAIN_NET_CONSUMERS_UP`, `DISPATCH:<stream>`, `CONN_LIFECYCLE` (`stream-sub-attach`, `controller-attach`, `controller-replaced`, `session-detach`, `transport-close`), `CONN_WRITE_POISONED`/`RETIRED`, stream `finished/next_seq`, session controller truth, and PTY `next_seq`. H1 yields clean logical replace/detach without a poisoned conn; H2 yields a stream cursor gap and no controller attach; H4 yields deadline poison/physical retirement. The existing `BRAIN_RESUMED` line is not evidence that attach streams were or were not re-served—it only reports PTY cursor reconstruction."
}