# Spike #1 — broker/brain no-terminate handoff

> Date: 2026-05-31. Status: **PASS (core thesis, Windows ConPTY)** with named open gaps.
> Throwaway code: `../spt-spikes/spike-01-broker-handoff` (outside the spt-core repo, not shipped).
> Drives ROADMAP Stage B Spike #1 + codex red-team FATAL #2/#3 (`docs/reviews/STAGE-A-codex-redteam.md`).

## Question

ADR-0004 claims a PTY child + an open client connection survive a daemon-logic ("brain") restart because a stable "broker" holds the load-bearing handles. Codex called this the single most-likely redesign trigger. **Does it actually hold on Windows ConPTY — the high-risk path?**

## Method

One Rust binary (`portable-pty` + std net), five modes: `broker`, `brain <ver>`, `child`, `client`, `run`.
- **broker** owns the *sole* PTY master reader+writer, the spawned child, the listening sockets, and every accepted client socket. Forwards PTY output to all sinks; routes brain `INPUT:` control lines to the PTY writer it owns. Never restarts.
- **brain** holds only a forwarding control socket — no PTY handle, no client fd. Killable/relaunchable.
- **client** = an external TCP consumer that must survive brain churn.
- **run** orchestrates: start broker+child, attach client, start brain v1, kill it, start brain v2 (which injects input post-restart), tear down, then assert from logs.

Invariants: **A** child alive whole run (no PTY EOF) · **B** `tick N` counter contiguous · **C** input reaches child after brain restart · **D** client stream contiguous across restart.

## Result

```
child pid 33880 survived | pty ticks 0..39 contiguous, no EOF
post-restart input echoed | client: 34 ticks, no gap across the brain kill/restart window
A PASS  B PASS  C PASS  D PASS  →  OVERALL PASS ✅
```

The ownership model works: brain death is just a closed forwarding socket; the broker's held handles keep the child and the live client stream intact, and the broker-owned writer still delivers input after the brain is replaced. This is the *brain-only* update class — the one ADR-0004 must preserve, and the routine case.

## Key finding — ConPTY DSR stall (not in any doc before)

ConPTY emits a cursor-position query `ESC [ 6 n` at startup and **withholds all child stdout until the terminal answers it**. First runs captured exactly 4 bytes (`\e[6n`) and zero program output — the child looked dead but was ticking fine standalone. Replying `ESC [ 1;1 R` from the reader immediately unblocked full output (`bytes=4` → `bytes=93`, HELLO/DONE present). A `cmd.exe` probe reproduced it identically, proving it's a ConPTY plumbing requirement, not a workload bug. → new hazard `REQ-HAZARD-CONPTY-DSR`; every ConPTY reader must auto-answer DSR.

Secondary gotcha: a ConPTY master does **not** EOF while the broker holds the writer, so any read loop must drain on a thread and never gate exit on a blocking `read()` returning. (Cost a build cycle — three hung `spike.exe` instances locked the binary against relink.)

## What this does NOT yet prove (feeds ADR-0004 §E before M3)

1. **Live Iroh/QUIC + file-transfer stream survival** across brain restart (codex FATAL #2). Only PTY + plain TCP tested. Plain-TCP survival is a weak proxy — QUIC carries process-local crypto/stream state. **Highest remaining risk.**
2. **Linux `forkpty` parity** — Windows-only run.
3. **100× restart + resize-under-load** — single restart only.
4. **Idempotent delivery** across the broker↔brain boundary (codex #14).

## Verdict

ADR-0004's broker/brain split is **not falsified** — the hardest OS path (ConPTY) passes, and the DSR stall that would have bitten the real terminal layer is now caught and documented. The split survives Stage A. Promote the four open gaps into the M3 daemon plan rather than re-opening the decision.
