# W2 — broker viewer/brain decouple: design proposal (todlando → doyle, 2026-07-07)

Proposal for REQ-HAZARD-BROKER-VIEWER-BRAIN-DECOUPLE (LIFECYCLE-TRUTH W2, triage
@907dd2d). doyle rules BEFORE impl; the REQ stays `required_stages = []` until then.
(Message parts of this proposal were eaten by the W5 delivery bug in transit —
this file is the authoritative copy; the numbered spt messages are superseded.)

## Invariant (the ruling target)

**No broker client ever observes a wedge because the BRAIN stalled.** Three legs:

1. Session output to a stalled brain-subscriber detaches BOUNDEDLY — the ring keeps
   the bytes, resume rewinds from `delivered_through` (machinery exists; a detached
   stalled brain self-heals by rewind).
2. Control verbs (take / release / detach-stamp / reattach-refuse) resolve against
   BROKER-owned truth — the `OutputLog` controller table + perch stamps; the broker
   is already the single writer of `driven_by`/`viewer_count`
   (REQ-HAZARD-CONTROL-STAMP-CONVERGENCE) — with NO brain round-trip.
3. Any residual brain round-trip is bounded + a loud timeout error, never a hang.

## Mechanics

**(a) Bounded sink on the brain conn.** Every broker→brain-connection socket write
adopts the SAME discipline `ControllerSink`/`ViewerSink` already prove: per-conn
writer thread + bounded queue + `try_send` + deadline-evict. A suspended/draining
brain conn is DETACHED like an evicted viewer; the broker control plane stays live;
a recovered brain resubscribes from its cursor and rewinds. The remaining BLOCKING
legs today are the dispatch-side `send_frame` acks/events on the shared
`Mutex<SendHalf>` (broker.rs ~2450–3180): a full socket there parks the dispatch
thread — that is the control-plane wedge the rig confirmed (rc `--take` hang,
`daemon status` healthy).

**(b) Broker-side stamp release.** Controller detach (socket death OR evict) fires
an IMMEDIATE broker-side stamp release: `mark_controller_gone` →
`stamp_divergence` convergence write, so `controlled-by` can never stay stuck
behind a dead transit. Reattach-refuse then consults live `has_controller` truth.

**(c) Take against broker truth.** `rc --take` resolves against the broker's
controller table alone; any leg that still transits the brain gets a bounded
deadline + a loud "brain stalled" outcome instead of a hang.

**(d) Ready ≠ drained.** The update-apply promotion gate gains a drained condition:
promote only when the OLD generation's subscriber conn is CLOSED or evicted — never
while blocked writes pend on it. Kills the 22:47 false-promote (`brain.ready` alone
no longer suffices).

**doc stage** = KNOWN-HAZARDS entry: *"PTY fan-out and the broker control plane
must never block on any single subscriber connection — bounded sinks everywhere,
the brain conn included; `brain.ready` ≠ subscribers drained."*

## Int (tonight's rig, encoded)

Daemon-hosted real session; suspend the brain child (NtSuspendProcess; SIGSTOP twin
on Unix); assert WITHIN the stall window: attached viewer keeps ticking, `rc --take`
completes, detach releases the stamp; resume the brain; assert no output lost
(cursor replay byte-identity).

## Questions for ruling

- **Q1** Brain-conn evict deadline: reuse `CONTROLLER_WRITE_DEADLINE`, or a longer
  brain-specific bound (the brain drains bursty — 6 sessions + psyches + WAN on the
  incident night)?
- **Q2** Scope: move the take/release verb set WHOLLY broker-side this wave, or
  minimal diff = bounded-timeout the brain round-trip and move only the STAMP
  release broker-side?
- **Q3** After a stall-evict, does the broker nudge the brain to resubscribe
  (active ping) or wait for the brain's own next subscribe (passive, simpler)?
