{
  "summary": "The exact candidate bounds the spawn reply read correctly, but does not bound or cancel the broker's spawn operation. Both reported reds stop in the interval after waiter_armed and before translation_ready, whose substantive work is synchronous translation-process creation and its initial stdin write\u2014not NetHost or journal startup. The 2s fixture budget covers PTY creation plus translation startup, leaving approximately 1.108s (g1) and 0.672s (g7) after waiter_armed. Existing evidence favors accumulated Windows startup latency over a direct #302 runtime/journal deadlock, without identifying the blocked syscall.",
  "files": [
    {
      "path": ".worktrees/297-enforcement-shape/crates/spt-daemon/src/brain.rs",
      "description": "Pump construction 499-519; spawn request/reply loop 830-851; fixed deadline 1041-1043; Spawned decode 1208-1215; request writes 2555-2561; Split timeout semantics 2582-2621."
    },
    {
      "path": ".worktrees/297-enforcement-shape/crates/spt-daemon/src/broker.rs",
      "description": "Per-connection synchronous dispatch 6052-6110; dispatch_spawn 6487-6492; phase closure 8112-8128; PTY creation and subscriber setup 8361-8487; waiter_armed 8672; translation startup 4702-4765 and 8682-8694; table insertion/reply 8696-8740."
    },
    {
      "path": ".worktrees/297-enforcement-shape/crates/spt-daemon/src/translation.rs",
      "description": "Translation child Command::spawn and reader-thread creation 247-299; synchronous Init stdin mutex/write_all/flush 311-320. No ready handshake."
    },
    {
      "path": ".worktrees/297-enforcement-shape/crates/spt-daemon/src/conn.rs",
      "description": "Broker reply writes have their own gate-plus-write deadline 610-686; retirement tokens distinguish deadline poison from organic I/O failure 331-360."
    },
    {
      "path": ".worktrees/297-enforcement-shape/crates/spt-daemon/src/msg.rs",
      "description": "SpawnReq 498-556 carries neither timeout nor cancellation/request ID; Spawned 560-566 contains session ID and optional PID."
    },
    {
      "path": ".worktrees/297-enforcement-shape/crates/spt-daemon/src/frame.rs",
      "description": "Envelope 125-145 contains protocol_version, kind, payload\u2014not request/reply correlation ID."
    },
    {
      "path": ".worktrees/297-enforcement-shape/crates/spt-store/src/hostlabel.rs",
      "description": "os_hostname 24-28 uses COMPUTERNAME environment lookup on Windows 46-51; no network lookup or hostname subprocess."
    },
    {
      "path": ".worktrees/297-enforcement-shape/crates/spt-proto/src/emit.rs",
      "description": "emit_line_err 187-193 synchronously takes stderr's lock, writes the line, and ignores the result; relevant to interpreting the last printed phase."
    },
    {
      "path": ".worktrees/297-enforcement-shape/docs/KNOWN-HAZARDS.md",
      "description": "6.9 at429-432 documents output discarded while waiting for Spawned; 7.6 at501-528 specifies Split and fixed per-call deadlines; 7.8 at538-542 describes separate broker QUIC operation bounds."
    },
    {
      "path": ".worktrees/297-enforcement-shape/docs/adr/0018-broker-brain-process-isolation-restoration.md",
      "description": "Decisions 1,4,5,8 at23-30 define broker resource ownership, IPC boundary, and journal custody; process survival is independent of the requesting brain."
    },
    {
      "path": ".worktrees/297-enforcement-shape/docs/adr/0041-endpoint-lifecycle-truth.md",
      "description": "Decisions 4-6 at62-81 define stamp convergence, atomic spawn-policy distinction, and lifecycle authority; fresh creation is not an attach/controller success claim."
    },
    {
      "path": ".worktrees/297-enforcement-shape/CONTEXT.md",
      "description": "Broker/brain ownership at35-39; remote-attach controller/viewer model near403-408."
    }
  ],
  "architecture": "A Split Brain writes a Spawn envelope, then waits on its reader-thread channel with one absolute reply deadline. A dedicated broker connection handler executes spawn synchronously: gate \u2192 PTY \u2192 input/controller/drain/waiter \u2192 translation process plus Init \u2192 session table insertion \u2192 bounded broker connection write of Spawned. The request does not propagate its deadline into this operation. NetHost and EffectJournal are broker-owned but absent from this direct spawn/translation-startup call chain.",
  "report": "## Scope and evidence status\nRead-only source investigation of the named candidate worktree; no edits, processes, validation, tests, debugger attachment, or credential reads. Incident timings and fixture contract below are Main-provided evidence, not independently re-executed findings. Navigation used exact-worktree grep/read only; no root-source LSP references were substituted. Main reported that worktree LSP references are empty while root references point to different source, so reference-search completeness must not be inferred from those results.\n\n## Precise causal route\n1. `Brain::cold_start_pump` (`brain.rs:499-519`) connects/handshakes, splits the connection, and stores `Some(io_timeout)`. `split_with_reader` (`375-404`) runs blocking `read_frame` on a dedicated native thread and forwards complete frame results to an unbounded MPSC channel. `PUMP_IPC_READER: spawned` proves thread entry, not successful reading of any reply.\n2. `spawn_session` (`604-606`) calls `spawn_session_pid` (`830-851`). It sends `KIND_SPAWN` first, then computes `call_deadline()` once (`836`; definition `1041-1043`). Each loop iteration uses the same deadline. Interleaved Output/Exit/other events cannot renew it. The send itself uses synchronous `write_frame` (`2555-2561`) outside that read budget; connect and handshake also precede it. Thus this is a bounded reply-wait, not a complete RPC-duration guarantee.\n3. `read_frame_until` (`2582-2621`) on Split calls `frames.recv_timeout(deadline.saturating_duration_since(now))`. Empty-at-expiry returns `TimedOut` with the exact string `brain IPC read deadline elapsed` (`2605-2607`). Whole plus Some(deadline) instead returns Unsupported (`2586-2594`), excluding silent Whole-carrier unbounded-read behavior for this reported error. One nuance: zero remaining time still drains already-queued frames; there is no absolute rejection of frames queued at/after the boundary. Therefore the code bounds waiting, not all possible CPU time spent draining an indefinitely nonempty queue.\n4. `Broker::handle_conn` (`6052-6110`) reads the request, synchronously calls `dispatch_spawn` on that connection's handler (`6092-6095`), then records the successful session subscription. `dispatch_spawn` decodes and calls `dispatch_spawn_policy` (`6487-6492`). There is no spawn operation deadline or cancellation token in `SpawnReq` (`msg.rs:498-556`). A client read timeout does not interrupt this dispatch; handler EOF cleanup is not reached until dispatch returns and the read loop observes failure.\n5. The reported phases establish that the gate was claimed immediately and PTY creation plus birth-identity sampling completed (`broker.rs:8391-8415`). The child already exists before `pty_created`. Input writer, automatic placeholder controller, output drain and native exit-waiter are then started (`8417-8550`). Waiting for child death happens on its separate waiter thread, not the spawn handler. `controller-attach` is not Spawned and does not imply translation startup or table insertion completed.\n6. After printed `waiter_armed` (`8672`), the next substantive startup call is `build_translation` (`8682-8694`). It creates a second process through `TranslationChild::spawn` (`broker.rs:4710`, `translation.rs:247-299`): configure piped stdin/stdout and inherited stderr; call synchronous `Command::spawn` (`266`); extract pipe handles; create a stdout reader thread (`274`). No timeout wraps process creation or thread creation.\n7. `build_translation` next obtains Windows hostname via COMPUTERNAME environment lookup (`hostlabel.rs:24-28,46-51`), then synchronously sends one `ToBinary::Init` (`broker.rs:4714-4717`). `TranslationChild::send` serializes JSON, locks stdin, performs two `write_all` calls and `flush` (`translation.rs:311-320`), with no timeout. The Init error is ignored. At this initial call the child has not yet been shared with the inject worker, so competing startup callers taking this child's stdin mutex are not demonstrated; pipe/OS write delay is the relevant possible wait, not presumed existing-worker mutex contention.\n8. `build_translation` allocates worker state and starts the native inject-worker thread (`broker.rs:4718-4759`), then returns immediately. It does NOT wait for a translation ready response, first stdout command, Init acknowledgement, or inject commit. Missing/unspawnable binary logs `TRANSLATION_SPAWN_FAILED` and returns None (`4760-4765`); that failure ordinarily degrades delivery but continues spawning, rather than returning this timeout directly.\n9. `translation_ready` is emitted (`8696`), then `sessions` is locked and the HostedSession inserted (`8698-8716`); `bringups_in_flight` is locked/cleared (`8723`, helper `8070-8072`); `row_inserted` is emitted (`8728`). Spawned is serialized and sent (`8730-8738`). `send_frame` discards the result of `BrokerConn::write` (`10258-10260`). That write has its own absolute gate-plus-OS-write deadline (`conn.rs:610-686`) whose default is 15s (`broker.rs:226-241`), independently overrideable; it is not capped to this client's remaining 2s.\n\n## Last-phase precision and every remaining wait class\nThe visible `waiter_armed` line localizes an interval, not one syscall. The phase closure prints FIRST (`8113-8116`), then locks `wake_inflight` and updates its claim (`8119-8127`). Therefore the still-unfinished tail of the waiter_armed phase itself is a possible mutex wait before build_translation starts. The next phase also takes stderr's synchronous lock before writing (`emit.rs:187-193`). Absence of translation_ready is therefore consistent with: claim-lock tail; OS/process or thread creation; Init stdin write; scheduling delay anywhere in that interval; or blocking/lost phase emission. Ordinary remaining waits after successful translation are session-table and in-flight-ledger mutexes, phase claim mutexes, logging locks/I/O, and the bounded serialized broker reply write. Those later sites would normally be preceded by translation_ready or row_inserted; they do not naturally explain the supplied last-phase cutoff if the log is complete.\n\n## Budget mismatch proven versus inferred\nMain read fixture lines110-133: `attach_ipc_deadline()` defaults to exactly 2s and accepts `SPT_ATTACH_IPC_DEADLINE_MS`; its comment mentions explicit CI 30000ms for shared-box request_attach starvation. The same helper is reused by spawn_xlate_session. Baseline must remain 2s; silently setting 30s would change the admitted experiment.\n\nSource proves this 2s read budget covers both first PTY setup and second translation-process startup, because Spawned is produced only after both. Main's timings: g1 waiter_armed892ms leaves about1108ms; g7 waiter_armed1328ms leaves about672ms relative to broker phase zero (not perfectly synchronized to the client's post-send clock). Both test bodies finish around2.04s. ConPTY alone had already returned before expiry; 'ConPTY remained stuck' is not the reported state. Accumulated ordinary startup latency exhausting the shared budget remains plausible.\n\nThere is no 2s broker spawn limit. The 120s pty_creating /30s other-phase ceilings (`broker.rs:309-343`) are duplicate-wake wedge detectors, explicitly NOT operation performance budgets and NOT a cancellation timer. They do not protect this request. The 5s inject-commit bound (`622`) is subsequent worker choreography, not startup acknowledgement. The default15s broker write bound only starts when a frame write is attempted. KH7.8's10s broker QUIC-operation timeout belongs to net operations, not this spawn path.\n\n## Correlation, poison, and late replies\n`Envelope` has only protocol_version/kind/payload (`frame.rs:125-145`); `Spawned` has session_id/pid (`msg.rs:560-566`). The spawn wait accepts any Spawned event, with no request-ID matching (`brain.rs:837-845`). Reusing the same Brain after a timed-out spawn could therefore consume a late Spawned as the reply to a subsequent request. That is a real API misuse hazard but not an established cause here: Main describes a newly constructed Brain in each helper and immediate panic on the first failed spawn. A late reply is the consequence of broker overrun, not an explanation for why this first wait lacked its reply.\n\nThe timeout branch does not itself latch an internal Brain poison flag, cancel the broker, or abort the reader. 'Poison' is the caller/supervisor policy described in KH7.6 and the API comments: abandon/restart rather than retry on the same carrier. The dedicated reader retains framing ownership, so caller timeout does not itself abandon a partially read frame. A missing/late complete reply and an incomplete/blocked transport read are indistinguishable to recv_timeout, but invalid decoded payload/framing errors would ordinarily surface a different error. Broker-side connection poison is separate and logged as `CONN_WRITE_POISONED` or `CONN_WRITE_RETIRED` (`conn.rs:331-360`). Pure Spawned-write failure would normally occur after row_inserted, which is absent in both supplied tails.\n\n## Ranked falsifiable explanations\n1. **Accumulated normal Windows startup / scheduling cost exhausts a too-short spawn reply budget.** Strongest fit: first process consumes45%/66% of budget, followed by unbounded synchronous creation of another process; next phase missing; timing ends at the client deadline. Prediction: existing full broker tails from equivalent failures eventually show translation_ready and row_inserted after2s, or an independently admitted longer-observation capture shows that continuation without intervention. A permanent last-phase stall with healthy scheduling falsifies mere slow completion. No claim that a wider timeout is the fix.\n2. **Translation startup specifically wedges or takes exceptional latency, rather than merely accumulating ordinary overhead.** Candidate sites Command::spawn, reader/inject thread creation, or initial stdin write. Prediction: a passive stack/process snapshot captured during the interval identifies CreateProcess/pipe/thread creation or WriteFile, and translation_ready remains absent while unrelated broker requests still progress. A child existing with Init already observed and handler beyond these calls falsifies this branch. Initial small Init to a fresh pipe makes a persistent full-pipe explanation weaker than process creation absent contrary evidence; no ready-handshake wait exists to blame.\n3. **Local shared-lock or log-sink/scheduler stall inside phase boundaries.** waiter_armed printed before wake_inflight locking; phase logging itself is synchronous. Prediction: a passive stack shows claim mutex or stderr lock/write, or correlated phase records across otherwise unrelated sessions stall together. If a complete sink records smooth unrelated phases and stack shows Command::spawn, this explanation loses support. This can share machine-level resource pressure with NetHost without being a NetHost-owned await/deadlock.\n4. **Reply transport/reader scheduling or malformed late frame correlation.** Architecturally possible but poor match to absent translation_ready/row_inserted. Prediction: full logs must first recover row_inserted before timeout, together with relevant same-connection writer retirement, pump-reader failure/starvation, or complete reply delivery to the channel that the caller did not reach. If the broker never exits translation startup before expiry, transport/correlation is downstream and cannot be the primary stall. Cold-started single-call helpers falsify the prior-late-Spawned variant.\n5. **Direct #302 NetHost2 runtime/journal deadlock.** Lowest direct-cause support: the investigated call graph invokes neither NetHost nor EffectJournal. Prediction required for promotion: an actual ownership chain from this spawn handler's blocked resource to a NetHost runtime/journal owner, not just simultaneous net presence. A stalled net reactor counter alone proves net trouble, not spawn causality; translation/process-stack evidence with no shared owner falsifies this specific direct linkage. Shared host CPU/thread/process-creation pressure remains a possible indirect relationship.\n\n## Next useful existing evidence/probe\nFirst consume the complete per-endpoint broker tail, not just the panic window: search the same labels for translation_ready, row_inserted, row_removed, TRANSLATION_SPAWN_FAILED, and PUMP_IPC_READER: exited; correlate CONN_WRITE_POISONED/RETIRED by conn attribution. translation_ready after expiry separates delayed startup from reply delivery; translation_ready before expiry but row_inserted absent moves attention to table/ledger/phase locks; row_inserted before expiry moves attention to connection writer/reader. row_removed before insertion indicates early child exit, not proof that Spawned was sent. None of these existing phase records distinguishes Command::spawn from Init WriteFile. The next discriminating passive probe, if separately authorized, is a timed thread-stack/process snapshot during the waiter_armed\u2192translation_ready interval plus translation-child existence; this assignment performed no attachment or execution. Preserve the baseline2s contract and report any observation-cohort deadline change explicitly.\n\nDomain/ADR reading reinforces the distinction: broker resources survive brain failure (CONTEXT35-39; ADR0018 decisions1/4/5/8), controller ownership is independent of completed process startup (CONTEXT remote-attach model; ADR0044), and authoritative session-table existence precedes a truthful Spawned reply (ADR0041 policies). Neither a controller-attach line nor a client timeout establishes a registered live session or its absence."
}
