# Spike #5 — 100× brain restart + resize-under-load stress

> Date: 2026-06-01. Status: **PASS** (with two ConPTY-semantics findings for M3a).
> Throwaway code: `../spt-spikes/spike-05-restart-stress` (outside the spt-core repo, not shipped).
> Closes ADR-0004 §E.3 (codex #3). Windows ConPTY — the high-risk path. M3b close-gate.

## Question

Spike #1 proved ONE clean brain restart. Before the broker can be trusted as the always-on "stable kernel", codex #3 asked for churn: **does the broker survive 100 rapid brain kill/restart cycles, with a PTY resize racing the read loop, with no leak / no hang / no lost byte?**

## Method

One Rust binary (`portable-pty` + std net), modes `child`, `broker`, `brain`, `client`, `run`. Built on Spike #1, with the brain reduced to a churn target and two additions:
- **Resize-under-load thread** in the broker: hammers the PTY master with four alternating sizes (80×24 … 240×60) every 30 ms while the reader drains — the exact resize-vs-read race codex flagged. The master is held behind a `Mutex` so the resize thread can drive it concurrently with the (independently-owned) reader/writer.
- **100× churn orchestrator**: spawns + connects + kills + reaps a brain 100 times against the SAME broker + SAME child + SAME live client, with a **watchdog** that hard-exits if the run exceeds a 120 s deadline (a hang from the resize/read race or an fd leak fails loud instead of hanging forever).

Invariants: **A** exactly one child PID for the whole run (no respawn/leak) · **B** no PTY EOF across all restarts · **C** no lost byte across churn · **D** run completes under deadline (no hang).

## Result

```
child pid: ONE for the whole run (spawned 1×) | 100 brain restarts in ~4.3s
no PTY EOF | observation window 17..=302, every tick value present | no hang
A PASS  B PASS  C PASS  D PASS  →  OVERALL PASS ✅   (stable across runs)
```

The broker is unbothered by brain churn: one child and one PTY for the whole run, the live client never loses a byte across 100 restarts, and the resize thread racing the reader never deadlocks or hangs. The stable-kernel premise holds under stress.

## Key findings — ConPTY is a screen buffer, not a raw pipe (binding on M3a)

Invariant C failed twice before the *check* was corrected — and both failures were real ConPTY semantics, not broker bugs. They are the value of this spike:

1. **Resize triggers a full repaint that re-emits buffered lines.** Under the resize thread, the client stream had ~4500 `tick` lines but only ~286 distinct values — a resize makes ConPTY redraw the current viewport, so the byte stream legitimately *duplicates and rewinds* (e.g. …301, 302, then back to 280…). **Strict monotonic contiguity (Spike #1's check) is wrong for a resizing ConPTY.** A terminal consumer is fine with this (it repaints identically); a *transfer* must never run over ConPTY.

2. **A consumer attaching mid-session gets only the current viewport, not scrollback.** The "missing" values were exactly `0..16` — ticks emitted before the client connected (~1.4 s of startup). ConPTY replays only the visible viewport (~24 rows) on attach, not history. Within the client's actual observation window `[min_seen, max]`, coverage is complete — no churn loss.

The correct "no lost byte" test is therefore **value-set coverage over the observation window** `[min_seen, max]`, robust to repaint reorder/dup and to viewport-only attach. Both findings are inputs to **REQ-TERM-3** (byte-stream streaming): M3a must define attach/replay semantics — does a remote terminal get scrollback or just the viewport? — and must NOT model the terminal stream as exactly-once-ordered bytes (that contract belongs to the transfer boundary, Spike #6).

## What this does NOT prove

- Linux `forkpty` resize/churn semantics (Spike #4 — `forkpty` is a raw pipe, no repaint, so its stream *is* monotonic; the two streams have different contracts).
- Long-duration leak detection beyond 100 cycles (no RSS/handle-count sampling here — A only asserts single-child + clean reap; a slow leak over thousands of cycles is out of scope).

## Verdict

ADR-0004 §E.3 closes **PASS**. The broker survives churn + resize-under-load with no leak/hang/lost-byte. The two ConPTY findings are promoted into M3a's REQ-TERM-3 design (viewport vs scrollback attach; terminal-stream contract ≠ transfer-stream contract) rather than re-opening any decision.
