   Compiling spt-daemon v0.12.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
   Compiling spt v0.12.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 11.30s
     Running tests\dummy_harness_e2e.rs (target-seam\debug\deps\dummy_harness_e2e-9c610c804600b6fe.exe)

running 1 test
test endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches ... === DIAGNOSTIC: run_ok=true online=true harness_pid=Some(122144) alive_after=true rc_connected=true rc_saw_tick=false rc_stdout_bytes=0 ===
=== endpoint run stderr ===
ENDPOINT_RUN:dummy7 adapter=dummyharness pid=Some(122144) session=cab6d02df84a81ef (harness binds its perch on startup)
ENDPOINT_RUN_STARTED:dummy7 (attach with `spt rc dummy7`)

=== rc stderr ===
PUMP_IPC_READER: spawned

=== rc stdout (first 400) ===

=== brain/broker stderr ===
BRAIN_UP: connected to broker spt-daemon-broker-ef7a4e74ac215745.sock (pid 25708, generation 0, start-reason cold)
DIAG_BECOME_CTRL sid=1 from_seq=0 ring_len=0 next_seq=0 by=None
DIAG_DRAIN sid=1 chunk_bytes=16
DIAG_DRAIN sid=1 chunk_bytes=110
DIAG_DRAIN sid=1 chunk_bytes=47
DIAG_DRAIN sid=1 chunk_bytes=28
DIAG_DRAIN sid=1 chunk_bytes=29
DIAG_DRAIN sid=1 chunk_bytes=29
BRAIN_NET_CONSUMERS_UP: net enabled (node e1af60251264f8bd69fb62f303755ac9ea10696c2b699159b72ed35d08106111) — dispatcher + peer pump started
PUMP_IPC_READER: spawned
DIAG_DRAIN sid=1 chunk_bytes=29
DIAG_DRAIN sid=1 chunk_bytes=29
DIAG_SLOG_APPEND stream=1 seq=0 bytes=66 has_sub=false
DIAG_SLOG_ATTACH stream=2 from_seq=0 ring_len=0 next_seq=0
DIAG_SLOG_APPEND stream=1 seq=1 bytes=39 has_sub=false
DIAG_SLOG_APPEND stream=1 seq=2 bytes=39 has_sub=false
DIAG_SLOG_ATTACH stream=1 from_seq=0 ring_len=3 next_seq=3
DIAG_SLOG_ATTACH stream=1 from_seq=0 ring_len=3 next_seq=3
DIAG_SERVE_ATTACH session_id=1 from_seq=0 intent=Control origin=e1af60251264f8bd69fb62f303755ac9ea10696c2b699159b72ed35d08106111
DIAG_BECOME_CTRL sid=1 from_seq=0 ring_len=8 next_seq=8 by=Some("e1af60251264f8bd69fb62f303755ac9ea10696c2b699159b72ed35d08106111")
DIAG_SERVE_FWD seq=0 bytes=16
DIAG_SERVE_FWD seq=1 bytes=110
DIAG_DRAIN sid=1 chunk_bytes=29

WAVE2-LEAD: rc attached but received 0 bytes, no DUMMY_HARNESS_TICK — endpoint-run harness PTY output not reaching the attach (root-cause pending).
ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 13.64s


Now the topology is exposed. stream 1 / stream 2 = the loopback pair. Critical anomalies:

1. `DIAG_SLOG_ATTACH stream=1 from_seq=0 ring_len=3` appears **TWICE** — two `attach` calls on stream 1's log, but `StreamLog.subscriber` is a SINGLE slot (L153) → the second OVERWRITES the first.
2. `DIAG_SERVE_FWD seq=0/seq1` fired, but there is **NO `DIAG_SLOG_APPEND` for the 16/110-byte forwarded output** — serve's `net_stream_send` bytes never landed in any stream log, so rc never got them.

So serve's output-forward `net_stream_send` is NOT delivering to the loopback peer's read pump. Read the loopback send routing + stream-pair wiring.


340	    fn detach_if(&mut self, sub: &SharedSend) {
341	        if let Some(cur) = &self.subscriber {
342	            if Arc::ptr_eq(cur, sub) {
343	                self.subscriber = None;
344	            }
345	        }
346	    }
347	}
348
349	/// A stream's send half — the same two transports as [`ConnKind`]. The
350	/// `serve_attach`/operator pump and the `StreamLog` read discipline are
351	/// IDENTICAL across both; only this leaf write differs (QUIC stream vs the
352	/// write end of an in-process duplex). That is the M12 W1.5 one-pump invariant
353	/// made structural: a loopback attach and a cross-node attach diverge at this
354	/// leaf and nowhere above it.
355	enum SendHalf {
356	    Quic(SendStream),
357	    Loopback(WriteHalf<DuplexStream>),
358	}
359
360	impl SendHalf {
361	    /// Write all `bytes` (no-op on empty). Runs on the host runtime.
362	    async fn write_all(&mut self, bytes: &[u8]) -> io::Result<()> {
363	        match self {
364	            SendHalf::Quic(s) => s
365	                .write_all(bytes)
366	                .await
367	                .map_err(|e| io::Error::other(e.to_string())),
368	            SendHalf::Loopback(w) => w.write_all(bytes).await,
369	        }
370	    }
371
372	    /// Close the send side so the peer's read pump sees a clean end (QUIC
373	    /// `finish` / duplex `shutdown`).
374	    async fn finish(&mut self) -> io::Result<()> {
375	        match self {
376	            SendHalf::Quic(s) => s.finish().map_err(|e| io::Error::other(e.to_string())),
377	            SendHalf::Loopback(w) => w.shutdown().await,
378	        }
379	    }
380	}
381
382	/// A stream's read half — fed into the single read pump in [`register_stream`].
383	/// QUIC reads via quinn's chunk API; loopback via the read end of the duplex.
384	/// Both append to the same [`StreamLog`], so the subscriber (and everything
385	/// above it) cannot tell the transports apart.
386	enum RecvHalf {
387	    Quic(RecvStream),
388	    Loopback(ReadHalf<DuplexStream>),
389	}
390
391	/// One live stream the broker holds (QUIC or loopback): the send half (brain
392	/// writes ride it over IPC) + the sequenced read log.
393	struct StreamEntry {
394	    conn_id: u64,
395	    remote_id_hex: String,
396	    /// Whether THIS node opened the stream (`open_stream`) vs the peer
397	    /// (accept loop). The inbound dispatcher (D9-1) claims only
398	    /// peer-initiated streams; without this fact the table is ambiguous —
399	    /// it holds locally-opened outbound streams too (gotcha: a dispatcher
400	    /// that served its own requester stream would deadlock on itself).
401	    initiated_locally: bool,
402	    /// The send half, serialized behind an async mutex (writes happen on the
403	    /// host's runtime via `block_on`).
404	    send: tokio::sync::Mutex<SendHalf>,
405	    log: Arc<Mutex<StreamLog>>,
406	    /// Wakes the read pump when a retentive (tunnel) stream's ring drains below
407	    /// its backpressure cap (M11-W3, REQ-SHELL-4) — see [`StreamLog::retentive_cap`].
408	    /// Unused by ordinary (non-retentive) streams.
409	    room: Arc<tokio::sync::Notify>,
410	}
411
412	/// Tables shared between the host's sync surface and its runtime tasks
413	/// (accept loop, per-conn stream acceptors, per-stream read pumps).
414	struct NetShared {
415	    next_conn_id: AtomicU64,
416	    conns: Mutex<HashMap<u64, ConnEntry>>,
417	    next_stream_id: AtomicU64,
418	    streams: Mutex<HashMap<u64, Arc<StreamEntry>>>,
419	    ring_cap: usize,
420	    /// The conn-liveness event log (D4c) — one per host, all conns interleaved.
421	    presence: Mutex<PresenceLog>,
422	}
423
424	impl NetShared {
425	    fn new() -> Self {
426	        NetShared {
427	            next_conn_id: AtomicU64::new(1),
428	            conns: Mutex::new(HashMap::new()),
429	            next_stream_id: AtomicU64::new(1),
430	            streams: Mutex::new(HashMap::new()),
431	            ring_cap: DEFAULT_STREAM_RING_CHUNKS,
432	            presence: Mutex::new(PresenceLog::new(DEFAULT_STREAM_RING_CHUNKS)),
433	        }
434	    }
435	}
436
437	/// Register a live connection: mint its broker id and spawn its inbound-stream
438	/// acceptor. Must run inside the host's runtime context (the accept loop, or a
439	/// `block_on` future) — it spawns tasks.
440	fn register_conn(
441	    shared: &Arc<NetShared>,
442	    conn: Connection,
443	    proven_subnets: HashSet<String>,
444	) -> (u64, String) {
445	    let remote_id_hex = PublicKey::from_bytes(conn.remote_id().as_bytes())
446	        .expect("an iroh EndpointId is always a valid ed25519 public key")
447	        .to_hex();
448	    let id = shared.next_conn_id.fetch_add(1, Ordering::Relaxed);
449	    shared.conns.lock().unwrap().insert(
450	        id,
451	        ConnEntry {
452	            kind: ConnKind::Quic(conn.clone()),
453	            remote_id_hex: remote_id_hex.clone(),
454	            proven_subnets,
455	        },
456	    );
457
458	    // Presence: announce the row BEFORE spawning the closed-watcher, so the
459	    // `connected` event can never race behind its own `disconnected` (D4c).

    fn detach_if(&mut self, sub: &SharedSend) {
        if let Some(cur) = &self.subscriber {
            if Arc::ptr_eq(cur, sub) {
                self.subscriber = None;
            }
        }
    }
}

/// A stream's send half — the same two transports as [`ConnKind`]. The
/// `serve_attach`/operator pump and the `StreamLog` read discipline are
/// IDENTICAL across both; only this leaf write differs (QUIC stream vs the
/// write end of an in-process duplex). That is the M12 W1.5 one-pump invariant
/// made structural: a loopback attach and a cross-node attach diverge at this
/// leaf and nowhere above it.
enum SendHalf {
    Quic(SendStream),
    Loopback(WriteHalf<DuplexStream>),
}

impl SendHalf {
    /// Write all `bytes` (no-op on empty). Runs on the host runtime.
    async fn write_all(&mut self, bytes: &[u8]) -> io::Result<()> {
        match self {
            SendHalf::Quic(s) => s
                .write_all(bytes)
                .await
                .map_err(|e| io::Error::other(e.to_string())),
            SendHalf::Loopback(w) => w.write_all(bytes).await,
        }
    }

    /// Close the send side so the peer's read pump sees a clean end (QUIC
    /// `finish` / duplex `shutdown`).
    async fn finish(&mut self) -> io::Result<()> {
        match self {
            SendHalf::Quic(s) => s.finish().map_err(|e| io::Error::other(e.to_string())),
            SendHalf::Loopback(w) => w.shutdown().await,
        }
    }
}

/// A stream's read half — fed into the single read pump in [`register_stream`].
/// QUIC reads via quinn's chunk API; loopback via the read end of the duplex.
/// Both append to the same [`StreamLog`], so the subscriber (and everything
/// above it) cannot tell the transports apart.
enum RecvHalf {
    Quic(RecvStream),
    Loopback(ReadHalf<DuplexStream>),
}

/// One live stream the broker holds (QUIC or loopback): the send half (brain
/// writes ride it over IPC) + the sequenced read log.
struct StreamEntry {
    conn_id: u64,
    remote_id_hex: String,
    /// Whether THIS node opened the stream (`open_stream`) vs the peer
    /// (accept loop). The inbound dispatcher (D9-1) claims only
    /// peer-initiated streams; without this fact the table is ambiguous —
    /// it holds locally-opened outbound streams too (gotcha: a dispatcher
    /// that served its own requester stream would deadlock on itself).
    initiated_locally: bool,
    /// The send half, serialized behind an async mutex (writes happen on the
    /// host's runtime via `block_on`).
    send: tokio::sync::Mutex<SendHalf>,
    log: Arc<Mutex<StreamLog>>,
    /// Wakes the read pump when a retentive (tunnel) stream's ring drains below
    /// its backpressure cap (M11-W3, REQ-SHELL-4) — see [`StreamLog::retentive_cap`].
    /// Unused by ordinary (non-retentive) streams.
    room: Arc<tokio::sync::Notify>,
}

