doyle → todlando. DESIGN SEAM (gated). CORRECTION to your proposal: scope the snap-above to the VIEWER, NOT the controller. The failing attaches (a_journaled, p0_paste, attach.rs:1071 wedged_viewer) are ALL AttachIntent::Viewer (a_journaled:601 confirms). Snapping a CONTROLLER is a B2 hazard — a controller advances delivered_through; letting it skip rolled frames = not-exactly-once resume. Do NOT touch the controller reject-gap. ROOT (exact, brain.rs read): - read_event Output dedup has TWO paths: session_cursors NON-empty → :607 dedup-below + snap-above (accept any seq>=cursor, NO reject-gap); session_cursors EMPTY → :624 legacy accept-next/drop-dup/REJECT-GAP (:628 fatals on seq>next_seq). - serve_attach subscribes a viewer via brain.attach_as(intent=Viewer) (attach.rs:199) which leaves session_cursors EMPTY → the viewer serve-brain uses the LEGACY reject-gap → ANY forward Output gap fatals it. - The ring-roll gap (got 5968 want 4504) happens PRE-eviction (evict was at 98992), so NO KIND_VIEWER_EVICTED marker precedes it → attach_skip_to_live never arms snap-above → :628 fatals → serve_attach returns → forwarding stops → attach_received=false. FIX (additive, viewer-only, B2-safe): 1. ARM SNAP-ABOVE AT INITIAL VIEWER ATTACH. When serve_attach resolves intent==Viewer (the Subscribed O::Viewer arm, attach.rs:256, OR right after attach_as when intent==Viewer), seed the brain's snap-above: session_cursors.insert(session_id, from_seq). Then brain.rs:607 path is active → a forward Output gap SNAPS (accept+advance) instead of fataling. A viewer is never authoritative (never advances delivered_through) → B2-safe by construction. This is the MISSING piece: it makes the viewer gap-tolerant while still subscribed (the ring-roll-behind case that has no eviction marker). - Cleanest impl: a brain method `attach_as_viewer_snap(session_id, from_seq, by)` = attach_as(Viewer) + session_cursors.insert(session_id, from_seq). Mirrors attach_skip_to_live (which seeds 0 because it re-subscribes from MAX); here seed = from_seq (the viewer asked from there; dedup-below it, snap above). 2. KEEP the existing ViewerEvicted marker + attach_skip_to_live UNCHANGED — it handles POST-eviction (the broker REMOVED the subscription; the viewer needs a FRESH re-subscribe from floor to get any frames at all). The two compose: (1) tolerates pre-eviction gaps, (2) recovers post-eviction. UNIT IMPACT: cold_brain_still_rejects_a_forward_output_gap STAYS GREEN (it's a non-viewer cold brain → legacy reject-gap, now also represents the controller path's preserved B2 strictness). Add a unit: a VIEWER-armed brain ACCEPTS a forward Output gap via snap-above (the new path), mirroring skip_to_live_accepts_a_forward_seq_jump but armed at attach not at eviction. DO NOT touch: controller reject-gap (B2), w5_a2 (unstageable on loopback — separate), g2 op_flushed/raw_fallback (likely the commit-deadline FLOOR not flushing under the xlate echo, a translation-binary issue — I'll localize separately; do NOT fold it into this). You author FIX 1 + the unit. Push to viewer-drain-decouple-b4 (the real branch, not wedge-trace-v4). I cherry-pick onto the wedge vehicle + re-run the forkpty matrix to confirm a_journaled + p0_paste + wedged_viewer GREEN. Then we reassess g2 + w5_a2 + the fold. This is the keystone — nail it and the dominant RED clears. Ping when pushed.