1620	                .is_some_and(|tr| tr.floor.lock().unwrap().buffer_if_held(bytes))
1621	        };
1622	        match req.op_id {
1623	            Some(op) => {
1624	                let key = (req.session_id, op);
1625	                let outcome = self
1626	                    .journal
1627	                    .apply_once(key, EffectKind::PtyWrite, || {
1628	                        // Buffer-or-ENQUEUE INSIDE the journal effect so the op is
1629	                        // still recorded applied + acked (dedup-safe) whether the byte
1630	                        // buffers (inject floor held), enqueues, or is dropped under
1631	                        // backpressure. The effect is now the non-blocking enqueue —
1632	                        // PtyWrite is ephemeral, so Applied-at-enqueue is sound
1633	                        // (V0.13.0-P0 design); the ack means "accepted + ordered + will
1634	                        // land," and rc does not gate on landing.
1635	                        if !buffered(&bytes) {
1636	                            input.enqueue(bytes.clone());
1637	                        }
1638	                        Ok(())
1639	                    })
1640	                    .map_err(|e| format!("input enqueue failed: {e}"))?;
1641	                // Ack exactly-once: both Applied and Deduped mean "accepted + ordered".
1642	                send_frame(
1643	                    send,
1644	                    &applied_envelope(req.session_id, op, outcome == Outcome::Applied),
1645	                );
1646	                Ok(())
1647	            }
1648	            None => {
1649	                if !buffered(&bytes) {
1650	                    input.enqueue(bytes);
1651	                }
1652	                Ok(())
1653	            }
1654	        }
1655	    }
1656
1657	    /// Deliver an inbound message to an spt-hosted endpoint by ENDPOINT ID:
1658	    /// resolve the endpoint to its hosted session and inject the bytes straight
1659	    /// into that session's PTY (REQ-SEND-SPT-HOSTED). The endpoint-keyed

                .is_some_and(|tr| tr.floor.lock().unwrap().buffer_if_held(bytes))
        };
        match req.op_id {
            Some(op) => {
                let key = (req.session_id, op);
                let outcome = self
                    .journal
                    .apply_once(key, EffectKind::PtyWrite, || {
                        // Buffer-or-ENQUEUE INSIDE the journal effect so the op is
                        // still recorded applied + acked (dedup-safe) whether the byte
                        // buffers (inject floor held), enqueues, or is dropped under
                        // backpressure. The effect is now the non-blocking enqueue —
                        // PtyWrite is ephemeral, so Applied-at-enqueue is sound
                        // (V0.13.0-P0 design); the ack means "accepted + ordered + will
                        // land," and rc does not gate on landing.
                        if !buffered(&bytes) {
                            input.enqueue(bytes.clone());
                        }
                        Ok(())
                    })
                    .map_err(|e| format!("input enqueue failed: {e}"))?;
                // Ack exactly-once: both Applied and Deduped mean "accepted + ordered".
                send_frame(
                    send,
                    &applied_envelope(req.session_id, op, outcome == Outcome::Applied),
                );
                Ok(())
            }
            None => {
                if !buffered(&bytes) {
                    input.enqueue(bytes);
                }
                Ok(())
            }
        }
    }

    /// Deliver an inbound message to an spt-hosted endpoint by ENDPOINT ID:
    /// resolve the endpoint to its hosted session and inject the bytes straight
    /// into that session's PTY (REQ-SEND-SPT-HOSTED). The endpoint-keyed


Edit

                    .map_err(|e| format!("input enqueue failed: {e}"))?;
                // Ack exactly-once: both Applied and Deduped mean "accepted + ordered".
                send_frame(
                    send,
                    &applied_envelope(req.session_id, op, outcome == Outcome::Applied),
                );
                Ok(())

                    .map_err(|e| format!("input enqueue failed: {e}"))?;
                // Ack exactly-once — but ONLY when the caller requested it
                // (REQ-HAZARD-INPUT-ACK-BACKPRESSURE). The fire-and-forward
                // operator/rc path sends `ack=false`: writing the ack back onto the
                // same brain↔broker conn it is flooding with input fills the return
                // direction and deadlocks the per-conn handler (the wedge). With no
                // ack the handler never writes back while servicing the flood, so it
                // always drains. The acked path (`shellchan`, one-at-a-time) still
                // gets its `KIND_APPLIED`. Dedup is unaffected either way (the
                // applied-set is the exactly-once anchor, not the ack frame).
                if req.ack {
                    send_frame(
                        send,
                        &applied_envelope(req.session_id, op, outcome == Outcome::Applied),
                    );
                }
                Ok(())


The file C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\src\broker.rs has been updated successfully. (file state is current in your context — no need to Read it back)

                    .map_err(|e| format!("input enqueue failed: {e}"))?;
                // Ack exactly-once: both Applied and Deduped mean "accepted + ordered".
                send_frame(
                    send,
                    &applied_envelope(req.session_id, op, outcome == Outcome::Applied),
                );
                Ok(())

                    .map_err(|e| format!("input enqueue failed: {e}"))?;
                // Ack exactly-once — but ONLY when the caller requested it
                // (REQ-HAZARD-INPUT-ACK-BACKPRESSURE). The fire-and-forward
                // operator/rc path sends `ack=false`: writing the ack back onto the
                // same brain↔broker conn it is flooding with input fills the return
                // direction and deadlocks the per-conn handler (the wedge). With no
                // ack the handler never writes back while servicing the flood, so it
                // always drains. The acked path (`shellchan`, one-at-a-time) still
                // gets its `KIND_APPLIED`. Dedup is unaffected either way (the
                // applied-set is the exactly-once anchor, not the ack frame).
                if req.ack {
                    send_frame(
                        send,
                        &applied_envelope(req.session_id, op, outcome == Outcome::Applied),
                    );
                }
                Ok(())

-                // Ack exactly-once: both Applied and Deduped mean "accepted + ordered".

-                    &applied_envelope(req.session_id, op, outcome == Outcome::Applied),

+                // Ack exactly-once — but ONLY when the caller requested it

+                // (REQ-HAZARD-INPUT-ACK-BACKPRESSURE). The fire-and-forward

+                // operator/rc path sends `ack=false`: writing the ack back onto the

+                // same brain↔broker conn it is flooding with input fills the return

+                // direction and deadlocks the per-conn handler (the wedge). With no

+                // ack the handler never writes back while servicing the flood, so it

+                // always drains. The acked path (`shellchan`, one-at-a-time) still

+                // gets its `KIND_APPLIED`. Dedup is unaffected either way (the

+                // applied-set is the exactly-once anchor, not the ack frame).

+                        &applied_envelope(req.session_id, op, outcome == Outcome::Applied),


Update remaining InputReq literals (tests) to add `ack`.


