# V0.13.0 P0 — JIT build plan (PTY input single-writer thread; paste-wedge)

**For: the post-self-clear todlando context.** P2 (resume-blank) is DONE + gate-passed by doyle @153c9c9. This is the NEXT body of work: **P0, the active v0.13.0 ship-blocker.** Build it, surface green to doyle for gate.

**Design (authoritative):** `V0.13.0-P0-PTY-INPUT-WRITER-DESIGN.md` (repo root). Read it first.
**REQ:** `REQ-HAZARD-PTY-INPUT-WRITER-WEDGE` — already SEEDED in `traceable-reqs.toml` (`required_stages = []`, ~line 1249). **ACTIVATE to `["impl","unit","int"]` registry-first** before tagging evidence.

## The bug (one line)
Paste into `spt rc` → a burst fills the ConPTY input buffer → `session.write_input(&bytes)` runs SYNCHRONOUSLY on the broker dispatch thread and blocks → dispatch can't service the next frame (re-attach subscribe / become_controller restore-write / inject-floor flush) → broker WEDGES (`brain IPC read deadline`). Completes the W1b-DEFERRED fix (2) of `REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE`.

## The fix
One dedicated **input-writer thread per session** = the SOLE caller of the blocking `write_input`, fed by a **bounded FIFO** (`sync_channel`). Every caller ENQUEUES (`try_send`) + returns immediately. Queue full ⇒ DROP excess + stamp session `INPUT_BACKPRESSURE` (never wedge; a slow harness self-heals as the writer drains). Order preserved (single FIFO + single writer); exactly-once preserved (PtyWrite is ephemeral — `apply_once` effect = the non-blocking enqueue → Applied).

## MIRROR THE EXISTING OUTPUT-SIDE PATTERN
The broker ALREADY has the dedicated-writer-thread + bounded-channel pattern for OUTPUT — copy its shape for INPUT:
- `broker.rs:602 viewer_writer` / `controller_writer` (~367) — writer thread draining a `sync_channel` to a blocking write.
- `ViewerSink` (~111) / `ControllerSink` (~175) — the `{ SyncSender, JoinHandle }` holder; thread exits when `tx` drops.
- `add_viewer` (~398) / `set_controller` (~350) — spawn-the-writer sites.
Apply the SAME to the PTY write side: a `{ SyncSender<InputRecord>, JoinHandle }` on the session, thread = `input_writer` draining to `write_input`.

## Anchors (input/write side)
- `PtySession` — owns PTY master/writer + `write_input`; held as `Arc<PtySession>` on `HostedSession` (`broker.rs:766-768`). Seam = `spt_term::SessionSurface` (CONTEXT L435). The writer thread owns the sole `write_input` access.
- `HostedSession` struct (`broker.rs:764-780`) — natural home for the new input-writer sink (`{ tx: SyncSender<InputRecord>, _writer: JoinHandle<()> }`), spawned where `HostedSession`/`PtySession` is created (find the `HostedSession { session, drain, log, .. }` construction site).
- `dispatch_input` (`broker.rs:1459`) — the broker-dispatch write caller; also where inject-floor BUFFERING of operator bytes happens. Replace its `write_input` with enqueue.
- `flush_inject_floor` (`broker.rs:787`) — calls `session.write_input(&bytes)`; the W2 inject-floor flush. Its writes move to the writer thread (enqueue). The open→drain→commit→flush choreography (`run_inject_worker` ~819) keeps working with the lone writer.
- `serve_attach` → `brain.send_effect` (`attach.rs:197`; `brain.rs:449 send_effect`) → KIND_INPUT → broker dispatch → `dispatch_input`. The operator-keystroke path; it reaches `write_input` via dispatch_input, so fixing dispatch_input covers it.
- `apply_once(key, PtyWrite, effect)` — make the `effect` the enqueue (reserve key under lock → `try_send` → Applied). PtyWrite already ephemeral (`effect.rs:189-199` invariant) so Applied-at-enqueue is sound. Find apply_once in spt-daemon (grep `apply_once`).
- `INPUT_BACKPRESSURE` — NEW health/status signal. Check how existing session health/status is stamped (`stamp_driven_by`/`stamp_viewer_count` ~563-574 write to the perch via OutputLog; or `spt_store::liveness` STATUS_*). Pick the established surface; a wedged-harness drop must be operator-visible.

## Build order
1. ACTIVATE `REQ-HAZARD-PTY-INPUT-WRITER-WEDGE` = `["impl","unit","int"]`; add a note in `REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE` that fix (2) is delivered here.
2. Define `InputRecord` + the bounded `sync_channel` + `input_writer` thread fn (mirror viewer_writer). Capacity sized for a generous paste (few hundred KB of records — design §20).
3. Add the input-writer sink to `HostedSession`/`PtySession`; spawn at session creation; thread is the sole `write_input` caller.
4. Convert callers to enqueue (`try_send`): `dispatch_input`, `flush_inject_floor`, the send_effect→KIND_INPUT path. Full channel ⇒ DROP + stamp `INPUT_BACKPRESSURE`.
5. `apply_once` PtyWrite effect = enqueue.
6. Inject-floor choreography stays correct under the lone writer (verify run_inject_worker + flush still order-correct).
7. doc: KNOWN-HAZARDS 7.17 + CONTEXT note (PTY input single-writer; callers enqueue never block; drop+signal on wedged harness).
8. Tests (DELEGATE to `spt-test-engineer` subagent per memory `delegate-tests-to-spt-test-engineer`; run background to stay reachable):
   - unit: writer-thread drain ORDER; bounded-queue DROP/HEAL decision (INJECTED channel, no wall-clock); exactly-once enqueue (PtyWrite reserve→enqueue→Applied; dedup).
   - int: **REPRO-FIRST, non-vacuous** (W1b's int escaped this). Real broker+PTY+rc, NO mocks. A harness stand-in that STOPS draining stdin so `write_input` genuinely PARKS → RED pre-fix (concurrent rc attach gets `brain IPC read deadline`/no output) → GREEN post-fix (concurrent attach OPENS + RECEIVES output, operator keystroke still accepted, DROP+`INPUT_BACKPRESSURE` on saturation + HEAL on resume). cfg(unix) forkpty park leg covered (W1b gravity-linux follow-up folds in).
9. GATES (run independently, spt-verification-gates memory): `cargo clippy --workspace --all-targets -- -D warnings` = 0 · `traceable-reqs check` EXIT=0 (`+impl +unit +int`) · `cargo run -p xtask -- check` (docs-drift) · rebuild real bin · spt-daemon e2e needs `--test-threads=1`.
10. Commit (trailer `Co-authored by: todlando`) on branch `v0.13.0-delivery-control`; surface P0 green to doyle (`$OWL send doyle`). doyle gates; then operator HITL (paste a large block in a real CC rc session: lands, no wedge, can still attach).

## doyle gate criteria (from design §52)
Repro genuinely blocks write_input (RED pre-fix) · concurrent attach opens+gets output under a parked write · operator keystroke still accepted · DROP+`INPUT_BACKPRESSURE` on saturation, heal on resume · exactly-once + order preserved · inject-floor choreography intact · Unix forkpty leg · clippy/traceable/docs-drift.

## State
- Branch `v0.13.0-delivery-control`. P2 @153c9c9 gate-passed. P1 (`REQ-RC-WIN-PASTE`) DEPENDS ON P0 — build P0 first, then P1.
- TZ-form (P2 design D) left as `%z` offset pending operator HITL; doyle asking operator — DON'T rework unless doyle relays.
- Live as `todlando`, Psyche companion active, gate = doyle, adapter follow-on = perri/peri.
