199	            parent_pid: None,
200	            cwd: None,
201	            status: None,
202	            last_active_ms: None,
203	            driven_by: None,
204	            viewer_count: None,
205	            rest_state: None,
206	            dormant_since_ms: None,
207	            auto_suspend_after_ms: None,
208	            resources: None,
209	            home_subnet: None,
210	            adapter: None,
211	            psyche_host_error: None,
212	            controllable: None,
213	        }
214	    }
215	}
216
217	/// The serialized read-modify-write path for `info.json` (M11-W0.4,
218	/// REQ-DAEMON-1): EVERY mutating setter funnels through here so concurrent
219	/// writers — cross-process by design (the brain's pulse stamps recency/rest, the
220	/// spt process's signoff marks offline, the broker stamps viewers) — cannot lose
221	/// an update. The lost-update this closes: a pulse `set_last_active` read→write
222	/// interleaving a `mark_offline` could clobber `status=offline` back to online,
223	/// so the un-host never fired and a signed-off agent kept its Psyche.

            parent_pid: None,
            cwd: None,
            status: None,
            last_active_ms: None,
            driven_by: None,
            viewer_count: None,
            rest_state: None,
            dormant_since_ms: None,
            auto_suspend_after_ms: None,
            resources: None,
            home_subnet: None,
            adapter: None,
            psyche_host_error: None,
            controllable: None,
        }
    }
}

/// The serialized read-modify-write path for `info.json` (M11-W0.4,
/// REQ-DAEMON-1): EVERY mutating setter funnels through here so concurrent
/// writers — cross-process by design (the brain's pulse stamps recency/rest, the
/// spt process's signoff marks offline, the broker stamps viewers) — cannot lose
/// an update. The lost-update this closes: a pulse `set_last_active` read→write
/// interleaving a `mark_offline` could clobber `status=offline` back to online,
/// so the un-host never fired and a signed-off agent kept its Psyche.


Edit

            psyche_host_error: None,
            controllable: None,
        }
    }
}

            psyche_host_error: None,
            controllable: None,
            input_backpressure: None,
        }
    }
}


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

            psyche_host_error: None,
            controllable: None,
        }
    }
}

            psyche_host_error: None,
            controllable: None,
            input_backpressure: None,
        }
    }
}

adapter: None,
psyche_host_error: None,
controllable: None,
+            input_backpressure: None,
}
}
}


425	        assert!(raw.contains("\"state\":\"live_agent\""));
426	    }
427
428	    // [unit->REQ-HAZARD-INFO-JSON-TORN-READ] BUSY sentinel: full parse keeps it;
429	    // read_pid yields None (no usable pid).
430	    #[test]
431	    fn busy_sentinel_round_trips_and_read_pid_is_none() {
432	        let d = perch();
433	        let info = InfoJson {
434	            id: "doyle".to_string(),
435	            started: "t".to_string(),
436	            pid: PidValue::Busy("BUSY".to_string()),
437	            session_id: "sid".to_string(),
438	            state: "ready_agent".to_string(),
439	            parent_pid: None,
440	            cwd: None,
441	            status: None,
442	            last_active_ms: None,
443	            driven_by: None,
444	            viewer_count: None,
445	            rest_state: None,
446	            dormant_since_ms: None,
447	            auto_suspend_after_ms: None,
448	            resources: None,
449	            home_subnet: None,
450	            adapter: None,
451	            psyche_host_error: None,
452	            controllable: None,
453	        };
454	        write_info(d.path(), &info).unwrap();

        assert!(raw.contains("\"state\":\"live_agent\""));
    }

    // [unit->REQ-HAZARD-INFO-JSON-TORN-READ] BUSY sentinel: full parse keeps it;
    // read_pid yields None (no usable pid).
    #[test]
    fn busy_sentinel_round_trips_and_read_pid_is_none() {
        let d = perch();
        let info = InfoJson {
            id: "doyle".to_string(),
            started: "t".to_string(),
            pid: PidValue::Busy("BUSY".to_string()),
            session_id: "sid".to_string(),
            state: "ready_agent".to_string(),
            parent_pid: None,
            cwd: None,
            status: None,
            last_active_ms: None,
            driven_by: None,
            viewer_count: None,
            rest_state: None,
            dormant_since_ms: None,
            auto_suspend_after_ms: None,
            resources: None,
            home_subnet: None,
            adapter: None,
            psyche_host_error: None,
            controllable: None,
        };
        write_info(d.path(), &info).unwrap();


            psyche_host_error: None,
            controllable: None,
        };
        write_info(d.path(), &info).unwrap();

            psyche_host_error: None,
            controllable: None,
            input_backpressure: None,
        };
        write_info(d.path(), &info).unwrap();


            psyche_host_error: None,
            controllable: None,
        };
        write_info(d.path(), &info).unwrap();

            psyche_host_error: None,
            controllable: None,
            input_backpressure: None,
        };
        write_info(d.path(), &info).unwrap();


Now the setter, after set_viewer_count.


pub fn set_viewer_count(perch_path: &Path, count: u32) -> std::io::Result<()> {
    mutate_info(perch_path, |rec| {
        rec.viewer_count = if count == 0 { None } else { Some(count) }
    })
}

pub fn set_viewer_count(perch_path: &Path, count: u32) -> std::io::Result<()> {
    mutate_info(perch_path, |rec| {
        rec.viewer_count = if count == 0 { None } else { Some(count) }
    })
}

/// Stamp (`true`) or clear (`false`) the session's PTY `input_backpressure`
/// signal (v0.13.0 P0, REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The broker is the
/// single writer (it owns the per-session input-writer thread): `true` is set on
/// a full-FIFO DROP, `false` clears the field on the next accepted enqueue
/// (heal-on-resume). Serialized via [`mutate_info`]; best-effort like
/// [`set_viewer_count`].
// [impl->REQ-HAZARD-PTY-INPUT-WRITER-WEDGE]
pub fn set_input_backpressure(perch_path: &Path, dropping: bool) -> std::io::Result<()> {
    mutate_info(perch_path, |rec| {
        rec.input_backpressure = if dropping { Some(true) } else { None }
    })
}


pub fn set_viewer_count(perch_path: &Path, count: u32) -> std::io::Result<()> {
    mutate_info(perch_path, |rec| {
        rec.viewer_count = if count == 0 { None } else { Some(count) }
    })
}

/// Stamp (`true`) or clear (`false`) the session's PTY `input_backpressure`
/// signal (v0.13.0 P0, REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The broker is the
/// single writer (it owns the per-session input-writer thread): `true` is set on
/// a full-FIFO DROP, `false` clears the field on the next accepted enqueue
/// (heal-on-resume). Serialized via [`mutate_info`]; best-effort like
/// [`set_viewer_count`].
// [impl->REQ-HAZARD-PTY-INPUT-WRITER-WEDGE]
pub fn set_input_backpressure(perch_path: &Path, dropping: bool) -> std::io::Result<()> {
    mutate_info(perch_path, |rec| {
        rec.input_backpressure = if dropping { Some(true) } else { None }
    })
}

+/// Stamp (`true`) or clear (`false`) the session's PTY `input_backpressure`

+/// signal (v0.13.0 P0, REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The broker is the

+/// single writer (it owns the per-session input-writer thread): `true` is set on

+/// a full-FIFO DROP, `false` clears the field on the next accepted enqueue

+/// (heal-on-resume). Serialized via [`mutate_info`]; best-effort like

+pub fn set_input_backpressure(perch_path: &Path, dropping: bool) -> std::io::Result<()> {

+        rec.input_backpressure = if dropping { Some(true) } else { None }

/// Stamp (`Some(reason)`) or clear (`None`) the psyche-host-failure state on a

/// live perch (v0.8.1, REQ-HAZARD-LIVEHOST-BOOT-RACE) — the harness-reachable

/// mirror of the brain's invisible `LIVEHOST_PSYCHE_FAIL` stderr line.


Activate REQ + registry note

Set REQ-HAZARD-PTY-INPUT-WRITER-WEDGE required_stages = ["impl","unit","int"]; add note in REQ-HAZARD-EFFECT-JOURNAL-PTY-WEDGE that fix (2) is delivered here.

Add set_input_backpressure to spt-store

Add input_backpressure: Option<bool> field to InfoJson + set_input_backpressure setter mirroring set_viewer_count.

Build input-writer thread + sink in broker

Define InputRecord + InputWriter (bounded sync_channel + writer thread = sole write_input caller) mirroring viewer_writer/controller_writer. enqueue() try_send; full=>DROP+stamp INPUT_BACKPRESSURE; heal on accepted enqueue. Spawn at dispatch_spawn, store on HostedSession.

Convert write_input callers to enqueue

dispatch_input (apply_once effect + None branch), dispatch_endpoint_input, run_inject_worker, flush_inject_floor -> enqueue via InputWriter. Preserve inject-floor choreography under lone writer.

Docs: KNOWN-HAZARDS 7.17 + CONTEXT note

KNOWN-HAZARDS 7.17 entry + CONTEXT note: PTY input single-writer; callers enqueue never block; drop+signal on wedged harness.

Tests via spt-test-engineer (unit+int)

Delegate to spt-test-engineer (background): unit (drain order, drop/heal injected channel, exactly-once enqueue) + int (repro-first park, concurrent attach, keystroke accepted, DROP+backpressure+heal, unix forkpty leg).

Gates + commit + surface to doyle

clippy --workspace -D warnings=0; traceable-reqs check EXIT=0; xtask check docs-drift; rebuild real bin; commit (trailer Co-authored by: todlando); $OWL send doyle green.


Check for other InfoJson struct literals needing the new field.


crates\spt\src\picker\view.rs:450:            controllable: None,
crates\spt\src\picker\model.rs:250:    pub controllable: Option<bool>,
crates\spt\src\picker\model.rs:875:            controllable: None,
crates\spt\src\picker\data.rs:204:                controllable: rec.as_ref().and_then(|r| r.controllable),
crates\spt\src\picker\data.rs:258:                controllable: None,
crates\spt\src\picker\data.rs:364:            controllable: None,
crates\spt\src\api\startup.rs:190:    controllable: Option<bool>,
crates\spt-store\src\info.rs:176:    pub controllable: Option<bool>,
crates\spt-store\src\info.rs:212:            controllable: None,
crates\spt-store\src\info.rs:465:            controllable: None,
traceable-reqs.toml:532:[Omitted long matching line]
crates\spt-daemon\src\livehost.rs:839:            let seed = |id: &str, state: &str, controllable: Option<bool>| {


455	            last_active_ms: None,
456	            driven_by: None,
457	            viewer_count: None,
458	            rest_state: None,
459	            dormant_since_ms: None,
460	            auto_suspend_after_ms: None,
461	            resources: None,
462	            home_subnet: None,
463	            adapter: None,
464	            psyche_host_error: None,
465	            controllable: None,
466	            input_backpressure: None,
467	        };
468	        write_info(d.path(), &info).unwrap();
469	        assert_eq!(
470	            read_info(d.path()).unwrap().pid,
471	            PidValue::Busy("BUSY".to_string())
472	        );
473	        assert_eq!(read_pid(d.path()), None, "BUSY must yield no numeric pid");
474	    }

            last_active_ms: None,
            driven_by: None,
            viewer_count: None,
            rest_state: None,
            dormant_since_ms: None,
            auto_suspend_after_ms: None,
            resources: None,
            home_subnet: None,
            adapter: None,
            psyche_host_error: None,
            controllable: None,
            input_backpressure: None,
        };
        write_info(d.path(), &info).unwrap();
        assert_eq!(
            read_info(d.path()).unwrap().pid,
            PidValue::Busy("BUSY".to_string())
        );
        assert_eq!(read_pid(d.path()), None, "BUSY must yield no numeric pid");
    }