114	    // Inject a line; it must round-trip through the real PTY back as output.
115	    send(
116	        &mut conn,
117	        KIND_INPUT,
118	        serde_json::to_value(InputReq {
119	            session_id: sid,
120	            data_b64: encode_bytes(b"INJECT-ALPHA\r"),
121	            op_id: None,
122	        })
123	        .unwrap(),

    // Inject a line; it must round-trip through the real PTY back as output.
    send(
        &mut conn,
        KIND_INPUT,
        serde_json::to_value(InputReq {
            session_id: sid,
            data_b64: encode_bytes(b"INJECT-ALPHA\r"),
            op_id: None,
        })
        .unwrap(),


        serde_json::to_value(InputReq {
            session_id: sid,
            data_b64: encode_bytes(b"INJECT-ALPHA\r"),
            op_id: None,
        })
        .unwrap(),

        serde_json::to_value(InputReq {
            session_id: sid,
            data_b64: encode_bytes(b"INJECT-ALPHA\r"),
            op_id: None,
            ack: true,
        })
        .unwrap(),


The file C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\tests\broker.rs has been updated successfully. (file state is current in your context — no need to Read it back)

        serde_json::to_value(InputReq {
            session_id: sid,
            data_b64: encode_bytes(b"INJECT-ALPHA\r"),
            op_id: None,
        })
        .unwrap(),

        serde_json::to_value(InputReq {
            session_id: sid,
            data_b64: encode_bytes(b"INJECT-ALPHA\r"),
            op_id: None,
            ack: true,
        })
        .unwrap(),


355	            .unwrap_or_else(|_| teardown_panic(&stop, "could not open a raw input connection"));
356	        send_hello(&mut raw, Role::Brain)
357	            .unwrap_or_else(|_| teardown_panic(&stop, "raw hello failed"));
358	        let input = InputReq {
359	            session_id: sid,
360	            data_b64: encode_bytes(b"D7-1 POST-SWAP FUNCTIONAL PROBE\r"),
361	            op_id: None,
362	        };
363	        write_frame(
364	            &mut raw,
365	            &Envelope::new(KIND_INPUT, serde_json::to_value(input).expect("InputReq serializes")),
366	        )

            .unwrap_or_else(|_| teardown_panic(&stop, "could not open a raw input connection"));
        send_hello(&mut raw, Role::Brain)
            .unwrap_or_else(|_| teardown_panic(&stop, "raw hello failed"));
        let input = InputReq {
            session_id: sid,
            data_b64: encode_bytes(b"D7-1 POST-SWAP FUNCTIONAL PROBE\r"),
            op_id: None,
        };
        write_frame(
            &mut raw,
            &Envelope::new(KIND_INPUT, serde_json::to_value(input).expect("InputReq serializes")),
        )


        let input = InputReq {
            session_id: sid,
            data_b64: encode_bytes(b"D7-1 POST-SWAP FUNCTIONAL PROBE\r"),
            op_id: None,
        };

        let input = InputReq {
            session_id: sid,
            data_b64: encode_bytes(b"D7-1 POST-SWAP FUNCTIONAL PROBE\r"),
            op_id: None,
            ack: true,
        };


The file C:\Users\decid\Documents\projects\spt-core\crates\spt\tests\brain_survive.rs has been updated successfully. (file state is current in your context — no need to Read it back)

        let input = InputReq {
            session_id: sid,
            data_b64: encode_bytes(b"D7-1 POST-SWAP FUNCTIONAL PROBE\r"),
            op_id: None,
        };

        let input = InputReq {
            session_id: sid,
            data_b64: encode_bytes(b"D7-1 POST-SWAP FUNCTIONAL PROBE\r"),
            op_id: None,
            ack: true,
        };


Compile-check primary.


Bash

cargo check -p spt-daemon 2>&1 | tail -15

Type-check spt-daemon primary fix


    Checking spt-daemon v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.94s


P1 ctrl+V wedge — diagnose then fix

HITL re-open: ctrl+V (~10+ chars) wedges broker; right-click fine. Diagnostic build @b39e8a0 (branch v0.13.0-p1-ctrlv-diag) surfaced to doyle with env matrix (SPT_RC_CTRLV isolation + SPT_RC_DEBUG_PASTE + SPT_BROKER_DEBUG_INPUT/OUTPUT). Awaiting operator stderr capture (via doyle) to pin input-dispatch vs controller-output-write block. Then design+build the real fix (doyle suspects symmetric-P0-for-output async controller writer) on a gated branch off f7cf380.

Mint REQ-HAZARD-INPUT-ACK-BACKPRESSURE + amend REQ-RC-WIN-PASTE

Registry-first: new hazard REQ [impl,unit,int] for the input-ack IPC deadlock. Amend REQ-RC-WIN-PASTE to right-click-only scope (ctrl+V = WT-native).

Primary fix: conditional ack

InputReq.ack serde-default=true; brain send_effect_no_ack for operator path (serve_attach); dispatch_input only send_frame(applied) when req.ack; shellchan keeps ack=true.

Secondary: remove dead ctrl+V interception

Remove is_ctrl_v arm + fn + unit; keep right-click bracketed paste + EnableMouseCapture. ctrl+V rides WT-native paste.

Docs: KNOWN-HAZARDS deadlock + amend 7.18

New KNOWN-HAZARDS entry for input-ack backpressure deadlock (+N-1 broker-resident caveat). Amend 7.18: ctrl+V WT-native, right-click bracketed only.

Tests (spt-test-engineer) + gates + commit + doyle

Delegate repro-first int (N=64 flood deadlock) + units (ack=true emits/false doesn't, exactly-once no-ack dedup). Gates clippy/traceable/docs-drift/real-bin; commit; surface green to doyle.


Primary compiles. Task 15 done. Now secondary — remove dead ctrl+V interception from clean rc.rs.


Updated task #15 status


Updated task #16 status


462	#[cfg(windows)]
463	fn spawn_stdin_reader_events(tx: mpsc::Sender<StdinMsg>) {
464	    use crossterm::event::{self, Event, KeyEventKind};
465	    std::thread::spawn(move || {
466	        let mut armed = false;
467	        while let Ok(ev) = event::read() {
468	            match ev {
469	                // ctrl+V paste gesture (REQ-RC-WIN-PASTE): read the LOCAL clipboard
470	                // and inject a BRACKETED paste, BEFORE the detach SM / key
471	                // translation — but only when NOT detach-armed (ctrl-b takes
472	                // precedence; ctrl+V is not the prefix). A multi-line paste lands
473	                // intact with no submit-storm; an empty/failed clipboard no-ops.
474	                Event::Key(ke) if ke.kind == KeyEventKind::Press && !armed && is_ctrl_v(&ke) => {
475	                    if let Some(bytes) = clipboard_paste(read_clipboard) {
476	                        if tx.send(StdinMsg::Bytes(bytes)).is_err() {
477	                            break;
478	                        }
479	                    }
480	                }
481	                // Right-click paste gesture (REQ-RC-WIN-PASTE): same bracketed
482	                // clipboard paste. Every OTHER mouse event is dropped — CC has no
483	                // mouse features, so capturing mouse deprives it of nothing.
484	                Event::Mouse(me) => {
485	                    if mouse_is_paste(&me) {
486	                        if let Some(bytes) = clipboard_paste(read_clipboard) {
487	                            if tx.send(StdinMsg::Bytes(bytes)).is_err() {
488	                                break;
489	                            }
490	                        }
491	                    }
492	                }
493	                Event::Key(ke) if ke.kind == KeyEventKind::Press => {
494	                    match key_event_step(&mut armed, ke) {
495	                        KeyAction::Detach => {
496	                            let _ = tx.send(StdinMsg::Detach);
497	                            return;
498	                        }
499	                        KeyAction::Forward(bytes) => {
500	                            if !bytes.is_empty() && tx.send(StdinMsg::Bytes(bytes)).is_err() {
501	                                break;
502	                            }
503	                        }
504	                        KeyAction::Swallow => {}
505	                    }
506	                }
507	                Event::Paste(s) => {
508	                    if tx.send(StdinMsg::Bytes(s.into_bytes())).is_err() {
509	                        break;
510	                    }
511	                }
512	                _ => {}
513	            }
514	        }
515	    });
516	}
517

#[cfg(windows)]
fn spawn_stdin_reader_events(tx: mpsc::Sender<StdinMsg>) {
    use crossterm::event::{self, Event, KeyEventKind};
    std::thread::spawn(move || {
        let mut armed = false;
        while let Ok(ev) = event::read() {
            match ev {
                // ctrl+V paste gesture (REQ-RC-WIN-PASTE): read the LOCAL clipboard
                // and inject a BRACKETED paste, BEFORE the detach SM / key
                // translation — but only when NOT detach-armed (ctrl-b takes
                // precedence; ctrl+V is not the prefix). A multi-line paste lands
                // intact with no submit-storm; an empty/failed clipboard no-ops.
                Event::Key(ke) if ke.kind == KeyEventKind::Press && !armed && is_ctrl_v(&ke) => {
                    if let Some(bytes) = clipboard_paste(read_clipboard) {
                        if tx.send(StdinMsg::Bytes(bytes)).is_err() {
                            break;
                        }
                    }
                }
                // Right-click paste gesture (REQ-RC-WIN-PASTE): same bracketed
                // clipboard paste. Every OTHER mouse event is dropped — CC has no
                // mouse features, so capturing mouse deprives it of nothing.
                Event::Mouse(me) => {
                    if mouse_is_paste(&me) {
                        if let Some(bytes) = clipboard_paste(read_clipboard) {
                            if tx.send(StdinMsg::Bytes(bytes)).is_err() {
                                break;
                            }
                        }
                    }
                }
                Event::Key(ke) if ke.kind == KeyEventKind::Press => {
                    match key_event_step(&mut armed, ke) {
                        KeyAction::Detach => {
                            let _ = tx.send(StdinMsg::Detach);
                            return;
                        }
                        KeyAction::Forward(bytes) => {
                            if !bytes.is_empty() && tx.send(StdinMsg::Bytes(bytes)).is_err() {
                                break;
                            }
                        }
                        KeyAction::Swallow => {}
                    }
                }
                Event::Paste(s) => {
                    if tx.send(StdinMsg::Bytes(s.into_bytes())).is_err() {
                        break;
                    }
                }
                _ => {}
            }
        }
    });
}


Edit

        let mut armed = false;
        while let Ok(ev) = event::read() {
            match ev {
                // ctrl+V paste gesture (REQ-RC-WIN-PASTE): read the LOCAL clipboard
                // and inject a BRACKETED paste, BEFORE the detach SM / key
                // translation — but only when NOT detach-armed (ctrl-b takes
                // precedence; ctrl+V is not the prefix). A multi-line paste lands
                // intact with no submit-storm; an empty/failed clipboard no-ops.
                Event::Key(ke) if ke.kind == KeyEventKind::Press && !armed && is_ctrl_v(&ke) => {
                    if let Some(bytes) = clipboard_paste(read_clipboard) {
                        if tx.send(StdinMsg::Bytes(bytes)).is_err() {
                            break;
                        }
                    }
                }
                // Right-click paste gesture (REQ-RC-WIN-PASTE): same bracketed
                // clipboard paste. Every OTHER mouse event is dropped — CC has no
                // mouse features, so capturing mouse deprives it of nothing.
                Event::Mouse(me) => {

        let mut armed = false;
        while let Ok(ev) = event::read() {
            match ev {
                // Right-click paste gesture (REQ-RC-WIN-PASTE): read the LOCAL
                // clipboard and inject a BRACKETED paste (ESC[200~ … ESC[201~) — the
                // ONLY client-originated paste we can drive. (ctrl+V is NOT
                // intercepted: Windows Terminal consumes ctrl+V as its own paste
                // accelerator — it delivers only a Key RELEASE, never a Press, and
                // injects the clipboard as a char-by-char key flood — so an
                // interception arm could never fire. ctrl+V now rides WT's native
                // paste as keystrokes; the broker no longer wedges on the flood
                // (REQ-HAZARD-INPUT-ACK-BACKPRESSURE). Bracketed fidelity = right-click.)
                // Every OTHER mouse event is dropped — CC has no mouse features, so
                // capturing mouse deprives it of nothing.
                Event::Mouse(me) => {


The file C:\Users\decid\Documents\projects\spt-core\crates\spt\src\rc.rs has been updated successfully. (file state is current in your context — no need to Read it back)

        let mut armed = false;
        while let Ok(ev) = event::read() {
            match ev {
                // ctrl+V paste gesture (REQ-RC-WIN-PASTE): read the LOCAL clipboard
                // and inject a BRACKETED paste, BEFORE the detach SM / key
                // translation — but only when NOT detach-armed (ctrl-b takes
                // precedence; ctrl+V is not the prefix). A multi-line paste lands
                // intact with no submit-storm; an empty/failed clipboard no-ops.
                Event::Key(ke) if ke.kind == KeyEventKind::Press && !armed && is_ctrl_v(&ke) => {
                    if let Some(bytes) = clipboard_paste(read_clipboard) {
                        if tx.send(StdinMsg::Bytes(bytes)).is_err() {
                            break;
                        }
                    }
                }
                // Right-click paste gesture (REQ-RC-WIN-PASTE): same bracketed
                // clipboard paste. Every OTHER mouse event is dropped — CC has no
                // mouse features, so capturing mouse deprives it of nothing.
                Event::Mouse(me) => {

        let mut armed = false;
        while let Ok(ev) = event::read() {
            match ev {
                // Right-click paste gesture (REQ-RC-WIN-PASTE): read the LOCAL
                // clipboard and inject a BRACKETED paste (ESC[200~ … ESC[201~) — the
                // ONLY client-originated paste we can drive. (ctrl+V is NOT
                // intercepted: Windows Terminal consumes ctrl+V as its own paste
                // accelerator — it delivers only a Key RELEASE, never a Press, and
                // injects the clipboard as a char-by-char key flood — so an
                // interception arm could never fire. ctrl+V now rides WT's native
                // paste as keystrokes; the broker no longer wedges on the flood
                // (REQ-HAZARD-INPUT-ACK-BACKPRESSURE). Bracketed fidelity = right-click.)
                // Every OTHER mouse event is dropped — CC has no mouse features, so
                // capturing mouse deprives it of nothing.
                Event::Mouse(me) => {

-                // ctrl+V paste gesture (REQ-RC-WIN-PASTE): read the LOCAL clipboard

-                // and inject a BRACKETED paste, BEFORE the detach SM / key

-                // translation — but only when NOT detach-armed (ctrl-b takes

-                // precedence; ctrl+V is not the prefix). A multi-line paste lands

-                // intact with no submit-storm; an empty/failed clipboard no-ops.

-                Event::Key(ke) if ke.kind == KeyEventKind::Press && !armed && is_ctrl_v(&ke) => {

-                    if let Some(bytes) = clipboard_paste(read_clipboard) {

-                        if tx.send(StdinMsg::Bytes(bytes)).is_err() {

-                // Right-click paste gesture (REQ-RC-WIN-PASTE): same bracketed

-                // clipboard paste. Every OTHER mouse event is dropped — CC has no

-                // mouse features, so capturing mouse deprives it of nothing.

+                // Right-click paste gesture (REQ-RC-WIN-PASTE): read the LOCAL

+                // clipboard and inject a BRACKETED paste (ESC[200~ … ESC[201~) — the

+                // ONLY client-originated paste we can drive. (ctrl+V is NOT

+                // intercepted: Windows Terminal consumes ctrl+V as its own paste

+                // accelerator — it delivers only a Key RELEASE, never a Press, and

+                // injects the clipboard as a char-by-char key flood — so an

+                // interception arm could never fire. ctrl+V now rides WT's native

+                // paste as keystrokes; the broker no longer wedges on the flood

+                // (REQ-HAZARD-INPUT-ACK-BACKPRESSURE). Bracketed fidelity = right-click.)

+                // Every OTHER mouse event is dropped — CC has no mouse features, so

+                // capturing mouse deprives it of nothing.


Remove the `is_ctrl_v` fn + its unit test.


/// Whether a key event is the ctrl+V paste gesture (`Ctrl` + `v`). Pure so it
/// unit-tests without a console.
// [impl->REQ-RC-WIN-PASTE]
#[cfg(windows)]
fn is_ctrl_v(ke: &crossterm::event::KeyEvent) -> bool {
    use crossterm::event::{KeyCode, KeyModifiers};
    ke.code == KeyCode::Char('v') && ke.modifiers.contains(KeyModifiers::CONTROL)
}

/// Whether a mouse event is the right-click paste gesture (right button DOWN).

/// Whether a mouse event is the right-click paste gesture (right button DOWN).


/// Whether a key event is the ctrl+V paste gesture (`Ctrl` + `v`). Pure so it
/// unit-tests without a console.
// [impl->REQ-RC-WIN-PASTE]
#[cfg(windows)]
fn is_ctrl_v(ke: &crossterm::event::KeyEvent) -> bool {
    use crossterm::event::{KeyCode, KeyModifiers};
    ke.code == KeyCode::Char('v') && ke.modifiers.contains(KeyModifiers::CONTROL)
}

/// Whether a mouse event is the right-click paste gesture (right button DOWN).

-/// Whether a key event is the ctrl+V paste gesture (`Ctrl` + `v`). Pure so it

-fn is_ctrl_v(ke: &crossterm::event::KeyEvent) -> bool {

-    use crossterm::event::{KeyCode, KeyModifiers};

-    ke.code == KeyCode::Char('v') && ke.modifiers.contains(KeyModifiers::CONTROL)

/// Every other mouse event (left/middle, up/move/drag/scroll) is a non-gesture

/// the reader drops — CC has no mouse features, so capturing mouse costs nothing.


    // [unit->REQ-RC-WIN-PASTE] the ctrl+V gesture is detected ONLY for Ctrl+v — a
    // bare 'v', a Ctrl+other, or 'v' with a different modifier is not the paste key.
    #[cfg(windows)]
    #[test]
    fn is_ctrl_v_only_for_ctrl_plus_v() {
        use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
        assert!(is_ctrl_v(&KeyEvent::new(KeyCode::Char('v'), KeyModifiers::CONTROL)));
        // Ctrl + Shift + v still counts (CONTROL present) — paste regardless of shift.
        assert!(is_ctrl_v(&KeyEvent::new(
            KeyCode::Char('v'),
            KeyModifiers::CONTROL | KeyModifiers::SHIFT
        )));
        assert!(!is_ctrl_v(&KeyEvent::new(KeyCode::Char('v'), KeyModifiers::NONE)));
        assert!(!is_ctrl_v(&KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL)));
        assert!(!is_ctrl_v(&KeyEvent::new(KeyCode::Char('v'), KeyModifiers::ALT)));
    }

    // [unit->REQ-RC-WIN-PASTE] the right-click paste gesture is ONLY right-button

// [unit->REQ-RC-WIN-PASTE] the right-click paste gesture is ONLY right-button


    // [unit->REQ-RC-WIN-PASTE] the ctrl+V gesture is detected ONLY for Ctrl+v — a
    // bare 'v', a Ctrl+other, or 'v' with a different modifier is not the paste key.
    #[cfg(windows)]
    #[test]
    fn is_ctrl_v_only_for_ctrl_plus_v() {
        use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
        assert!(is_ctrl_v(&KeyEvent::new(KeyCode::Char('v'), KeyModifiers::CONTROL)));
        // Ctrl + Shift + v still counts (CONTROL present) — paste regardless of shift.
        assert!(is_ctrl_v(&KeyEvent::new(
            KeyCode::Char('v'),
            KeyModifiers::CONTROL | KeyModifiers::SHIFT
        )));
        assert!(!is_ctrl_v(&KeyEvent::new(KeyCode::Char('v'), KeyModifiers::NONE)));
        assert!(!is_ctrl_v(&KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL)));
        assert!(!is_ctrl_v(&KeyEvent::new(KeyCode::Char('v'), KeyModifiers::ALT)));
    }

    // [unit->REQ-RC-WIN-PASTE] the right-click paste gesture is ONLY right-button

-    // [unit->REQ-RC-WIN-PASTE] the ctrl+V gesture is detected ONLY for Ctrl+v — a

-    // bare 'v', a Ctrl+other, or 'v' with a different modifier is not the paste key.

-    fn is_ctrl_v_only_for_ctrl_plus_v() {

-        use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};

-        assert!(is_ctrl_v(&KeyEvent::new(KeyCode::Char('v'), KeyModifiers::CONTROL)));

-        // Ctrl + Shift + v still counts (CONTROL present) — paste regardless of shift.

-        assert!(is_ctrl_v(&KeyEvent::new(

-            KeyModifiers::CONTROL | KeyModifiers::SHIFT

-        assert!(!is_ctrl_v(&KeyEvent::new(KeyCode::Char('v'), KeyModifiers::NONE)));

-        assert!(!is_ctrl_v(&KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL)));

-        assert!(!is_ctrl_v(&KeyEvent::new(KeyCode::Char('v'), KeyModifiers::ALT)));

// DOWN; every other mouse event (left/middle, up/move/drag/scroll) is dropped.


Compile-check secondary.


Bash

Build spt after ctrl+V removal


   Compiling spt-daemon v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
   Compiling spt v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 11.15s


Both fixes compile. Task 16 done. Now mint REQ + amend (registry-first). Read P1 registry region.


1259	# ── P1 (operator HITL 2026-06-19, post-deploy dogfood; doyle /diagnose + design): rc paste broken (ctrl+V + right-click) ──
1260	[[requirements]]
1261	id = "REQ-RC-WIN-PASTE"
1262	title = "In an `spt rc` session neither ctrl+V nor right-click pastes (CC explicitly supports ctrl+V). ROOT (doyle /diagnose): RawGuard does only enable_raw_mode (no bracketed paste / no mouse capture / no clipboard interception); the Windows console delivers a paste as synthetic per-char KEY EVENTs (no crossterm Event::Paste), and ctrl+V translates to bare ^V forwarded to CC — but CC runs DAEMON-SIDE with NO access to the operator's LOCAL clipboard, so remote paste is fundamentally CLIENT-ORIGINATED. A multi-line paste-as-keys also becomes a \\r submit-storm. FIX (doyle design, V0.13.0-P1-RC-PASTE-DESIGN.md, cfg(windows), folds into the bug-2 event path): on a paste gesture rc reads the LOCAL clipboard + forwards a BRACKETED PASTE (ESC[200~ + content + ESC[201~); CC has bracketed-paste mode on (its TUI sets ESC[?2004h) so it treats it as a paste — content intact, no submit-storm, harness-AGNOSTIC. ctrl+V: intercept Char('v')+CONTROL in the event loop -> read_clipboard -> bracketed paste. Right-click: RawGuard also EnableMouseCapture (disables console QuickEdit + enables ENABLE_MOUSE_INPUT so right-click surfaces as Event::Mouse on legacy cmd/powershell) -> right-button -> read_clipboard -> bracketed paste; DROP all other mouse (CC has no mouse features, operator-confirmed, so capture costs nothing). read_clipboard = clipboard-win crate (cfg(windows), minimal); empty/failed = clean no-op. Content forwarded VERBATIM (literal pasted text, no per-char translation). Unix UNCHANGED (its terminal pastes natively through the byte pump). DEPENDS ON P0 (a paste chunk must not wedge the broker). (v0.13.0)"
1263	required_stages = ["doc", "impl", "unit"]  # ACTIVATED v0.13.0 P1 (todlando, last build item; doyle gates). DEPENDS ON P0 (@0675b37, landed) — a paste chunk rides P0's single-writer thread without wedging. NO int (live clipboard + console mouse = HITL, REQ-RUN-PICKER/RC-1 precedent; pure pieces unit-tested). impl = (cfg(windows), folds into the bug-2 event path) `wrap_bracketed_paste` (ESC[200~ + content + ESC[201~) + ctrl+V intercept (Char('v')+CONTROL, when not detach-armed) + right-mouse paste in `spawn_stdin_reader_events` (other mouse dropped) + `read_clipboard` (clipboard-win, empty/fail = clean no-op) + RawGuard EnableMouseCapture(+DisableMouseCapture on drop). unit = bracketed framing exact + content verbatim (no translation) + ctrl+V detect + mouse classify (right-down ⇒ paste, all else ⇒ drop) + injected-reader paste builder (Some ⇒ wrapped, empty/None ⇒ no-op). doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated: ctrl+V/right-click read the LOCAL clipboard + inject a bracketed paste; Unix rides the terminal byte stream). PLANNED: impl = RawGuard EnableMouseCapture (+restore on drop) + ctrl+V/right-mouse clipboard intercept in the bug-2 event path + bracketed-paste wrap + clipboard-win read. unit = bracketed-paste framing (exact ESC[200~/ESC[201~) + gesture->paste decision (ctrl+V/right-mouse=>clipboard-wrap, other mouse=>drop, injected reader) + content verbatim. doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated; Unix rides the terminal byte stream). DEPENDS ON P0. doyle gates; operator HITL (ctrl+V AND right-click paste, multi-line, no submit-storm, no wedge)."
1264

# ── P1 (operator HITL 2026-06-19, post-deploy dogfood; doyle /diagnose + design): rc paste broken (ctrl+V + right-click) ──
[[requirements]]
id = "REQ-RC-WIN-PASTE"
title = "In an `spt rc` session neither ctrl+V nor right-click pastes (CC explicitly supports ctrl+V). ROOT (doyle /diagnose): RawGuard does only enable_raw_mode (no bracketed paste / no mouse capture / no clipboard interception); the Windows console delivers a paste as synthetic per-char KEY EVENTs (no crossterm Event::Paste), and ctrl+V translates to bare ^V forwarded to CC — but CC runs DAEMON-SIDE with NO access to the operator's LOCAL clipboard, so remote paste is fundamentally CLIENT-ORIGINATED. A multi-line paste-as-keys also becomes a \\r submit-storm. FIX (doyle design, V0.13.0-P1-RC-PASTE-DESIGN.md, cfg(windows), folds into the bug-2 event path): on a paste gesture rc reads the LOCAL clipboard + forwards a BRACKETED PASTE (ESC[200~ + content + ESC[201~); CC has bracketed-paste mode on (its TUI sets ESC[?2004h) so it treats it as a paste — content intact, no submit-storm, harness-AGNOSTIC. ctrl+V: intercept Char('v')+CONTROL in the event loop -> read_clipboard -> bracketed paste. Right-click: RawGuard also EnableMouseCapture (disables console QuickEdit + enables ENABLE_MOUSE_INPUT so right-click surfaces as Event::Mouse on legacy cmd/powershell) -> right-button -> read_clipboard -> bracketed paste; DROP all other mouse (CC has no mouse features, operator-confirmed, so capture costs nothing). read_clipboard = clipboard-win crate (cfg(windows), minimal); empty/failed = clean no-op. Content forwarded VERBATIM (literal pasted text, no per-char translation). Unix UNCHANGED (its terminal pastes natively through the byte pump). DEPENDS ON P0 (a paste chunk must not wedge the broker). (v0.13.0)"
required_stages = ["doc", "impl", "unit"]  # ACTIVATED v0.13.0 P1 (todlando, last build item; doyle gates). DEPENDS ON P0 (@0675b37, landed) — a paste chunk rides P0's single-writer thread without wedging. NO int (live clipboard + console mouse = HITL, REQ-RUN-PICKER/RC-1 precedent; pure pieces unit-tested). impl = (cfg(windows), folds into the bug-2 event path) `wrap_bracketed_paste` (ESC[200~ + content + ESC[201~) + ctrl+V intercept (Char('v')+CONTROL, when not detach-armed) + right-mouse paste in `spawn_stdin_reader_events` (other mouse dropped) + `read_clipboard` (clipboard-win, empty/fail = clean no-op) + RawGuard EnableMouseCapture(+DisableMouseCapture on drop). unit = bracketed framing exact + content verbatim (no translation) + ctrl+V detect + mouse classify (right-down ⇒ paste, all else ⇒ drop) + injected-reader paste builder (Some ⇒ wrapped, empty/None ⇒ no-op). doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated: ctrl+V/right-click read the LOCAL clipboard + inject a bracketed paste; Unix rides the terminal byte stream). PLANNED: impl = RawGuard EnableMouseCapture (+restore on drop) + ctrl+V/right-mouse clipboard intercept in the bug-2 event path + bracketed-paste wrap + clipboard-win read. unit = bracketed-paste framing (exact ESC[200~/ESC[201~) + gesture->paste decision (ctrl+V/right-mouse=>clipboard-wrap, other mouse=>drop, injected reader) + content verbatim. doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated; Unix rides the terminal byte stream). DEPENDS ON P0. doyle gates; operator HITL (ctrl+V AND right-click paste, multi-line, no submit-storm, no wedge)."


Amend REQ-RC-WIN-PASTE (right-click-only scope) + insert the new hazard REQ.


Edit

doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated; Unix rides the terminal byte stream). DEPENDS ON P0. doyle gates; operator HITL (ctrl+V AND right-click paste, multi-line, no submit-storm, no wedge)."

doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated; Unix rides the terminal byte stream). DEPENDS ON P0. doyle gates; operator HITL (ctrl+V AND right-click paste, multi-line, no submit-storm, no wedge). AMENDED v0.13.0 P1b (HITL re-open 2026-06-19, doyle): SCOPE narrowed to RIGHT-CLICK-ONLY for the client-originated bracketed paste — the ctrl+V interception is REMOVED (dead + redundant): Windows Terminal CONSUMES ctrl+V as its own paste accelerator (delivers only a Key kind=RELEASE, never a Press, so the Press-guarded is_ctrl_v arm could NEVER fire) AND injects the clipboard as a char-by-char KEY FLOOD spt-core cannot intercept. ctrl+V now rides WT's native paste as keystrokes (multi-line may submit-storm — acceptable per operator; bracketed fidelity = right-click). The is_ctrl_v fn + its unit are removed; impl/unit evidence relocates to the surviving right-click path (wrap_bracketed_paste/mouse_is_paste/clipboard_paste/read_clipboard/RawGuard mouse-capture + their units). The flood-no-wedge guarantee moves to REQ-HAZARD-INPUT-ACK-BACKPRESSURE (the deadlock that the ctrl+V flood exposed). Right-click bracketed paste + mouse-capture UNCHANGED."


The file C:\Users\decid\Documents\projects\spt-core\traceable-reqs.toml has been updated successfully. (file state is current in your context — no need to Read it back)

title = "In an `spt rc` session neither ctrl+V nor right-click pastes (CC explicitly supports ctrl+V). ROOT (doyle /diagnose): RawGuard does only enable_raw_mode (no bracketed paste / no mouse capture / no clipboard interception); the Windows console delivers a paste as synthetic per-char KEY EVENTs (no crossterm Event::Paste), and ctrl+V translates to bare ^V forwarded to CC — but CC runs DAEMON-SIDE with NO access to the operator's LOCAL clipboard, so remote paste is fundamentally CLIENT-ORIGINATED. A multi-line paste-as-keys also becomes a \\r submit-storm. FIX (doyle design, V0.13.0-P1-RC-PASTE-DESIGN.md, cfg(windows), folds into the bug-2 event path): on a paste gesture rc reads the LOCAL clipboard + forwards a BRACKETED PASTE (ESC[200~ + content + ESC[201~); CC has bracketed-paste mode on (its TUI sets ESC[?2004h) so it treats it as a paste — content intact, no submit-storm, harness-AGNOSTIC. ctrl+V: intercept Char('v')+CONTROL in the event loop -> read_clipboard -> bracketed paste. Right-click: RawGuard also EnableMouseCapture (disables console QuickEdit + enables ENABLE_MOUSE_INPUT so right-click surfaces as Event::Mouse on legacy cmd/powershell) -> right-button -> read_clipboard -> bracketed paste; DROP all other mouse (CC has no mouse features, operator-confirmed, so capture costs nothing). read_clipboard = clipboard-win crate (cfg(windows), minimal); empty/failed = clean no-op. Content forwarded VERBATIM (literal pasted text, no per-char translation). Unix UNCHANGED (its terminal pastes natively through the byte pump). DEPENDS ON P0 (a paste chunk must not wedge the broker). (v0.13.0)"

-required_stages = ["doc", "impl", "unit"]  # ACTIVATED v0.13.0 P1 (todlando, last build item; doyle gates). DEPENDS ON P0 (@0675b37, landed) — a paste chunk rides P0's single-writer thread without wedging. NO int (live clipboard + console mouse = HITL, REQ-RUN-PICKER/RC-1 precedent; pure pieces unit-tested). impl = (cfg(windows), folds into the bug-2 event path) `wrap_bracketed_paste` (ESC[200~ + content + ESC[201~) + ctrl+V intercept (Char('v')+CONTROL, when not detach-armed) + right-mouse paste in `spawn_stdin_reader_events` (other mouse dropped) + `read_clipboard` (clipboard-win, empty/fail = clean no-op) + RawGuard EnableMouseCapture(+DisableMouseCapture on drop). unit = bracketed framing exact + content verbatim (no translation) + ctrl+V detect + mouse classify (right-down ⇒ paste, all else ⇒ drop) + injected-reader paste builder (Some ⇒ wrapped, empty/None ⇒ no-op). doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated: ctrl+V/right-click read the LOCAL clipboard + inject a bracketed paste; Unix rides the terminal byte stream). PLANNED: impl = RawGuard EnableMouseCapture (+restore on drop) + ctrl+V/right-mouse clipboard intercept in the bug-2 event path + bracketed-paste wrap + clipboard-win read. unit = bracketed-paste framing (exact ESC[200~/ESC[201~) + gesture->paste decision (ctrl+V/right-mouse=>clipboard-wrap, other mouse=>drop, injected reader) + content verbatim. doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated; Unix rides the terminal byte stream). DEPENDS ON P0. doyle gates; operator HITL (ctrl+V AND right-click paste, multi-line, no submit-storm, no wedge)."

+required_stages = ["doc", "impl", "unit"]  # ACTIVATED v0.13.0 P1 (todlando, last build item; doyle gates). DEPENDS ON P0 (@0675b37, landed) — a paste chunk rides P0's single-writer thread without wedging. NO int (live clipboard + console mouse = HITL, REQ-RUN-PICKER/RC-1 precedent; pure pieces unit-tested). impl = (cfg(windows), folds into the bug-2 event path) `wrap_bracketed_paste` (ESC[200~ + content + ESC[201~) + ctrl+V intercept (Char('v')+CONTROL, when not detach-armed) + right-mouse paste in `spawn_stdin_reader_events` (other mouse dropped) + `read_clipboard` (clipboard-win, empty/fail = clean no-op) + RawGuard EnableMouseCapture(+DisableMouseCapture on drop). unit = bracketed framing exact + content verbatim (no translation) + ctrl+V detect + mouse classify (right-down ⇒ paste, all else ⇒ drop) + injected-reader paste builder (Some ⇒ wrapped, empty/None ⇒ no-op). doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated: ctrl+V/right-click read the LOCAL clipboard + inject a bracketed paste; Unix rides the terminal byte stream). PLANNED: impl = RawGuard EnableMouseCapture (+restore on drop) + ctrl+V/right-mouse clipboard intercept in the bug-2 event path + bracketed-paste wrap + clipboard-win read. unit = bracketed-paste framing (exact ESC[200~/ESC[201~) + gesture->paste decision (ctrl+V/right-mouse=>clipboard-wrap, other mouse=>drop, injected reader) + content verbatim. doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated; Unix rides the terminal byte stream). DEPENDS ON P0. doyle gates; operator HITL (ctrl+V AND right-click paste, multi-line, no submit-storm, no wedge). AMENDED v0.13.0 P1b (HITL re-open 2026-06-19, doyle): SCOPE narrowed to RIGHT-CLICK-ONLY for the client-originated bracketed paste — the ctrl+V interception is REMOVED (dead + redundant): Windows Terminal CONSUMES ctrl+V as its own paste accelerator (delivers only a Key kind=RELEASE, never a Press, so the Press-guarded is_ctrl_v arm could NEVER fire) AND injects the clipboard as a char-by-char KEY FLOOD spt-core cannot intercept. ctrl+V now rides WT's native paste as keystrokes (multi-line may submit-storm — acceptable per operator; bracketed fidelity = right-click). The is_ctrl_v fn + its unit are removed; impl/unit evidence relocates to the surviving right-click path (wrap_bracketed_paste/mouse_is_paste/clipboard_paste/read_clipboard/RawGuard mouse-capture + their units). The flood-no-wedge guarantee moves to REQ-HAZARD-INPUT-ACK-BACKPRESSURE (the deadlock that the ctrl+V flood exposed). Right-click bracketed paste + mouse-capture UNCHANGED."


doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated; Unix rides the terminal byte stream). DEPENDS ON P0. doyle gates; operator HITL (ctrl+V AND right-click paste, multi-line, no submit-storm, no wedge). AMENDED v0.13.0 P1b

doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated; Unix rides the terminal byte stream). DEPENDS ON P0. doyle gates; operator HITL (ctrl+V AND right-click paste, multi-line, no submit-storm, no wedge). AMENDED-MARKER v0.13.0 P1b


-required_stages = ["doc", "impl", "unit"]  # ACTIVATED v0.13.0 P1 (todlando, last build item; doyle gates). DEPENDS ON P0 (@0675b37, landed) — a paste chunk rides P0's single-writer thread without wedging. NO int (live clipboard + console mouse = HITL, REQ-RUN-PICKER/RC-1 precedent; pure pieces unit-tested). impl = (cfg(windows), folds into the bug-2 event path) `wrap_bracketed_paste` (ESC[200~ + content + ESC[201~) + ctrl+V intercept (Char('v')+CONTROL, when not detach-armed) + right-mouse paste in `spawn_stdin_reader_events` (other mouse dropped) + `read_clipboard` (clipboard-win, empty/fail = clean no-op) + RawGuard EnableMouseCapture(+DisableMouseCapture on drop). unit = bracketed framing exact + content verbatim (no translation) + ctrl+V detect + mouse classify (right-down ⇒ paste, all else ⇒ drop) + injected-reader paste builder (Some ⇒ wrapped, empty/None ⇒ no-op). doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated: ctrl+V/right-click read the LOCAL clipboard + inject a bracketed paste; Unix rides the terminal byte stream). PLANNED: impl = RawGuard EnableMouseCapture (+restore on drop) + ctrl+V/right-mouse clipboard intercept in the bug-2 event path + bracketed-paste wrap + clipboard-win read. unit = bracketed-paste framing (exact ESC[200~/ESC[201~) + gesture->paste decision (ctrl+V/right-mouse=>clipboard-wrap, other mouse=>drop, injected reader) + content verbatim. doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated; Unix rides the terminal byte stream). DEPENDS ON P0. doyle gates; operator HITL (ctrl+V AND right-click paste, multi-line, no submit-storm, no wedge). AMENDED v0.13.0 P1b (HITL re-open 2026-06-19, doyle): SCOPE narrowed to RIGHT-CLICK-ONLY for the client-originated bracketed paste — the ctrl+V interception is REMOVED (dead + redundant): Windows Terminal CONSUMES ctrl+V as its own paste accelerator (delivers only a Key kind=RELEASE, never a Press, so the Press-guarded is_ctrl_v arm could NEVER fire) AND injects the clipboard as a char-by-char KEY FLOOD spt-core cannot intercept. ctrl+V now rides WT's native paste as keystrokes (multi-line may submit-storm — acceptable per operator; bracketed fidelity = right-click). The is_ctrl_v fn + its unit are removed; impl/unit evidence relocates to the surviving right-click path (wrap_bracketed_paste/mouse_is_paste/clipboard_paste/read_clipboard/RawGuard mouse-capture + their units). The flood-no-wedge guarantee moves to REQ-HAZARD-INPUT-ACK-BACKPRESSURE (the deadlock that the ctrl+V flood exposed). Right-click bracketed paste + mouse-capture UNCHANGED."

+required_stages = ["doc", "impl", "unit"]  # ACTIVATED v0.13.0 P1 (todlando, last build item; doyle gates). DEPENDS ON P0 (@0675b37, landed) — a paste chunk rides P0's single-writer thread without wedging. NO int (live clipboard + console mouse = HITL, REQ-RUN-PICKER/RC-1 precedent; pure pieces unit-tested). impl = (cfg(windows), folds into the bug-2 event path) `wrap_bracketed_paste` (ESC[200~ + content + ESC[201~) + ctrl+V intercept (Char('v')+CONTROL, when not detach-armed) + right-mouse paste in `spawn_stdin_reader_events` (other mouse dropped) + `read_clipboard` (clipboard-win, empty/fail = clean no-op) + RawGuard EnableMouseCapture(+DisableMouseCapture on drop). unit = bracketed framing exact + content verbatim (no translation) + ctrl+V detect + mouse classify (right-down ⇒ paste, all else ⇒ drop) + injected-reader paste builder (Some ⇒ wrapped, empty/None ⇒ no-op). doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated: ctrl+V/right-click read the LOCAL clipboard + inject a bracketed paste; Unix rides the terminal byte stream). PLANNED: impl = RawGuard EnableMouseCapture (+restore on drop) + ctrl+V/right-mouse clipboard intercept in the bug-2 event path + bracketed-paste wrap + clipboard-win read. unit = bracketed-paste framing (exact ESC[200~/ESC[201~) + gesture->paste decision (ctrl+V/right-mouse=>clipboard-wrap, other mouse=>drop, injected reader) + content verbatim. doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated; Unix rides the terminal byte stream). DEPENDS ON P0. doyle gates; operator HITL (ctrl+V AND right-click paste, multi-line, no submit-storm, no wedge). AMENDED-MARKER v0.13.0 P1b (HITL re-open 2026-06-19, doyle): SCOPE narrowed to RIGHT-CLICK-ONLY for the client-originated bracketed paste — the ctrl+V interception is REMOVED (dead + redundant): Windows Terminal CONSUMES ctrl+V as its own paste accelerator (delivers only a Key kind=RELEASE, never a Press, so the Press-guarded is_ctrl_v arm could NEVER fire) AND injects the clipboard as a char-by-char KEY FLOOD spt-core cannot intercept. ctrl+V now rides WT's native paste as keystrokes (multi-line may submit-storm — acceptable per operator; bracketed fidelity = right-click). The is_ctrl_v fn + its unit are removed; impl/unit evidence relocates to the surviving right-click path (wrap_bracketed_paste/mouse_is_paste/clipboard_paste/read_clipboard/RawGuard mouse-capture + their units). The flood-no-wedge guarantee moves to REQ-HAZARD-INPUT-ACK-BACKPRESSURE (the deadlock that the ctrl+V flood exposed). Right-click bracketed paste + mouse-capture UNCHANGED."


no submit-storm, no wedge). AMENDED-MARKER v0.13.0 P1b

no submit-storm, no wedge). AMENDED v0.13.0 P1b


no submit-storm, no wedge). AMENDED v0.13.0 P1b

-required_stages = ["doc", "impl", "unit"]  # ACTIVATED v0.13.0 P1 (todlando, last build item; doyle gates). DEPENDS ON P0 (@0675b37, landed) — a paste chunk rides P0's single-writer thread without wedging. NO int (live clipboard + console mouse = HITL, REQ-RUN-PICKER/RC-1 precedent; pure pieces unit-tested). impl = (cfg(windows), folds into the bug-2 event path) `wrap_bracketed_paste` (ESC[200~ + content + ESC[201~) + ctrl+V intercept (Char('v')+CONTROL, when not detach-armed) + right-mouse paste in `spawn_stdin_reader_events` (other mouse dropped) + `read_clipboard` (clipboard-win, empty/fail = clean no-op) + RawGuard EnableMouseCapture(+DisableMouseCapture on drop). unit = bracketed framing exact + content verbatim (no translation) + ctrl+V detect + mouse classify (right-down ⇒ paste, all else ⇒ drop) + injected-reader paste builder (Some ⇒ wrapped, empty/None ⇒ no-op). doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated: ctrl+V/right-click read the LOCAL clipboard + inject a bracketed paste; Unix rides the terminal byte stream). PLANNED: impl = RawGuard EnableMouseCapture (+restore on drop) + ctrl+V/right-mouse clipboard intercept in the bug-2 event path + bracketed-paste wrap + clipboard-win read. unit = bracketed-paste framing (exact ESC[200~/ESC[201~) + gesture->paste decision (ctrl+V/right-mouse=>clipboard-wrap, other mouse=>drop, injected reader) + content verbatim. doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated; Unix rides the terminal byte stream). DEPENDS ON P0. doyle gates; operator HITL (ctrl+V AND right-click paste, multi-line, no submit-storm, no wedge). AMENDED-MARKER v0.13.0 P1b (HITL re-open 2026-06-19, doyle): SCOPE narrowed to RIGHT-CLICK-ONLY for the client-originated bracketed paste — the ctrl+V interception is REMOVED (dead + redundant): Windows Terminal CONSUMES ctrl+V as its own paste accelerator (delivers only a Key kind=RELEASE, never a Press, so the Press-guarded is_ctrl_v arm could NEVER fire) AND injects the clipboard as a char-by-char KEY FLOOD spt-core cannot intercept. ctrl+V now rides WT's native paste as keystrokes (multi-line may submit-storm — acceptable per operator; bracketed fidelity = right-click). The is_ctrl_v fn + its unit are removed; impl/unit evidence relocates to the surviving right-click path (wrap_bracketed_paste/mouse_is_paste/clipboard_paste/read_clipboard/RawGuard mouse-capture + their units). The flood-no-wedge guarantee moves to REQ-HAZARD-INPUT-ACK-BACKPRESSURE (the deadlock that the ctrl+V flood exposed). Right-click bracketed paste + mouse-capture UNCHANGED."


The flood-no-wedge guarantee moves to REQ-HAZARD-INPUT-ACK-BACKPRESSURE (the deadlock that the ctrl+V flood exposed). Right-click bracketed paste + mouse-capture UNCHANGED."

The flood-no-wedge guarantee moves to REQ-HAZARD-INPUT-ACK-BACKPRESSURE (the deadlock that the ctrl+V flood exposed). Right-click bracketed paste + mouse-capture UNCHANGED."

# ── P1b (operator HITL re-open 2026-06-19, doyle /diagnose on the enhanced rc+broker capture): an input FLOOD deadlocks the broker via the applied-ack ──
[[requirements]]
id = "REQ-HAZARD-INPUT-ACK-BACKPRESSURE"
title = "A FLOOD of operator input on one brain↔broker connection deadlocks the broker PERMANENTLY (entire broker — no new/existing attach; the controller stays latched because the per-conn handler can't process the detach). ROOT (doyle /diagnose, code-grounded + HITL capture, the v0.13.0 P1 ctrl+V re-open): `serve_attach` processes a whole `NetStreamData` batch of N operator `Input` records in its inner `for rec in decoder.push()` loop, calling `brain.send_effect(op_id, &bytes)` N times WITHOUT returning to `read_event()` — so the brain writes N `KIND_INPUT` frames back-to-back and drains nothing. The broker's single-threaded per-conn handler answers EACH with `send_frame(applied_envelope)` on the SAME conn (B5 exactly-once ack, KNOWN-HAZARDS 7.2). With the brain not reading, the broker→brain return direction fills (~10 frames = the IPC pipe buffer) → `send_frame` BLOCKS → the handler stops reading → the brain's writes block too → mutual full-duplex DEADLOCK. Capture pinned it: 11 input frames, write_input 11/11 (P0 holds — the PTY write is fine), ack send START=11 / END=10 (frame #11's applied-ack never returns). Same class as the v0.12.1 L0 two-conn split. Windows Terminal's ctrl+V paste accelerator was the trigger (injects the clipboard as a char-by-char key flood) but the deadlock is generic to ANY input flood, NOT ctrl+V-specific and NOT a P0 (PTY-write) or W1 (output-drain) regression. The applied-ack is load-bearing ONLY for `shellchan` (one-at-a-time spool delivery WAITS on `BrokerEvent::Applied`); `serve_attach` DISCARDS it (the operator/rc path is fire-and-forward, op_id for dedup only, never gates on the ack). FIX (doyle-approved): CONDITIONAL ACK — `InputReq` gains `ack: bool` (serde default = true, N-1-safe: an older brain's input still acks = today's behavior). `serve_attach`'s operator path calls `send_effect_no_ack` (ack=false) → `dispatch_input` writes NO applied frame → the per-conn handler never writes back while servicing the flood → it always drains → no deadlock (cures ANY input flood). `shellchan` keeps `send_effect` (ack=true) and its `Applied`-wait. Exactly-once PRESERVED: the broker still dedups by (session, op_id) at the applied-set regardless of the ack. N-1 caveat: an OLD resident broker (self-update window) ignores `ack=false` → still acks → the deadlock persists until a broker restart (inherent KNOWN-HAZARDS 7.9 broker-resident-wire-change class). (v0.13.0)"
required_stages = ["impl", "unit", "int"]  # ACTIVATED v0.13.0 P1b (todlando, the real ship-blocker behind the ctrl+V wedge; doyle gates). impl = InputReq.ack (serde default_true) + Brain::send_effect_no_ack (send_effect_inner shared body) + serve_attach operator path -> send_effect_no_ack + dispatch_input gates send_frame(applied) on req.ack. unit = InputReq.ack serde default=true on an absent field (N-1) + dispatch_input emits the applied frame when ack=true and NONE when ack=false + exactly-once on the no-ack path (a replayed/duplicate op_id still dedups at apply_once, no double PTY write). int = REPRO-FIRST (the keystone, the exact gate the wedge needed): a flood of N>pipe-buffer (deterministic, e.g. 64) operator input frames through serve_attach on ONE conn -> PRE-FIX deadlocks (ack-send-START-without-END, handler stops draining; a concurrent attach / the detach cannot be serviced) -> POST-FIX drains all N + the session stays live + a concurrent attach still OPENS. Real broker+brain, NO mocks. doc = KNOWN-HAZARDS entry (the deadlock + the N-1 broker-resident caveat). clippy/traceable/docs-drift."


The flood-no-wedge guarantee moves to REQ-HAZARD-INPUT-ACK-BACKPRESSURE (the deadlock that the ctrl+V flood exposed). Right-click bracketed paste + mouse-capture UNCHANGED."

# ── P1b (operator HITL re-open 2026-06-19, doyle /diagnose on the enhanced rc+broker capture): an input FLOOD deadlocks the broker via the applied-ack ──
[[requirements]]
id = "REQ-HAZARD-INPUT-ACK-BACKPRESSURE"
title = "A FLOOD of operator input on one brain↔broker connection deadlocks the broker PERMANENTLY (entire broker — no new/existing attach; the controller stays latched because the per-conn handler can't process the detach). ROOT (doyle /diagnose, code-grounded + HITL capture, the v0.13.0 P1 ctrl+V re-open): `serve_attach` processes a whole `NetStreamData` batch of N operator `Input` records in its inner `for rec in decoder.push()` loop, calling `brain.send_effect(op_id, &bytes)` N times WITHOUT returning to `read_event()` — so the brain writes N `KIND_INPUT` frames back-to-back and drains nothing. The broker's single-threaded per-conn handler answers EACH with `send_frame(applied_envelope)` on the SAME conn (B5 exactly-once ack, KNOWN-HAZARDS 7.2). With the brain not reading, the broker→brain return direction fills (~10 frames = the IPC pipe buffer) → `send_frame` BLOCKS → the handler stops reading → the brain's writes block too → mutual full-duplex DEADLOCK. Capture pinned it: 11 input frames, write_input 11/11 (P0 holds — the PTY write is fine), ack send START=11 / END=10 (frame #11's applied-ack never returns). Same class as the v0.12.1 L0 two-conn split. Windows Terminal's ctrl+V paste accelerator was the trigger (injects the clipboard as a char-by-char key flood) but the deadlock is generic to ANY input flood, NOT ctrl+V-specific and NOT a P0 (PTY-write) or W1 (output-drain) regression. The applied-ack is load-bearing ONLY for `shellchan` (one-at-a-time spool delivery WAITS on `BrokerEvent::Applied`); `serve_attach` DISCARDS it (the operator/rc path is fire-and-forward, op_id for dedup only, never gates on the ack). FIX (doyle-approved): CONDITIONAL ACK — `InputReq` gains `ack: bool` (serde default = true, N-1-safe: an older brain's input still acks = today's behavior). `serve_attach`'s operator path calls `send_effect_no_ack` (ack=false) → `dispatch_input` writes NO applied frame → the per-conn handler never writes back while servicing the flood → it always drains → no deadlock (cures ANY input flood). `shellchan` keeps `send_effect` (ack=true) and its `Applied`-wait. Exactly-once PRESERVED: the broker still dedups by (session, op_id) at the applied-set regardless of the ack. N-1 caveat: an OLD resident broker (self-update window) ignores `ack=false` → still acks → the deadlock persists until a broker restart (inherent KNOWN-HAZARDS 7.9 broker-resident-wire-change class). (v0.13.0)"
required_stages = ["impl", "unit", "int"]  # ACTIVATED v0.13.0 P1b (todlando, the real ship-blocker behind the ctrl+V wedge; doyle gates). impl = InputReq.ack (serde default_true) + Brain::send_effect_no_ack (send_effect_inner shared body) + serve_attach operator path -> send_effect_no_ack + dispatch_input gates send_frame(applied) on req.ack. unit = InputReq.ack serde default=true on an absent field (N-1) + dispatch_input emits the applied frame when ack=true and NONE when ack=false + exactly-once on the no-ack path (a replayed/duplicate op_id still dedups at apply_once, no double PTY write). int = REPRO-FIRST (the keystone, the exact gate the wedge needed): a flood of N>pipe-buffer (deterministic, e.g. 64) operator input frames through serve_attach on ONE conn -> PRE-FIX deadlocks (ack-send-START-without-END, handler stops draining; a concurrent attach / the detach cannot be serviced) -> POST-FIX drains all N + the session stays live + a concurrent attach still OPENS. Real broker+brain, NO mocks. doc = KNOWN-HAZARDS entry (the deadlock + the N-1 broker-resident caveat). clippy/traceable/docs-drift."

required_stages = ["doc", "impl", "unit"]  # ACTIVATED v0.13.0 P1 (todlando, last build item; doyle gates). DEPENDS ON P0 (@0675b37, landed) — a paste chunk rides P0's single-writer thread without wedging. NO int (live clipboard + console mouse = HITL, REQ-RUN-PICKER/RC-1 precedent; pure pieces unit-tested). impl = (cfg(windows), folds into the bug-2 event path) `wrap_bracketed_paste` (ESC[200~ + content + ESC[201~) + ctrl+V intercept (Char('v')+CONTROL, when not detach-armed) + right-mouse paste in `spawn_stdin_reader_events` (other mouse dropped) + `read_clipboard` (clipboard-win, empty/fail = clean no-op) + RawGuard EnableMouseCapture(+DisableMouseCapture on drop). unit = bracketed framing exact + content verbatim (no translation) + ctrl+V detect + mouse classify (right-down ⇒ paste, all else ⇒ drop) + injected-reader paste builder (Some ⇒ wrapped, empty/None ⇒ no-op). doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated: ctrl+V/right-click read the LOCAL clipboard + inject a bracketed paste; Unix rides the terminal byte stream). PLANNED: impl = RawGuard EnableMouseCapture (+restore on drop) + ctrl+V/right-mouse clipboard intercept in the bug-2 event path + bracketed-paste wrap + clipboard-win read. unit = bracketed-paste framing (exact ESC[200~/ESC[201~) + gesture->paste decision (ctrl+V/right-mouse=>clipboard-wrap, other mouse=>drop, injected reader) + content verbatim. doc = KNOWN-HAZARDS + CONTEXT note (rc Windows paste client-originated; Unix rides the terminal byte stream). DEPENDS ON P0. doyle gates; operator HITL (ctrl+V AND right-click paste, multi-line, no submit-storm, no wedge). AMENDED v0.13.0 P1b (HITL re-open 2026-06-19, doyle): SCOPE narrowed to RIGHT-CLICK-ONLY for the client-originated bracketed paste — the ctrl+V interception is REMOVED (dead + redundant): Windows Terminal CONSUMES ctrl+V as its own paste accelerator (delivers only a Key kind=RELEASE, never a Press, so the Press-guarded is_ctrl_v arm could NEVER fire) AND injects the clipboard as a char-by-char KEY FLOOD spt-core cannot intercept. ctrl+V now rides WT's native paste as keystrokes (multi-line may submit-storm — acceptable per operator; bracketed fidelity = right-click). The is_ctrl_v fn + its unit are removed; impl/unit evidence relocates to the surviving right-click path (wrap_bracketed_paste/mouse_is_paste/clipboard_paste/read_clipboard/RawGuard mouse-capture + their units). The flood-no-wedge guarantee moves to REQ-HAZARD-INPUT-ACK-BACKPRESSURE (the deadlock that the ctrl+V flood exposed). Right-click bracketed paste + mouse-capture UNCHANGED."

+# ── P1b (operator HITL re-open 2026-06-19, doyle /diagnose on the enhanced rc+broker capture): an input FLOOD deadlocks the broker via the applied-ack ──

+title = "A FLOOD of operator input on one brain↔broker connection deadlocks the broker PERMANENTLY (entire broker — no new/existing attach; the controller stays latched because the per-conn handler can't process the detach). ROOT (doyle /diagnose, code-grounded + HITL capture, the v0.13.0 P1 ctrl+V re-open): `serve_attach` processes a whole `NetStreamData` batch of N operator `Input` records in its inner `for rec in decoder.push()` loop, calling `brain.send_effect(op_id, &bytes)` N times WITHOUT returning to `read_event()` — so the brain writes N `KIND_INPUT` frames back-to-back and drains nothing. The broker's single-threaded per-conn handler answers EACH with `send_frame(applied_envelope)` on the SAME conn (B5 exactly-once ack, KNOWN-HAZARDS 7.2). With the brain not reading, the broker→brain return direction fills (~10 frames = the IPC pipe buffer) → `send_frame` BLOCKS → the handler stops reading → the brain's writes block too → mutual full-duplex DEADLOCK. Capture pinned it: 11 input frames, write_input 11/11 (P0 holds — the PTY write is fine), ack send START=11 / END=10 (frame #11's applied-ack never returns). Same class as the v0.12.1 L0 two-conn split. Windows Terminal's ctrl+V paste accelerator was the trigger (injects the clipboard as a char-by-char key flood) but the deadlock is generic to ANY input flood, NOT ctrl+V-specific and NOT a P0 (PTY-write) or W1 (output-drain) regression. The applied-ack is load-bearing ONLY for `shellchan` (one-at-a-time spool delivery WAITS on `BrokerEvent::Applied`); `serve_attach` DISCARDS it (the operator/rc path is fire-and-forward, op_id for dedup only, never gates on the ack). FIX (doyle-approved): CONDITIONAL ACK — `InputReq` gains `ack: bool` (serde default = true, N-1-safe: an older brain's input still acks = today's behavior). `serve_attach`'s operator path calls `send_effect_no_ack` (ack=false) → `dispatch_input` writes NO applied frame → the per-conn handler never writes back while servicing the flood → it always drains → no deadlock (cures ANY input flood). `shellchan` keeps `send_effect` (ack=true) and its `Applied`-wait. Exactly-once PRESERVED: the broker still dedups by (session, op_id) at the applied-set regardless of the ack. N-1 caveat: an OLD resident broker (self-update window) ignores `ack=false` → still acks → the deadlock persists until a broker restart (inherent KNOWN-HAZARDS 7.9 broker-resident-wire-change class). (v0.13.0)"

+required_stages = ["impl", "unit", "int"]  # ACTIVATED v0.13.0 P1b (todlando, the real ship-blocker behind the ctrl+V wedge; doyle gates). impl = InputReq.ack (serde default_true) + Brain::send_effect_no_ack (send_effect_inner shared body) + serve_attach operator path -> send_effect_no_ack + dispatch_input gates send_frame(applied) on req.ack. unit = InputReq.ack serde default=true on an absent field (N-1) + dispatch_input emits the applied frame when ack=true and NONE when ack=false + exactly-once on the no-ack path (a replayed/duplicate op_id still dedups at apply_once, no double PTY write). int = REPRO-FIRST (the keystone, the exact gate the wedge needed): a flood of N>pipe-buffer (deterministic, e.g. 64) operator input frames through serve_attach on ONE conn -> PRE-FIX deadlocks (ack-send-START-without-END, handler stops draining; a concurrent attach / the detach cannot be serviced) -> POST-FIX drains all N + the session stays live + a concurrent attach still OPENS. Real broker+brain, NO mocks. doc = KNOWN-HAZARDS entry (the deadlock + the N-1 broker-resident caveat). clippy/traceable/docs-drift."


Add impl-evidence tags on the real changes (msg field, broker gate, attach path).


    /// brain's `input` (no `ack` field) still acks — today's behavior, N-1-safe.
    #[serde(default = "default_true")]
    pub ack: bool,
}

    /// brain's `input` (no `ack` field) still acks — today's behavior, N-1-safe.
    // [impl->REQ-HAZARD-INPUT-ACK-BACKPRESSURE]
    #[serde(default = "default_true")]
    pub ack: bool,
}


The file C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\src\msg.rs has been updated successfully. (file state is current in your context — no need to Read it back)

    /// brain's `input` (no `ack` field) still acks — today's behavior, N-1-safe.
    #[serde(default = "default_true")]
    pub ack: bool,
}

    /// brain's `input` (no `ack` field) still acks — today's behavior, N-1-safe.
    // [impl->REQ-HAZARD-INPUT-ACK-BACKPRESSURE]
    #[serde(default = "default_true")]
    pub ack: bool,
}

/// ack). Exactly-once is unaffected: dedup happens at the broker's applied-set

/// regardless of whether the ack is emitted. `default = true` so an OLDER

+    // [impl->REQ-HAZARD-INPUT-ACK-BACKPRESSURE]


                // applied-set is the exactly-once anchor, not the ack frame).
                if req.ack {

                // applied-set is the exactly-once anchor, not the ack frame).
                // [impl->REQ-HAZARD-INPUT-ACK-BACKPRESSURE]
                if req.ack {


                // applied-set is the exactly-once anchor, not the ack frame).
                if req.ack {

                // applied-set is the exactly-once anchor, not the ack frame).
                // [impl->REQ-HAZARD-INPUT-ACK-BACKPRESSURE]
                if req.ack {

// always drains. The acked path (`shellchan`, one-at-a-time) still

// gets its `KIND_APPLIED`. Dedup is unaffected either way (the

+                // [impl->REQ-HAZARD-INPUT-ACK-BACKPRESSURE]


                                // broker's per-conn handler. Dedup still holds at the
                                // broker (op_id applied-set).
                                Some(ServeRole::Controller) => {
                                    brain.send_effect_no_ack(op_id, &bytes)?
                                }

                                // broker's per-conn handler. Dedup still holds at the
                                // broker (op_id applied-set).
                                // [impl->REQ-HAZARD-INPUT-ACK-BACKPRESSURE]
                                Some(ServeRole::Controller) => {
                                    brain.send_effect_no_ack(op_id, &bytes)?
                                }


The file C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\src\attach.rs has been updated successfully. (file state is current in your context — no need to Read it back)

                                // broker's per-conn handler. Dedup still holds at the
                                // broker (op_id applied-set).
                                Some(ServeRole::Controller) => {
                                    brain.send_effect_no_ack(op_id, &bytes)?
                                }

                                // broker's per-conn handler. Dedup still holds at the
                                // broker (op_id applied-set).
                                // [impl->REQ-HAZARD-INPUT-ACK-BACKPRESSURE]
                                Some(ServeRole::Controller) => {
                                    brain.send_effect_no_ack(op_id, &bytes)?
                                }

// back would fill the return direction and deadlock the

+                                // [impl->REQ-HAZARD-INPUT-ACK-BACKPRESSURE]


P1 ctrl+V wedge — diagnose then fix

Primary fix: conditional ack


Task 14 done. Docs (task 17): amend KNOWN-HAZARDS 7.18 + add 7.19 (deadlock). Read current 7.18.


<owl_messages>
<owl_message from="doyle" priority="high">NEW (folds into your P1 rework — operator HITL): scroll is BROKEN in rc. ROOT = P1's EnableMouseCapture made WT forward ALL mouse to rc incl scroll-wheel; our mouse handler drops everything except right-down -> scroll dropped -> CC never scrolls (and WT's native scrollback is stolen by the capture). Operator RULING: keep mouse capture + right-click bracketed paste AND forward scroll to CC (don't drop it).

DESIGN (proper terminal-proxy; folds into the same mouse handler you're reworking):
1. TRACK CC's mouse-reporting mode: scan CC's OUTPUT stream (the AttachDecoder/stdout path) for the DECSET set/reset — ESC[?1000h/1002h/1003h (mouse on) + ESC[?1006h (SGR ext) and their ...l (off). Keep a bool mouse_mode (+ sgr ext). CC is a rich TUI, almost certainly enables it.
2. MOUSE HANDLER (with capture on):
   - right-button DOWN -> bracketed clipboard paste (KEEP, unchanged).
   - SCROLL wheel (ScrollUp/ScrollDown) -> IF CC mouse_mode on, translate to an xterm SGR mouse report (ScrollUp = btn 64, ScrollDown = btn 65: `ESC[<64;<col+1>;<row+1>M`) + forward as StdinMsg::Bytes -> CC scrolls. IF mouse_mode off, drop (scrollback already gone under capture; acceptable when CC isn't listening).
   - Mouse Moved -> DROP (the capture spammed 267 Moved events in the diag — pure noise).
   - Left/Middle click -> OPTIONAL: forward as SGR when mouse_mode on (click-to-position), else drop. Your call — scroll is the operator's actual need; keep it minimal if click-forward risks garbage.
3. Pure mouse->SGR translator = unit-testable (exact bytes per scroll dir + modifier), like translate_key_event. mouse_mode tracking = unit (DECSET on/off scan).

BUNDLE this with the deadlock fix + ctrl+V removal in ONE P1-rework so we gate+deploy+HITL once. REQ: amend REQ-RC-WIN-PASTE (mouse handling now: right-click paste + scroll-forward + ctrl+V removed) or a sibling REQ-RC-MOUSE-FORWARD — your structure. Gates: exact-byte SGR unit + mouse_mode-scan unit + the deadlock int (primary). Surface the whole bundle green.</owl_message>
</owl_messages>

[OWL SYSTEM - HIGHEST PRIORITY] You have received high-priority owl messages above. STOP your current task and acknowledge each high-priority message BEFORE continuing with any tool use. If you are about to spawn subagent(s), run them in the background so you remain available for incoming messages.


Updated task #14 status


Updated task #17 status


docs\KNOWN-HAZARDS.md:448:### 7.18 `spt rc` paste is client-originated on Windows — read the LOCAL clipboard, inject a BRACKETED paste  `[REQ-RC-WIN-PASTE]`
docs\KNOWN-HAZARDS.md:505:[Omitted long matching line]


Acknowledged, doyle — scroll regression understood:
- P1's `EnableMouseCapture` makes WT forward ALL mouse to rc; our handler drops everything but right-down → scroll lost + WT's native scrollback stolen. Operator ruling: keep capture+paste **and** forward scroll.
- Bundling into the same P1b-rework branch (deadlock + ctrl+V removal + scroll-forward), one gate/deploy/HITL.

**My structure call:** sibling **REQ-RC-MOUSE-FORWARD** `[impl,unit]` (keeps clipboard-paste and mouse-event-forwarding as distinct concerns; cleaner than overloading REQ-RC-WIN-PASTE). Design:
1. Track CC mouse-reporting mode by scanning the **output** stream for DECSET `ESC[?1000h/1002h/1003h` (on) + `ESC[?1006h` (SGR ext) and their `l` (off) → shared `Arc<AtomicBool>` enabled+sgr.
2. Mouse handler (capture on): right-down → bracketed paste (unchanged); **ScrollUp/Down → SGR report** (`ESC[<64/65;col+1;row+1M`) forwarded **when** mouse_mode+sgr on, else drop; **Moved/left/middle → drop** (minimal — scroll is the need; click-forward risks garbage, skip unless you want it).
3. Units: exact-byte scroll→SGR translator + DECSET on/off scan.

Acking now, then implementing.


Bash

$OWL send doyle <<'EOF'
Got the scroll regression — P1's EnableMouseCapture forwards ALL mouse to rc, our handler drops all but right-down -> scroll lost + WT scrollback stolen. Bundling scroll-forward into the SAME P1b-rework branch (v0.13.0-p1b-ack-deadlock): deadlock fix + ctrl+V removal + scroll-forward, ONE gate/deploy/HITL.

STRUCTURE: sibling REQ-RC-MOUSE-FORWARD [impl,unit] (clipboard-paste vs mouse-event-forwarding = distinct concerns). DESIGN per your proxy:
1. Track CC mouse-mode: scan the OUTPUT stream (the bytes rc writes to stdout) for DECSET ESC[?1000h/1002h/1003h (mouse on) + ESC[?1006h (SGR ext) and their ...l (off) -> shared Arc<AtomicBool> {enabled, sgr}; pump-thread writes, stdin-thread reads.
2. Mouse handler (capture on): right-DOWN -> bracketed paste (KEEP). ScrollUp/Down -> SGR report ESC[<64;col+1;row+1M (up=64) / ESC[<65;..M (down=65), forwarded ONLY when enabled && sgr (CC sets 1006; if enabled-but-not-sgr I DROP rather than emit a legacy X10 report CC may misread -- conservative, your "minimal" call). Moved/left/middle -> DROP (scroll is the need; click-forward risks garbage, skipping unless you want click-to-position).
3. Units: exact-byte scroll->SGR (per dir + the col/row+1 offset) + DECSET on/off scan (1000/1002/1003/1006 set & reset, mixed streams, split-across-chunk if cheap).

One judgment to flag: SGR mouse coords need the wheel event's col/row. crossterm MouseEvent carries column/row (0-based) -> I emit col+1/row+1 (xterm 1-based). Building now; will surface the whole bundle (deadlock int + ctrl+V removal + scroll units) green for gate.
EOF

Ack scroll regression + design to doyle


➜ SENT:doyle


Now implement scroll-forward. Add task, then find the rc output→stdout path to hook the DECSET scan.


TaskCreate

Scroll-forward: mouse_mode scan + scroll→SGR

REQ-RC-MOUSE-FORWARD: track CC mouse-mode via DECSET scan on output stream (Arc<AtomicBool> enabled+sgr); forward ScrollUp/Down as SGR reports when on; keep right-click paste; drop moved/clicks. Units: exact-byte SGR + DECSET scan.


Task #19 created successfully: Scroll-forward: mouse_mode scan + scroll→SGR


crates\spt\src\rc.rs:301:/// mouse-capture escapes written to its stdout. Stable within a session.
crates\spt\src\rc.rs:318:        // only — a piped stdin keeps a clean stdout for the e2e byte tests.
crates\spt\src\rc.rs:321:            let _ = crossterm::execute!(std::io::stdout(), crossterm::event::EnableMouseCapture);
crates\spt\src\rc.rs:330:            let _ = crossterm::execute!(std::io::stdout(), crossterm::event::DisableMouseCapture);
crates\spt\src\rc.rs:407:fn spawn_stdin_reader(tx: mpsc::Sender<StdinMsg>) {
crates\spt\src\rc.rs:512:pub fn run_attach(endpoint_id: &str, intent: AttachIntent) -> Result<(), String> {
crates\spt\src\rc.rs:588:        spawn_stdin_reader(tx);
crates\spt\src\rc.rs:601:    let mut stdout = std::io::stdout();
crates\spt\src\rc.rs:608:        &mut stdout,
crates\spt\src\rc.rs:616:            let _ = writeln!(stdout, "\r\n[session '{endpoint_id}' ended ({code:?})]");
crates\spt\src\rc.rs:620:            let _ = writeln!(stdout, "\r\n[detached — '{endpoint_id}' still running]");
crates\spt\src\rc.rs:626:                stdout,
crates\spt\src\rc.rs:637:                stdout,
crates\spt\src\rc.rs:649:                stdout,
crates\spt\src\rc.rs:728:/// `AttachRecord::Output` → stdout; end on `Exit`, EOF, or detach.
crates\spt\src\rc.rs:736:    stdout: &mut std::io::Stdout,
crates\spt\src\rc.rs:822:                            stdout.write_all(&bytes).map_err(|e| format!("write: {e}"))?;
crates\spt\src\rc.rs:823:                            stdout.flush().map_err(|e| format!("flush: {e}"))?;
crates\spt\src\rc.rs:838:                                        stdout,
crates\spt\src\rc.rs:841:                                    let _ = stdout.flush();


578	    // as `NetStreamData` events (the open + Request do not auto-subscribe).
579	    brain
580	        .net_stream_subscribe(stream_id, 0)
581	        .map_err(|e| format!("stream subscribe: {e}"))?;
582
583	    let _raw = RawGuard::enable();
584	    let rx = if view {
585	        None
586	    } else {
587	        let (tx, rx) = mpsc::channel();
588	        spawn_stdin_reader(tx);
589	        Some(rx)
590	    };
591
592	    // Controller: send the INITIAL terminal size so the PTY matches from the
593	    // start (REQ-RCVIEW-1). Best-effort — a failure just leaves the default size.
594	    if !view {
595	        if let Ok((cols, rows)) = crossterm::terminal::size() {
596	            let _ = send_attach_resize(&mut brain, stream_id, rows, cols);
597	        }
598	    }
599
600	    let mut decoder = AttachDecoder::new();
601	    let mut stdout = std::io::stdout();
602	    let outcome = pump(
603	        &mut brain,
604	        stream_id,
605	        &ops,
606	        rx.as_ref(),
607	        &mut decoder,
608	        &mut stdout,
609	        !view, // controller drives resize
610	    );
611
612	    // Out of raw mode (guard drops after) before the parting line.
613	    drop(_raw);
614	    match outcome {
615	        Ok(PumpEnd::Exited(code)) => {
616	            let _ = writeln!(stdout, "\r\n[session '{endpoint_id}' ended ({code:?})]");
617	            Ok(())
618	        }
619	        Ok(PumpEnd::Detached) => {
620	            let _ = writeln!(stdout, "\r\n[detached — '{endpoint_id}' still running]");
621	            Ok(())
622	        }

    // as `NetStreamData` events (the open + Request do not auto-subscribe).
    brain
        .net_stream_subscribe(stream_id, 0)
        .map_err(|e| format!("stream subscribe: {e}"))?;

    let _raw = RawGuard::enable();
    let rx = if view {
        None
    } else {
        let (tx, rx) = mpsc::channel();
        spawn_stdin_reader(tx);
        Some(rx)
    };

    // Controller: send the INITIAL terminal size so the PTY matches from the
    // start (REQ-RCVIEW-1). Best-effort — a failure just leaves the default size.
    if !view {
        if let Ok((cols, rows)) = crossterm::terminal::size() {
            let _ = send_attach_resize(&mut brain, stream_id, rows, cols);
        }
    }

    let mut decoder = AttachDecoder::new();
    let mut stdout = std::io::stdout();
    let outcome = pump(
        &mut brain,
        stream_id,
        &ops,
        rx.as_ref(),
        &mut decoder,
        &mut stdout,
        !view, // controller drives resize
    );

    // Out of raw mode (guard drops after) before the parting line.
    drop(_raw);
    match outcome {
        Ok(PumpEnd::Exited(code)) => {
            let _ = writeln!(stdout, "\r\n[session '{endpoint_id}' ended ({code:?})]");
            Ok(())
        }
        Ok(PumpEnd::Detached) => {
            let _ = writeln!(stdout, "\r\n[detached — '{endpoint_id}' still running]");
            Ok(())
        }


Implementing scroll-forward. Add cross-platform `MouseMode` + DECSET scanner after `StdinMsg`.


<owl_messages>
<owl_message from="doyle" priority="high">Design approved — all correct calls:
- col+1/row+1 (crossterm 0-based -> xterm 1-based SGR): CORRECT, that's the xterm convention.
- DROP when enabled-but-not-sgr (don't emit legacy X10): right call — conservative beats garbage CC may misread.
- Moved/left/middle DROP: fine, scroll is the need; no click-to-position (avoids garbage).
- Sibling REQ-RC-MOUSE-FORWARD [impl,unit]: good separation from clipboard-paste.
- mouse-mode Arc<AtomicBool>{enabled,sgr}, pump writes / stdin reads: clean.

One gate note: make the DECSET scan robust to the sequence SPLIT ACROSS output chunks (a unit for split ESC[?1006h is worth it — CC's enable may land across a decoder boundary). You flagged "if cheap" — do it, it's a real edge.

Build the bundle. Surface green: deadlock repro-int (keystone) + ctrl+V-removal + scroll-SGR/DECSET units. Then I gate -> re-deploy -> operator HITL all.</owl_message>
</owl_messages>

[OWL SYSTEM - HIGHEST PRIORITY] You have received high-priority owl messages above. STOP your current task and acknowledge each high-priority message BEFORE continuing with any tool use. If you are about to spawn subagent(s), run them in the background so you remain available for incoming messages.


44
45	/// A message from the stdin reader thread to the pump.
46	enum StdinMsg {
47	    /// Raw bytes to forward to the session as `AttachRecord::Input`.
48	    Bytes(Vec<u8>),
49	    /// The operator hit the detach keybind — end the viewport, leave the session.
50	    Detach,
51	}
52
53	/// Mint operator op ids from a random 64-bit base so two `spt rc` sessions never
54	/// collide in the broker's net-op journal (a journaled `send_attach_input` op is
55	/// exactly-once at the PTY — a reused id from a prior session would be deduped,

/// A message from the stdin reader thread to the pump.
enum StdinMsg {
    /// Raw bytes to forward to the session as `AttachRecord::Input`.
    Bytes(Vec<u8>),
    /// The operator hit the detach keybind — end the viewport, leave the session.
    Detach,
}

/// Mint operator op ids from a random 64-bit base so two `spt rc` sessions never
/// collide in the broker's net-op journal (a journaled `send_attach_input` op is
/// exactly-once at the PTY — a reused id from a prior session would be deduped,


Acked, doyle — all design calls confirmed, and the split-across-chunks DECSET scan is exactly why I'm building a carry-buffer scanner (with a split unit). Building the bundle now.


Bash

$OWL send doyle <<'EOF'
All approvals noted. Split-across-chunks already in the design: a MouseModeScanner with a bounded carry buffer (pump-local, single-threaded) that prepends leftover partial-ESC bytes to the next chunk before scanning, writes results into the shared Arc<MouseMode>{enabled,sgr} atomics. Unit covers ESC[?1006h split across two feeds + ESC[?1002;1006h combined + set/reset. Building the full bundle (deadlock conditional-ack + ctrl+V removal + scroll-SGR/DECSET) on v0.13.0-p1b-ack-deadlock; deadlock repro-int delegated to spt-test-engineer. Surfacing green when gates pass.
EOF

Ack split-chunk note


Now build the scroll-forward machinery. Cross-platform `MouseMode` + scanner after `StdinMsg`.


Edit

/// A message from the stdin reader thread to the pump.
enum StdinMsg {
    /// Raw bytes to forward to the session as `AttachRecord::Input`.
    Bytes(Vec<u8>),
    /// The operator hit the detach keybind — end the viewport, leave the session.
    Detach,
}

/// A message from the stdin reader thread to the pump.
enum StdinMsg {
    /// Raw bytes to forward to the session as `AttachRecord::Input`.
    Bytes(Vec<u8>),
    /// The operator hit the detach keybind — end the viewport, leave the session.
    Detach,
}

/// The harness's MOUSE-REPORTING mode, tracked from its OUTPUT stream's DECSET
/// sequences (REQ-RC-MOUSE-FORWARD). On Windows `spt rc` captures the mouse (for
/// right-click paste, REQ-RC-WIN-PASTE), which means WT forwards scroll-wheel
/// events to us instead of scrolling its own buffer — so to keep scroll working we
/// must FORWARD wheel events to the harness, but ONLY when the harness has asked
/// for mouse reporting (else the bytes are garbage). The pump thread WRITES these
/// (scanning output); the Windows stdin reader READS them to decide whether to
/// forward a scroll. Two atomics so the cross-thread read is lock-free. Tracked on
/// all platforms (the scan is cheap + keeps the model correct); only the Windows
/// reader acts on it (Unix terminals scroll natively, no capture).
#[derive(Default)]
struct MouseMode {
    /// The harness requested mouse reporting (DECSET 1000 / 1002 / 1003).
    enabled: std::sync::atomic::AtomicBool,
    /// The harness requested SGR extended coordinates (DECSET 1006). We forward a
    /// scroll only when this is on (the modern encoding the harness will parse).
    sgr: std::sync::atomic::AtomicBool,
}

/// Scans a harness OUTPUT byte stream for the mouse-reporting DECSET set/reset
/// sequences and updates a [`MouseMode`] (REQ-RC-MOUSE-FORWARD). Pump-local +
/// single-threaded, so it owns a small `carry` buffer to survive a sequence SPLIT
/// across two output chunks (the harness's mouse-enable can land across a decoder
/// boundary). Recognizes `ESC[?<n>[;<n>...]h|l` private-mode sequences; for each
/// `n` in {1000,1002,1003} sets `enabled = (final=='h')`, for 1006 sets `sgr`.
#[derive(Default)]
struct MouseModeScanner {
    /// A partial `ESC[?…` left dangling at a chunk boundary, prepended next feed.
    carry: Vec<u8>,
}

/// The longest incomplete private-mode prefix we keep across a chunk boundary. A
/// longer dangling `ESC[?…` with no final `h`/`l` is not a mode-set we care about
/// (runaway) — dropped rather than buffered unboundedly.
const MOUSE_DECSET_CARRY_MAX: usize = 32;

impl MouseModeScanner {
    /// Feed one output chunk; apply any complete DECSET mouse-mode toggles to
    /// `mode`, carrying a trailing partial sequence to the next call.
    fn feed(&mut self, mode: &MouseMode, chunk: &[u8]) {
        // Fast path: no ESC anywhere and nothing carried ⇒ nothing to do.
        if self.carry.is_empty() && !chunk.contains(&0x1b) {
            return;
        }
        let mut buf = std::mem::take(&mut self.carry);
        buf.extend_from_slice(chunk);
        let mut i = 0;
        while i < buf.len() {
            if buf[i] != 0x1b {
                i += 1;
                continue;
            }
            match parse_decset_private(&buf[i..]) {
                DecsetParse::Complete { consumed, params, set } => {
                    for p in params {
                        apply_mouse_mode(mode, p, set);
                    }
                    i += consumed;
                }
                // An incomplete `ESC[?…` at the tail: carry it (bounded) for the
                // next chunk; a too-long dangling run is treated as not-a-DECSET.
                DecsetParse::Incomplete => {
                    let tail = &buf[i..];
                    if tail.len() <= MOUSE_DECSET_CARRY_MAX {
                        self.carry = tail.to_vec();
                    }
                    return;
                }
                // Not a private-mode sequence — skip this ESC, keep scanning.
                DecsetParse::NotDecset => i += 1,
            }
        }
    }
}

/// Result of trying to parse a private-mode set/reset at the start of `s` (which
/// begins with `ESC`).
enum DecsetParse {
    /// A complete `ESC[?<params>h|l`: the byte length consumed, the numeric
    /// params, and `set` (`true` for `h`, `false` for `l`).
    Complete { consumed: usize, params: Vec<u16>, set: bool },
    /// `ESC[?…` with no final `h`/`l` yet (chunk boundary) — carry + retry.
    Incomplete,
    /// `s` does not start a private-mode sequence (`ESC` of something else).
    NotDecset,
}

/// Parse a single `ESC[?<digits>[;<digits>…]h|l` private-mode sequence at the
/// start of `s`. Pure, so the split-across-chunk behavior unit-tests directly.
fn parse_decset_private(s: &[u8]) -> DecsetParse {
    // Need at least `ESC [ ?`.
    if s.len() < 3 {
        // Could be the very start of one split across the boundary.
        if s.first() == Some(&0x1b) && s.get(1).is_none_or(|&b| b == b'[')
            && s.get(2).is_none_or(|&b| b == b'?')
        {
            return DecsetParse::Incomplete;
        }
        return DecsetParse::NotDecset;
    }
    if s[1] != b'[' || s[2] != b'?' {
        return DecsetParse::NotDecset;
    }
    let mut params: Vec<u16> = Vec::new();
    let mut cur: Option<u32> = None;
    let mut j = 3;
    while j < s.len() {
        match s[j] {
            b'0'..=b'9' => {
                let d = (s[j] - b'0') as u32;
                cur = Some(cur.unwrap_or(0).saturating_mul(10).saturating_add(d));
            }
            b';' => {
                params.push(cur.unwrap_or(0).min(u16::MAX as u32) as u16);
                cur = None;
            }
            b'h' | b'l' => {
                if let Some(v) = cur {
                    params.push(v.min(u16::MAX as u32) as u16);
                }
                return DecsetParse::Complete {
                    consumed: j + 1,
                    params,
                    set: s[j] == b'h',
                };
            }
            // Any other byte ⇒ this isn't a clean private-mode set/reset.
            _ => return DecsetParse::NotDecset,
        }
        j += 1;
    }
    // Ran out before a final `h`/`l` ⇒ split across the chunk boundary.
    DecsetParse::Incomplete
}

/// Apply one DECSET private-mode toggle to the tracked [`MouseMode`].
fn apply_mouse_mode(mode: &MouseMode, param: u16, set: bool) {
    use std::sync::atomic::Ordering::Release;
    match param {
        1000 | 1002 | 1003 => mode.enabled.store(set, Release),
        1006 => mode.sgr.store(set, Release),
        _ => {}
    }
}