/// Tables shared between the host's sync surface and its runtime tasks
/// (accept loop, per-conn stream acceptors, per-stream read pumps).
struct NetShared {
    next_conn_id: AtomicU64,
    conns: Mutex<HashMap<u64, ConnEntry>>,
    next_stream_id: AtomicU64,
    streams: Mutex<HashMap<u64, Arc<StreamEntry>>>,
    ring_cap: usize,
    /// The conn-liveness event log (D4c) — one per host, all conns interleaved.
    presence: Mutex<PresenceLog>,
}

impl NetShared {
    fn new() -> Self {
        NetShared {
            next_conn_id: AtomicU64::new(1),
            conns: Mutex::new(HashMap::new()),
            next_stream_id: AtomicU64::new(1),
            streams: Mutex::new(HashMap::new()),
            ring_cap: DEFAULT_STREAM_RING_CHUNKS,
            presence: Mutex::new(PresenceLog::new(DEFAULT_STREAM_RING_CHUNKS)),
        }
    }
}

/// Register a live connection: mint its broker id and spawn its inbound-stream
/// acceptor. Must run inside the host's runtime context (the accept loop, or a
/// `block_on` future) — it spawns tasks.
fn register_conn(
    shared: &Arc<NetShared>,
    conn: Connection,
    proven_subnets: HashSet<String>,
) -> (u64, String) {
    let remote_id_hex = PublicKey::from_bytes(conn.remote_id().as_bytes())
        .expect("an iroh EndpointId is always a valid ed25519 public key")
        .to_hex();
    let id = shared.next_conn_id.fetch_add(1, Ordering::Relaxed);
    shared.conns.lock().unwrap().insert(
        id,
        ConnEntry {
            kind: ConnKind::Quic(conn.clone()),
            remote_id_hex: remote_id_hex.clone(),
            proven_subnets,
        },
    );

    // Presence: announce the row BEFORE spawning the closed-watcher, so the
    // `connected` event can never race behind its own `disconnected` (D4c).


Grep

fn dispatch_net_stream_send|fn send_on_stream|fn open_stream|fn register_stream|fn open_loopback_pair|fn request_attach


crates\spt-daemon\src\attach.rs:304:pub fn request_attach(
crates\spt-daemon\src\broker.rs:1375:    fn dispatch_net_stream_send(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {
crates\spt-daemon\src\nethost.rs:509:fn register_stream(
crates\spt-daemon\src\nethost.rs:1014:    pub fn open_stream(&self, conn_id: u64) -> io::Result<u64> {
crates\spt-daemon\src\nethost.rs:1207:    pub fn open_loopback_pair(&self) -> io::Result<(u64, u64)> {


304	pub fn request_attach(
305	    brain: &mut Brain,
306	    conn_id: u64,
307	    session_id: u64,
308	    from_seq: u64,
309	    open_op: u64,
310	    intent: AttachIntent,
311	) -> io::Result<u64> {
312	    let opened = brain.net_open_stream(conn_id, Some(open_op))?;
313	    let line = ndjson::encode_line(&AttachRecord::Request {
314	        session_id,
315	        from_seq,
316	        intent,
317	    });
318	    brain.net_stream_send(opened.stream_id, &line, None, false)?;
319	    Ok(opened.stream_id)
320	}
321
322	/// Operator-side (CONTROLLER): send a resize record onto an attach stream —
323	/// the controller's viewport size, forwarded into the target PTY (REQ-RCVIEW-1,
324	/// controller-exclusive: the broker rejects a resize from a viewer's stream).
325	/// Sent on attach (initial size) and on every window-change. Unjournaled (a
326	/// stale resize is harmless — the next one corrects it).
327	// [impl->REQ-RCVIEW-1]
328	pub fn send_attach_resize(
329	    brain: &mut Brain,
330	    stream_id: u64,
331	    rows: u16,
332	    cols: u16,
333	) -> io::Result<()> {
334	    let line = ndjson::encode_line(&AttachRecord::Resize { rows, cols });
335	    brain.net_stream_send(stream_id, &line, None, false)?;
336	    Ok(())
337	}
338
339	/// Operator-side: send one input record (the keystrokes) onto an attach
340	/// stream. `wire_op` is the operator-minted durable id the TARGET journals the
341	/// PTY write under — exactly-once at the keys, however often the record
342	/// crosses the wire. The send itself is unjournaled (see module docs).
343	// [impl->REQ-INST-8]
344	pub fn send_attach_input(
345	    brain: &mut Brain,
346	    stream_id: u64,
347	    bytes: &[u8],
348	    wire_op: u64,
349	) -> io::Result<()> {
350	    let line = ndjson::encode_line(&AttachRecord::Input {
351	        data_b64: encode_bytes(bytes),
352	        op_id: wire_op,
353	    });
354	    brain.net_stream_send(stream_id, &line, None, false)?;
355	    Ok(())
356	}
357

pub fn request_attach(
    brain: &mut Brain,
    conn_id: u64,
    session_id: u64,
    from_seq: u64,
    open_op: u64,
    intent: AttachIntent,
) -> io::Result<u64> {
    let opened = brain.net_open_stream(conn_id, Some(open_op))?;
    let line = ndjson::encode_line(&AttachRecord::Request {
        session_id,
        from_seq,
        intent,
    });
    brain.net_stream_send(opened.stream_id, &line, None, false)?;
    Ok(opened.stream_id)
}

/// Operator-side (CONTROLLER): send a resize record onto an attach stream —
/// the controller's viewport size, forwarded into the target PTY (REQ-RCVIEW-1,
/// controller-exclusive: the broker rejects a resize from a viewer's stream).
/// Sent on attach (initial size) and on every window-change. Unjournaled (a
/// stale resize is harmless — the next one corrects it).
// [impl->REQ-RCVIEW-1]
pub fn send_attach_resize(
    brain: &mut Brain,
    stream_id: u64,
    rows: u16,
    cols: u16,
) -> io::Result<()> {
    let line = ndjson::encode_line(&AttachRecord::Resize { rows, cols });
    brain.net_stream_send(stream_id, &line, None, false)?;
    Ok(())
}

/// Operator-side: send one input record (the keystrokes) onto an attach
/// stream. `wire_op` is the operator-minted durable id the TARGET journals the
/// PTY write under — exactly-once at the keys, however often the record
/// crosses the wire. The send itself is unjournaled (see module docs).
// [impl->REQ-INST-8]
pub fn send_attach_input(
    brain: &mut Brain,
    stream_id: u64,
    bytes: &[u8],
    wire_op: u64,
) -> io::Result<()> {
    let line = ndjson::encode_line(&AttachRecord::Input {
        data_b64: encode_bytes(bytes),
        op_id: wire_op,
    });
    brain.net_stream_send(stream_id, &line, None, false)?;
    Ok(())
}


The cross-wiring is the key. Decoding the trace: rc opens stream **2** (writes Request → cross-wired into stream **1**'s log, which serve reads). serve is invoked on accepted stream **1**, subscribes stream 1, and forwards Output via `net_stream_send(1)` — which **should** cross-wire into stream 2's log for rc. But **no `DIAG_SLOG_APPEND stream=2` ever fires** after the forwards. So the serve→rc reverse direction of the loopback isn't wired. Read `open_stream` (loopback) + `register_stream` to see how the duplex pair is built.


1014	    pub fn open_stream(&self, conn_id: u64) -> io::Result<u64> {
1015	        enum Backing {
1016	            Quic(Connection),
1017	            Loopback,
1018	        }
1019	        let (backing, remote_id_hex) = {
1020	            let conns = self.shared.conns.lock().unwrap();
1021	            let e = conns.get(&conn_id).ok_or_else(|| {
1022	                io::Error::new(io::ErrorKind::NotFound, format!("no such conn {conn_id}"))
1023	            })?;
1024	            let backing = match &e.kind {
1025	                ConnKind::Quic(c) => Backing::Quic(c.clone()),
1026	                ConnKind::Loopback => Backing::Loopback,
1027	            };
1028	            (backing, e.remote_id_hex.clone())
1029	        };
1030	        let shared = Arc::clone(&self.shared);
1031	        match backing {
1032	            // Bounded (REQ-HAZARD-BROKER-QUIC-DEADLINE): `open_bi` on a black-holed
1033	            // peer can block on stream flow-control credit the dead peer never grants.
1034	            Backing::Quic(conn) => self.bounded_block_on(async move {
1035	                let (send, recv) = conn
1036	                    .open_bi()
1037	                    .await
1038	                    .map_err(|e| io::Error::other(e.to_string()))?;
1039	                Ok(register_stream(
1040	                    &shared,
1041	                    conn_id,
1042	                    &remote_id_hex,
1043	                    SendHalf::Quic(send),
1044	                    RecvHalf::Quic(recv),
1045	                    true,
1046	                ))
1047	            }),
1048	            Backing::Loopback => self.runtime.handle().block_on(async move {
1049	                // One duplex carries BOTH directions: operator writes reach the
1050	                // peer's read pump and vice-versa. Split each end into a read +
1051	                // write half so each row owns exactly its leg.
1052	                let (op_io, peer_io) = tokio::io::duplex(STREAM_READ_CHUNK);
1053	                let (op_r, op_w) = tokio::io::split(op_io);
1054	                let (peer_r, peer_w) = tokio::io::split(peer_io);
1055	                // Register the peer (inbound) row first so it is in the table
1056	                // before the operator can write its Request onto the wire — the
1057	                // dispatcher must never miss the stream.
1058	                register_stream(
1059	                    &shared,
1060	                    conn_id,
1061	                    &remote_id_hex,
1062	                    SendHalf::Loopback(peer_w),
1063	                    RecvHalf::Loopback(peer_r),
1064	                    false,
1065	                );
1066	                Ok(register_stream(
1067	                    &shared,
1068	                    conn_id,
1069	                    &remote_id_hex,
1070	                    SendHalf::Loopback(op_w),
1071	                    RecvHalf::Loopback(op_r),
1072	                    true,
1073	                ))
1074	            }),
1075	        }
1076	    }
1077
1078	    /// Record that journaled open `op_id` minted `stream_id` (inside the
1079	    /// `apply_once` closure — same discipline as [`Self::record_dial_op`]).
1080	    pub fn record_stream_op(&self, op_id: u64, stream_id: u64) {
1081	        self.stream_ops.lock().unwrap().insert(op_id, stream_id);
1082	    }
1083
1084	    /// The stream a journaled open `op_id` minted, if still held.
1085	    pub fn stream_op_id(&self, op_id: u64) -> Option<u64> {
1086	        self.stream_ops.lock().unwrap().get(&op_id).copied()
1087	    }
1088
1089	    /// A held stream's `(conn_id, remote_id_hex)`.
1090	    pub fn stream_conn(&self, stream_id: u64) -> Option<(u64, String)> {
1091	        self.shared
1092	            .streams
1093	            .lock()
1094	            .unwrap()
1095	            .get(&stream_id)
1096	            .map(|e| (e.conn_id, e.remote_id_hex.clone()))
1097	    }
1098
1099	    /// Write `bytes` to a held stream's send side; `finish` closes the send
1100	    /// side after the write (the peer's read pump then sees a clean end).
1101	    // [impl->REQ-DAEMON-2]
1102	    pub fn send_stream(&self, stream_id: u64, bytes: &[u8], finish: bool) -> io::Result<()> {
1103	        let entry = {
1104	            let streams = self.shared.streams.lock().unwrap();
1105	            Arc::clone(streams.get(&stream_id).ok_or_else(|| {
1106	                io::Error::new(
1107	                    io::ErrorKind::NotFound,
1108	                    format!("no such stream {stream_id}"),
1109	                )
1110	            })?)
1111	        };
1112	        let bytes = bytes.to_vec();
1113	        // Bounded (REQ-HAZARD-BROKER-QUIC-DEADLINE): `write_all` / `finish` on a
1114	        // black-holed QUIC stream blocks on flow-control the dead peer never grants.
1115	        // Loopback (in-process) never hangs, so the bound is a no-op for it.
1116	        self.bounded_block_on(async move {
1117	            let mut send = entry.send.lock().await;
1118	            if !bytes.is_empty() {
1119	                send.write_all(&bytes).await?;
1120	            }
1121	            if finish {
1122	                send.finish().await?;
1123	            }
1124	            Ok(())
1125	        })
1126	    }
1127
1128	    /// Every held stream's info — the query a re-attaching brain uses to find
1129	    /// streams (including peer-initiated ones that arrived while it was down).
1130	    pub fn stream_infos(&self) -> Vec<NetStreamInfo> {
1131	        let streams = self.shared.streams.lock().unwrap();
1132	        streams
1133	            .iter()
1134	            .map(|(id, e)| {
1135	                let log = e.log.lock().unwrap();
1136	                NetStreamInfo {
1137	                    stream_id: *id,
1138	                    conn_id: e.conn_id,
1139	                    remote_id_hex: e.remote_id_hex.clone(),
1140	                    next_seq: log.next_seq,
1141	                    finished: log.finished,
1142	                    initiated_locally: e.initiated_locally,
1143	                }
1144	            })
1145	            .collect()
1146	    }
1147
1148	    /// Mark a held stream **retentive** with backpressure `cap` chunks (M11-W3,
1149	    /// REQ-SHELL-4): its read log stops evicting (no byte loss) and its read pump
1150	    /// lags the sender once the undrained ring reaches `cap` (no unbounded growth).
1151	    /// Used for shell-tunnel streams; ordinary streams stay bounded-overwrite.
1152	    /// No-op if the stream is gone.
1153	    // [impl->REQ-SHELL-4]
1154	    pub fn mark_stream_retentive(&self, stream_id: u64, cap: usize) {
1155	        if let Some(e) = self.shared.streams.lock().unwrap().get(&stream_id) {
1156	            e.log.lock().unwrap().set_retentive(cap);
1157	        }
1158	    }
1159
1160	    /// Drain a retentive stream's undrained bytes (the shell-tunnel recv cursor):
1161	    /// pops the whole ring in order, concatenated, and wakes the read pump (which
1162	    /// may have been lagging the sender at the backpressure cap). Returns
1163	    /// `(bytes, finished)`; `finished` ⇒ the read side ended (the poller closes).
1164	    /// `None` ⇒ no such stream. Empty bytes + `finished == false` ⇒ nothing pending.
1165	    // [impl->REQ-SHELL-4]
1166	    pub fn drain_stream(&self, stream_id: u64) -> Option<(Vec<u8>, bool)> {
1167	        let entry = Arc::clone(self.shared.streams.lock().unwrap().get(&stream_id)?);
1168	        let drained = entry.log.lock().unwrap().drain();
1169	        // The drain freed ring room — wake a pump that was backpressure-parked.
1170	        entry.room.notify_one();
1171	        Some(drained)
1172	    }
1173
1174	    /// A held stream's current undrained ring depth in chunks (test introspection
1175	    /// for the retentive backpressure bound). `None` ⇒ no such stream.
1176	    #[cfg(test)]
1177	    fn ring_len(&self, stream_id: u64) -> Option<usize> {
1178	        self.shared
1179	            .streams
1180	            .lock()
1181	            .unwrap()
1182	            .get(&stream_id)
1183	            .map(|e| e.log.lock().unwrap().ring.len())
1184	    }
1185
1186	    /// Inbound (peer-initiated) stream rows currently held on `conn_id` — the
1187	    /// candidate set [`Self::open_loopback_pair`] diffs to find the peer it just
1188	    /// minted.

    pub fn open_stream(&self, conn_id: u64) -> io::Result<u64> {
        enum Backing {
            Quic(Connection),
            Loopback,
        }
        let (backing, remote_id_hex) = {
            let conns = self.shared.conns.lock().unwrap();
            let e = conns.get(&conn_id).ok_or_else(|| {
                io::Error::new(io::ErrorKind::NotFound, format!("no such conn {conn_id}"))
            })?;
            let backing = match &e.kind {
                ConnKind::Quic(c) => Backing::Quic(c.clone()),
                ConnKind::Loopback => Backing::Loopback,
            };
            (backing, e.remote_id_hex.clone())
        };
        let shared = Arc::clone(&self.shared);
        match backing {
            // Bounded (REQ-HAZARD-BROKER-QUIC-DEADLINE): `open_bi` on a black-holed
            // peer can block on stream flow-control credit the dead peer never grants.
            Backing::Quic(conn) => self.bounded_block_on(async move {
                let (send, recv) = conn
                    .open_bi()
                    .await
                    .map_err(|e| io::Error::other(e.to_string()))?;
                Ok(register_stream(
                    &shared,
                    conn_id,
                    &remote_id_hex,
                    SendHalf::Quic(send),
                    RecvHalf::Quic(recv),
                    true,
                ))
            }),
            Backing::Loopback => self.runtime.handle().block_on(async move {
                // One duplex carries BOTH directions: operator writes reach the
                // peer's read pump and vice-versa. Split each end into a read +
                // write half so each row owns exactly its leg.
                let (op_io, peer_io) = tokio::io::duplex(STREAM_READ_CHUNK);
                let (op_r, op_w) = tokio::io::split(op_io);
                let (peer_r, peer_w) = tokio::io::split(peer_io);
                // Register the peer (inbound) row first so it is in the table
                // before the operator can write its Request onto the wire — the
                // dispatcher must never miss the stream.
                register_stream(
                    &shared,
                    conn_id,
                    &remote_id_hex,
                    SendHalf::Loopback(peer_w),
                    RecvHalf::Loopback(peer_r),
                    false,
                );
                Ok(register_stream(
                    &shared,
                    conn_id,
                    &remote_id_hex,
                    SendHalf::Loopback(op_w),
                    RecvHalf::Loopback(op_r),
                    true,
                ))
            }),
        }
    }

    /// Record that journaled open `op_id` minted `stream_id` (inside the
    /// `apply_once` closure — same discipline as [`Self::record_dial_op`]).
    pub fn record_stream_op(&self, op_id: u64, stream_id: u64) {
        self.stream_ops.lock().unwrap().insert(op_id, stream_id);
    }

    /// The stream a journaled open `op_id` minted, if still held.
    pub fn stream_op_id(&self, op_id: u64) -> Option<u64> {
        self.stream_ops.lock().unwrap().get(&op_id).copied()
    }

    /// A held stream's `(conn_id, remote_id_hex)`.
    pub fn stream_conn(&self, stream_id: u64) -> Option<(u64, String)> {
        self.shared
            .streams
            .lock()
            .unwrap()
            .get(&stream_id)
            .map(|e| (e.conn_id, e.remote_id_hex.clone()))
    }

    /// Write `bytes` to a held stream's send side; `finish` closes the send
    /// side after the write (the peer's read pump then sees a clean end).
    // [impl->REQ-DAEMON-2]
    pub fn send_stream(&self, stream_id: u64, bytes: &[u8], finish: bool) -> io::Result<()> {
        let entry = {
            let streams = self.shared.streams.lock().unwrap();
            Arc::clone(streams.get(&stream_id).ok_or_else(|| {
                io::Error::new(
                    io::ErrorKind::NotFound,
                    format!("no such stream {stream_id}"),
                )
            })?)
        };
        let bytes = bytes.to_vec();
        // Bounded (REQ-HAZARD-BROKER-QUIC-DEADLINE): `write_all` / `finish` on a
        // black-holed QUIC stream blocks on flow-control the dead peer never grants.
        // Loopback (in-process) never hangs, so the bound is a no-op for it.
        self.bounded_block_on(async move {
            let mut send = entry.send.lock().await;
            if !bytes.is_empty() {
                send.write_all(&bytes).await?;
            }
            if finish {
                send.finish().await?;
            }
            Ok(())
        })
    }

    /// Every held stream's info — the query a re-attaching brain uses to find
    /// streams (including peer-initiated ones that arrived while it was down).
    pub fn stream_infos(&self) -> Vec<NetStreamInfo> {
        let streams = self.shared.streams.lock().unwrap();
        streams
            .iter()
            .map(|(id, e)| {
                let log = e.log.lock().unwrap();
                NetStreamInfo {
                    stream_id: *id,
                    conn_id: e.conn_id,
                    remote_id_hex: e.remote_id_hex.clone(),
                    next_seq: log.next_seq,
                    finished: log.finished,
                    initiated_locally: e.initiated_locally,
                }
            })
            .collect()
    }

    /// Mark a held stream **retentive** with backpressure `cap` chunks (M11-W3,
    /// REQ-SHELL-4): its read log stops evicting (no byte loss) and its read pump
    /// lags the sender once the undrained ring reaches `cap` (no unbounded growth).
    /// Used for shell-tunnel streams; ordinary streams stay bounded-overwrite.
    /// No-op if the stream is gone.
    // [impl->REQ-SHELL-4]
    pub fn mark_stream_retentive(&self, stream_id: u64, cap: usize) {
        if let Some(e) = self.shared.streams.lock().unwrap().get(&stream_id) {
            e.log.lock().unwrap().set_retentive(cap);
        }
    }

    /// Drain a retentive stream's undrained bytes (the shell-tunnel recv cursor):
    /// pops the whole ring in order, concatenated, and wakes the read pump (which
    /// may have been lagging the sender at the backpressure cap). Returns
    /// `(bytes, finished)`; `finished` ⇒ the read side ended (the poller closes).
    /// `None` ⇒ no such stream. Empty bytes + `finished == false` ⇒ nothing pending.
    // [impl->REQ-SHELL-4]
    pub fn drain_stream(&self, stream_id: u64) -> Option<(Vec<u8>, bool)> {
        let entry = Arc::clone(self.shared.streams.lock().unwrap().get(&stream_id)?);
        let drained = entry.log.lock().unwrap().drain();
        // The drain freed ring room — wake a pump that was backpressure-parked.
        entry.room.notify_one();
        Some(drained)
    }

    /// A held stream's current undrained ring depth in chunks (test introspection
    /// for the retentive backpressure bound). `None` ⇒ no such stream.
    #[cfg(test)]
    fn ring_len(&self, stream_id: u64) -> Option<usize> {
        self.shared
            .streams
            .lock()
            .unwrap()
            .get(&stream_id)
            .map(|e| e.log.lock().unwrap().ring.len())
    }

    /// Inbound (peer-initiated) stream rows currently held on `conn_id` — the
    /// candidate set [`Self::open_loopback_pair`] diffs to find the peer it just
    /// minted.


The loopback duplex math checks out on paper: rc opens stream **2** (op: op_w/op_r), serve gets stream **1** (peer: peer_w/peer_r). rc→serve: op_w→peer_r→stream1 log ✓ (matches trace). serve→rc: serve writes stream1's send=peer_w → should be read by op_r→stream2 log. But stream 2 never appends. So **stream 2's read pump (op_r) isn't delivering peer_w's writes.** Read `register_stream` — does it spawn a read pump for the op (locally-initiated) row?


509	fn register_stream(
510	    shared: &Arc<NetShared>,
511	    conn_id: u64,
512	    remote_id_hex: &str,
513	    send: SendHalf,
514	    recv: RecvHalf,
515	    initiated_locally: bool,
516	) -> u64 {
517	    let id = shared.next_stream_id.fetch_add(1, Ordering::Relaxed);
518	    let log = Arc::new(Mutex::new(StreamLog::new(id, shared.ring_cap)));
519	    let room = Arc::new(tokio::sync::Notify::new());
520	    shared.streams.lock().unwrap().insert(
521	        id,
522	        Arc::new(StreamEntry {
523	            conn_id,
524	            remote_id_hex: remote_id_hex.to_string(),
525	            initiated_locally,
526	            send: tokio::sync::Mutex::new(send),
527	            log: Arc::clone(&log),
528	            room: Arc::clone(&room),
529	        }),
530	    );
531
532	    // The read pump — the stream's single producer. A clean end (peer finish)
533	    // and a torn end (conn lost) both finish the log; the buffered chunks stay
534	    // replayable either way. The transport differs only at this read leaf
535	    // (QUIC chunk API vs the loopback duplex's `AsyncRead`); both append to the
536	    // same `StreamLog`, so nothing above the pump can tell them apart.
537	    //
538	    // Backpressure (M11-W3, REQ-SHELL-4): a retentive (tunnel) log never evicts,
539	    // so before pulling the next chunk the pump WAITS while the undrained ring is
540	    // at its `retentive_cap`. The unread bytes stay in the transport's buffer, so
541	    // QUIC stream flow-control (or the loopback duplex's bounded buffer) lags the
542	    // SENDER — "lag, never loss" (CONTEXT:262). A `drain` wakes `room`. Ordinary
543	    // streams have `retentive_cap == 0`, so this gate is a no-op for them.
544	    tokio::spawn(async move {
545	        match recv {
546	            RecvHalf::Quic(mut recv) => loop {
547	                await_ring_room(&log, &room).await;
548	                match recv.read_chunk(STREAM_READ_CHUNK).await {
549	                    Ok(Some(chunk)) => log.lock().unwrap().append(&chunk.bytes),
550	                    Ok(None) | Err(_) => {
551	                        log.lock().unwrap().finish();
552	                        break;
553	                    }
554	                }
555	            },
556	            RecvHalf::Loopback(mut recv) => {
557	                let mut buf = vec![0u8; STREAM_READ_CHUNK];
558	                loop {
559	                    await_ring_room(&log, &room).await;
560	                    match recv.read(&mut buf).await {
561	                        Ok(0) | Err(_) => {
562	                            log.lock().unwrap().finish();
563	                            break;
564	                        }
565	                        Ok(n) => log.lock().unwrap().append(&buf[..n]),
566	                    }
567	                }
568	            }
569	        }
570	    });
571
572	    id
573	}
574
575	/// Backpressure gate for a retentive (tunnel) read pump: park until the undrained
576	/// ring has room below `retentive_cap`, woken by a `drain` on `room`. A no-op for
577	/// an ordinary stream (`retentive_cap == 0`). Holds no lock across the await.
578	// [impl->REQ-SHELL-4]
579	async fn await_ring_room(log: &Arc<Mutex<StreamLog>>, room: &tokio::sync::Notify) {
580	    loop {
581	        {
582	            let l = log.lock().unwrap();
583	            if l.retentive_cap == 0 || l.ring.len() < l.retentive_cap {
584	                return;
585	            }
586	        }
587	        room.notified().await;
588	    }
589	}
590
591	/// The broker-owned net endpoint + live connection/stream tables (see module
592	/// docs).
593	pub struct NetHost {
594	    /// The dedicated tokio runtime hosting the endpoint and its accept loop.
595	    /// Dropping the host drops the runtime, stopping every net task with it.
596	    runtime: tokio::runtime::Runtime,
597	    endpoint: Arc<NetEndpoint>,
598	    shared: Arc<NetShared>,
599	    /// `op_id → conn_id` for exactly-once dial replies: a deduped replay of a
600	    /// journaled dial is answered with the connection the first apply opened.
601	    dial_ops: Mutex<HashMap<u64, u64>>,
602	    /// `op_id → stream_id` for exactly-once stream-open replies (D4b), same
603	    /// discipline as `dial_ops`. Shares the one net op-id namespace.
604	    stream_ops: Mutex<HashMap<u64, u64>>,
605	    /// This node's pubkey (the seed-proof's local channel-binding identity).
606	    local_id: PublicKey,
607	    /// The connect-time seed-proof credential source (Mesh-D2). `None` disables
608	    /// the proof (see [`NetConfig::membership`]).
609	    membership: Option<MembershipSource>,
610	    /// The on-connect roster-exchange seam (Mesh-D4, REQ-MESH-2), set on the live
611	    /// host after `start` via [`Self::set_roster_exchange`] (kept off `NetConfig`
612	    /// so the many hermetic test configs need no change — a roster exchange is a
613	    /// layer atop the proof, opt-in per host). `None` ⇒ no roster leg. Read +
614	    /// cloned per connect on the net runtime, so the accept loop (spawned in
615	    /// `start`, before the host exists) shares it through this `Arc`.
616	    roster: Arc<Mutex<Option<RosterExchange>>>,
617	    /// The broker's single in-process loopback connection (M12 W1.5, B1),
618	    /// minted lazily on the first [`Self::dial_loopback`] and reused thereafter
619	    /// — a local `spt rc` needs exactly one. `Some(conn_id)` once minted; the
620	    /// row lives for the host (no handshake to lose, no closed-watcher).
621	    loopback_conn: Mutex<Option<u64>>,
622	    /// Serializes [`Self::open_loopback_pair`] (M11-W3) so the peer-row discovery
623	    /// (a before/after diff on the loopback conn's inbound rows) sees exactly the
624	    /// one row this open minted — concurrent pair-opens on the shared loopback conn
625	    /// would otherwise race the diff.
626	    loopback_pair_lock: Mutex<()>,
627	    /// The broker-side QUIC-op deadline in ms (REQ-HAZARD-BROKER-QUIC-DEADLINE),
628	    /// default [`BROKER_QUIC_OP_TIMEOUT_MS`]; the hermetic black-hole tests lower

fn register_stream(
    shared: &Arc<NetShared>,
    conn_id: u64,
    remote_id_hex: &str,
    send: SendHalf,
    recv: RecvHalf,
    initiated_locally: bool,
) -> u64 {
    let id = shared.next_stream_id.fetch_add(1, Ordering::Relaxed);
    let log = Arc::new(Mutex::new(StreamLog::new(id, shared.ring_cap)));
    let room = Arc::new(tokio::sync::Notify::new());
    shared.streams.lock().unwrap().insert(
        id,
        Arc::new(StreamEntry {
            conn_id,
            remote_id_hex: remote_id_hex.to_string(),
            initiated_locally,
            send: tokio::sync::Mutex::new(send),
            log: Arc::clone(&log),
            room: Arc::clone(&room),
        }),
    );

    // The read pump — the stream's single producer. A clean end (peer finish)
    // and a torn end (conn lost) both finish the log; the buffered chunks stay
    // replayable either way. The transport differs only at this read leaf
    // (QUIC chunk API vs the loopback duplex's `AsyncRead`); both append to the
    // same `StreamLog`, so nothing above the pump can tell them apart.
    //
    // Backpressure (M11-W3, REQ-SHELL-4): a retentive (tunnel) log never evicts,
    // so before pulling the next chunk the pump WAITS while the undrained ring is
    // at its `retentive_cap`. The unread bytes stay in the transport's buffer, so
    // QUIC stream flow-control (or the loopback duplex's bounded buffer) lags the
    // SENDER — "lag, never loss" (CONTEXT:262). A `drain` wakes `room`. Ordinary
    // streams have `retentive_cap == 0`, so this gate is a no-op for them.
    tokio::spawn(async move {
        match recv {
            RecvHalf::Quic(mut recv) => loop {
                await_ring_room(&log, &room).await;
                match recv.read_chunk(STREAM_READ_CHUNK).await {
                    Ok(Some(chunk)) => log.lock().unwrap().append(&chunk.bytes),
                    Ok(None) | Err(_) => {
                        log.lock().unwrap().finish();
                        break;
                    }
                }
            },
            RecvHalf::Loopback(mut recv) => {
                let mut buf = vec![0u8; STREAM_READ_CHUNK];
                loop {
                    await_ring_room(&log, &room).await;
                    match recv.read(&mut buf).await {
                        Ok(0) | Err(_) => {
                            log.lock().unwrap().finish();
                            break;
                        }
                        Ok(n) => log.lock().unwrap().append(&buf[..n]),
                    }
                }
            }
        }
    });

    id
}

/// Backpressure gate for a retentive (tunnel) read pump: park until the undrained
/// ring has room below `retentive_cap`, woken by a `drain` on `room`. A no-op for
/// an ordinary stream (`retentive_cap == 0`). Holds no lock across the await.
// [impl->REQ-SHELL-4]
async fn await_ring_room(log: &Arc<Mutex<StreamLog>>, room: &tokio::sync::Notify) {
    loop {
        {
            let l = log.lock().unwrap();
            if l.retentive_cap == 0 || l.ring.len() < l.retentive_cap {
                return;
            }
        }
        room.notified().await;
    }
}

/// The broker-owned net endpoint + live connection/stream tables (see module
/// docs).
pub struct NetHost {
    /// The dedicated tokio runtime hosting the endpoint and its accept loop.
    /// Dropping the host drops the runtime, stopping every net task with it.
    runtime: tokio::runtime::Runtime,
    endpoint: Arc<NetEndpoint>,
    shared: Arc<NetShared>,
    /// `op_id → conn_id` for exactly-once dial replies: a deduped replay of a
    /// journaled dial is answered with the connection the first apply opened.
    dial_ops: Mutex<HashMap<u64, u64>>,
    /// `op_id → stream_id` for exactly-once stream-open replies (D4b), same
    /// discipline as `dial_ops`. Shares the one net op-id namespace.
    stream_ops: Mutex<HashMap<u64, u64>>,
    /// This node's pubkey (the seed-proof's local channel-binding identity).
    local_id: PublicKey,
    /// The connect-time seed-proof credential source (Mesh-D2). `None` disables
    /// the proof (see [`NetConfig::membership`]).
    membership: Option<MembershipSource>,
    /// The on-connect roster-exchange seam (Mesh-D4, REQ-MESH-2), set on the live
    /// host after `start` via [`Self::set_roster_exchange`] (kept off `NetConfig`
    /// so the many hermetic test configs need no change — a roster exchange is a
    /// layer atop the proof, opt-in per host). `None` ⇒ no roster leg. Read +
    /// cloned per connect on the net runtime, so the accept loop (spawned in
    /// `start`, before the host exists) shares it through this `Arc`.
    roster: Arc<Mutex<Option<RosterExchange>>>,
    /// The broker's single in-process loopback connection (M12 W1.5, B1),
    /// minted lazily on the first [`Self::dial_loopback`] and reused thereafter
    /// — a local `spt rc` needs exactly one. `Some(conn_id)` once minted; the
    /// row lives for the host (no handshake to lose, no closed-watcher).
    loopback_conn: Mutex<Option<u64>>,
    /// Serializes [`Self::open_loopback_pair`] (M11-W3) so the peer-row discovery
    /// (a before/after diff on the loopback conn's inbound rows) sees exactly the
    /// one row this open minted — concurrent pair-opens on the shared loopback conn
    /// would otherwise race the diff.
    loopback_pair_lock: Mutex<()>,
    /// The broker-side QUIC-op deadline in ms (REQ-HAZARD-BROKER-QUIC-DEADLINE),
    /// default [`BROKER_QUIC_OP_TIMEOUT_MS`]; the hermetic black-hole tests lower


register_stream spawns a read pump for BOTH rows, so stream 2 (op_r) should read peer_w's writes. The duplex math is correct. Yet stream 2 never appends. And the known-good `local_attach_via_loopback...` test DOES deliver over loopback — so the duplex itself works. The difference must be the **inbound dispatcher** path (production invokes serve_attach via the dispatcher; the test may serve manually). The double `DIAG_SLOG_ATTACH stream=1` is a strong clue. Find the dispatcher + serve_attach callers.


15://! claims only rows with `initiated_locally == false` — serving our own
46://! Each claimed stream gets its own worker thread with its **own** brain IPC
75:use crate::attach::serve_attach;
206:    let mut claimed: HashSet<u64> = HashSet::new();
214:            if s.initiated_locally || s.next_seq == 0 || !claimed.insert(s.stream_id) {
283:/// One claimed stream's worker: peek → classify → run the family's serve
350:                Some(endpoint) => match serve_attach(&mut brain, stream_id, &endpoint, origin, 0) {
355:                    // Refuse politely: finish our send side, claim stays.


195	/// stream to a family worker, until `stop` is set. Blocks the calling thread
196	/// (the daemon spawns it on its own); workers detach (each ends with its
197	/// stream).
198	pub fn run_dispatch_loop(
199	    broker_name: &str,
200	    paths: &DispatchPaths,
201	    registry: Arc<RegistryHost>,
202	    stop: &AtomicBool,
203	    poll: Duration,
204	) -> io::Result<()> {
205	    let mut brain = connect(broker_name)?;
206	    let mut claimed: HashSet<u64> = HashSet::new();
207
208	    while !stop.load(Ordering::Relaxed) {
209	        let reply = brain.net_streams()?;
210	        for s in reply.streams {
211	            // Claim only peer-initiated rows with bytes (a QUIC stream is
212	            // invisible until its first write anyway — gotcha #6 — but the
213	            // next_seq guard also skips a row whose pump hasn't appended yet).
214	            if s.initiated_locally || s.next_seq == 0 || !claimed.insert(s.stream_id) {
215	                continue;
216	            }
217	            let name = broker_name.to_string();
218	            let origin = s.remote_id_hex.clone();
219	            let stream_id = s.stream_id;
220	            let p = paths.clone();
221	            let reg = Arc::clone(&registry);
222	            thread::spawn(move || {
223	                let outcome = worker(&name, stream_id, &origin, &p, &reg);
224	                match outcome {
225	                    DispatchOutcome::Served(_) => {}
226	                    other => eprintln!("DISPATCH:{stream_id}:{other:?}"),
227	                }
228	            });
229	        }
230	        thread::sleep(poll);
231	    }
232	    Ok(())
233	}
234
235	/// Connect a fresh brain IPC client (retrying through broker startup).
236	fn connect(broker_name: &str) -> io::Result<Brain> {
237	    let mut last = io::Error::other("broker never answered");
238	    for _ in 0..300 {
239	        match Brain::cold_start(broker_name, now_ms()) {
240	            Ok(b) => return Ok(b),
241	            Err(e) => last = e,
242	        }
243	        thread::sleep(Duration::from_millis(10));
244	    }
245	    Err(last)
246	}
247
248	fn now_ms() -> u64 {
249	    std::time::SystemTime::now()
250	        .duration_since(std::time::UNIX_EPOCH)
251	        .map(|d| d.as_millis() as u64)
252	        .unwrap_or(0)
253	}
254
255	/// Peek the stream's first NDJSON line on a throwaway brain connection.
256	fn peek_first_line(broker_name: &str, stream_id: u64) -> io::Result<Option<serde_json::Value>> {
257	    let mut brain = connect(broker_name)?;
258	    brain.net_stream_subscribe(stream_id, 0)?;
259	    let mut buf: Vec<u8> = Vec::new();
260	    loop {
261	        match brain.read_event()? {
262	            BrokerEvent::NetStreamData {
263	                stream_id: sid,
264	                bytes,
265	                ..
266	            } if sid == stream_id => {
267	                buf.extend_from_slice(&bytes);
268	                if let Some(nl) = buf.iter().position(|&b| b == b'\n') {
269	                    return Ok(serde_json::from_slice(&buf[..nl]).ok());
270	                }
271	            }
272	            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {
273	                // Finished without a complete line: classify what we have
274	                // (a line without a trailing newline before EOF still counts).
275	                return Ok(serde_json::from_slice(&buf).ok());
276	            }
277	            BrokerEvent::Error { message } => return Err(io::Error::other(message)),
278	            _ => {}
279	        }
280	    }
281	}
282
283	/// One claimed stream's worker: peek → classify → run the family's serve
284	/// loop on a fresh brain connection.
285	fn worker(
286	    broker_name: &str,
287	    stream_id: u64,
288	    origin: &str,
289	    paths: &DispatchPaths,
290	    registry: &RegistryHost,
291	) -> DispatchOutcome {
292	    let line = match peek_first_line(broker_name, stream_id) {
293	        Ok(Some(v)) => v,
294	        Ok(None) => return DispatchOutcome::Unknown,
295	        Err(e) => return DispatchOutcome::Failed(e.to_string()),
296	    };
297	    let family = classify_first_line(&line);
298
299	    let mut brain = match connect(broker_name) {
300	        Ok(b) => b,
301	        Err(e) => return DispatchOutcome::Failed(e.to_string()),
302	    };
303
304	    match family {
305	        StreamFamily::Sync => {
306	            let cs = match ContextStore::open_or_init() {
307	                Ok(cs) => cs,
308	                Err(e) => return DispatchOutcome::Failed(e.to_string()),
309	            };
310	            let policy = SyncPolicy::load();
311	            match serve_sync(
312	                &mut brain,
313	                stream_id,
314	                origin,
315	                0,
316	                &cs,
317	                &policy,
318	                &paths.scratch,
319	            ) {
320	                Ok(out) => DispatchOutcome::Served(format!("{out:?}")),
321	                Err(e) => DispatchOutcome::Failed(e.to_string()),
322	            }
323	        }
324	        StreamFamily::Update => {
325	            let cache = ReleaseCache::open(&paths.releases);
326	            let roster = RosterStore::load();
327	            match serve_update(&mut brain, stream_id, origin, 0, &cache, &roster) {
328	                Ok(out) => DispatchOutcome::Served(format!("{out:?}")),
329	                Err(e) => DispatchOutcome::Failed(e.to_string()),
330	            }
331	        }
332	        StreamFamily::Notif => serve_notif_feed(&mut brain, stream_id, origin),
333	        StreamFamily::WanMsg => serve_wan_feed(&mut brain, stream_id, origin, paths, registry),
334	        StreamFamily::Registry => serve_registry_feed(&mut brain, stream_id, origin, registry),
335	        StreamFamily::Attach => {
336	            // The gate subject is the session's OWNING endpoint, resolved
337	            // from the broker's table (the spawn's label) — never from wire
338	            // bytes. An unlabeled or unknown session refuses, fail-closed.
339	            let session_id = line.get("session_id").and_then(|v| v.as_u64());
340	            let endpoint = session_id.and_then(|sid| {
341	                brain.sessions().ok().and_then(|reply| {
342	                    reply
343	                        .sessions
344	                        .iter()
345	                        .find(|s| s.session_id == sid && !s.endpoint.is_empty())
346	                        .map(|s| s.endpoint.clone())
347	                })
348	            });
349	            match endpoint {
350	                Some(endpoint) => match serve_attach(&mut brain, stream_id, &endpoint, origin, 0) {
351	                    Ok(out) => DispatchOutcome::Served(format!("{out:?}")),
352	                    Err(e) => DispatchOutcome::Failed(e.to_string()),
353	                },
354	                None => {
355	                    // Refuse politely: finish our send side, claim stays.
356	                    let _ = brain.net_stream_send(stream_id, &[], None, true);
357	                    DispatchOutcome::Served("attach refused: unlabeled/unknown session".into())
358	                }
359	            }
360	        }
361	        StreamFamily::Xfer => {
362	            // The wire names whose whitelist gates (the rule set); the origin
363	            // it is checked against stays handshake-proven. Blank refuses.
364	            let endpoint = line
365	                .get("endpoint")
366	                .and_then(|v| v.as_str())
367	                .filter(|e| !e.is_empty())
368	                .map(str::to_string);
369	            match endpoint {

/// stream to a family worker, until `stop` is set. Blocks the calling thread
/// (the daemon spawns it on its own); workers detach (each ends with its
/// stream).
pub fn run_dispatch_loop(
    broker_name: &str,
    paths: &DispatchPaths,
    registry: Arc<RegistryHost>,
    stop: &AtomicBool,
    poll: Duration,
) -> io::Result<()> {
    let mut brain = connect(broker_name)?;
    let mut claimed: HashSet<u64> = HashSet::new();

    while !stop.load(Ordering::Relaxed) {
        let reply = brain.net_streams()?;
        for s in reply.streams {
            // Claim only peer-initiated rows with bytes (a QUIC stream is
            // invisible until its first write anyway — gotcha #6 — but the
            // next_seq guard also skips a row whose pump hasn't appended yet).
            if s.initiated_locally || s.next_seq == 0 || !claimed.insert(s.stream_id) {
                continue;
            }
            let name = broker_name.to_string();
            let origin = s.remote_id_hex.clone();
            let stream_id = s.stream_id;
            let p = paths.clone();
            let reg = Arc::clone(&registry);
            thread::spawn(move || {
                let outcome = worker(&name, stream_id, &origin, &p, &reg);
                match outcome {
                    DispatchOutcome::Served(_) => {}
                    other => eprintln!("DISPATCH:{stream_id}:{other:?}"),
                }
            });
        }
        thread::sleep(poll);
    }
    Ok(())
}

/// Connect a fresh brain IPC client (retrying through broker startup).
fn connect(broker_name: &str) -> io::Result<Brain> {
    let mut last = io::Error::other("broker never answered");
    for _ in 0..300 {
        match Brain::cold_start(broker_name, now_ms()) {
            Ok(b) => return Ok(b),
            Err(e) => last = e,
        }
        thread::sleep(Duration::from_millis(10));
    }
    Err(last)
}

fn now_ms() -> u64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_millis() as u64)
        .unwrap_or(0)
}

/// Peek the stream's first NDJSON line on a throwaway brain connection.
fn peek_first_line(broker_name: &str, stream_id: u64) -> io::Result<Option<serde_json::Value>> {
    let mut brain = connect(broker_name)?;
    brain.net_stream_subscribe(stream_id, 0)?;
    let mut buf: Vec<u8> = Vec::new();
    loop {
        match brain.read_event()? {
            BrokerEvent::NetStreamData {
                stream_id: sid,
                bytes,
                ..
            } if sid == stream_id => {
                buf.extend_from_slice(&bytes);
                if let Some(nl) = buf.iter().position(|&b| b == b'\n') {
                    return Ok(serde_json::from_slice(&buf[..nl]).ok());
                }
            }
            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {
                // Finished without a complete line: classify what we have
                // (a line without a trailing newline before EOF still counts).
                return Ok(serde_json::from_slice(&buf).ok());
            }
            BrokerEvent::Error { message } => return Err(io::Error::other(message)),
            _ => {}
        }
    }
}

/// One claimed stream's worker: peek → classify → run the family's serve
/// loop on a fresh brain connection.
fn worker(
    broker_name: &str,
    stream_id: u64,
    origin: &str,
    paths: &DispatchPaths,
    registry: &RegistryHost,
) -> DispatchOutcome {
    let line = match peek_first_line(broker_name, stream_id) {
        Ok(Some(v)) => v,
        Ok(None) => return DispatchOutcome::Unknown,
        Err(e) => return DispatchOutcome::Failed(e.to_string()),
    };
    let family = classify_first_line(&line);

    let mut brain = match connect(broker_name) {
        Ok(b) => b,
        Err(e) => return DispatchOutcome::Failed(e.to_string()),
    };

    match family {
        StreamFamily::Sync => {
            let cs = match ContextStore::open_or_init() {
                Ok(cs) => cs,
                Err(e) => return DispatchOutcome::Failed(e.to_string()),
            };
            let policy = SyncPolicy::load();
            match serve_sync(
                &mut brain,
                stream_id,
                origin,
                0,
                &cs,
                &policy,
                &paths.scratch,
            ) {
                Ok(out) => DispatchOutcome::Served(format!("{out:?}")),
                Err(e) => DispatchOutcome::Failed(e.to_string()),
            }
        }
        StreamFamily::Update => {
            let cache = ReleaseCache::open(&paths.releases);
            let roster = RosterStore::load();
            match serve_update(&mut brain, stream_id, origin, 0, &cache, &roster) {
                Ok(out) => DispatchOutcome::Served(format!("{out:?}")),
                Err(e) => DispatchOutcome::Failed(e.to_string()),
            }
        }
        StreamFamily::Notif => serve_notif_feed(&mut brain, stream_id, origin),
        StreamFamily::WanMsg => serve_wan_feed(&mut brain, stream_id, origin, paths, registry),
        StreamFamily::Registry => serve_registry_feed(&mut brain, stream_id, origin, registry),
        StreamFamily::Attach => {
            // The gate subject is the session's OWNING endpoint, resolved
            // from the broker's table (the spawn's label) — never from wire
            // bytes. An unlabeled or unknown session refuses, fail-closed.
            let session_id = line.get("session_id").and_then(|v| v.as_u64());
            let endpoint = session_id.and_then(|sid| {
                brain.sessions().ok().and_then(|reply| {
                    reply
                        .sessions
                        .iter()
                        .find(|s| s.session_id == sid && !s.endpoint.is_empty())
                        .map(|s| s.endpoint.clone())
                })
            });
            match endpoint {
                Some(endpoint) => match serve_attach(&mut brain, stream_id, &endpoint, origin, 0) {
                    Ok(out) => DispatchOutcome::Served(format!("{out:?}")),
                    Err(e) => DispatchOutcome::Failed(e.to_string()),
                },
                None => {
                    // Refuse politely: finish our send side, claim stays.
                    let _ = brain.net_stream_send(stream_id, &[], None, true);
                    DispatchOutcome::Served("attach refused: unlabeled/unknown session".into())
                }
            }
        }
        StreamFamily::Xfer => {
            // The wire names whose whitelist gates (the rule set); the origin
            // it is checked against stays handshake-proven. Blank refuses.
            let endpoint = line
                .get("endpoint")
                .and_then(|v| v.as_str())
                .filter(|e| !e.is_empty())
                .map(str::to_string);
            match endpoint {


FOUND the double-attach source: dispatcher `peek_first_line` (L258) subscribes stream 1, then `worker`→`serve_attach` (L113) subscribes stream 1 again — both reading rc's INPUT. That's benign. The serve→rc OUTPUT direction (`net_stream_send(1)`→peer_w→op_r→stream2) is what's dead. Static wiring looks correct, so the known-good test must deliver differently. Read `attach_drive_detach`.


Bash

grep -n "fn attach_drive_detach\|fn net_broker\|serve_attach\|open_loopback\|spawn\|dispatch\|run_dispatch" crates/spt-daemon/tests/attach.rs | head -40

Find how the known-good test serves attach


27:use spt_daemon::attach::{request_attach, send_attach_input, serve_attach, AttachServeOutcome};
38:/// Every test inits it: serve_attach best-effort-stamps the endpoint perch's
66:fn net_broker(name: &str, dir: &std::path::Path) -> Arc<Broker> {
71:    thread::spawn(move || {
87:fn echo_spawn_req() -> SpawnReq {
105:/// production dispatch loop does (dispatch.rs) — and it is REQUIRED for the
123:/// `expected_origin`, never in the `serve_attach`+render pump below. There is no
133:fn attach_drive_detach(
156:    let server = thread::spawn(move || {
158:            serve_attach(&mut target, stream_a, &endpoint_owned, &origin, 0).expect("serve");
258:// `serve_attach` pump), NOT a self-dial — exactly the doyle-approved fallback.
281:// attach, end to end: `harnesshost::launch_harness_brokered_in` spawns a harness's
297:    // binary standing in for a harness — the bringup spawns it into broker A's
308:    let spawned = spt_daemon::harnesshost::launch_harness_brokered_in(
311:    .expect("spt-hosted bringup spawns into broker A's PTY");
324:        request_attach(&mut operator, conn.conn_id, spawned.session_id, 0, 2, AttachIntent::Control).expect("request");
332:    let server = thread::spawn(move || {
333:        let outcome = serve_attach(&mut target, stream_a, "cc-ep", &origin, 0).expect("serve");
372:    // Target A: spawn the echo child.
375:        .spawn_session(echo_spawn_req())
376:        .expect("spawn echo child");
406:// a single broker spawns a real PTY session; the operator acquires a
409:// `serve_attach`+render pump a cross-node operator uses. The proof that "both
429:        .spawn_session(echo_spawn_req())
430:        .expect("spawn echo child");
455:// [int->REQ-HOST-RUN-2] the broker spawns the PTY child in the requested
458:// we asked for — end to end (wire field → broker spawn → PTY cwd). Asserted on
462:fn broker_spawns_the_pty_child_in_the_requested_cwd() {
486:        .spawn_session(SpawnReq {
495:        .expect("spawn cwd-printing child in the project cwd");
524:    // Target A life 1: spawn the child; operator B dials + requests + types.
527:        .spawn_session(echo_spawn_req())
528:        .expect("spawn echo child");
594:    // worst-case replay. serve_attach re-handles the replayed Request (gate +
601:    let server = thread::spawn(move || {
602:        let outcome = serve_attach(&mut life2, stream_a, "ling", &origin2, 0).expect("re-serve");
679:    // the spawn). So the labeled spawn is the faithful shape.
681:        .spawn_session(SpawnReq {
683:            ..echo_spawn_req()
685:        .expect("spawn echo child");


95	        rows: 24,
96	        cols: 80,
97	        endpoint: String::new(),
98	        cwd: None,
99	        env: Default::default(),
100	    }
101	}
102
103	/// Poll the brain's `net-streams` until a PEER-initiated stream appears; return
104	/// `(stream_id, remote_id_hex)`. Filtering on `!initiated_locally` is what the
105	/// production dispatch loop does (dispatch.rs) — and it is REQUIRED for the
106	/// single-broker loopback case, where the table holds BOTH the operator's own
107	/// row and the cross-wired peer row: the serve target is always the peer row.
108	fn wait_for_stream(brain: &mut Brain) -> (u64, String) {
109	    for _ in 0..400 {
110	        let reply = brain.net_streams().expect("net-streams");
111	        if let Some(s) = reply.streams.iter().find(|s| !s.initiated_locally) {
112	            return (s.stream_id, s.remote_id_hex.clone());
113	        }
114	        thread::sleep(Duration::from_millis(5));
115	    }
116	    panic!("attach stream never appeared at the target's broker");
117	}
118
119	/// The operator+serve drive shared by EVERY attach transport — the M12 W1.5
120	/// one-pump invariant made STRUCTURAL: cross-node (QUIC conn) and local
121	/// (broker loopback conn) call this identically; they diverge ONLY in how
122	/// `conn_id` was acquired (`net_dial` vs `net_dial_loopback`) and in
123	/// `expected_origin`, never in the `serve_attach`+render pump below. There is no
124	/// second code path that could drift.
125	///
126	/// Given an acquired `conn_id` on `operator` and the `target` broker connection
127	/// holding `session_id` for `endpoint`: open the attach stream, serve it on
128	/// `target` under `expected_origin` (asserted == the stream table's proven
129	/// origin), drive `marker` through the PTY and render it back over the wire,
130	/// then detach and confirm the serve loop ended `Detached` (session outlives).
131	/// Returns the rendered viewport bytes + the (still-live) target brain so the
132	/// caller can assert session survival and clean up.
133	fn attach_drive_detach(
134	    operator: &mut Brain,
135	    mut target: Brain,
136	    conn_id: u64,
137	    session_id: u64,
138	    endpoint: &str,
139	    expected_origin: &str,
140	    marker: &[u8],
141	) -> (Vec<u8>, Brain) {
142	    let stream_b = request_attach(operator, conn_id, session_id, 0, 2, AttachIntent::Control).expect("request");
143	    operator
144	        .net_stream_subscribe(stream_b, 0)
145	        .expect("operator subscribe");
146
147	    // The target serves the inbound attach stream under the proven origin
148	    // (KH 7.5 — the stream table, never payload). Cross-node: the handshake id.
149	    // Loopback: this node's own id (broker-minted), which gates same-node.
150	    let (stream_a, origin) = wait_for_stream(&mut target);
151	    assert_eq!(
152	        origin, expected_origin,
153	        "serve gate subject IS the stream table's proven origin"
154	    );
155	    let endpoint_owned = endpoint.to_string();
156	    let server = thread::spawn(move || {
157	        let outcome =
158	            serve_attach(&mut target, stream_a, &endpoint_owned, &origin, 0).expect("serve");
159	        (outcome, target)
160	    });
161
162	    // Drive the marker through; the echo streams back over the SAME pump.
163	    let mut keys = marker.to_vec();
164	    keys.push(b'\r');
165	    send_attach_input(operator, stream_b, &keys, 1).expect("input");
166	    let mut decoder = AttachDecoder::new();
167	    let (mut cursor, mut viewport) = (0u64, Vec::new());
168	    render_until(
169	        operator,
170	        stream_b,
171	        &mut decoder,
172	        &mut cursor,
173	        &mut viewport,
174	        marker,
175	    );
176
177	    // Detach: the viewport ends with the session still running.
178	    operator
179	        .net_stream_send(stream_b, &[], None, true)
180	        .expect("detach");
181	    let (outcome, target) = server.join().expect("serve thread");
182	    assert_eq!(outcome, AttachServeOutcome::Detached);
183	    (viewport, target)
184	}
185
186	/// Operator-side viewport: consume attach-stream records, dedup `Output` by
187	/// seq, append rendered bytes to `buf`, until `needle` is rendered. Returns
188	/// the rendered-through cursor (last accepted seq + 1).
189	fn render_until(
190	    brain: &mut Brain,
191	    stream_id: u64,
192	    decoder: &mut AttachDecoder,
193	    next_seq: &mut u64,
194	    buf: &mut Vec<u8>,
195	    needle: &[u8],
196	) {
197	    let contains = |hay: &[u8], needle: &[u8]| hay.windows(needle.len()).any(|w| w == needle);
198	    while !contains(buf, needle) {
199	        match brain.read_event().expect("operator read event") {
200	            BrokerEvent::NetStreamData {
201	                stream_id: sid,
202	                bytes,
203	                ..
204	            } if sid == stream_id => {
205	                for rec in decoder.push(&bytes) {
206	                    if let AttachRecord::Output { seq, data_b64 } = rec {
207	                        if seq < *next_seq {
208	                            continue; // replay duplicate — dedup by cursor
209	                        }
210	                        assert_eq!(seq, *next_seq, "no forward gap in the viewport");
211	                        *next_seq = seq + 1;
212	                        buf.extend_from_slice(&decode_bytes(&data_b64).expect("chunk b64"));
213	                    }
214	                }
215	            }
216	            BrokerEvent::NetStreamEof { .. } => panic!("stream ended before the marker rendered"),
217	            BrokerEvent::Error { message } => panic!("operator broker error: {message}"),
218	            _ => {}
219	        }
220	    }
221	}
222
223	/// The prefix of `hay` through (and including) the first occurrence of
224	/// `needle` — the deterministic comparison window when two readers share a
225	/// "stop once the buffer contains the needle" rule but batch events
226	/// differently (each side's overshoot past the needle is chunking
227	/// nondeterminism, not a delivery difference).
228	fn prefix_through<'a>(hay: &'a [u8], needle: &[u8]) -> &'a [u8] {
229	    let pos = hay
230	        .windows(needle.len())
231	        .position(|w| w == needle)
232	        .expect("the stop rule guarantees the needle rendered");
233	    &hay[..pos + needle.len()]
234	}
235
236	/// Count non-overlapping occurrences of `needle` in `hay`.
237	fn count(hay: &[u8], needle: &[u8]) -> usize {
238	    if needle.is_empty() || hay.len() < needle.len() {
239	        return 0;
240	    }
241	    let (mut n, mut i) = (0, 0);
242	    while i + needle.len() <= hay.len() {
243	        if &hay[i..i + needle.len()] == needle {
244	            n += 1;
245	            i += needle.len();
246	        } else {
247	            i += 1;
248	        }
249	    }
250	    n
251	}
252
253	// [int->REQ-RC-1] loopback self-dial is REFUSED by the transport — pins the
254	// finding that drives `spt rc`'s LOCAL transport choice. A node cannot QUIC-dial
255	// its own node id (iroh: "Connecting to ourself is not supported"), so local
256	// attach CANNOT ride a self-dialed QUIC conn. The one-pump invariant therefore
257	// holds via the fallback transport (a broker-local loopback conn feeding the same
258	// `serve_attach` pump), NOT a self-dial — exactly the doyle-approved fallback.
259	// Cross-node attach (the prize) is unaffected: it dials a real remote peer.
260	#[test]
261	fn loopback_self_dial_is_refused_local_uses_fallback_transport() {
262	    init_home();
263	    let dir = tempfile::tempdir().expect("tempdir");
264	    let name = unique_name();
265	    let _broker = net_broker(&name, &dir.path().join("solo"));
266	    let mut brain = connect_retry(&name);
267
268	    let status = brain.net_status().expect("status");
269	    // Dialing our OWN advertised address is refused at the transport — fail-fast,

        rows: 24,
        cols: 80,
        endpoint: String::new(),
        cwd: None,
        env: Default::default(),
    }
}

/// Poll the brain's `net-streams` until a PEER-initiated stream appears; return
/// `(stream_id, remote_id_hex)`. Filtering on `!initiated_locally` is what the
/// production dispatch loop does (dispatch.rs) — and it is REQUIRED for the
/// single-broker loopback case, where the table holds BOTH the operator's own
/// row and the cross-wired peer row: the serve target is always the peer row.
fn wait_for_stream(brain: &mut Brain) -> (u64, String) {
    for _ in 0..400 {
        let reply = brain.net_streams().expect("net-streams");
        if let Some(s) = reply.streams.iter().find(|s| !s.initiated_locally) {
            return (s.stream_id, s.remote_id_hex.clone());
        }
        thread::sleep(Duration::from_millis(5));
    }
    panic!("attach stream never appeared at the target's broker");
}

/// The operator+serve drive shared by EVERY attach transport — the M12 W1.5
/// one-pump invariant made STRUCTURAL: cross-node (QUIC conn) and local
/// (broker loopback conn) call this identically; they diverge ONLY in how
/// `conn_id` was acquired (`net_dial` vs `net_dial_loopback`) and in
/// `expected_origin`, never in the `serve_attach`+render pump below. There is no
/// second code path that could drift.
///
/// Given an acquired `conn_id` on `operator` and the `target` broker connection
/// holding `session_id` for `endpoint`: open the attach stream, serve it on
/// `target` under `expected_origin` (asserted == the stream table's proven
/// origin), drive `marker` through the PTY and render it back over the wire,
/// then detach and confirm the serve loop ended `Detached` (session outlives).
/// Returns the rendered viewport bytes + the (still-live) target brain so the
/// caller can assert session survival and clean up.
fn attach_drive_detach(
    operator: &mut Brain,
    mut target: Brain,
    conn_id: u64,
    session_id: u64,
    endpoint: &str,
    expected_origin: &str,
    marker: &[u8],
) -> (Vec<u8>, Brain) {
    let stream_b = request_attach(operator, conn_id, session_id, 0, 2, AttachIntent::Control).expect("request");
    operator
        .net_stream_subscribe(stream_b, 0)
        .expect("operator subscribe");

    // The target serves the inbound attach stream under the proven origin
    // (KH 7.5 — the stream table, never payload). Cross-node: the handshake id.
    // Loopback: this node's own id (broker-minted), which gates same-node.
    let (stream_a, origin) = wait_for_stream(&mut target);
    assert_eq!(
        origin, expected_origin,
        "serve gate subject IS the stream table's proven origin"
    );
    let endpoint_owned = endpoint.to_string();
    let server = thread::spawn(move || {
        let outcome =
            serve_attach(&mut target, stream_a, &endpoint_owned, &origin, 0).expect("serve");
        (outcome, target)
    });

    // Drive the marker through; the echo streams back over the SAME pump.
    let mut keys = marker.to_vec();
    keys.push(b'\r');
    send_attach_input(operator, stream_b, &keys, 1).expect("input");
    let mut decoder = AttachDecoder::new();
    let (mut cursor, mut viewport) = (0u64, Vec::new());
    render_until(
        operator,
        stream_b,
        &mut decoder,
        &mut cursor,
        &mut viewport,
        marker,
    );

    // Detach: the viewport ends with the session still running.
    operator
        .net_stream_send(stream_b, &[], None, true)
        .expect("detach");
    let (outcome, target) = server.join().expect("serve thread");
    assert_eq!(outcome, AttachServeOutcome::Detached);
    (viewport, target)
}

/// Operator-side viewport: consume attach-stream records, dedup `Output` by
/// seq, append rendered bytes to `buf`, until `needle` is rendered. Returns
/// the rendered-through cursor (last accepted seq + 1).
fn render_until(
    brain: &mut Brain,
    stream_id: u64,
    decoder: &mut AttachDecoder,
    next_seq: &mut u64,
    buf: &mut Vec<u8>,
    needle: &[u8],
) {
    let contains = |hay: &[u8], needle: &[u8]| hay.windows(needle.len()).any(|w| w == needle);
    while !contains(buf, needle) {
        match brain.read_event().expect("operator read event") {
            BrokerEvent::NetStreamData {
                stream_id: sid,
                bytes,
                ..
            } if sid == stream_id => {
                for rec in decoder.push(&bytes) {
                    if let AttachRecord::Output { seq, data_b64 } = rec {
                        if seq < *next_seq {
                            continue; // replay duplicate — dedup by cursor
                        }
                        assert_eq!(seq, *next_seq, "no forward gap in the viewport");
                        *next_seq = seq + 1;
                        buf.extend_from_slice(&decode_bytes(&data_b64).expect("chunk b64"));
                    }
                }
            }
            BrokerEvent::NetStreamEof { .. } => panic!("stream ended before the marker rendered"),
            BrokerEvent::Error { message } => panic!("operator broker error: {message}"),
            _ => {}
        }
    }
}

/// The prefix of `hay` through (and including) the first occurrence of
/// `needle` — the deterministic comparison window when two readers share a
/// "stop once the buffer contains the needle" rule but batch events
/// differently (each side's overshoot past the needle is chunking
/// nondeterminism, not a delivery difference).
fn prefix_through<'a>(hay: &'a [u8], needle: &[u8]) -> &'a [u8] {
    let pos = hay
        .windows(needle.len())
        .position(|w| w == needle)
        .expect("the stop rule guarantees the needle rendered");
    &hay[..pos + needle.len()]
}

/// Count non-overlapping occurrences of `needle` in `hay`.
fn count(hay: &[u8], needle: &[u8]) -> usize {
    if needle.is_empty() || hay.len() < needle.len() {
        return 0;
    }
    let (mut n, mut i) = (0, 0);
    while i + needle.len() <= hay.len() {
        if &hay[i..i + needle.len()] == needle {
            n += 1;
            i += needle.len();
        } else {
            i += 1;
        }
    }
    n
}

// [int->REQ-RC-1] loopback self-dial is REFUSED by the transport — pins the
// finding that drives `spt rc`'s LOCAL transport choice. A node cannot QUIC-dial
// its own node id (iroh: "Connecting to ourself is not supported"), so local
// attach CANNOT ride a self-dialed QUIC conn. The one-pump invariant therefore
// holds via the fallback transport (a broker-local loopback conn feeding the same
// `serve_attach` pump), NOT a self-dial — exactly the doyle-approved fallback.
// Cross-node attach (the prize) is unaffected: it dials a real remote peer.
#[test]
fn loopback_self_dial_is_refused_local_uses_fallback_transport() {
    init_home();
    let dir = tempfile::tempdir().expect("tempdir");
    let name = unique_name();
    let _broker = net_broker(&name, &dir.path().join("solo"));
    let mut brain = connect_retry(&name);

    let status = brain.net_status().expect("status");
    // Dialing our OWN advertised address is refused at the transport — fail-fast,


MAJOR difference: the known-good `attach_drive_detach` (L156) serves **directly** — no `peek_first_line`. Production's dispatcher calls `peek_first_line` (subscribes a throwaway brain to stream 1, reads, drops) BEFORE `serve_attach`. Also the test's operator drains concurrently. The evidence (2 forwards then stop, zero stream-2 appends) points to serve **blocking on `net_stream_send`** into a full loopback duplex whose reader (op_r pump) isn't draining. Check the duplex buffer size + net runtime worker count, and confirm with an "after-send" diagnostic.


grep -rn "STREAM_READ_CHUNK\s*[:=]\|const STREAM_READ_CHUNK\|worker_threads\|new_multi_thread\|new_current_thread\|fn build_runtime\|runtime::Builder" crates/spt-daemon/src/nethost.rs

Find duplex chunk size and net runtime worker count


104:const STREAM_READ_CHUNK: usize = 64 * 1024;
652:        let runtime = tokio::runtime::Builder::new_multi_thread()
653:            .worker_threads(2)


64KB duplex buffer — 126 bytes can't block it. So sends returned; my buffer-block theory is wrong. So either `become_controller` only wrote 2 replay frames to serve's IPC (write failed at seq2 → `break`), or serve stopped reading. And separately op_r never drained the 126 bytes already in the duplex. Add per-seq diagnostics in `become_controller`'s replay loop + an after-send marker + op_r pump activity.


254	            self.next_seq
255	        );
256	        self.controller = Some(Arc::clone(&sub));
257	        self.controller_by = by;
258	        let mut last_ok: Option<u64> = None;
259	        if let Ok(mut s) = sub.lock() {
260	            for (seq, bytes) in self.ring.iter().filter(|(s, _)| *s >= from_seq) {
261	                let frame = output_envelope(self.session_id, *seq, bytes);
262	                if write_frame(&mut *s, &frame).is_ok() {
263	                    last_ok = Some(*seq);
264	                } else {
265	                    break; // stop at the first failed write; cursor stays put
266	                }
267	            }
268	        }
269	        if let Some(seq) = last_ok {
270	            self.delivered_through = advanced_cursor(self.delivered_through, seq, true);
271	        }
272	        self.stamp_driven_by();
273	    }
274
275	    /// Add a read-only viewer: snapshot the ring from `from_seq` into the writer
276	    /// thread (NOT through the bounded live queue — replay must not trip the
277	    /// overflow eviction), seed it the current size frame first, then run the
278	    /// live queue. The drain thread fans live frames in via `try_send`.
279	    fn add_viewer(&mut self, sub: SharedSend, from_seq: u64) {

            self.next_seq
        );
        self.controller = Some(Arc::clone(&sub));
        self.controller_by = by;
        let mut last_ok: Option<u64> = None;
        if let Ok(mut s) = sub.lock() {
            for (seq, bytes) in self.ring.iter().filter(|(s, _)| *s >= from_seq) {
                let frame = output_envelope(self.session_id, *seq, bytes);
                if write_frame(&mut *s, &frame).is_ok() {
                    last_ok = Some(*seq);
                } else {
                    break; // stop at the first failed write; cursor stays put
                }
            }
        }
        if let Some(seq) = last_ok {
            self.delivered_through = advanced_cursor(self.delivered_through, seq, true);
        }
        self.stamp_driven_by();
    }

    /// Add a read-only viewer: snapshot the ring from `from_seq` into the writer
    /// thread (NOT through the bounded live queue — replay must not trip the
    /// overflow eviction), seed it the current size frame first, then run the
    /// live queue. The drain thread fans live frames in via `try_send`.
    fn add_viewer(&mut self, sub: SharedSend, from_seq: u64) {


Edit

        if let Ok(mut s) = sub.lock() {
            for (seq, bytes) in self.ring.iter().filter(|(s, _)| *s >= from_seq) {
                let frame = output_envelope(self.session_id, *seq, bytes);
                if write_frame(&mut *s, &frame).is_ok() {
                    last_ok = Some(*seq);
                } else {
                    break; // stop at the first failed write; cursor stays put
                }
            }
        }

        if let Ok(mut s) = sub.lock() {
            for (seq, bytes) in self.ring.iter().filter(|(s, _)| *s >= from_seq) {
                let frame = output_envelope(self.session_id, *seq, bytes);
                let ok = write_frame(&mut *s, &frame).is_ok();
                eprintln!("DIAG_REPLAY_WRITE seq={seq} ok={ok}");
                if ok {
                    last_ok = Some(*seq);
                } else {
                    break; // stop at the first failed write; cursor stays put
                }
            }
        }


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)

        if let Ok(mut s) = sub.lock() {
            for (seq, bytes) in self.ring.iter().filter(|(s, _)| *s >= from_seq) {
                let frame = output_envelope(self.session_id, *seq, bytes);
                if write_frame(&mut *s, &frame).is_ok() {
                    last_ok = Some(*seq);
                } else {
                    break; // stop at the first failed write; cursor stays put
                }
            }
        }

        if let Ok(mut s) = sub.lock() {
            for (seq, bytes) in self.ring.iter().filter(|(s, _)| *s >= from_seq) {
                let frame = output_envelope(self.session_id, *seq, bytes);
                let ok = write_frame(&mut *s, &frame).is_ok();
                eprintln!("DIAG_REPLAY_WRITE seq={seq} ok={ok}");
                if ok {
                    last_ok = Some(*seq);
                } else {
                    break; // stop at the first failed write; cursor stays put
                }
            }
        }

-                if write_frame(&mut *s, &frame).is_ok() {

+                let ok = write_frame(&mut *s, &frame).is_ok();

+                eprintln!("DIAG_REPLAY_WRITE seq={seq} ok={ok}");


                // Unjournaled by design (see module docs): `seq` rides inside
                // the record, and the operator's render cursor is the dedup.
                brain.net_stream_send(stream_id, &line, None, false)?;
            }

                // Unjournaled by design (see module docs): `seq` rides inside
                // the record, and the operator's render cursor is the dedup.
                brain.net_stream_send(stream_id, &line, None, false)?;
                eprintln!("DIAG_SERVE_FWD_DONE seq={seq}");
            }


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

                // Unjournaled by design (see module docs): `seq` rides inside
                // the record, and the operator's render cursor is the dedup.
                brain.net_stream_send(stream_id, &line, None, false)?;
            }

                // Unjournaled by design (see module docs): `seq` rides inside
                // the record, and the operator's render cursor is the dedup.
                brain.net_stream_send(stream_id, &line, None, false)?;
                eprintln!("DIAG_SERVE_FWD_DONE seq={seq}");
            }

+                eprintln!("DIAG_SERVE_FWD_DONE seq={seq}");

BrokerEvent::Exit { code, .. } if attached => {

let line = ndjson::encode_line(&AttachRecord::Exit { code });


            RecvHalf::Loopback(mut recv) => {
                let mut buf = vec![0u8; STREAM_READ_CHUNK];
                loop {
                    await_ring_room(&log, &room).await;
                    match recv.read(&mut buf).await {
                        Ok(0) | Err(_) => {
                            log.lock().unwrap().finish();
                            break;
                        }
                        Ok(n) => log.lock().unwrap().append(&buf[..n]),
                    }
                }
            }

            RecvHalf::Loopback(mut recv) => {
                let pump_id = { log.lock().unwrap().stream_id };
                eprintln!("DIAG_OPR_PUMP_START stream={pump_id}");
                let mut buf = vec![0u8; STREAM_READ_CHUNK];
                loop {
                    await_ring_room(&log, &room).await;
                    match recv.read(&mut buf).await {
                        Ok(0) | Err(_) => {
                            eprintln!("DIAG_OPR_PUMP_END stream={pump_id}");
                            log.lock().unwrap().finish();
                            break;
                        }
                        Ok(n) => {
                            eprintln!("DIAG_OPR_PUMP_READ stream={pump_id} n={n}");
                            log.lock().unwrap().append(&buf[..n])
                        }
                    }
                }
            }


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

            RecvHalf::Loopback(mut recv) => {
                let mut buf = vec![0u8; STREAM_READ_CHUNK];
                loop {
                    await_ring_room(&log, &room).await;
                    match recv.read(&mut buf).await {
                        Ok(0) | Err(_) => {
                            log.lock().unwrap().finish();
                            break;
                        }
                        Ok(n) => log.lock().unwrap().append(&buf[..n]),
                    }
                }
            }

            RecvHalf::Loopback(mut recv) => {
                let pump_id = { log.lock().unwrap().stream_id };
                eprintln!("DIAG_OPR_PUMP_START stream={pump_id}");
                let mut buf = vec![0u8; STREAM_READ_CHUNK];
                loop {
                    await_ring_room(&log, &room).await;
                    match recv.read(&mut buf).await {
                        Ok(0) | Err(_) => {
                            eprintln!("DIAG_OPR_PUMP_END stream={pump_id}");
                            log.lock().unwrap().finish();
                            break;
                        }
                        Ok(n) => {
                            eprintln!("DIAG_OPR_PUMP_READ stream={pump_id} n={n}");
                            log.lock().unwrap().append(&buf[..n])
                        }
                    }
                }
            }

+                let pump_id = { log.lock().unwrap().stream_id };

+                eprintln!("DIAG_OPR_PUMP_START stream={pump_id}");

+                            eprintln!("DIAG_OPR_PUMP_END stream={pump_id}");

-                        Ok(n) => log.lock().unwrap().append(&buf[..n]),

+                            eprintln!("DIAG_OPR_PUMP_READ stream={pump_id} n={n}");

+                            log.lock().unwrap().append(&buf[..n])


Bash

CARGO_TARGET_DIR=target-seam cargo test -p spt --test dummy_harness_e2e -- --nocapture --test-threads=1 2>&1 | grep -E "DIAG_|WAVE2-LEAD|DIAGNOSTIC|test result" | head -80

Rerun with replay-write + opr-pump + send-done diagnostics


test endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches ... === DIAGNOSTIC: run_ok=true online=true harness_pid=Some(177268) alive_after=true rc_connected=true rc_saw_tick=false rc_stdout_bytes=0 ===
DIAG_BECOME_CTRL sid=1 from_seq=0 ring_len=0 next_seq=0 by=None
DIAG_DRAIN sid=1 chunk_bytes=16
DIAG_DRAIN sid=1 chunk_bytes=110
DIAG_DRAIN sid=1 chunk_bytes=47
DIAG_DRAIN sid=1 chunk_bytes=28
DIAG_DRAIN sid=1 chunk_bytes=29
DIAG_DRAIN sid=1 chunk_bytes=29
DIAG_DRAIN sid=1 chunk_bytes=29
DIAG_DRAIN sid=1 chunk_bytes=29
DIAG_OPR_PUMP_START stream=1
DIAG_OPR_PUMP_START stream=2
DIAG_DRAIN sid=1 chunk_bytes=29
DIAG_OPR_PUMP_READ stream=1 n=66
DIAG_SLOG_APPEND stream=1 seq=0 bytes=66 has_sub=false
DIAG_SLOG_ATTACH stream=2 from_seq=0 ring_len=0 next_seq=0
DIAG_OPR_PUMP_READ stream=1 n=39
DIAG_SLOG_APPEND stream=1 seq=1 bytes=39 has_sub=false
DIAG_OPR_PUMP_READ stream=1 n=39
DIAG_SLOG_APPEND stream=1 seq=2 bytes=39 has_sub=false
DIAG_SLOG_ATTACH stream=1 from_seq=0 ring_len=3 next_seq=3
DIAG_SLOG_ATTACH stream=1 from_seq=0 ring_len=3 next_seq=3
DIAG_SERVE_ATTACH session_id=1 from_seq=0 intent=Control origin=7c545e773dcf4a5543d66d072bfc48311298d9dc70dd1843c40fcab6a675dc8e
DIAG_BECOME_CTRL sid=1 from_seq=0 ring_len=9 next_seq=9 by=Some("7c545e773dcf4a5543d66d072bfc48311298d9dc70dd1843c40fcab6a675dc8e")
DIAG_REPLAY_WRITE seq=0 ok=true
DIAG_SERVE_FWD seq=0DIAG_REPLAY_WRITE seq=1 ok=true bytes=16
DIAG_REPLAY_WRITE seq=2 ok=true
DIAG_SERVE_FWD_DONE seq=0
DIAG_REPLAY_WRITE seq=3 ok=DIAG_SERVE_FWD seq=1true
DIAG_REPLAY_WRITE seq=4 ok=true
DIAG_DRAIN sid=1 chunk_bytes=29
WAVE2-LEAD: rc attached but received 0 bytes, no DUMMY_HARNESS_TICK — endpoint-run harness PTY output not reaching the attach (root-cause pending).
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 13.47s


Decisive new data:
- `DIAG_OPR_PUMP_START stream=1` **and** `stream=2` — both read pumps started.
- `DIAG_OPR_PUMP_READ stream=1 n=66/39/39` — stream 1 (rc→serve, op_w→peer_r) delivers fine.
- `DIAG_REPLAY_WRITE seq=0..4 ok=true` — become_controller replays fine; serve forwards (`DIAG_SERVE_FWD_DONE seq=0`).
- **NO `DIAG_OPR_PUMP_READ stream=2` EVER** — serve writes stream-1's send (peer_w), but stream 2's pump (op_r) never reads it. One duplex direction delivers, the reverse never does.

The known-good test exercises the same `open_stream` reverse direction and works — so the duplex code is fine; something environmental stalls op_r. Prime suspect: the **2-worker net runtime** (nethost.rs:653). Decisive 1-line experiment — bump workers, rerun:


648	    /// per key — the ALPN is the pre-trust/trusted boundary (ADR-0005).
649	    /// The meet-rotation listener loop rides the same runtime.
650	    // [impl->REQ-DAEMON-2]
651	    // [impl->REQ-SUBNET-2]
652	    pub fn start(cfg: NetConfig) -> io::Result<NetHost> {
653	        // Hermeticity guard (REQ-PAIR-8): a loopback-scoped endpoint is the
654	        // "no network reached" promise — the ceremony clock's lazy NTP query
655	        // must honor it (tests must never probe a real NTP server). Real
656	        // scopes keep the decision-18 offset correction.
657	        spt_net::net::pairing::ntp::set_enabled(!matches!(cfg.scope, BindScope::Loopback));
658	        let runtime = tokio::runtime::Builder::new_multi_thread()
659	            .worker_threads(2)

    /// per key — the ALPN is the pre-trust/trusted boundary (ADR-0005).
    /// The meet-rotation listener loop rides the same runtime.
    // [impl->REQ-DAEMON-2]
    // [impl->REQ-SUBNET-2]
    pub fn start(cfg: NetConfig) -> io::Result<NetHost> {
        // Hermeticity guard (REQ-PAIR-8): a loopback-scoped endpoint is the
        // "no network reached" promise — the ceremony clock's lazy NTP query
        // must honor it (tests must never probe a real NTP server). Real
        // scopes keep the decision-18 offset correction.
        spt_net::net::pairing::ntp::set_enabled(!matches!(cfg.scope, BindScope::Loopback));
        let runtime = tokio::runtime::Builder::new_multi_thread()
            .worker_threads(2)


658	        let runtime = tokio::runtime::Builder::new_multi_thread()
659	            .worker_threads(2)
660	            .thread_name("spt-broker-net")
661	            .enable_all()
662	            .build()?;
663	        let endpoint = runtime
664	            .block_on(NetEndpoint::bind_scoped(
665	                &cfg.identity,
666	                cfg.relay.clone(),
667	                cfg.discovery,
668	                vec![
669	                    SPT_NET_ALPN.to_vec(),
670	                    spt_net::net::pairing::wire::SPT_PAIR_ALPN.to_vec(),
671	                ],

        let runtime = tokio::runtime::Builder::new_multi_thread()
            .worker_threads(2)
            .thread_name("spt-broker-net")
            .enable_all()
            .build()?;
        let endpoint = runtime
            .block_on(NetEndpoint::bind_scoped(
                &cfg.identity,
                cfg.relay.clone(),
                cfg.discovery,
                vec![
                    SPT_NET_ALPN.to_vec(),
                    spt_net::net::pairing::wire::SPT_PAIR_ALPN.to_vec(),
                ],


Edit

        let runtime = tokio::runtime::Builder::new_multi_thread()
            .worker_threads(2)
            .thread_name("spt-broker-net")

        let runtime = tokio::runtime::Builder::new_multi_thread()
            .worker_threads(8)
            .thread_name("spt-broker-net")


        let runtime = tokio::runtime::Builder::new_multi_thread()
            .worker_threads(2)
            .thread_name("spt-broker-net")

        let runtime = tokio::runtime::Builder::new_multi_thread()
            .worker_threads(8)
            .thread_name("spt-broker-net")

// scopes keep the decision-18 offset correction.


Bash

CARGO_TARGET_DIR=target-seam cargo test -p spt --test dummy_harness_e2e -- --nocapture --test-threads=1 2>&1 | grep -E "rc_saw_tick|rc_stdout_bytes|OPR_PUMP_READ stream=2|SERVE_FWD_DONE|test result|WAVE2" | head -40

Rerun with 8 net workers — does rc get output?


test endpoint_run_brings_up_a_long_lived_dummy_harness_and_rc_attaches ... === DIAGNOSTIC: run_ok=true online=true harness_pid=Some(45068) alive_after=true rc_connected=true rc_saw_tick=false rc_stdout_bytes=0 ===
DIAG_REPLAY_WRITE seq=2 ok=DIAG_SERVE_FWD_DONE seq=0true
WAVE2-LEAD: rc attached but received 0 bytes, no DUMMY_HARNESS_TICK — endpoint-run harness PTY output not reaching the attach (root-cause pending).
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 13.48s


Still 0 bytes with 8 workers — **not** worker starvation. Revert that. The reverse duplex direction genuinely fails in production but works in the test. Need the FULL ordered broker log (greps truncate interleaved stderr). Revert workers, capture full log to a file.


Edit

        let runtime = tokio::runtime::Builder::new_multi_thread()
            .worker_threads(8)
            .thread_name("spt-broker-net")

        let runtime = tokio::runtime::Builder::new_multi_thread()
            .worker_threads(2)
            .thread_name("spt-broker-net")


        let runtime = tokio::runtime::Builder::new_multi_thread()
            .worker_threads(8)
            .thread_name("spt-broker-net")

        let runtime = tokio::runtime::Builder::new_multi_thread()
            .worker_threads(2)
            .thread_name("spt-broker-net")


Bash

CARGO_TARGET_DIR=target-seam cargo test -p spt --test dummy_harness_e2e -- --nocapture --test-threads=1 > /tmp/dummyrun.log 2>&1; grep -aE "DIAG_|SERVE_|OPR_|REPLAY|SLOG|BECOME|WAVE2|rc_stdout" /tmp/dummyrun.log | nl | head -70

Full capture, numbered diagnostic lines