449	            pid: PidValue::Busy("BUSY".to_string()),
450	            session_id: "sid".to_string(),
451	            state: "ready_agent".to_string(),
452	            parent_pid: None,
453	            cwd: None,
454	            status: None,
455	            last_active_ms: None,
456	            driven_by: None,
457	            viewer_count: None,
458	            rest_state: None,
459	            dormant_since_ms: None,
460	            auto_suspend_after_ms: None,

            pid: PidValue::Busy("BUSY".to_string()),
            session_id: "sid".to_string(),
            state: "ready_agent".to_string(),
            parent_pid: None,
            cwd: None,
            status: None,
            last_active_ms: None,
            driven_by: None,
            viewer_count: None,
            rest_state: None,
            dormant_since_ms: None,
            auto_suspend_after_ms: None,


spt-store done. Now broker. Check imports first.


1	//! The broker kernel (B1 + B2) — the stable half of the broker/brain split.
2	//!
3	//! Per ADR-0004 §B the broker holds **only** the resources a live consumer
4	//! would lose continuity on if the logic restarted: the PTY master+writer and
5	//! the spawned child (both via [`spt_term::PtySession`]), and the accepted
6	//! client / listening sockets. It runs **no logic** — no routing, registry,
7	//! manifest parse, or lifecycle. It is a relay: spawn a child under a PTY,
8	//! forward its output to the brain, inject the brain's input, report exit.
9	//!
10	//! ## Gapless handoff (B2 — the load-bearing invariant)
11	//!
12	//! A brain may be killed and restarted at any moment (a routine self-update, a
13	//! crash). The broker — and every hosted child — must survive that untouched,
14	//! and the new brain must re-attach to the output stream **gaplessly and
15	//! exactly-once** (Spike #1 made real). Two mechanisms:
16	//!
17	//! - **Sequenced, buffered output.** Each session's output is appended to an
18	//!   [`OutputLog`]: every chunk gets a monotonic per-session `seq` and is held
19	//!   in a bounded ring. The current subscriber (a brain connection) receives
20	//!   chunks live; all subscriber writes happen *under the log lock*, so frames
21	//!   reach the brain in strict `seq` order with no interleave.
22	//! - **Resume from a cursor.** A reconnecting brain sends `subscribe` with its
23	//!   last-seen `seq + 1`; the broker replays the ring from there, then streams
24	//!   live. The brain dedups by `seq`. Output produced *while no brain was
25	//!   attached* sits in the ring and is replayed on the next subscribe — so a
26	//!   brain restart loses nothing.
27	//!
28	//! Sessions are therefore **broker-owned and outlive any one connection**: a
29	//! brain disconnect only detaches the subscriber; the child, its PTY, and the
30	//! output log persist. A session leaves the table only when its child exits
31	//! (reaped by the exit waiter) or is explicitly killed.
32	// [impl->REQ-DAEMON-2]
33
34	use std::collections::{HashMap, VecDeque};
35	use std::io;
36	use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
37	use std::sync::mpsc::{channel, sync_channel, Receiver, RecvTimeoutError, Sender, SyncSender, TrySendError};
38	use std::sync::{Arc, Mutex};
39	use std::thread::{self, JoinHandle};
40	use std::time::{Duration, Instant};
41
42	use interprocess::local_socket::{prelude::*, SendHalf, Stream};
43	use spt_term::{Drain, PtySession, SessionSurface, SurfaceSize};
44
45	use spt_net::net::attach::AttachIntent;
46	use spt_net::net::endpoint::EndpointAddr;
47	use spt_store::perch::{resolve_perch_path, ParentHint};
48
49	use crate::codec::{read_frame, write_frame};
50	use crate::effect::{EffectJournal, EffectKind, Outcome};
51	use crate::frame::{Envelope, Role};
52	use crate::msg::{
53	    applied_envelope, decode_bytes, displaced_envelope, endpoint_injected_envelope,
54	    net_dialed_envelope, output_envelope,
55	    size_envelope, subscribed_envelope, BrainRestarted, EndpointInputReq,

//! The broker kernel (B1 + B2) — the stable half of the broker/brain split.
//!
//! Per ADR-0004 §B the broker holds **only** the resources a live consumer
//! would lose continuity on if the logic restarted: the PTY master+writer and
//! the spawned child (both via [`spt_term::PtySession`]), and the accepted
//! client / listening sockets. It runs **no logic** — no routing, registry,
//! manifest parse, or lifecycle. It is a relay: spawn a child under a PTY,
//! forward its output to the brain, inject the brain's input, report exit.
//!
//! ## Gapless handoff (B2 — the load-bearing invariant)
//!
//! A brain may be killed and restarted at any moment (a routine self-update, a
//! crash). The broker — and every hosted child — must survive that untouched,
//! and the new brain must re-attach to the output stream **gaplessly and
//! exactly-once** (Spike #1 made real). Two mechanisms:
//!
//! - **Sequenced, buffered output.** Each session's output is appended to an
//!   [`OutputLog`]: every chunk gets a monotonic per-session `seq` and is held
//!   in a bounded ring. The current subscriber (a brain connection) receives
//!   chunks live; all subscriber writes happen *under the log lock*, so frames
//!   reach the brain in strict `seq` order with no interleave.
//! - **Resume from a cursor.** A reconnecting brain sends `subscribe` with its
//!   last-seen `seq + 1`; the broker replays the ring from there, then streams
//!   live. The brain dedups by `seq`. Output produced *while no brain was
//!   attached* sits in the ring and is replayed on the next subscribe — so a
//!   brain restart loses nothing.
//!
//! Sessions are therefore **broker-owned and outlive any one connection**: a
//! brain disconnect only detaches the subscriber; the child, its PTY, and the
//! output log persist. A session leaves the table only when its child exits
//! (reaped by the exit waiter) or is explicitly killed.
// [impl->REQ-DAEMON-2]

use std::collections::{HashMap, VecDeque};
use std::io;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::mpsc::{channel, sync_channel, Receiver, RecvTimeoutError, Sender, SyncSender, TrySendError};
use std::sync::{Arc, Mutex};
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant};

use interprocess::local_socket::{prelude::*, SendHalf, Stream};
use spt_term::{Drain, PtySession, SessionSurface, SurfaceSize};

use spt_net::net::attach::AttachIntent;
use spt_net::net::endpoint::EndpointAddr;
use spt_store::perch::{resolve_perch_path, ParentHint};

