{
  "summary": "## Release identity and intended v0.34 contract\n\n- `v0.33.0` is tag object/ref `21a46d5…`, with release commit `42d06a8…` (confirmed independently by the regression-archeology peer). The v0.34 W1 implementation then landed as:\n  - `ad7fde8acb97b5c839316121d341375b077092df` — retirement, durable opener, retryable claims;\n  - `2a110eed6f97d3dab96ed301d1b6d1df9a2cc002` — finished Attach is terminal/unserved, while request/reply protocols whose read side is FIN remain servable;\n  - `84253572cd5e0cef57201ba671a8c201c6dbbdd8` — retirement is eligibility/visibility only, never physical stream teardown;\n  - `571d6a55e9949ce1b96aef1ba00f5e007a0a9521` — ADR-0038 records that ruling.\n- The release branch commit is `eb9fd8643921930ce21cc7ef0751a27360fc14bc` (`release: v0.34.0 (counter 61) — REDISPATCH-TRUTH W1`); the published v0.34 tag target is `b7b074c2509c1015fe341185662b4622d661f274`.\n- ADR-0038 and requirements `REQ-HAZARD-REDISPATCH-CONTROL-STEAL`, `REQ-REDISPATCH-FINISHED-RETIRE`, `REQ-STREAM-OPENER-DURABLE`, and `REQ-DISPATCH-CLAIM-RETRY` define the intended behavior: a fresh brain must not replay terminal historical Attach streams; an active stream remains classifiable after its 4,096-chunk ring rolls; transient worker setup failures retry; legitimate same-operator active Attach reconstruction remains allowed.\n- `CHANGELOG.md` promises that `spt update`, `spt update --restart`, and `spt daemon refresh` no longer disturb an attached remote session. That promise is accurate for the intended brain-only redispatch fix, but overstates `--restart`: `cli.rs` calls its final `Finish` leg “LETHAL,” explicitly saying it performs a full daemon restart and kills an invoking hosted PTY. `rc.rs` masks an expected broker sever with a visible, bounded 30-second reconnect loop. A silent frozen viewport is therefore not the intended `--restart` UX; a 30-second countdown banner is.\n- The v0.34 release also shipped with malformed release-note metadata: the v0.34 heading consumed the preceding `## [0.33.0]` heading, so the workflow’s section extractor could publish both releases as one body. Commit `d0c9f33bc577080cdd922c2b1aa99bb5932dc7e7` restored the heading after the tag. `release.yml` only checks that the requested section is nonempty; it does not validate a contiguous version ladder. The runbook now records this exact v0.34 incident and recommends a heading-ladder check.\n\n## What changed that can amplify a 20–30 second stall\n\nThe v0.34 W1 change adds no 20–30 second timer. Its dispatcher cadence is 100 ms; retries are at 500 ms and 1 second, with at most three attempts. The exact 15-second physical write deadline and 30-second RC reconnect window both predate the v0.34 delta.\n\nThe field trace supplied by Main materially changes the diagnosis:\n\n- 33 `CONN_WRITE_POISONED` incidents are attributed to `stream-subscriber` connections.\n- Every poisoned write reaches the configured bound at approximately 15,000 ms.\n- The corresponding `DISPATCH` failure is logged 15.1–62 seconds after write start; recent examples are 17.6, 25.4, 29.1, 30.1, 31.1, 36.6, 59.4, and 61.6 seconds.\n- The cold generation has 4,361 `stream-sub-attach` records and 584 10-second `PUMP_PEER_FAIL` events.\n- Stream 4834’s write began 37.354 seconds after the recent webbie controller attach and poisoned at +52.366 seconds.\n\nThis is an exact source match for the first 15 seconds and a plausible v0.34 amplification for the remainder:\n\n1. `run_dispatch_loop` enumerates every eligible broker stream every 100 ms and immediately calls `thread::spawn` once per newly claimed row. There is no semaphore, worker pool, queue bound, batch cap, or backpressure. A 4,361-row cold enumeration can therefore create thousands of simultaneous OS threads/connections.\n2. v0.34’s durable classification performs a fresh opener RPC connection per row. `first_line` falls back to the old subscribe/replay path on **any** opener RPC error, not only an old broker’s unknown-verb response. The family worker then opens another broker connection and subscribes to the actual stream.\n3. `serve_attach` sends `net-stream-subscribe`, then—when it has the pinned Request—immediately sends the PTY-session `attach_as` command before it begins its event-read loop. On the broker, `StreamLog::attach` installs the subscriber and synchronously writes every retained chunk while holding that stream’s `Mutex<StreamLog>`. Under a thousands-thread cold-start burst, the client worker can be descheduled or otherwise fail to drain the named pipe promptly; the broker’s Windows `WriteFileEx` fills/blocks.\n4. `BrokerConn` enforces the 15-second absolute write deadline. `CONN_WRITE_POISONED` is emitted when the watchdog marks the operation timed out and requests `CancelIoEx`; it does **not** mean the write completed or the physical connection has severed.\n5. After logging poison, the watchdog waits in 100 ms slices while the original `write_frame` remains in flight, repeatedly calling `CancelIoEx`. It invokes `DisconnectNamedPipe` only after `write_frame` reports completion. Meanwhile, the dispatcher’s ordinary `BrainConn::Whole` uses a direct blocking `read_frame` with no read deadline. The worker cannot return `DispatchOutcome::Failed`, and thus cannot emit the `DISPATCH` failure or release its claim, until the server connection is actually severed/EOFs.\n6. [INFERENCE] The field’s variable 0.1–47 second post-poison tail is therefore explained by delayed Windows cancellation completion plus severe scheduler pressure from the unbounded worker burst. It is not a second intentional timeout. This is falsifiable by adding lifecycle stamps for `cancel-issued`, `write-returned/inflight-cleared`, `sever`, client EOF, and worker outcome; the long interval should occur between poison/cancel and inflight-clear/sever.\n7. v0.33’s one-shot claim abandoned a stream after this failure. v0.34 changes every `Failed` result into a retry after 500 ms, then 1 second, up to three total attempts. Each attempt can install a fresh subscriber and synchronously replay the ring again. Thus v0.34 can turn one permanent loss into recurrent 15-second transport wedges: one deadline plus cancellation lag naturally gives 20–30 seconds; two near-minimal poisoned attempts plus the 500 ms backoff gives approximately 30.5 seconds; three attempts can approach or exceed 45 seconds. This is the strongest source-grounded v0.33→v0.34 regression relation.\n8. Retirement does not break the loop on failure: only `DispatchOutcome::Served` triggers `net_stream_retire`. A poisoned worker returns `Failed`, so its row remains eligible for the new retry policy. Across future brain generations, the per-process claim map is empty again.\n\n## Causal tie to PTY/RC stalls, and its limits\n\n- [INFERENCE, strong if stream identity correlates] A poisoned target-side Attach subscriber directly stalls the wire→session leg: operator input/resize records cannot reach the PTY worker while synchronous replay owns that stream log and the worker connection is wedged. The terminal therefore feels frozen even though the ConPTY child and broker-held PTY output log are healthy. Stream 4834 should be decoded/classified from its pinned opener and matched to the webbie Attach; if it is that active controller’s Attach stream, the +37.354s start/+52.366s poison is direct incident evidence, not merely timing similarity.\n- The stream lock is per QUIC stream, not the global PTY/session lock. The immediate mechanism does **not** by itself stall the ConPTY child, the broker’s session `OutputLog`, or every broker IPC connection. It predicts an affected remote attach/input stream, with unrelated PTYs and fresh control-plane probes remaining live.\n- [INFERENCE] Thousands of simultaneous dispatcher workers/connections can add process-wide scheduler and IPC pressure, so several of approximately five active PTYs may appear to stall together. That wider effect must be measured; the lock topology alone does not prove a broker-wide stall.\n- The separate 30-second `rc` reconnect path begins only after the viewport sees a reconnectable EOF/sever and should paint a countdown banner. During the poison→sever delay, `rc` sees no EOF and can remain silently frozen. A trace showing a silent freeze first and a countdown only after `sever` would distinguish these phases.\n- A 10-second `PUMP_PEER_FAIL` is peer-pump failure timing, not sufficient evidence of a PTY stall. The reported 584 events indicate substantial concurrent network churn/load, but the exact causal seam for the 15-second incidents is the attributed stream-subscriber writer.\n- A finished Attach should be classified from its pinned opener, stopped by `finished_row_is_terminal`, and retired **before** `serve_attach` subscribes. Therefore poison stream IDs that all decode to already-finished Attach rows would falsify the intended v0.34 gate or demonstrate stale/mixed broker semantics. Conversely, active Attach or other request/reply families are expected to subscribe and remain exposed.\n\n## Compatibility relation\n\n- The W1 wire changes are additive, but a v0.34 brain on an older broker cannot use durable opener or retirement verbs. `first_line` falls back to ring replay; retirement is best-effort and silently ignored until the broker cycles. The existing N-1 test checks only handshake/heartbeat steady state and does not create stream rows or exercise either new verb.\n- This mixed-image gap contradicts the broad changelog promise for ordinary brain-only apply: the new brain can run, but the old broker cannot fully deliver v0.34 lifecycle truth. It is not the explanation for the supplied field trace if broker and brain are both confirmed v0.34, but it remains a release-contract hole.\n- Adding broker verbs does not change the declared IPC version/resource ABI, so the update classifier can label the release `BrainOnly`; the project already exposes `broker_image`/`broker_stale` precisely because installed brain and running broker may differ. A broker refresh/full restart activates the new verbs but invokes the separate reconnect/restart tradeoff.\n\n## Missing coverage that allowed this regression class\n\n1. `redispatch.rs` D1/D1b/D2/D3 tests correctness for a handful of streams. D3 injects one failure **before setup**, so it proves claim-state arithmetic but never exercises a failed/poisoned subscriber, synchronous replay, cancellation lag, or repeated reattachment.\n2. There is no dispatcher concurrency/burst test with thousands of retained rows and five or more simultaneous active remote Attach/PTYS. Existing `brain_resume_conn_deadlock.rs` uses six flood PTYs and checks broker heartbeat liveness, not dispatcher cold rediscovery, RC viewport gaps, input latency, or stream-subscriber poison.\n3. `conn_blackhole_lifecycle.rs` and `brain_decouple.rs` cover controller/session subscribers, not `NetHost::StreamLog` subscribers driven through `run_dispatch_loop`. They shrink the production 15-second bound to 400 ms and assert eventual retirement/probe health, not the interval from `CONN_WRITE_POISONED` to `write_frame` completion/sever/client EOF.\n4. `daemon_refresh_e2e.rs` proves a local hosted child PID, broker PID, and basic functionality survive refresh. It does not keep five real cross-node RC Attach streams under continuous input/output and assert maximum visible gap through a fresh dispatcher generation.\n5. `n1_pairing.rs` proves new-brain/old-broker handshake and heartbeat only; it has zero stream rows and no opener/retire call. It cannot detect the mixed-image v0.34 semantic gap.\n6. No release check validates consecutive changelog headings, which is why the v0.33 heading disappeared in the tagged v0.34 notes.\n\n## Focused fix and test design\n\nPriority fix design:\n\n1. Bound redispatch concurrency at the source. Replace per-row unbounded `thread::spawn` with a small fixed worker pool/semaphore and a bounded pending queue; batch enumeration so one cold 4,361-row table cannot instantiate 4,361 connections/threads in one sweep. Preserve one claim state per stream and never hold the claims mutex during worker I/O.\n2. Remove synchronous socket I/O from `StreamLog::attach` while preserving replay-before-live ordering. The boring safe pattern is a per-subscriber dedicated writer/queue, analogous to the existing viewer writer: install the subscriber and enqueue the retained replay under the stream lock, release the lock, and let the writer perform framed I/O. Live append only enqueues nonblocking. Bound the queue and retain cursor/ring fallback so overload drops to resume semantics rather than blocking the QUIC read pump.\n3. Make retry classification narrower. A pre-setup transient connect/opener race may retry; a deadline-poisoned subscriber/replay under system overload should not immediately repeat the same expensive replay blindly. At minimum, use a much longer/circuit-broken backoff for `TimedOut` and cap global retries. Do not treat every I/O failure identically.\n4. Tighten opener fallback: fall back to ring peek only for the explicit old-broker unknown-verb response. A timeout/poison/transport error should return `Failed` under the bounded scheduler, not open another subscription/replay connection.\n5. Instrument the Windows poison lifecycle before changing teardown safety: log CancelIoEx return/GetLastError, inflight-clear, `DisconnectNamedPipe`, handler cleanup, client EOF, dispatch family/attempt, and active/pending worker gauges. If cancel-to-completion remains long, revise the Windows abort primitive so peer read closure is prompt without releasing a frame buffer while its overlapped write is still live; the present code intentionally waits for that buffer-release handshake, so immediate sever must be designed, not guessed.\n6. Correct the release contract: separate brain-only refresh/update survival from `--restart`’s lethal full cycle and visible RC reconnect; either require broker feature activation for W1 or state the old-broker degradation explicitly.\n\nFocused regression tests:\n\n- Windows integration test: create at least five active remote Attach sessions plus thousands of retained stale/nonterminal streams, start a fresh production dispatcher, drive unique input and autonomous output markers continuously, and assert a bounded maximum marker gap, no `CONN_WRITE_POISONED`, bounded worker/handler counts, and independent `sessions`/`net-status` probe latency.\n- Poison lifecycle test through **NetHost stream subscription**, not session controller subscription: black-hole a stream-subscriber connection, exercise real dispatcher retry, and assert `poison → inflight-clear → sever → client EOF → DispatchOutcome` within a tight post-deadline bound. Keep a shortened-deadline structural CI variant and add a Windows release/HITL run using the real 15-second bound.\n- Retry test: inject failure after a subscriber is installed or after its replay blocks. Assert one stream cannot produce repeated 15-second wedges and that retry does not exceed the global concurrency budget.\n- Refresh E2E: cycle the brain while five remote RC streams remain active; assert child PID and broker output seq continue, operator input echoes, viewports continue, no old output retypes, no control steal, and no reconnect banner for a brain-only cycle.\n- Mixed-image E2E: v0.34 brain against v0.33 broker with actual active/finished streams; explicitly assert the documented degraded behavior or require broker activation. Handshake-only is insufficient.\n- Release lint: parse all `## [x.y.z]` headings and reject missing/duplicate/non-descending neighbors before notes extraction.\n\n## Fast falsification matrix for Doyle\n\n- Broker session output sequence advances during the visible freeze, child PID unchanged, fresh broker probes answer, and affected Attach input does not echo: local ConPTY is healthy; the per-stream subscriber/redispatch path is causal.\n- Broker session output sequence itself stops while child should be producing, but IPC probes answer: investigate that session’s ConPTY/output drain, not RC transport.\n- Independent fresh `sessions`/`net-status` probes also block: broker-wide IPC/scheduler pressure is present; capture dispatcher worker/handler counts and thread scheduling.\n- `CONN_WRITE_POISONED` stream ID is the affected active Attach and poison/cancel precedes recovery: causal tie confirmed.\n- Poison IDs are unrelated protocol families/peers and affected Attach stream has uninterrupted data: timing correlation is incidental.\n- Long post-poison interval is between cancel and inflight-clear/sever: current watchdog mechanism confirmed. If sever occurs promptly but `DISPATCH` remains late, the delay is instead client scheduling/logging after EOF.",
  "files": [
    {
      "path": "CHANGELOG.md",
      "description": "v0.34 user-facing promise at lines 49–56: update/refresh, including `--restart`, should not disturb an attached remote session; current file also shows restored separate v0.33 heading."
    },
    {
      "path": ".git/logs/HEAD",
      "description": "Exact v0.34 implementation/release history: W1 commits at lines 1458–1461, release branch commit at 1465, tag-line integration at 1467, and post-tag v0.33-heading repair at 1469."
    },
    {
      "path": ".git/logs/refs/heads/release/v0.34.0",
      "description": "Release branch creation and exact counter-61 release commit `eb9fd864…`."
    },
    {
      "path": "docs/adr/0038-broker-stream-lifecycle-truth.md",
      "description": "Canonical v0.34 design: terminal retirement, pinned opener, retryable claims, unchanged active reconstruction, and eligibility-only retirement consequences."
    },
    {
      "path": "docs/REDISPATCH-TRUTH-TRIAGE.md",
      "description": "Field motivation (4/5 endpoints frozen twice), intended W1 scope, v0.33-boundary dispatch, and acceptance gates."
    },
    {
      "path": "traceable-reqs.toml",
      "description": "Requirements at lines 2433–2450 for controller-steal prevention, retirement, durable opener, and retry claims; integration evidence descriptions reveal D3 is only a pre-setup synthetic transient."
    },
    {
      "path": "crates/spt-daemon/src/dispatch.rs",
      "description": "Core regression seam: 100 ms poll, 500 ms/1 s retry with max three attempts (lines 77–230); unbounded per-row thread spawning (299–361); opener-on-new-connection with broad error fallback to replay subscription (391–452); Attach terminal gate and serve path (479 onward)."
    },
    {
      "path": "crates/spt-daemon/src/nethost.rs",
      "description": "`StreamLog::append/finish/attach` performs subscriber writes synchronously under the per-stream log lock (roughly 284–351); subscribe calls `log.lock().attach` (1676–1693); stream enumeration excludes retired rows and retirement only clears eligibility/ring (1518–1599)."
    },
    {
      "path": "crates/spt-daemon/src/conn.rs",
      "description": "Windows bounded connection writer: 15-second deadline ownership, poison/cancel, 100 ms recancel loop, and sever only after inflight write completion (roughly 179–399 and 459–604). Explains why poison log precedes dispatch failure by a variable interval."
    },
    {
      "path": "crates/spt-daemon/src/brain.rs",
      "description": "Ordinary `BrainConn::Whole` clients do direct blocking reads with no deadline; only pump Split connections use channel deadlines (199–333 and 1734–1778). Dispatcher worker therefore awaits actual sever/EOF after poison."
    },
    {
      "path": "crates/spt-daemon/src/attach.rs",
      "description": "Target-side Attach flow: net-stream subscription, second connection for output forwarding, pinned Request handling, then session attach before the event read loop (279–443). Establishes the stream-subscriber/input-leg causal boundary."
    },
    {
      "path": "crates/spt-daemon/src/broker.rs",
      "description": "Each broker client gets one bounded `BrokerConn`; `handle_conn` synchronously dispatches `KIND_NET_STREAM_SUBSCRIBE` and cleans stream subscriptions only after receive EOF (roughly 2980–3230)."
    },
    {
      "path": "crates/spt/src/cli.rs",
      "description": "Update planning explicitly defines `Finish`/`--restart` as lethal full-daemon restart that kills an invoking hosted PTY, while ordinary FetchApply is brain-only (5038–5098)."
    },
    {
      "path": "crates/spt/src/rc.rs",
      "description": "Visible RC reconnect loop and exact 30-second window with one-second pauses (1545–1665); distinguishes intended broker-bounce UX from silent pre-sever poison stall."
    },
    {
      "path": "crates/spt-daemon/tests/redispatch.rs",
      "description": "W1 D1/D1b/D2/D3 integration coverage; D3 injects a single transient before setup, so it never covers subscriber replay poison or recurring retry stalls."
    },
    {
      "path": "crates/spt-daemon/tests/brain_resume_conn_deadlock.rs",
      "description": "Six-session flood coverage checks heartbeat liveness, not cold dispatcher fan-out, RC input/output gaps, or stream-subscriber timeout behavior."
    },
    {
      "path": "crates/spt-daemon/tests/conn_blackhole_lifecycle.rs",
      "description": "Windows blackhole coverage for session controller connections with shortened deadline; not a NetHost stream subscriber through the dispatcher."
    },
    {
      "path": "crates/spt/tests/daemon_refresh_e2e.rs",
      "description": "Proves local broker/PTY child survival through a brain cycle but has no five-way remote Attach/RC continuity assertion."
    },
    {
      "path": "crates/spt/tests/n1_pairing.rs",
      "description": "New-brain/old-broker N-1 test covers handshake and heartbeat only; no stream opener, retirement, replay, or active session row."
    },
    {
      "path": ".github/workflows/release.yml",
      "description": "Extracts requested CHANGELOG section and only rejects empty notes; no version-ladder/neighbor validation, allowing the v0.34/v0.33 heading accident."
    },
    {
      "path": "docs/RELEASE-RUNBOOK.md",
      "description": "Records that v0.34 consumed the v0.33 heading and recommends a contiguous heading grep; currently a manual self-check/candidate CI lint."
    }
  ],
  "architecture": "A remote controller opens a QUIC Attach stream. The stable broker owns the QUIC stream table and ConPTY/session handles; the replaceable brain’s dispatcher enumerates peer-initiated rows and creates a worker per claim. A worker classifies from the broker-pinned opener, subscribes a local broker IPC connection to the Attach stream, then attaches that same brain connection to the broker-held PTY session; a second broker connection forwards session output back onto QUIC. In v0.34, claim failures retry up to three times and successful exchanges retire from future enumeration. The critical pressure path is `fresh dispatcher → unbounded workers/connections → synchronous StreamLog replay under per-stream lock → Windows named-pipe write blocks → 15s BrokerConn poison/cancel → delayed inflight completion/sever → blocking Whole-brain read finally EOFs → DispatchOutcome::Failed → v0.34 retry`. This is a local RC/stream transport stall when confined to one Attach stream, not a ConPTY output stall. It becomes a broader IPC/scheduler stall only through the thousands-worker fan-out. A full broker restart is a separate path: it deliberately kills hosted PTYs and `rc` uses a visible 30-second reconnect window; a brain-only refresh is supposed to preserve broker, children, streams, and viewport continuously."
}