# Spike #4 — Linux `forkpty` parity

> Date: 2026-06-01. Status: **PASS**.
> Code: Spike #1 verbatim (`../spt-spikes/spike-01-broker-handoff`), rerun on Linux.
> Run host: `gravity-linux` (Ubuntu 22.04, x86_64, rustc 1.96.0) over Tailscale ssh.
> Closes ADR-0004 §E.2. M3a close-gate.

## Question

Spike #1 proved the broker/brain no-terminate handoff on Windows ConPTY — the high-risk path. ADR-0004 §E.2 asked the obvious parity question: **does the same ownership model hold on Linux `forkpty`?** `portable-pty` selects `forkpty` automatically on Unix, so this is Spike #1's binary, unchanged, on a Linux host.

## Method

No new code. The dev box has no WSL, so the existing `spike-01-broker-handoff` crate (Cargo.toml + src/main.rs) was scp'd to `gravity-linux`, built with cargo 1.96.0, and run `./spike run` three times. The broker spawns `current_exe child` under a `forkpty` master, holds the reader+writer+child+sockets; brain v1 is killed and brain v2 injects input post-restart; a live client must survive the churn. The ConPTY-DSR auto-answer branch is inert on Linux (a normal `forkpty` child never emits `ESC[6n`), so it is a harmless no-op.

Invariants (same as Spike #1): **A** child survives brain restart (no PTY EOF) · **B** PTY counter contiguous · **C** input reaches child after brain restart · **D** live client stream contiguous across restart.

## Result

```
child pid survived | pty ticks 0..39 contiguous, no EOF
post-restart input echoed | client 34 ticks, no gap across the kill/restart window
A PASS  B PASS  C PASS  D PASS  →  OVERALL PASS ✅   (3/3 runs)
```

Identical to Spike #1's Windows result. The broker/brain ownership model is OS-parity: brain death is a closed forwarding socket; the `forkpty` master + child + client fd held by the broker are untouched, and the broker-owned writer still delivers input after the brain is replaced.

## Key finding — `forkpty` is a raw pipe, ConPTY is a screen buffer

Invariant **B passed with the STRICT monotonic-contiguity check** — the very check that *failed* on ConPTY-under-resize in Spike #5. `forkpty` does not repaint on resize and does not replay a viewport on attach; its master is a raw byte pipe, so the tick stream is strictly ordered with no duplication. This confirms the Spike #5 conclusion from the other side: **the two backends have different stream contracts**, and M3a's `SessionSurface` abstraction (REQ-TERM-1) must not assume ConPTY's repaint/viewport semantics on Unix nor `forkpty`'s raw-pipe semantics on Windows. The byte-stream layer (REQ-TERM-3) sees a clean ordered stream on Linux and a repaint-bearing stream on Windows.

## Verdict

ADR-0004 §E.2 closes **PASS**. Linux `forkpty` parity holds with zero code change. Combined with Spikes #3/#5/#6, **all four ADR-0004 §E gaps are now closed** — the Phase-0 spike-gate is complete and the broker/brain split is validated on both OSes. The OS stream-contract divergence is promoted into M3a's `SessionSurface` design rather than re-opening any decision.