use crate::codec::{read_frame, write_frame};
use crate::effect::{EffectJournal, EffectKind, Outcome};
use crate::frame::{Envelope, Role};
use crate::msg::{
    applied_envelope, decode_bytes, displaced_envelope, endpoint_injected_envelope,
    net_dialed_envelope, output_envelope,
    size_envelope, subscribed_envelope, BrainRestarted, EndpointInputReq,


Build the input-writer. First add `input` field to HostedSession.


struct HostedSession {
    /// The PTY + child (PTY master/writer + spawned child process).
    session: Arc<PtySession>,
    /// The output drain pump (kept so teardown can stop it).
    #[allow(dead_code)] // held for ownership/teardown; not read directly
    drain: Drain,

struct HostedSession {
    /// The PTY + child (PTY master/writer + spawned child process).
    session: Arc<PtySession>,
    /// The session's dedicated PTY input-writer (v0.13.0 P0,
    /// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE): the SOLE caller of the blocking
    /// `write_input`. Every dispatch-side write ENQUEUES here and returns at once,
    /// so a paste burst that fills the harness's input buffer parks only the
    /// writer thread, never the broker dispatch thread.
    input: Arc<InputWriter>,
    /// The output drain pump (kept so teardown can stop it).
    #[allow(dead_code)] // held for ownership/teardown; not read directly
    drain: Drain,


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)

struct HostedSession {
    /// The PTY + child (PTY master/writer + spawned child process).
    session: Arc<PtySession>,
    /// The output drain pump (kept so teardown can stop it).
    #[allow(dead_code)] // held for ownership/teardown; not read directly
    drain: Drain,

struct HostedSession {
    /// The PTY + child (PTY master/writer + spawned child process).
    session: Arc<PtySession>,
    /// The session's dedicated PTY input-writer (v0.13.0 P0,
    /// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE): the SOLE caller of the blocking
    /// `write_input`. Every dispatch-side write ENQUEUES here and returns at once,
    /// so a paste burst that fills the harness's input buffer parks only the
    /// writer thread, never the broker dispatch thread.
    input: Arc<InputWriter>,
    /// The output drain pump (kept so teardown can stop it).
    #[allow(dead_code)] // held for ownership/teardown; not read directly
    drain: Drain,

+    /// The session's dedicated PTY input-writer (v0.13.0 P0,

+    /// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE): the SOLE caller of the blocking

+    /// `write_input`. Every dispatch-side write ENQUEUES here and returns at once,

+    /// so a paste burst that fills the harness's input buffer parks only the

+    /// writer thread, never the broker dispatch thread.


Now insert InputWriter definitions + convert flush_inject_floor.


/// Drain the inject floor's buffered controller input to the PTY **after** an
/// injected sequence, then release the floor. Each step takes the buffered bytes
/// OFF the lock before writing (the W1 rule: never hold a lock across a PTY
/// write); input arriving mid-flush keeps buffering, so strict order is preserved
/// until the buffer drains empty and the floor frees.
fn flush_inject_floor(floor: &Mutex<InjectFloor>, session: &PtySession) {
    loop {
        let chunk = floor.lock().unwrap().take_or_release();
        match chunk {
            Some(bytes) => {
                let _ = session.write_input(&bytes);
            }
            None => break,
        }
    }
}

/// One record queued to a session's dedicated PTY input-writer thread — the raw
/// bytes to write (v0.13.0 P0, REQ-HAZARD-PTY-INPUT-WRITER-WEDGE).
type InputRecord = Vec<u8>;

/// The per-session PTY input FIFO depth (records). Sized for a generous paste —
/// a wedged harness fills this and then DROPS, never blocking the dispatch
/// thread. The int gate shrinks it via `SPT_INPUT_QUEUE_DEPTH` so the
/// DROP+`INPUT_BACKPRESSURE` leg is forced in a handful of records rather than a
/// few hundred KB of real paste.
const INPUT_QUEUE_DEPTH: usize = 4096;

/// The effective input FIFO depth: production is [`INPUT_QUEUE_DEPTH`]; the int
/// gate overrides it (`SPT_INPUT_QUEUE_DEPTH`) to a tiny value to force a
/// deterministic queue saturation — the depth's PRESENCE (bounded, drop-on-full)
/// is the invariant, not its exact value.
fn input_queue_depth() -> usize {
    match std::env::var("SPT_INPUT_QUEUE_DEPTH") {
        Ok(n) => n
            .parse::<usize>()
            .ok()
            .filter(|d| *d > 0)
            .unwrap_or(INPUT_QUEUE_DEPTH),
        Err(_) => INPUT_QUEUE_DEPTH,
    }
}

/// One session's dedicated PTY input-writer (v0.13.0 P0,
/// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The `writer` thread is the SOLE caller of
/// the blocking [`SessionSurface::write_input`]; every other site enqueues a
/// record via [`InputWriter::enqueue`] (a non-blocking `try_send`) and returns
/// immediately. So a paste burst that fills the ConPTY/forkpty input buffer
/// parks ONLY this thread — the broker dispatch thread, attach-opens, resizes,
/// and every other session stay fully live (the W1b-deferred fix (2)). Mirrors
/// the OUTPUT-side [`ViewerSink`]/[`ControllerSink`] writer-thread pattern.
struct InputWriter {
    /// The bounded input FIFO to the writer thread. `try_send` only — a full
    /// queue (a genuinely wedged harness) DROPS, it never blocks.
    tx: SyncSender<InputRecord>,
    /// True while the FIFO is saturated and input is being dropped; cleared on
    /// the next accepted enqueue (heal-on-resume). Mirrored to the perch's
    /// `input_backpressure` so the operator sees the drop.
    backpressure: AtomicBool,
    /// The owning endpoint (the spawn's label; empty = unlabeled / raw test) —
    /// the perch whose `input_backpressure` this stamps. Empty ⇒ nowhere to record.
    endpoint: String,
    /// The writer thread handle (held for ownership; the thread drains until the
    /// last `tx` clone drops at session teardown, then exits).
    _writer: JoinHandle<()>,
}

impl InputWriter {
    /// Spawn a session's input-writer thread. `session` is the `Arc<PtySession>`
    /// whose PTY write handle this thread now exclusively owns.
    fn spawn(session: Arc<PtySession>, endpoint: String) -> Arc<InputWriter> {
        let (tx, rx) = sync_channel::<InputRecord>(input_queue_depth());
        let writer = thread::spawn(move || input_writer(session, rx));
        Arc::new(InputWriter {
            tx,
            backpressure: AtomicBool::new(false),
            endpoint,
            _writer: writer,
        })
    }

    /// Enqueue `bytes` for the PTY write thread — NON-BLOCKING. Returns `true`
    /// when accepted (ordered, will land), `false` when the FIFO was full and the
    /// record was DROPPED. A full queue stamps `INPUT_BACKPRESSURE` (once, on the
    /// rising edge); the next accepted enqueue clears it (heal). The dispatch
    /// thread never blocks here, however stuck the harness is.
    // [impl->REQ-HAZARD-PTY-INPUT-WRITER-WEDGE]
    fn enqueue(&self, bytes: InputRecord) -> bool {
        match self.tx.try_send(bytes) {
            Ok(()) => {
                // Heal: input is flowing again — clear a prior backpressure stamp.
                if self.backpressure.swap(false, Ordering::AcqRel) {
                    self.stamp_backpressure(false);
                }
                true
            }
            Err(TrySendError::Full(_)) => {
                // DROP + surface, but stamp only on the rising edge.
                if !self.backpressure.swap(true, Ordering::AcqRel) {
                    self.stamp_backpressure(true);
                }
                false
            }
            // The writer thread is gone (session teardown) — nothing to do.
            Err(TrySendError::Disconnected(_)) => false,
        }
    }

    /// Whether the FIFO is currently saturated + dropping (the unit/int probe).
    fn is_backpressured(&self) -> bool {
        self.backpressure.load(Ordering::Acquire)
    }

    /// Mirror the input-backpressure signal to the perch's `info.json` (the
    /// broker is the single writer). Best-effort: a session served without a
    /// perch (raw broker tests) has nowhere to record.
    fn stamp_backpressure(&self, dropping: bool) {
        if self.endpoint.is_empty() {
            return;
        }
        let perch = resolve_perch_path(&self.endpoint, ParentHint::Infer);
        let _ = spt_store::info::set_input_backpressure(&perch, dropping);
    }
}

/// A session's input-writer thread (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE): drain
/// records from `rx` and apply each via the BLOCKING
/// [`SessionSurface::write_input`] — the ONE place that touches the PTY writer. A
/// wedged harness parks this thread (and only this thread); a write error is
/// swallowed (the same best-effort the inline callers had). The thread exits
/// when the last `tx` clone drops at session teardown.
fn input_writer(session: Arc<PtySession>, rx: Receiver<InputRecord>) {
    while let Ok(bytes) = rx.recv() {
        let _ = session.write_input(&bytes);
    }
}

/// Drain the inject floor's buffered controller input to the PTY **after** an
/// injected sequence, then release the floor. Each step takes the buffered bytes
/// OFF the lock before ENQUEUEING it to the lone input-writer (the W1 rule: never
/// hold a lock across a PTY write — now never block on it either); input arriving
/// mid-flush keeps buffering, so strict order is preserved until the buffer
/// drains empty and the floor frees.
fn flush_inject_floor(floor: &Mutex<InjectFloor>, input: &InputWriter) {
    loop {
        let chunk = floor.lock().unwrap().take_or_release();
        match chunk {
            Some(bytes) => {
                input.enqueue(bytes);
            }
            None => break,
        }
    }
}


/// Drain the inject floor's buffered controller input to the PTY **after** an
/// injected sequence, then release the floor. Each step takes the buffered bytes
/// OFF the lock before writing (the W1 rule: never hold a lock across a PTY
/// write); input arriving mid-flush keeps buffering, so strict order is preserved
/// until the buffer drains empty and the floor frees.
fn flush_inject_floor(floor: &Mutex<InjectFloor>, session: &PtySession) {
    loop {
        let chunk = floor.lock().unwrap().take_or_release();
        match chunk {
            Some(bytes) => {
                let _ = session.write_input(&bytes);
            }
            None => break,
        }
    }
}

/// One record queued to a session's dedicated PTY input-writer thread — the raw
/// bytes to write (v0.13.0 P0, REQ-HAZARD-PTY-INPUT-WRITER-WEDGE).
type InputRecord = Vec<u8>;

/// The per-session PTY input FIFO depth (records). Sized for a generous paste —
/// a wedged harness fills this and then DROPS, never blocking the dispatch
/// thread. The int gate shrinks it via `SPT_INPUT_QUEUE_DEPTH` so the
/// DROP+`INPUT_BACKPRESSURE` leg is forced in a handful of records rather than a
/// few hundred KB of real paste.
const INPUT_QUEUE_DEPTH: usize = 4096;

/// The effective input FIFO depth: production is [`INPUT_QUEUE_DEPTH`]; the int
/// gate overrides it (`SPT_INPUT_QUEUE_DEPTH`) to a tiny value to force a
/// deterministic queue saturation — the depth's PRESENCE (bounded, drop-on-full)
/// is the invariant, not its exact value.
fn input_queue_depth() -> usize {
    match std::env::var("SPT_INPUT_QUEUE_DEPTH") {
        Ok(n) => n
            .parse::<usize>()
            .ok()
            .filter(|d| *d > 0)
            .unwrap_or(INPUT_QUEUE_DEPTH),
        Err(_) => INPUT_QUEUE_DEPTH,
    }
}

/// One session's dedicated PTY input-writer (v0.13.0 P0,
/// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The `writer` thread is the SOLE caller of
/// the blocking [`SessionSurface::write_input`]; every other site enqueues a
/// record via [`InputWriter::enqueue`] (a non-blocking `try_send`) and returns
/// immediately. So a paste burst that fills the ConPTY/forkpty input buffer
/// parks ONLY this thread — the broker dispatch thread, attach-opens, resizes,
/// and every other session stay fully live (the W1b-deferred fix (2)). Mirrors
/// the OUTPUT-side [`ViewerSink`]/[`ControllerSink`] writer-thread pattern.
struct InputWriter {
    /// The bounded input FIFO to the writer thread. `try_send` only — a full
    /// queue (a genuinely wedged harness) DROPS, it never blocks.
    tx: SyncSender<InputRecord>,
    /// True while the FIFO is saturated and input is being dropped; cleared on
    /// the next accepted enqueue (heal-on-resume). Mirrored to the perch's
    /// `input_backpressure` so the operator sees the drop.
    backpressure: AtomicBool,
    /// The owning endpoint (the spawn's label; empty = unlabeled / raw test) —
    /// the perch whose `input_backpressure` this stamps. Empty ⇒ nowhere to record.
    endpoint: String,
    /// The writer thread handle (held for ownership; the thread drains until the
    /// last `tx` clone drops at session teardown, then exits).
    _writer: JoinHandle<()>,
}

impl InputWriter {
    /// Spawn a session's input-writer thread. `session` is the `Arc<PtySession>`
    /// whose PTY write handle this thread now exclusively owns.
    fn spawn(session: Arc<PtySession>, endpoint: String) -> Arc<InputWriter> {
        let (tx, rx) = sync_channel::<InputRecord>(input_queue_depth());
        let writer = thread::spawn(move || input_writer(session, rx));
        Arc::new(InputWriter {
            tx,
            backpressure: AtomicBool::new(false),
            endpoint,
            _writer: writer,
        })
    }

    /// Enqueue `bytes` for the PTY write thread — NON-BLOCKING. Returns `true`
    /// when accepted (ordered, will land), `false` when the FIFO was full and the
    /// record was DROPPED. A full queue stamps `INPUT_BACKPRESSURE` (once, on the
    /// rising edge); the next accepted enqueue clears it (heal). The dispatch
    /// thread never blocks here, however stuck the harness is.
    // [impl->REQ-HAZARD-PTY-INPUT-WRITER-WEDGE]
    fn enqueue(&self, bytes: InputRecord) -> bool {
        match self.tx.try_send(bytes) {
            Ok(()) => {
                // Heal: input is flowing again — clear a prior backpressure stamp.
                if self.backpressure.swap(false, Ordering::AcqRel) {
                    self.stamp_backpressure(false);
                }
                true
            }
            Err(TrySendError::Full(_)) => {
                // DROP + surface, but stamp only on the rising edge.
                if !self.backpressure.swap(true, Ordering::AcqRel) {
                    self.stamp_backpressure(true);
                }
                false
            }
            // The writer thread is gone (session teardown) — nothing to do.
            Err(TrySendError::Disconnected(_)) => false,
        }
    }

    /// Whether the FIFO is currently saturated + dropping (the unit/int probe).
    fn is_backpressured(&self) -> bool {
        self.backpressure.load(Ordering::Acquire)
    }

    /// Mirror the input-backpressure signal to the perch's `info.json` (the
    /// broker is the single writer). Best-effort: a session served without a
    /// perch (raw broker tests) has nowhere to record.
    fn stamp_backpressure(&self, dropping: bool) {
        if self.endpoint.is_empty() {
            return;
        }
        let perch = resolve_perch_path(&self.endpoint, ParentHint::Infer);
        let _ = spt_store::info::set_input_backpressure(&perch, dropping);
    }
}

/// A session's input-writer thread (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE): drain
/// records from `rx` and apply each via the BLOCKING
/// [`SessionSurface::write_input`] — the ONE place that touches the PTY writer. A
/// wedged harness parks this thread (and only this thread); a write error is
/// swallowed (the same best-effort the inline callers had). The thread exits
/// when the last `tx` clone drops at session teardown.
fn input_writer(session: Arc<PtySession>, rx: Receiver<InputRecord>) {
    while let Ok(bytes) = rx.recv() {
        let _ = session.write_input(&bytes);
    }
}

/// Drain the inject floor's buffered controller input to the PTY **after** an
/// injected sequence, then release the floor. Each step takes the buffered bytes
/// OFF the lock before ENQUEUEING it to the lone input-writer (the W1 rule: never
/// hold a lock across a PTY write — now never block on it either); input arriving
/// mid-flush keeps buffering, so strict order is preserved until the buffer
/// drains empty and the floor frees.
fn flush_inject_floor(floor: &Mutex<InjectFloor>, input: &InputWriter) {
    loop {
        let chunk = floor.lock().unwrap().take_or_release();
        match chunk {
            Some(bytes) => {
                input.enqueue(bytes);
            }
            None => break,
        }
    }
}

+/// One record queued to a session's dedicated PTY input-writer thread — the raw

+/// bytes to write (v0.13.0 P0, REQ-HAZARD-PTY-INPUT-WRITER-WEDGE).

+/// The per-session PTY input FIFO depth (records). Sized for a generous paste —

+/// a wedged harness fills this and then DROPS, never blocking the dispatch

+/// thread. The int gate shrinks it via `SPT_INPUT_QUEUE_DEPTH` so the

+/// DROP+`INPUT_BACKPRESSURE` leg is forced in a handful of records rather than a

+/// The effective input FIFO depth: production is [`INPUT_QUEUE_DEPTH`]; the int

+/// gate overrides it (`SPT_INPUT_QUEUE_DEPTH`) to a tiny value to force a

+/// deterministic queue saturation — the depth's PRESENCE (bounded, drop-on-full)

+/// is the invariant, not its exact value.

+    match std::env::var("SPT_INPUT_QUEUE_DEPTH") {

+            .unwrap_or(INPUT_QUEUE_DEPTH),

+/// One session's dedicated PTY input-writer (v0.13.0 P0,

+/// REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The `writer` thread is the SOLE caller of

+/// the blocking [`SessionSurface::write_input`]; every other site enqueues a

+/// record via [`InputWriter::enqueue`] (a non-blocking `try_send`) and returns

+/// immediately. So a paste burst that fills the ConPTY/forkpty input buffer

+/// parks ONLY this thread — the broker dispatch thread, attach-opens, resizes,

+/// and every other session stay fully live (the W1b-deferred fix (2)). Mirrors

+/// the OUTPUT-side [`ViewerSink`]/[`ControllerSink`] writer-thread pattern.

+    /// The bounded input FIFO to the writer thread. `try_send` only — a full

+    /// queue (a genuinely wedged harness) DROPS, it never blocks.

+    /// True while the FIFO is saturated and input is being dropped; cleared on

+    /// the next accepted enqueue (heal-on-resume). Mirrored to the perch's

+    /// `input_backpressure` so the operator sees the drop.

+    /// The owning endpoint (the spawn's label; empty = unlabeled / raw test) —

+    /// the perch whose `input_backpressure` this stamps. Empty ⇒ nowhere to record.

+    /// The writer thread handle (held for ownership; the thread drains until the

+    /// last `tx` clone drops at session teardown, then exits).

+    /// Spawn a session's input-writer thread. `session` is the `Arc<PtySession>`

+    /// whose PTY write handle this thread now exclusively owns.

+    fn spawn(session: Arc<PtySession>, endpoint: String) -> Arc<InputWriter> {

+        let (tx, rx) = sync_channel::<InputRecord>(input_queue_depth());

+        let writer = thread::spawn(move || input_writer(session, rx));

+            backpressure: AtomicBool::new(false),

+    /// Enqueue `bytes` for the PTY write thread — NON-BLOCKING. Returns `true`

+    /// when accepted (ordered, will land), `false` when the FIFO was full and the

+    /// record was DROPPED. A full queue stamps `INPUT_BACKPRESSURE` (once, on the

+    /// rising edge); the next accepted enqueue clears it (heal). The dispatch

+    /// thread never blocks here, however stuck the harness is.

+    // [impl->REQ-HAZARD-PTY-INPUT-WRITER-WEDGE]

+    fn enqueue(&self, bytes: InputRecord) -> bool {

+        match self.tx.try_send(bytes) {

+                // Heal: input is flowing again — clear a prior backpressure stamp.

+                if self.backpressure.swap(false, Ordering::AcqRel) {

+                    self.stamp_backpressure(false);

+            Err(TrySendError::Full(_)) => {

+                // DROP + surface, but stamp only on the rising edge.

+                if !self.backpressure.swap(true, Ordering::AcqRel) {

+                    self.stamp_backpressure(true);

+            // The writer thread is gone (session teardown) — nothing to do.

+            Err(TrySendError::Disconnected(_)) => false,

+    /// Whether the FIFO is currently saturated + dropping (the unit/int probe).

+    fn is_backpressured(&self) -> bool {

+        self.backpressure.load(Ordering::Acquire)

+    /// Mirror the input-backpressure signal to the perch's `info.json` (the

+    /// broker is the single writer). Best-effort: a session served without a

+    /// perch (raw broker tests) has nowhere to record.

+    fn stamp_backpressure(&self, dropping: bool) {

+        let perch = resolve_perch_path(&self.endpoint, ParentHint::Infer);

+        let _ = spt_store::info::set_input_backpressure(&perch, dropping);

+/// A session's input-writer thread (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE): drain

+/// records from `rx` and apply each via the BLOCKING

+/// [`SessionSurface::write_input`] — the ONE place that touches the PTY writer. A

+/// wedged harness parks this thread (and only this thread); a write error is

+/// swallowed (the same best-effort the inline callers had). The thread exits

+/// when the last `tx` clone drops at session teardown.

+fn input_writer(session: Arc<PtySession>, rx: Receiver<InputRecord>) {

+        let _ = session.write_input(&bytes);

-/// OFF the lock before writing (the W1 rule: never hold a lock across a PTY

-/// write); input arriving mid-flush keeps buffering, so strict order is preserved

-/// until the buffer drains empty and the floor frees.

-fn flush_inject_floor(floor: &Mutex<InjectFloor>, session: &PtySession) {

+/// OFF the lock before ENQUEUEING it to the lone input-writer (the W1 rule: never

+/// hold a lock across a PTY write — now never block on it either); input arriving

+/// mid-flush keeps buffering, so strict order is preserved until the buffer

+fn flush_inject_floor(floor: &Mutex<InjectFloor>, input: &InputWriter) {

let chunk = floor.lock().unwrap().take_or_release();

-                let _ = session.write_input(&bytes);


Activate REQ + registry note

Add set_input_backpressure to spt-store

Convert write_input callers to enqueue

Docs: KNOWN-HAZARDS 7.17 + CONTEXT note

Tests via spt-test-engineer (unit+int)

Gates + commit + surface to doyle


Convert run_inject_worker to use InputWriter.


fn run_inject_worker(
    session: Arc<PtySession>,
    child: Arc<TranslationChild>,
    floor: Arc<Mutex<InjectFloor>>,
    event_rx: Receiver<String>,
    cmd_rx: Receiver<KeyCmd>,
    faulted: Arc<AtomicBool>,
) {

fn run_inject_worker(
    input: Arc<InputWriter>,
    child: Arc<TranslationChild>,
    floor: Arc<Mutex<InjectFloor>>,
    event_rx: Receiver<String>,
    cmd_rx: Receiver<KeyCmd>,
    faulted: Arc<AtomicBool>,
) {


fn run_inject_worker(
    session: Arc<PtySession>,
    child: Arc<TranslationChild>,
    floor: Arc<Mutex<InjectFloor>>,
    event_rx: Receiver<String>,
    cmd_rx: Receiver<KeyCmd>,
    faulted: Arc<AtomicBool>,
) {

fn run_inject_worker(
    input: Arc<InputWriter>,
    child: Arc<TranslationChild>,
    floor: Arc<Mutex<InjectFloor>>,
    event_rx: Receiver<String>,
    cmd_rx: Receiver<KeyCmd>,
    faulted: Arc<AtomicBool>,
) {


        floor.lock().unwrap().open();
        if child.send(&ToBinary::Event { envelope }).is_err() {
            // stdin write failed → the binary is gone; release the floor + fault.
            flush_inject_floor(&floor, &session);
            fault_translation(&faulted, &child, "stdin write failed");
            return;
        }

        floor.lock().unwrap().open();
        if child.send(&ToBinary::Event { envelope }).is_err() {
            // stdin write failed → the binary is gone; release the floor + fault.
            flush_inject_floor(&floor, &input);
            fault_translation(&faulted, &child, "stdin write failed");
            return;
        }


        floor.lock().unwrap().open();
        if child.send(&ToBinary::Event { envelope }).is_err() {
            // stdin write failed → the binary is gone; release the floor + fault.
            flush_inject_floor(&floor, &session);
            fault_translation(&faulted, &child, "stdin write failed");
            return;
        }

        floor.lock().unwrap().open();
        if child.send(&ToBinary::Event { envelope }).is_err() {
            // stdin write failed → the binary is gone; release the floor + fault.
            flush_inject_floor(&floor, &input);
            fault_translation(&faulted, &child, "stdin write failed");
            return;
        }

-            flush_inject_floor(&floor, &session);

+            flush_inject_floor(&floor, &input);


                Ok(KeyCmd::Key { key }) => {
                    if let Some(bytes) = key_to_bytes(&key) {
                        let _ = session.write_input(&bytes);
                    }
                }
                Ok(KeyCmd::Text { text }) => {
                    let _ = session.write_input(text.as_bytes());
                }

                Ok(KeyCmd::Key { key }) => {
                    if let Some(bytes) = key_to_bytes(&key) {
                        input.enqueue(bytes);
                    }
                }
                Ok(KeyCmd::Text { text }) => {
                    input.enqueue(text.into_bytes());
                }


                Ok(KeyCmd::Key { key }) => {
                    if let Some(bytes) = key_to_bytes(&key) {
                        let _ = session.write_input(&bytes);
                    }
                }
                Ok(KeyCmd::Text { text }) => {
                    let _ = session.write_input(text.as_bytes());
                }

                Ok(KeyCmd::Key { key }) => {
                    if let Some(bytes) = key_to_bytes(&key) {
                        input.enqueue(bytes);
                    }
                }
                Ok(KeyCmd::Text { text }) => {
                    input.enqueue(text.into_bytes());
                }

-                        let _ = session.write_input(&bytes);

+                        input.enqueue(bytes);

-                    let _ = session.write_input(text.as_bytes());

+                    input.enqueue(text.into_bytes());

// Clamp the pause to the remaining deadline so a huge/hostile


crates\spt-daemon\src\broker.rs:904:        let _ = session.write_input(&bytes);
crates\spt-daemon\src\broker.rs:932:/// then drain `{key}`/`{text}`/`{delay_ms}` into `session.write_input` until
crates\spt-daemon\src\broker.rs:1005:        flush_inject_floor(&floor, &session);


1000	            }
1001	        }
1002
1003	        // Flush buffered controller input AFTER the injected bytes + release the
1004	        // floor — in EVERY exit path, so operator keystrokes are never stranded.
1005	        flush_inject_floor(&floor, &session);
1006
1007	        if !committed {
1008	            fault_translation(&faulted, &child, "no {commit} within INJECT_COMMIT_DEADLINE");
1009	            return;
1010	        }
1011	    }

            }
        }

        // Flush buffered controller input AFTER the injected bytes + release the
        // floor — in EVERY exit path, so operator keystrokes are never stranded.
        flush_inject_floor(&floor, &session);

        if !committed {
            fault_translation(&faulted, &child, "no {commit} within INJECT_COMMIT_DEADLINE");
            return;
        }
    }


        // Flush buffered controller input AFTER the injected bytes + release the
        // floor — in EVERY exit path, so operator keystrokes are never stranded.
        flush_inject_floor(&floor, &session);

        if !committed {

        // Flush buffered controller input AFTER the injected bytes + release the
        // floor — in EVERY exit path, so operator keystrokes are never stranded.
        flush_inject_floor(&floor, &input);

        if !committed {


        // Flush buffered controller input AFTER the injected bytes + release the
        // floor — in EVERY exit path, so operator keystrokes are never stranded.
        flush_inject_floor(&floor, &session);

        if !committed {

        // Flush buffered controller input AFTER the injected bytes + release the
        // floor — in EVERY exit path, so operator keystrokes are never stranded.
        flush_inject_floor(&floor, &input);

        if !committed {

-        flush_inject_floor(&floor, &session);

+        flush_inject_floor(&floor, &input);

fault_translation(&faulted, &child, "no {commit} within INJECT_COMMIT_DEADLINE");


Grep

let session = Arc::new\(session\)|w_session = Arc::clone|run_inject_worker\(|HostedSession \{


crates\spt-daemon\src\broker.rs:766:struct HostedSession {
crates\spt-daemon\src\broker.rs:946:fn run_inject_worker(
crates\spt-daemon\src\broker.rs:1383:        let session = Arc::new(session);
crates\spt-daemon\src\broker.rs:1467:                    let w_session = Arc::clone(&session);
crates\spt-daemon\src\broker.rs:1472:                        run_inject_worker(w_session, w_child, w_floor, event_rx, cmd_rx, w_faulted);
crates\spt-daemon\src\broker.rs:1492:            HostedSession {


1383	        let session = Arc::new(session);
1384	        let id = self.next_id.fetch_add(1, Ordering::Relaxed);
1385	        let pid = session.process_id();
1386
1387	        // The output log, with the spawning connection pre-attached as the
1388	        // controller (identity `None` = LOCAL, not a remote driver) so no chunk
1389	        // is missed before an explicit subscribe — and so a remote attach to a
1390	        // spawned-but-undriven session takes control silently (no loud displace
1391	        // of the local-spawner placeholder; REQ-RCVIEW-1).
1392	        let log = Arc::new(Mutex::new(OutputLog::new(

        let session = Arc::new(session);
        let id = self.next_id.fetch_add(1, Ordering::Relaxed);
        let pid = session.process_id();

        // The output log, with the spawning connection pre-attached as the
        // controller (identity `None` = LOCAL, not a remote driver) so no chunk
        // is missed before an explicit subscribe — and so a remote attach to a
        // spawned-but-undriven session takes control silently (no loud displace
        // of the local-spawner placeholder; REQ-RCVIEW-1).
        let log = Arc::new(Mutex::new(OutputLog::new(


Edit

        let session = Arc::new(session);
        let id = self.next_id.fetch_add(1, Ordering::Relaxed);
        let pid = session.process_id();

        let session = Arc::new(session);
        let id = self.next_id.fetch_add(1, Ordering::Relaxed);
        let pid = session.process_id();

        // The session's lone PTY input-writer (P0): the SOLE caller of the
        // blocking `write_input`. Every dispatch-side write enqueues here, so a
        // paste burst parks only this thread — never the broker dispatch thread
        // (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The inject worker writes through it
        // too, so the lone writer enforces the W2 inject-floor order.
        let input = InputWriter::spawn(Arc::clone(&session), req.endpoint.clone());


        let session = Arc::new(session);
        let id = self.next_id.fetch_add(1, Ordering::Relaxed);
        let pid = session.process_id();

        // The session's lone PTY input-writer (P0): the SOLE caller of the
        // blocking `write_input`. Every dispatch-side write enqueues here, so a
        // paste burst parks only this thread — never the broker dispatch thread
        // (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The inject worker writes through it
        // too, so the lone writer enforces the W2 inject-floor order.
        let input = InputWriter::spawn(Arc::clone(&session), req.endpoint.clone());

+        // The session's lone PTY input-writer (P0): the SOLE caller of the

+        // blocking `write_input`. Every dispatch-side write enqueues here, so a

+        // paste burst parks only this thread — never the broker dispatch thread

+        // (REQ-HAZARD-PTY-INPUT-WRITER-WEDGE). The inject worker writes through it

+        // too, so the lone writer enforces the W2 inject-floor order.

+        let input = InputWriter::spawn(Arc::clone(&session), req.endpoint.clone());

// The output log, with the spawning connection pre-attached as the

// controller (identity `None` = LOCAL, not a remote driver) so no chunk

// is missed before an explicit subscribe — and so a remote attach to a


Now thread `input` into the inject worker spawn + HostedSession.


1465	                    let child = Arc::new(child);
1466	                    // First line: identity (content-free of any PTY bytes).
1467	                    let _ = child.send(&ToBinary::Init {
1468	                        endpoint_id: req.endpoint.clone(),
1469	                        node: spt_store::hostlabel::os_hostname().unwrap_or_default(),
1470	                    });
1471	                    let floor = Arc::new(Mutex::new(InjectFloor::default()));
1472	                    let faulted = Arc::new(AtomicBool::new(false));
1473	                    let (event_tx, event_rx) = channel::<String>();
1474	                    let w_session = Arc::clone(&session);
1475	                    let w_child = Arc::clone(&child);
1476	                    let w_floor = Arc::clone(&floor);
1477	                    let w_faulted = Arc::clone(&faulted);
1478	                    thread::spawn(move || {
1479	                        run_inject_worker(w_session, w_child, w_floor, event_rx, cmd_rx, w_faulted);
1480	                    });
1481	                    Some(Arc::new(Translation {
1482	                        floor,
1483	                        event_tx,
1484	                        child,
1485	                        faulted,
1486	                    }))
1487	                }
1488	                Err(e) => {
1489	                    eprintln!(
1490	                        "TRANSLATION_SPAWN_FAILED:{path}: {e} — degrading to raw inject"
1491	                    );
1492	                    None
1493	                }
1494	            }
1495	        });
1496
1497	        self.sessions.lock().unwrap().insert(
1498	            id,
1499	            HostedSession {
1500	                session,
1501	                drain,
1502	                log,
1503	                endpoint: req.endpoint.clone(),
1504	                translation,

                    let child = Arc::new(child);
                    // First line: identity (content-free of any PTY bytes).
                    let _ = child.send(&ToBinary::Init {
                        endpoint_id: req.endpoint.clone(),
                        node: spt_store::hostlabel::os_hostname().unwrap_or_default(),
                    });
                    let floor = Arc::new(Mutex::new(InjectFloor::default()));
                    let faulted = Arc::new(AtomicBool::new(false));
                    let (event_tx, event_rx) = channel::<String>();
                    let w_session = Arc::clone(&session);
                    let w_child = Arc::clone(&child);
                    let w_floor = Arc::clone(&floor);
                    let w_faulted = Arc::clone(&faulted);
                    thread::spawn(move || {
                        run_inject_worker(w_session, w_child, w_floor, event_rx, cmd_rx, w_faulted);
                    });
                    Some(Arc::new(Translation {
                        floor,
                        event_tx,
                        child,
                        faulted,
                    }))
                }
                Err(e) => {
                    eprintln!(
                        "TRANSLATION_SPAWN_FAILED:{path}: {e} — degrading to raw inject"
                    );
                    None
                }
            }
        });

        self.sessions.lock().unwrap().insert(
            id,
            HostedSession {
                session,
                drain,
                log,
                endpoint: req.endpoint.clone(),
                translation,


                    let (event_tx, event_rx) = channel::<String>();
                    let w_session = Arc::clone(&session);
                    let w_child = Arc::clone(&child);
                    let w_floor = Arc::clone(&floor);
                    let w_faulted = Arc::clone(&faulted);
                    thread::spawn(move || {
                        run_inject_worker(w_session, w_child, w_floor, event_rx, cmd_rx, w_faulted);
                    });

                    let (event_tx, event_rx) = channel::<String>();
                    let w_input = Arc::clone(&input);
                    let w_child = Arc::clone(&child);
                    let w_floor = Arc::clone(&floor);
                    let w_faulted = Arc::clone(&faulted);
                    thread::spawn(move || {
                        run_inject_worker(w_input, w_child, w_floor, event_rx, cmd_rx, w_faulted);
                    });


                    let (event_tx, event_rx) = channel::<String>();
                    let w_session = Arc::clone(&session);
                    let w_child = Arc::clone(&child);
                    let w_floor = Arc::clone(&floor);
                    let w_faulted = Arc::clone(&faulted);
                    thread::spawn(move || {
                        run_inject_worker(w_session, w_child, w_floor, event_rx, cmd_rx, w_faulted);
                    });

                    let (event_tx, event_rx) = channel::<String>();
                    let w_input = Arc::clone(&input);
                    let w_child = Arc::clone(&child);
                    let w_floor = Arc::clone(&floor);
                    let w_faulted = Arc::clone(&faulted);
                    thread::spawn(move || {
                        run_inject_worker(w_input, w_child, w_floor, event_rx, cmd_rx, w_faulted);
                    });

let floor = Arc::new(Mutex::new(InjectFloor::default()));

let faulted = Arc::new(AtomicBool::new(false));

-                    let w_session = Arc::clone(&session);

+                    let w_input = Arc::clone(&input);

-                        run_inject_worker(w_session, w_child, w_floor, event_rx, cmd_rx, w_faulted);

+                        run_inject_worker(w_input, w_child, w_floor, event_rx, cmd_rx, w_faulted);


            HostedSession {
                session,
                drain,
                log,
                endpoint: req.endpoint.clone(),
                translation,

            HostedSession {
                session,
                input,
                drain,
                log,
                endpoint: req.endpoint.clone(),
                translation,


            HostedSession {
                session,
                drain,
                log,
                endpoint: req.endpoint.clone(),
                translation,

            HostedSession {
                session,
                input,
                drain,
                log,
                endpoint: req.endpoint.clone(),
                translation,


Now dispatch_input + dispatch_endpoint_input. Re-read current dispatch_input.


1586	    /// With an `op_id` the write is routed through the broker-owned
1587	    /// [`EffectJournal`] for exactly-once delivery: a replay of the same
1588	    /// `(session_id, op_id)` after a brain crash is deduped (the PTY is not
1589	    /// written twice), and either outcome — fresh apply or dedup — is acked back
1590	    /// with an [`crate::msg::KIND_APPLIED`] frame so the brain can retire the
1591	    /// effect (B5 / KNOWN-HAZARDS 7.2). Without an `op_id` it is the legacy
1592	    /// direct write (no dedup, no ack).
1593	    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]
1594	    fn dispatch_input(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {
1595	        let req: InputReq =
1596	            serde_json::from_value(env.payload).map_err(|e| format!("bad input payload: {e}"))?;
1597	        let bytes = decode_bytes(&req.data_b64).map_err(|e| format!("bad input base64: {e}"))?;
1598	        // Clone the session Arc (and the translation handle, if any) out from under
1599	        // the sessions lock so the PTY write + fsync happen without holding the
1600	        // table lock (mirrors dispatch_subscribe).
1601	        let (session, translation) = {
1602	            let sessions = self.sessions.lock().unwrap();
1603	            let h = sessions
1604	                .get(&req.session_id)
1605	                .ok_or_else(|| format!("no such session {}", req.session_id))?;
1606	            (Arc::clone(&h.session), h.translation.clone())
1607	        };
1608	        // Layer C: when a (non-faulted) translation binary is attached, fire a
1609	        // CONTENT-FREE `{type:input}` ping so the binary can track operator idleness
1610	        // — best-effort, never carries the keystroke bytes (doyle gate #1, privacy).
1611	        if let Some(tr) = &translation {
1612	            if !tr.faulted.load(Ordering::Relaxed) {
1613	                let _ = tr.child.send(&ToBinary::Input);
1614	            }
1615	        }
1616	        // Layer C: if an injected sequence is mid-flight, the operator's keystrokes
1617	        // BUFFER in the inject floor and are flushed to the PTY after the sequence
1618	        // commits — so a stash/restore choreography can't be clobbered. Returns
1619	        // true when buffered (the caller must NOT write the PTY now).
1620	        let buffered = |bytes: &[u8]| -> bool {
1621	            translation
1622	                .as_ref()
1623	                .is_some_and(|tr| tr.floor.lock().unwrap().buffer_if_held(bytes))
1624	        };
1625	        match req.op_id {
1626	            Some(op) => {
1627	                let key = (req.session_id, op);
1628	                let outcome = self
1629	                    .journal
1630	                    .apply_once(key, EffectKind::PtyWrite, || {
1631	                        // Buffer-or-write INSIDE the journal effect so the op is still
1632	                        // recorded applied + acked (dedup-safe) whether the byte lands
1633	                        // on the PTY now or after the sequence flush.
1634	                        if buffered(&bytes) {
1635	                            return Ok(());
1636	                        }
1637	                        session
1638	                            .write_input(&bytes)
1639	                            .map_err(|e| io::Error::other(e.to_string()))
1640	                    })
1641	                    .map_err(|e| format!("write_input failed: {e}"))?;
1642	                // Ack exactly-once: both Applied and Deduped mean "durably landed".
1643	                send_frame(
1644	                    send,
1645	                    &applied_envelope(req.session_id, op, outcome == Outcome::Applied),
1646	                );
1647	                Ok(())
1648	            }
1649	            None => {
1650	                if buffered(&bytes) {
1651	                    return Ok(());
1652	                }
1653	                session
1654	                    .write_input(&bytes)
1655	                    .map_err(|e| format!("write_input failed: {e}"))
1656	            }
1657	        }
1658	    }
1659
1660	    /// Deliver an inbound message to an spt-hosted endpoint by ENDPOINT ID:

    /// With an `op_id` the write is routed through the broker-owned
    /// [`EffectJournal`] for exactly-once delivery: a replay of the same
    /// `(session_id, op_id)` after a brain crash is deduped (the PTY is not
    /// written twice), and either outcome — fresh apply or dedup — is acked back
    /// with an [`crate::msg::KIND_APPLIED`] frame so the brain can retire the
    /// effect (B5 / KNOWN-HAZARDS 7.2). Without an `op_id` it is the legacy
    /// direct write (no dedup, no ack).
    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]
    fn dispatch_input(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {
        let req: InputReq =
            serde_json::from_value(env.payload).map_err(|e| format!("bad input payload: {e}"))?;
        let bytes = decode_bytes(&req.data_b64).map_err(|e| format!("bad input base64: {e}"))?;
        // Clone the session Arc (and the translation handle, if any) out from under
        // the sessions lock so the PTY write + fsync happen without holding the
        // table lock (mirrors dispatch_subscribe).
        let (session, translation) = {
            let sessions = self.sessions.lock().unwrap();
            let h = sessions
                .get(&req.session_id)
                .ok_or_else(|| format!("no such session {}", req.session_id))?;
            (Arc::clone(&h.session), h.translation.clone())
        };
        // Layer C: when a (non-faulted) translation binary is attached, fire a
        // CONTENT-FREE `{type:input}` ping so the binary can track operator idleness
        // — best-effort, never carries the keystroke bytes (doyle gate #1, privacy).
        if let Some(tr) = &translation {
            if !tr.faulted.load(Ordering::Relaxed) {
                let _ = tr.child.send(&ToBinary::Input);
            }
        }
        // Layer C: if an injected sequence is mid-flight, the operator's keystrokes
        // BUFFER in the inject floor and are flushed to the PTY after the sequence
        // commits — so a stash/restore choreography can't be clobbered. Returns
        // true when buffered (the caller must NOT write the PTY now).
        let buffered = |bytes: &[u8]| -> bool {
            translation
                .as_ref()
                .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-write INSIDE the journal effect so the op is still
                        // recorded applied + acked (dedup-safe) whether the byte lands
                        // on the PTY now or after the sequence flush.
                        if buffered(&bytes) {
                            return Ok(());
                        }
                        session
                            .write_input(&bytes)
                            .map_err(|e| io::Error::other(e.to_string()))
                    })
                    .map_err(|e| format!("write_input failed: {e}"))?;
                // Ack exactly-once: both Applied and Deduped mean "durably landed".
                send_frame(
                    send,
                    &applied_envelope(req.session_id, op, outcome == Outcome::Applied),
                );
                Ok(())
            }
            None => {
                if buffered(&bytes) {
                    return Ok(());
                }
                session
                    .write_input(&bytes)
                    .map_err(|e| format!("write_input failed: {e}"))
            }
        }
    }

    /// Deliver an inbound message to an spt-hosted endpoint by ENDPOINT ID:


        // Clone the session Arc (and the translation handle, if any) out from under
        // the sessions lock so the PTY write + fsync happen without holding the
        // table lock (mirrors dispatch_subscribe).
        let (session, translation) = {
            let sessions = self.sessions.lock().unwrap();
            let h = sessions
                .get(&req.session_id)
                .ok_or_else(|| format!("no such session {}", req.session_id))?;
            (Arc::clone(&h.session), h.translation.clone())
        };

        // Clone the input-writer Arc (and the translation handle, if any) out from
        // under the sessions lock so the ENQUEUE happens without holding the table
        // lock (mirrors dispatch_subscribe). The enqueue is non-blocking, so the
        // dispatch thread is never parked here even when the harness has stalled
        // (P0 / REQ-HAZARD-PTY-INPUT-WRITER-WEDGE).
        let (input, translation) = {
            let sessions = self.sessions.lock().unwrap();
            let h = sessions
                .get(&req.session_id)
                .ok_or_else(|| format!("no such session {}", req.session_id))?;
            (Arc::clone(&h.input), h.translation.clone())
        };


        // Clone the session Arc (and the translation handle, if any) out from under
        // the sessions lock so the PTY write + fsync happen without holding the
        // table lock (mirrors dispatch_subscribe).
        let (session, translation) = {
            let sessions = self.sessions.lock().unwrap();
            let h = sessions
                .get(&req.session_id)
                .ok_or_else(|| format!("no such session {}", req.session_id))?;
            (Arc::clone(&h.session), h.translation.clone())
        };

        // Clone the input-writer Arc (and the translation handle, if any) out from
        // under the sessions lock so the ENQUEUE happens without holding the table
        // lock (mirrors dispatch_subscribe). The enqueue is non-blocking, so the
        // dispatch thread is never parked here even when the harness has stalled
        // (P0 / REQ-HAZARD-PTY-INPUT-WRITER-WEDGE).
        let (input, translation) = {
            let sessions = self.sessions.lock().unwrap();
            let h = sessions
                .get(&req.session_id)
                .ok_or_else(|| format!("no such session {}", req.session_id))?;
            (Arc::clone(&h.input), h.translation.clone())
        };

serde_json::from_value(env.payload).map_err(|e| format!("bad input payload: {e}"))?;

let bytes = decode_bytes(&req.data_b64).map_err(|e| format!("bad input base64: {e}"))?;

-        // Clone the session Arc (and the translation handle, if any) out from under

-        // the sessions lock so the PTY write + fsync happen without holding the

-        // table lock (mirrors dispatch_subscribe).

+        // Clone the input-writer Arc (and the translation handle, if any) out from

+        // under the sessions lock so the ENQUEUE happens without holding the table

+        // lock (mirrors dispatch_subscribe). The enqueue is non-blocking, so the

+        // dispatch thread is never parked here even when the harness has stalled

+        // (P0 / REQ-HAZARD-PTY-INPUT-WRITER-WEDGE).

-            (Arc::clone(&h.session), h.translation.clone())

+            (Arc::clone(&h.input), h.translation.clone())

// Layer C: when a (non-faulted) translation binary is attached, fire a

// CONTENT-FREE `{type:input}` ping so the binary can track operator idleness


                let outcome = self
                    .journal
                    .apply_once(key, EffectKind::PtyWrite, || {
                        // Buffer-or-write INSIDE the journal effect so the op is still
                        // recorded applied + acked (dedup-safe) whether the byte lands
                        // on the PTY now or after the sequence flush.
                        if buffered(&bytes) {
                            return Ok(());
                        }
                        session
                            .write_input(&bytes)
                            .map_err(|e| io::Error::other(e.to_string()))
                    })
                    .map_err(|e| format!("write_input failed: {e}"))?;
                // Ack exactly-once: both Applied and Deduped mean "durably landed".
                send_frame(
                    send,
                    &applied_envelope(req.session_id, op, outcome == Outcome::Applied),
                );
                Ok(())
            }
            None => {
                if buffered(&bytes) {
                    return Ok(());
                }
                session
                    .write_input(&bytes)
                    .map_err(|e| format!("write_input failed: {e}"))
            }
        }
    }

                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(())
            }
        }
    }


                let outcome = self
                    .journal
                    .apply_once(key, EffectKind::PtyWrite, || {
                        // Buffer-or-write INSIDE the journal effect so the op is still
                        // recorded applied + acked (dedup-safe) whether the byte lands
                        // on the PTY now or after the sequence flush.
                        if buffered(&bytes) {
                            return Ok(());
                        }
                        session
                            .write_input(&bytes)
                            .map_err(|e| io::Error::other(e.to_string()))
                    })
                    .map_err(|e| format!("write_input failed: {e}"))?;
                // Ack exactly-once: both Applied and Deduped mean "durably landed".
                send_frame(
                    send,
                    &applied_envelope(req.session_id, op, outcome == Outcome::Applied),
                );
                Ok(())
            }
            None => {
                if buffered(&bytes) {
                    return Ok(());
                }
                session
                    .write_input(&bytes)
                    .map_err(|e| format!("write_input failed: {e}"))
            }
        }
    }

                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(())
            }
        }
    }

-                        // Buffer-or-write INSIDE the journal effect so the op is still

-                        // recorded applied + acked (dedup-safe) whether the byte lands

-                        // on the PTY now or after the sequence flush.

-                        if buffered(&bytes) {

-                            return Ok(());

+                        // 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());

-                            .write_input(&bytes)

-                            .map_err(|e| io::Error::other(e.to_string()))

-                    .map_err(|e| format!("write_input failed: {e}"))?;

-                // Ack exactly-once: both Applied and Deduped mean "durably landed".

+                    .map_err(|e| format!("input enqueue failed: {e}"))?;

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

+                    input.enqueue(bytes);

-                    .write_input(&bytes)


Now dispatch_endpoint_input.


1676	    /// activity-gated routing (idle→inject / active→spool-for-poll-drain) is a
1677	    /// deferred follow wave. The write is logged so an active-state inject is
1678	    /// never silent.
1679	    // [impl->REQ-SEND-SPT-HOSTED]
1680	    fn dispatch_endpoint_input(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {
1681	        let req: EndpointInputReq = serde_json::from_value(env.payload)
1682	            .map_err(|e| format!("bad endpoint-input payload: {e}"))?;
1683	        let bytes =
1684	            decode_bytes(&req.data_b64).map_err(|e| format!("bad endpoint-input base64: {e}"))?;
1685	        // Resolve endpoint → (session, translation) under the lock, cloning the
1686	        // Arcs out so the PTY write happens without holding the table lock
1687	        // (dispatch_input rule).
1688	        let resolved = {
1689	            let sessions = self.sessions.lock().unwrap();
1690	            sessions
1691	                .values()
1692	                .find(|h| h.endpoint == req.endpoint)
1693	                .map(|h| (Arc::clone(&h.session), h.translation.clone()))
1694	        };
1695	        match resolved {
1696	            Some((session, translation)) => {
1697	                // Layer E: a live (non-faulted) translation binary OWNS the
1698	                // choreography — queue the whole `<EVENT>` envelope to the inject
1699	                // worker, which drives it through the binary and applies the
1700	                // resulting keystrokes atomically w.r.t. the controller. Raw inject
1701	                // is the DEGENERATE fallback when no binary is attached, it has
1702	                // faulted, or the worker channel is gone.
1703	                if let Some(tr) = &translation {
1704	                    if !tr.faulted.load(Ordering::Relaxed) {
1705	                        let envelope = String::from_utf8_lossy(&bytes).into_owned();
1706	                        if tr.event_tx.send(envelope).is_ok() {
1707	                            eprintln!(
1708	                                "ENDPOINT_INJECT:{} ({} bytes → translation binary)",
1709	                                req.endpoint,
1710	                                bytes.len()
1711	                            );
1712	                            send_frame(send, &endpoint_injected_envelope(&req.endpoint, true));
1713	                            return Ok(());
1714	                        }
1715	                        // worker receiver dropped → fall through to raw inject.
1716	                    }
1717	                }
1718	                // IDLE/direct raw inject. Logged (REQ-SEND-SPT-HOSTED MVP): activity
1719	                // is not yet gated, so note the raw write — never a silent mid-turn
1720	                // interleave passed off as done.
1721	                eprintln!(
1722	                    "ENDPOINT_INJECT:{} ({} bytes, idle-direct — activity not yet gated)",
1723	                    req.endpoint,
1724	                    bytes.len()
1725	                );
1726	                session
1727	                    .write_input(&bytes)
1728	                    .map_err(|e| format!("write_input failed: {e}"))?;
1729	                send_frame(send, &endpoint_injected_envelope(&req.endpoint, true));
1730	                Ok(())
1731	            }
1732	            // No hosted session for this endpoint — tell the caller to spool.
1733	            None => {
1734	                send_frame(send, &endpoint_injected_envelope(&req.endpoint, false));
1735	                Ok(())

    /// activity-gated routing (idle→inject / active→spool-for-poll-drain) is a
    /// deferred follow wave. The write is logged so an active-state inject is
    /// never silent.
    // [impl->REQ-SEND-SPT-HOSTED]
    fn dispatch_endpoint_input(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {
        let req: EndpointInputReq = serde_json::from_value(env.payload)
            .map_err(|e| format!("bad endpoint-input payload: {e}"))?;
        let bytes =
            decode_bytes(&req.data_b64).map_err(|e| format!("bad endpoint-input base64: {e}"))?;
        // Resolve endpoint → (session, translation) under the lock, cloning the
        // Arcs out so the PTY write happens without holding the table lock
        // (dispatch_input rule).
        let resolved = {
            let sessions = self.sessions.lock().unwrap();
            sessions
                .values()
                .find(|h| h.endpoint == req.endpoint)
                .map(|h| (Arc::clone(&h.session), h.translation.clone()))
        };
        match resolved {
            Some((session, translation)) => {
                // Layer E: a live (non-faulted) translation binary OWNS the
                // choreography — queue the whole `<EVENT>` envelope to the inject
                // worker, which drives it through the binary and applies the
                // resulting keystrokes atomically w.r.t. the controller. Raw inject
                // is the DEGENERATE fallback when no binary is attached, it has
                // faulted, or the worker channel is gone.
                if let Some(tr) = &translation {
                    if !tr.faulted.load(Ordering::Relaxed) {
                        let envelope = String::from_utf8_lossy(&bytes).into_owned();
                        if tr.event_tx.send(envelope).is_ok() {
                            eprintln!(
                                "ENDPOINT_INJECT:{} ({} bytes → translation binary)",
                                req.endpoint,
                                bytes.len()
                            );
                            send_frame(send, &endpoint_injected_envelope(&req.endpoint, true));
                            return Ok(());
                        }
                        // worker receiver dropped → fall through to raw inject.
                    }
                }
                // IDLE/direct raw inject. Logged (REQ-SEND-SPT-HOSTED MVP): activity
                // is not yet gated, so note the raw write — never a silent mid-turn
                // interleave passed off as done.
                eprintln!(
                    "ENDPOINT_INJECT:{} ({} bytes, idle-direct — activity not yet gated)",
                    req.endpoint,
                    bytes.len()
                );
                session
                    .write_input(&bytes)
                    .map_err(|e| format!("write_input failed: {e}"))?;
                send_frame(send, &endpoint_injected_envelope(&req.endpoint, true));
                Ok(())
            }
            // No hosted session for this endpoint — tell the caller to spool.
            None => {
                send_frame(send, &endpoint_injected_envelope(&req.endpoint, false));
                Ok(())


Activate REQ + registry note

Add set_input_backpressure to spt-store

Convert write_input callers to enqueue

Docs: KNOWN-HAZARDS 7.17 + CONTEXT note

Tests via spt-test-engineer (unit+int)

Gates + commit + surface to doyle


        // Resolve endpoint → (session, translation) under the lock, cloning the
        // Arcs out so the PTY write happens without holding the table lock
        // (dispatch_input rule).
        let resolved = {
            let sessions = self.sessions.lock().unwrap();
            sessions
                .values()
                .find(|h| h.endpoint == req.endpoint)
                .map(|h| (Arc::clone(&h.session), h.translation.clone()))
        };
        match resolved {
            Some((session, translation)) => {

        // Resolve endpoint → (input-writer, translation) under the lock, cloning the
        // Arcs out so the ENQUEUE happens without holding the table lock
        // (dispatch_input rule). The raw-inject path now enqueues to the lone
        // writer too, so it can never park the dispatch thread (P0).
        let resolved = {
            let sessions = self.sessions.lock().unwrap();
            sessions
                .values()
                .find(|h| h.endpoint == req.endpoint)
                .map(|h| (Arc::clone(&h.input), h.translation.clone()))
        };
        match resolved {
            Some((input, translation)) => {


        // Resolve endpoint → (session, translation) under the lock, cloning the
        // Arcs out so the PTY write happens without holding the table lock
        // (dispatch_input rule).
        let resolved = {
            let sessions = self.sessions.lock().unwrap();
            sessions
                .values()
                .find(|h| h.endpoint == req.endpoint)
                .map(|h| (Arc::clone(&h.session), h.translation.clone()))
        };
        match resolved {
            Some((session, translation)) => {

        // Resolve endpoint → (input-writer, translation) under the lock, cloning the
        // Arcs out so the ENQUEUE happens without holding the table lock
        // (dispatch_input rule). The raw-inject path now enqueues to the lone
        // writer too, so it can never park the dispatch thread (P0).
        let resolved = {
            let sessions = self.sessions.lock().unwrap();
            sessions
                .values()
                .find(|h| h.endpoint == req.endpoint)
                .map(|h| (Arc::clone(&h.input), h.translation.clone()))
        };
        match resolved {
            Some((input, translation)) => {

.map_err(|e| format!("bad endpoint-input payload: {e}"))?;

decode_bytes(&req.data_b64).map_err(|e| format!("bad endpoint-input base64: {e}"))?;

-        // Resolve endpoint → (session, translation) under the lock, cloning the

-        // Arcs out so the PTY write happens without holding the table lock

+        // Resolve endpoint → (input-writer, translation) under the lock, cloning the

+        // Arcs out so the ENQUEUE happens without holding the table lock

+        // (dispatch_input rule). The raw-inject path now enqueues to the lone

+        // writer too, so it can never park the dispatch thread (P0).

-                .map(|h| (Arc::clone(&h.session), h.translation.clone()))

+                .map(|h| (Arc::clone(&h.input), h.translation.clone()))

-            Some((session, translation)) => {

+            Some((input, translation)) => {

// Layer E: a live (non-faulted) translation binary OWNS the

// choreography — queue the whole `<EVENT>` envelope to the inject

// worker, which drives it through the binary and applies the


                eprintln!(
                    "ENDPOINT_INJECT:{} ({} bytes, idle-direct — activity not yet gated)",
                    req.endpoint,
                    bytes.len()
                );
                session
                    .write_input(&bytes)
                    .map_err(|e| format!("write_input failed: {e}"))?;
                send_frame(send, &endpoint_injected_envelope(&req.endpoint, true));
                Ok(())

                eprintln!(
                    "ENDPOINT_INJECT:{} ({} bytes, idle-direct — activity not yet gated)",
                    req.endpoint,
                    bytes.len()
                );
                input.enqueue(bytes);
                send_frame(send, &endpoint_injected_envelope(&req.endpoint, true));
                Ok(())


                eprintln!(
                    "ENDPOINT_INJECT:{} ({} bytes, idle-direct — activity not yet gated)",
                    req.endpoint,
                    bytes.len()
                );
                session
                    .write_input(&bytes)
                    .map_err(|e| format!("write_input failed: {e}"))?;
                send_frame(send, &endpoint_injected_envelope(&req.endpoint, true));
                Ok(())

                eprintln!(
                    "ENDPOINT_INJECT:{} ({} bytes, idle-direct — activity not yet gated)",
                    req.endpoint,
                    bytes.len()
                );
                input.enqueue(bytes);
                send_frame(send, &endpoint_injected_envelope(&req.endpoint, true));
                Ok(())

-                    .write_input(&bytes)

-                    .map_err(|e| format!("write_input failed: {e}"))?;


Remove the `is_backpressured` probe (unit tests read the field directly, same crate) to avoid dead-code.


    /// Whether the FIFO is currently saturated + dropping (the unit/int probe).
    fn is_backpressured(&self) -> bool {
        self.backpressure.load(Ordering::Acquire)
    }

    /// Mirror the input-backpressure signal to the perch's `info.json` (the

/// Mirror the input-backpressure signal to the perch's `info.json` (the


    /// Whether the FIFO is currently saturated + dropping (the unit/int probe).
    fn is_backpressured(&self) -> bool {
        self.backpressure.load(Ordering::Acquire)
    }

    /// Mirror the input-backpressure signal to the perch's `info.json` (the

-    /// Whether the FIFO is currently saturated + dropping (the unit/int probe).

-    fn is_backpressured(&self) -> bool {

-        self.backpressure.load(Ordering::Acquire)

/// broker is the single writer). Best-effort: a session served without a

/// perch (raw broker tests) has nowhere to record.


Check `io` still used in broker.rs + compile.


crates\spt-daemon\src\broker.rs:1050:    pub fn bind(name: &str) -> io::Result<Arc<Self>> {
crates\spt-daemon\src\broker.rs:1056:    pub fn bind_in(name: &str, journal_path: std::path::PathBuf) -> io::Result<Arc<Self>> {
crates\spt-daemon\src\broker.rs:1069:    ) -> io::Result<Arc<Self>> {
crates\spt-daemon\src\broker.rs:1084:    ) -> io::Result<Arc<Self>> {
crates\spt-daemon\src\broker.rs:1137:    pub fn serve(self: &Arc<Self>) -> io::Result<()> {
crates\spt-daemon\src\broker.rs:1149:    pub fn serve_once(self: &Arc<Self>) -> io::Result<JoinHandle<()>> {
crates\spt-daemon\src\broker.rs:1202:    fn handle_conn(self: &Arc<Self>, conn: Stream) -> io::Result<()> {


Bash

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

Type-check spt-daemon + spt-store


    Checking spt-store v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-store)
    Checking spt-msg v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-msg)
    Checking spt-runtime v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime)
    Checking spt-term v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-term)
    Checking spt-net v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
    Checking spt-live v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-live)
    Checking spt-daemon v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 5.33s


Compiles. Targeted clippy.


cargo clippy -p spt-daemon -p spt-store --all-targets -- -D warnings 2>&1 | tail -30

Clippy spt-daemon + spt-store all-targets


    Checking spt-store v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-store)
    Checking spt-msg v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-msg)
    Checking spt-runtime v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-runtime)
    Checking spt-term v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-term)
    Checking spt-net v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
    Checking spt-live v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-live)
    Checking spt-daemon v0.12.1 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.52s


Impl compiles clean. Mark tasks 1-4 done, do docs.


ToolSearch

Question: select:TaskUpdate


Question: select:TaskUpdate


Updated task #1 status


Updated task #2 status


Updated task #3 status


Updated task #4 status


Updated task #5 status


Now docs. Read KNOWN-HAZARDS 7.14-7.16 area + the CONTEXT seam line.