755	id = "REQ-HAZARD-CONPTY-DSR"
756	title = "ConPTY reader must auto-answer DSR (ESC[6n) or all child output stalls (5.5)"
757	required_stages = ["impl", "unit"]
758	[[requirements]]
759	id = "REQ-HAZARD-CHILD-CONSOLE-FLASH"
760	title = "Console-subsystem children of the console-less daemon spawn with CREATE_NO_WINDOW, or each spawn flashes a visible blank window on the user's desktop (5.8)"
761	required_stages = ["impl", "unit"]  # activated post-M7 field bug 2026-06-06: sync-pump git spawns flashed 2 windows/min on a Windows desktop; window-absence itself is untestable from a consoled test runner (child inherits the console) — the unit stage covers "flag does not break the spawn" (the error-87 regression class)
762	[[requirements]]
763	id = "REQ-HAZARD-INSTANT-UNDERFLOW"
764	title = "Scheduling never subtracts a Duration from Instant::now() (underflow-panics on a host booted more recently than the offset); 'due now / never run' is Option<Instant>=None gated on forward duration_since only (5.9)"
765	required_stages = ["impl", "unit"]  # activated 2026-06-07 CI failure: peer pump primed cadence legs with `Instant::now() - 86_400s`, panicking the pump thread on the sub-24h-uptime Windows runner (run 27082417706) so the subnet never converged. fix = peerloop::due(Option<Instant>) + None-seeded legs; unit asserts first-tick-due with zero instant subtraction (host-uptime-independent, the env-conditional E2E can't be the guard)
766	[[requirements]]
767	id = "REQ-HAZARD-PUMP-IPC-DEADLINE"
768	title = "The single-threaded peer pump's brain-IPC reads are deadline-bounded (PUMP_PEER_IO_TIMEOUT, total-wait per call); a TimedOut read POISONS the client and escalates to a SUPERVISED RESTART, never a per-peer retry — a black-holed peer must never wedge the whole pump"
769	required_stages = ["doc", "impl", "unit"]  # activated 2026-06-11: deployed v0.4.0 pump wedged 2.2h on hfenduleam — net_open_stream's unbounded `loop { read_event }` blocked on a peer conn the broker never replied for; the single-threaded pump froze and supervise_pump cannot rescue a BLOCKED thread (it only catches panic/error/return). Surfaced twice (2026-06-07 + this 2.2h wedge); the stall-warning was the band-aid, not the fix. fix = Brain pump-mode carrier SPLIT at construction (Brain::cold_start_pump → BrainConn::Split: a `pump-ipc-reader` thread does blocking read_frame on the RecvHalf → channel; main thread writes the SendHalf and reads with Receiver::recv_timeout for a per-call total-wait deadline; deadline re-armed on stream progress for the pull legs). The reader-thread+channel mechanism (NOT a non-blocking socket + poll) is mandatory because interprocess 2.4.2 on Windows named pipes has no portable read timeout and its set_nonblocking corrupts mid-stream (mesh E2E proven). A TimedOut bubbles out of run_peer_pump → supervised restart (fresh brain client + conn cache + WorkerLasts re-prime, the V4 stagger, idempotent). The broker-side bound (broker must never make a brain wait unbounded on a QUIC op) is deferred to the next broker-update batch (DEFERRED.md). doc = KNOWN-HAZARDS entry; unit = (a) a never-replying broker over a real socket → Brain::cold_start_pump → net_open_stream returns TimedOut (never blocks) [tests/pumpdeadline.rs], (b) the recovery tier-split (TimedOut → bubble/Err = supervised restart; ordinary Err → per-peer abort only)
770	[[requirements]]
771	id = "REQ-HAZARD-SUDO-SECURE-PATH"
772	title = "Elevation guidance on Unix names the binary's ABSOLUTE path under sudo (a user-local install ~/.local/bin · ~/.cargo/bin is not on sudo's secure_path, so bare `sudo spt` dies 'command not found'); gated commands auto-elevate on an interactive TTY, else print the runnable hint (5.10)"
773	required_stages = ["impl", "unit"]  # activated 2026-06-07 field report (KITSUBITO): `spt subnet create` refused unelevated, user ran `sudo spt …` → `sudo: spt: command not found` (spt in ~/.local/bin, off secure_path). fix = elevation::rerun_command emits `sudo <abs-exe> <args>` (shell-quoted) + try_auto_elevate re-execs under sudo on interactive Unix; main.rs drops the confusing DEELEVATED line. unit = absolute-path-not-bare-name + shell-quoting + windows-none + should_auto_elevate truth table (the sudo exec itself needs a real TTY → manual/kitsubito verified)
774	[[requirements]]
775	id = "REQ-HAZARD-SELF-ELEVATE"
776	title = "Self-elevation (REQ-ELEVATE-1) re-runs the EXACT original invocation with the binary's ABSOLUTE exe path — never widening privilege scope, never adding/altering args, never via a PATH-resolved bare name, never via a shell-interpolated command string (argv-array only, no `sh -c`); the elevated child drops state back to the user (composes with the 5.7 de-elevation) and NEVER re-elevates (loop-safe: decide_elevation_path returns AlreadyElevated whenever the process is already Elevated, on every OS). The user's UAC/polkit/sudo prompt is the only consent gate — we never bypass it; the print-hint floor prints the absolute-path command too. The unprivileged parent never depends on (pipes/captures) the privileged child's stdout."
777	required_stages = ["unit"]  # activated M12-W4 (privilege-escalation feature → mandatory hazard REQ, doyle M12-W4-RULING.md Q6). unit conformance: (1) every launcher's argv uses the absolute exe path + verbatim args (Win runas / pkexec / terminal-emulator / sudo); (2) decide_elevation_path → AlreadyElevated when elevated, every os (loop-safety); (3) the constructed launch argv is an array, never a shell string (no `sh -c`, no interpolation — assert the argv vector). REQ-HAZARD-SUDO-SECURE-PATH covers the Unix abs-path-under-sudo facet; this covers the cross-platform verbatim / no-widen / no-shell-injection / loop-safe facets. The actual elevated launch is manual-verify (real UAC/polkit/sudo).
778	[[requirements]]
779	id = "REQ-HAZARD-LOCAL-API-AUTH"
780	title = "Every local `api` mutation authenticated to an endpoint/session (codex #13)"
781	required_stages = ["impl", "unit"]
782	[[requirements]]
783	id = "REQ-HAZARD-RESTART-IDEMPOTENT"
784	title = "Idempotent/exactly-once delivery across brain restart at every broker boundary (codex #14)"
785	required_stages = ["impl", "unit", "int"]  # int activated M3b-B9: tests/idempotent.rs crashes the brain before-intent/before-effect/after-effect at the PTY-write boundary → exactly-once (Spike #6)
786	[[requirements]]
787	id = "REQ-HAZARD-UPDATE-ROLLBACK"
788	title = "Self-update rejects version rollback; metadata expiry + adapter content signing (codex #5)"
789	required_stages = ["impl", "unit"]  # activated M3c-C1: release::verify_metadata enforces monotonic version (rollback), metadata expiry, channel pinning, and key revocation. Adapter content signing is the C3 half (REQ-UPD-5/SEAM-UPDATE)
790	[[requirements]]
791	id = "REQ-HAZARD-DAEMON-HOSTED-LIVENESS"
792	title = "Daemon-hosted perches (Psyche, spt-hosted Self) derive liveness from the daemon endpoint table + info.json status, never is_process_alive(info.pid) (2.5)"
793	required_stages = ["impl", "unit", "int"]  # int activated M3b-B9: the daemon E2E spawns the hosted Psyche, then proves a dead summarizer pid does NOT flip the daemon-online perch offline
794	# --- Added 2026-06-09 from grill-with-docs (ADR-0018: broker/brain in-process-collapse regression); rule 3: register before satisfying, rule 5: inactive until the broker/brain split restoration milestone (next milestone) ---

id = "REQ-HAZARD-CONPTY-DSR"
title = "ConPTY reader must auto-answer DSR (ESC[6n) or all child output stalls (5.5)"
required_stages = ["impl", "unit"]
[[requirements]]
id = "REQ-HAZARD-CHILD-CONSOLE-FLASH"
title = "Console-subsystem children of the console-less daemon spawn with CREATE_NO_WINDOW, or each spawn flashes a visible blank window on the user's desktop (5.8)"
required_stages = ["impl", "unit"]  # activated post-M7 field bug 2026-06-06: sync-pump git spawns flashed 2 windows/min on a Windows desktop; window-absence itself is untestable from a consoled test runner (child inherits the console) — the unit stage covers "flag does not break the spawn" (the error-87 regression class)
[[requirements]]
id = "REQ-HAZARD-INSTANT-UNDERFLOW"
title = "Scheduling never subtracts a Duration from Instant::now() (underflow-panics on a host booted more recently than the offset); 'due now / never run' is Option<Instant>=None gated on forward duration_since only (5.9)"
required_stages = ["impl", "unit"]  # activated 2026-06-07 CI failure: peer pump primed cadence legs with `Instant::now() - 86_400s`, panicking the pump thread on the sub-24h-uptime Windows runner (run 27082417706) so the subnet never converged. fix = peerloop::due(Option<Instant>) + None-seeded legs; unit asserts first-tick-due with zero instant subtraction (host-uptime-independent, the env-conditional E2E can't be the guard)
[[requirements]]
id = "REQ-HAZARD-PUMP-IPC-DEADLINE"
title = "The single-threaded peer pump's brain-IPC reads are deadline-bounded (PUMP_PEER_IO_TIMEOUT, total-wait per call); a TimedOut read POISONS the client and escalates to a SUPERVISED RESTART, never a per-peer retry — a black-holed peer must never wedge the whole pump"
required_stages = ["doc", "impl", "unit"]  # activated 2026-06-11: deployed v0.4.0 pump wedged 2.2h on hfenduleam — net_open_stream's unbounded `loop { read_event }` blocked on a peer conn the broker never replied for; the single-threaded pump froze and supervise_pump cannot rescue a BLOCKED thread (it only catches panic/error/return). Surfaced twice (2026-06-07 + this 2.2h wedge); the stall-warning was the band-aid, not the fix. fix = Brain pump-mode carrier SPLIT at construction (Brain::cold_start_pump → BrainConn::Split: a `pump-ipc-reader` thread does blocking read_frame on the RecvHalf → channel; main thread writes the SendHalf and reads with Receiver::recv_timeout for a per-call total-wait deadline; deadline re-armed on stream progress for the pull legs). The reader-thread+channel mechanism (NOT a non-blocking socket + poll) is mandatory because interprocess 2.4.2 on Windows named pipes has no portable read timeout and its set_nonblocking corrupts mid-stream (mesh E2E proven). A TimedOut bubbles out of run_peer_pump → supervised restart (fresh brain client + conn cache + WorkerLasts re-prime, the V4 stagger, idempotent). The broker-side bound (broker must never make a brain wait unbounded on a QUIC op) is deferred to the next broker-update batch (DEFERRED.md). doc = KNOWN-HAZARDS entry; unit = (a) a never-replying broker over a real socket → Brain::cold_start_pump → net_open_stream returns TimedOut (never blocks) [tests/pumpdeadline.rs], (b) the recovery tier-split (TimedOut → bubble/Err = supervised restart; ordinary Err → per-peer abort only)
[[requirements]]
id = "REQ-HAZARD-SUDO-SECURE-PATH"
title = "Elevation guidance on Unix names the binary's ABSOLUTE path under sudo (a user-local install ~/.local/bin · ~/.cargo/bin is not on sudo's secure_path, so bare `sudo spt` dies 'command not found'); gated commands auto-elevate on an interactive TTY, else print the runnable hint (5.10)"
required_stages = ["impl", "unit"]  # activated 2026-06-07 field report (KITSUBITO): `spt subnet create` refused unelevated, user ran `sudo spt …` → `sudo: spt: command not found` (spt in ~/.local/bin, off secure_path). fix = elevation::rerun_command emits `sudo <abs-exe> <args>` (shell-quoted) + try_auto_elevate re-execs under sudo on interactive Unix; main.rs drops the confusing DEELEVATED line. unit = absolute-path-not-bare-name + shell-quoting + windows-none + should_auto_elevate truth table (the sudo exec itself needs a real TTY → manual/kitsubito verified)
[[requirements]]
id = "REQ-HAZARD-SELF-ELEVATE"
title = "Self-elevation (REQ-ELEVATE-1) re-runs the EXACT original invocation with the binary's ABSOLUTE exe path — never widening privilege scope, never adding/altering args, never via a PATH-resolved bare name, never via a shell-interpolated command string (argv-array only, no `sh -c`); the elevated child drops state back to the user (composes with the 5.7 de-elevation) and NEVER re-elevates (loop-safe: decide_elevation_path returns AlreadyElevated whenever the process is already Elevated, on every OS). The user's UAC/polkit/sudo prompt is the only consent gate — we never bypass it; the print-hint floor prints the absolute-path command too. The unprivileged parent never depends on (pipes/captures) the privileged child's stdout."
required_stages = ["unit"]  # activated M12-W4 (privilege-escalation feature → mandatory hazard REQ, doyle M12-W4-RULING.md Q6). unit conformance: (1) every launcher's argv uses the absolute exe path + verbatim args (Win runas / pkexec / terminal-emulator / sudo); (2) decide_elevation_path → AlreadyElevated when elevated, every os (loop-safety); (3) the constructed launch argv is an array, never a shell string (no `sh -c`, no interpolation — assert the argv vector). REQ-HAZARD-SUDO-SECURE-PATH covers the Unix abs-path-under-sudo facet; this covers the cross-platform verbatim / no-widen / no-shell-injection / loop-safe facets. The actual elevated launch is manual-verify (real UAC/polkit/sudo).
[[requirements]]
id = "REQ-HAZARD-LOCAL-API-AUTH"
title = "Every local `api` mutation authenticated to an endpoint/session (codex #13)"
required_stages = ["impl", "unit"]
[[requirements]]
id = "REQ-HAZARD-RESTART-IDEMPOTENT"
title = "Idempotent/exactly-once delivery across brain restart at every broker boundary (codex #14)"
required_stages = ["impl", "unit", "int"]  # int activated M3b-B9: tests/idempotent.rs crashes the brain before-intent/before-effect/after-effect at the PTY-write boundary → exactly-once (Spike #6)
[[requirements]]
id = "REQ-HAZARD-UPDATE-ROLLBACK"
title = "Self-update rejects version rollback; metadata expiry + adapter content signing (codex #5)"
required_stages = ["impl", "unit"]  # activated M3c-C1: release::verify_metadata enforces monotonic version (rollback), metadata expiry, channel pinning, and key revocation. Adapter content signing is the C3 half (REQ-UPD-5/SEAM-UPDATE)
[[requirements]]
id = "REQ-HAZARD-DAEMON-HOSTED-LIVENESS"
title = "Daemon-hosted perches (Psyche, spt-hosted Self) derive liveness from the daemon endpoint table + info.json status, never is_process_alive(info.pid) (2.5)"
required_stages = ["impl", "unit", "int"]  # int activated M3b-B9: the daemon E2E spawns the hosted Psyche, then proves a dead summarizer pid does NOT flip the daemon-online perch offline
# --- Added 2026-06-09 from grill-with-docs (ADR-0018: broker/brain in-process-collapse regression); rule 3: register before satisfying, rule 5: inactive until the broker/brain split restoration milestone (next milestone) ---


Prompt:
<task-notification>
<task-id>bwcps8752</task-id>
<tool-use-id>toolu_015F4hobyr5LAxxfWGEAQMis</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\1e7c582d-c03a-4db0-bd84-c7b3b7959ea0\tasks\bwcps8752.output</output-file>
<status>completed</status>
<summary>Background command "Ack doyle (corrected)" completed (exit code 0)</summary>
</task-notification>


Ack delivered. A-half REQ confirms this is the deferred B-half. Ground the seam — locate files.


Bash

echo "=== nethost.rs ==="; find . -name nethost.rs -not -path '*/target/*' 2>/dev/null; echo "=== broker.rs ==="; find . -name broker.rs -not -path '*/target/*' 2>/dev/null; echo "=== pump ==="; find . -path '*/target/*' -prune -o -name 'pump*.rs' -print 2>/dev/null; find . -path '*/target/*' -prune -o -name 'peerloop*.rs' -print 2>/dev/null; echo "=== test infra ==="; find . -path '*/target/*' -prune -o \( -name 'netbroker.rs' -o -name 'pump.rs' -o -name 'pumpdeadline.rs' \) -print 2>/dev/null

Locate seam + test files


=== nethost.rs ===
./crates/spt-daemon/src/nethost.rs
=== broker.rs ===
./crates/spt-daemon/src/broker.rs
./crates/spt-daemon/tests/broker.rs
=== pump ===
./crates/spt-daemon/tests/pump.rs
./crates/spt-daemon/tests/pumpdeadline.rs
=== test infra ===
./crates/spt-daemon/tests/netbroker.rs
./crates/spt-daemon/tests/pump.rs
./crates/spt-daemon/tests/pumpdeadline.rs


Found seam. Read the dispatch sites + nethost QUIC awaits.


1095	            KIND_BRAIN_RESTARTED,
1096	            serde_json::to_value(BrainRestarted { honored })
1097	                .expect("BrainRestarted serializes"),
1098	        );
1099	        send_frame(send, &frame);
1100	    }
1101
1102	    /// Dial a peer over the broker-owned endpoint (D4a).
1103	    ///
1104	    /// With an `op_id` the dial is routed through the broker-owned
1105	    /// [`EffectJournal`] under the reserved [`NET_EFFECT_SESSION`] namespace,
1106	    /// exactly like an idempotent PTY write: a replay of the same id after a
1107	    /// brain crash is deduped (no second connection) and answered with the
1108	    /// connection the first apply opened. The `conn_id` is recorded *inside*
1109	    /// the journaled closure, so a concurrent deduped replay always finds it.
1110	    /// A deduped id whose connection this process no longer holds (a broker
1111	    /// restart — the journal survives, connections don't) is a typed error
1112	    /// telling the brain to dial under a fresh id.
1113	    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]
1114	    fn dispatch_net_dial(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {
1115	        let req: NetDialReq = serde_json::from_value(env.payload)
1116	            .map_err(|e| format!("bad net-dial payload: {e}"))?;
1117	        let host = self
1118	            .net
1119	            .get()
1120	            .ok_or("net disabled: this broker was bound without a network host")?;
1121	        let addr: EndpointAddr =
1122	            serde_json::from_value(req.addr).map_err(|e| format!("bad net-dial addr: {e}"))?;
1123	        match req.op_id {
1124	            None => {
1125	                // Direct (non-journaled) dial — no dedup, like a no-op_id input.
1126	                let (conn_id, remote) = host.dial(addr).map_err(|e| e.to_string())?;
1127	                let observed = host.conn_remote_addr_json(conn_id);
1128	                send_frame(
1129	                    send,
1130	                    &net_dialed_envelope(conn_id, remote, None, true, observed),
1131	                );
1132	                Ok(())
1133	            }
1134	            Some(op) => {
1135	                let outcome = self
1136	                    .journal
1137	                    .apply_once((NET_EFFECT_SESSION, op), EffectKind::NetDial, || {
1138	                        let (conn_id, _) = host.dial(addr)?;
1139	                        host.record_dial_op(op, conn_id);
1140	                        Ok(())
1141	                    })
1142	                    .map_err(|e| format!("net-dial failed: {e}"))?;
1143	                let conn_id = host.dial_op_conn(op).ok_or(
1144	                    "net-dial op already applied but its connection is no longer held \
1145	                     (broker restarted); retry with a fresh op_id",
1146	                )?;
1147	                let remote = host
1148	                    .conn_remote_hex(conn_id)
1149	                    .ok_or("dialed connection missing from the conn table")?;
1150	                let applied_now = outcome == Outcome::Applied;
1151	                let observed = host.conn_remote_addr_json(conn_id);
1152	                send_frame(
1153	                    send,
1154	                    &net_dialed_envelope(conn_id, remote, Some(op), applied_now, observed),
1155	                );
1156	                Ok(())
1157	            }
1158	        }
1159	    }
1160
1161	    /// Mint (or reuse) the broker's single in-process loopback connection
1162	    /// (M12 W1.5, B1) and answer with `net-dialed`. The transport a LOCAL
1163	    /// `spt rc` rides — QUIC self-dial is refused by iroh, so local attach
1164	    /// uses a broker-minted loopback conn whose `remote_id_hex` is this node's
1165	    /// own id (the served stream then gates same-node, access short-circuit
1166	    /// Allow). Not journaled: the singleton is idempotent by reuse, and there
1167	    /// is no network leg a crash-replay could double.
1168	    // [impl->REQ-RC-1]
1169	    fn dispatch_net_dial_loopback(&self, send: &SharedSend) -> Result<(), String> {
1170	        let host = self
1171	            .net
1172	            .get()
1173	            .ok_or("net disabled: this broker was bound without a network host")?;
1174	        let (conn_id, remote) = host.dial_loopback();
1175	        send_frame(
1176	            send,
1177	            &net_dialed_envelope(conn_id, remote, None, true, serde_json::Value::Null),
1178	        );
1179	        Ok(())
1180	    }
1181
1182	    /// Run the joiner leg of the subnet pairing ceremony (M7 D3,
1183	    /// REQ-SUBNET-2) on the broker-owned endpoint — it binds and pins the
1184	    /// REAL node pubkey, so it cannot run anywhere else. Not journaled: a
1185	    /// repeated join is idempotent at the stores (re-pair keeps the existing
1186	    /// trust row + seed), and the TOTP code in the request must never land
1187	    /// in a durable journal. Blocks this client's channel for the ceremony's
1188	    /// (bounded) life; the reply is always a `pair-joined` frame, with
1189	    /// failures classified for the CLI's guided UX.
1190	    // [impl->REQ-SUBNET-2]
1191	    fn dispatch_pair_join(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {
1192	        let req: PairJoinReq = serde_json::from_value(env.payload)
1193	            .map_err(|e| format!("bad pair-join payload: {e}"))?;
1194	        let host = self
1195	            .net
1196	            .get()
1197	            .ok_or("net disabled: this broker was bound without a network host")?;
1198	        // The optional explicit responder address (the rig seam) decodes
1199	        // strictly — a malformed address is a usage error, not a dial flop.
1200	        let responder_addr = match req.responder_addr {
1201	            Some(v) => Some(
1202	                serde_json::from_value::<EndpointAddr>(v)
1203	                    .map_err(|e| format!("bad pair-join responder_addr: {e}"))?,
1204	            ),
1205	            None => None,
1206	        };
1207	        let reply = match host.pair_join(&req.subnet, &req.code, responder_addr) {
1208	            Ok(outcome) => PairJoinReply {
1209	                ok: true,
1210	                class: None,
1211	                retry_after_secs: None,
1212	                detail: outcome,
1213	            },
1214	            Err(fail) => {
1215	                use crate::pairhost::JoinFail;
1216	                let (class, retry) = match &fail {
1217	                    JoinFail::NoSeedHolder(_) => ("no-seed-holder", None),
1218	                    JoinFail::WrongCode => ("wrong-code", None),
1219	                    JoinFail::RateLimited(retry) => ("rate-limited", *retry),
1220	                    JoinFail::UnknownSubnet(_) => ("unknown-subnet", None),
1221	                    JoinFail::ClockSkew => ("clock-skew", None),
1222	                    JoinFail::Other(_) => ("other", None),
1223	                };
1224	                PairJoinReply {
1225	                    ok: false,
1226	                    class: Some(class.to_string()),
1227	                    retry_after_secs: retry,
1228	                    detail: fail.to_string(),
1229	                }
1230	            }
1231	        };
1232	        let frame = Envelope::new(
1233	            KIND_PAIR_JOINED,
1234	            serde_json::to_value(reply).expect("PairJoinReply serializes"),
1235	        );
1236	        send_frame(send, &frame);
1237	        Ok(())
1238	    }
1239
1240	    /// Open a bidi stream on a held connection (D4b). Journaled exactly-once
1241	    /// under the same discipline as [`Self::dispatch_net_dial`]: the minted
1242	    /// `stream_id` is recorded inside the `apply_once` closure, and a deduped
1243	    /// replay is answered with the stream the first apply opened.
1244	    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]
1245	    fn dispatch_net_stream_open(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {
1246	        let req: NetStreamOpenReq = serde_json::from_value(env.payload)
1247	            .map_err(|e| format!("bad net-stream-open payload: {e}"))?;
1248	        let host = self.net.get().ok_or("net disabled")?;
1249	        let build = |stream_id: u64, conn_id: u64, op_id: Option<u64>, applied_now: bool| {
1250	            Envelope::new(
1251	                KIND_NET_STREAM_OPENED,
1252	                serde_json::to_value(NetStreamOpened {
1253	                    stream_id,
1254	                    conn_id,
1255	                    op_id,
1256	                    applied_now,
1257	                })
1258	                .expect("NetStreamOpened serializes"),
1259	            )
1260	        };
1261	        match req.op_id {
1262	            None => {
1263	                let stream_id = host.open_stream(req.conn_id).map_err(|e| e.to_string())?;
1264	                send_frame(send, &build(stream_id, req.conn_id, None, true));
1265	                Ok(())
1266	            }
1267	            Some(op) => {
1268	                let outcome = self
1269	                    .journal
1270	                    .apply_once((NET_EFFECT_SESSION, op), EffectKind::NetSend, || {
1271	                        let stream_id = host.open_stream(req.conn_id)?;
1272	                        host.record_stream_op(op, stream_id);
1273	                        Ok(())
1274	                    })
1275	                    .map_err(|e| format!("net-stream-open failed: {e}"))?;
1276	                let stream_id = host.stream_op_id(op).ok_or(
1277	                    "stream-open op already applied but its stream is no longer held \
1278	                     (broker restarted); retry with a fresh op_id",
1279	                )?;
1280	                let applied_now = outcome == Outcome::Applied;
1281	                send_frame(send, &build(stream_id, req.conn_id, Some(op), applied_now));
1282	                Ok(())
1283	            }
1284	        }
1285	    }
1286
1287	    /// Write bytes to a held stream's send side (D4b). With an `op_id` the
1288	    /// write is exactly-once through the broker-owned journal (a replayed
1289	    /// chunk after a brain crash is not written twice — Spike #3 invariant D
1290	    /// on the send path) and acked with `net-sent`; without one it is a direct
1291	    /// write (no dedup, no ack), mirroring the PTY `input` contract.
1292	    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]
1293	    fn dispatch_net_stream_send(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {
1294	        let req: NetStreamSendReq = serde_json::from_value(env.payload)
1295	            .map_err(|e| format!("bad net-stream-send payload: {e}"))?;
1296	        let bytes =
1297	            decode_bytes(&req.data_b64).map_err(|e| format!("bad net-stream-send base64: {e}"))?;
1298	        let host = self.net.get().ok_or("net disabled")?;
1299	        match req.op_id {
1300	            None => host
1301	                .send_stream(req.stream_id, &bytes, req.finish)
1302	                .map_err(|e| e.to_string()),
1303	            Some(op) => {
1304	                let outcome = self
1305	                    .journal
1306	                    .apply_once((NET_EFFECT_SESSION, op), EffectKind::NetSend, || {
1307	                        host.send_stream(req.stream_id, &bytes, req.finish)
1308	                    })
1309	                    .map_err(|e| format!("net-stream-send failed: {e}"))?;
1310	                let frame = Envelope::new(
1311	                    KIND_NET_SENT,
1312	                    serde_json::to_value(NetSent {
1313	                        stream_id: req.stream_id,
1314	                        op_id: op,
1315	                        applied_now: outcome == Outcome::Applied,
1316	                    })
1317	                    .expect("NetSent serializes"),
1318	                );
1319	                send_frame(send, &frame);
1320	                Ok(())
1321	            }
1322	        }
1323	    }
1324

            KIND_BRAIN_RESTARTED,
            serde_json::to_value(BrainRestarted { honored })
                .expect("BrainRestarted serializes"),
        );
        send_frame(send, &frame);
    }

    /// Dial a peer over the broker-owned endpoint (D4a).
    ///
    /// With an `op_id` the dial is routed through the broker-owned
    /// [`EffectJournal`] under the reserved [`NET_EFFECT_SESSION`] namespace,
    /// exactly like an idempotent PTY write: a replay of the same id after a
    /// brain crash is deduped (no second connection) and answered with the
    /// connection the first apply opened. The `conn_id` is recorded *inside*
    /// the journaled closure, so a concurrent deduped replay always finds it.
    /// A deduped id whose connection this process no longer holds (a broker
    /// restart — the journal survives, connections don't) is a typed error
    /// telling the brain to dial under a fresh id.
    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]
    fn dispatch_net_dial(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {
        let req: NetDialReq = serde_json::from_value(env.payload)
            .map_err(|e| format!("bad net-dial payload: {e}"))?;
        let host = self
            .net
            .get()
            .ok_or("net disabled: this broker was bound without a network host")?;
        let addr: EndpointAddr =
            serde_json::from_value(req.addr).map_err(|e| format!("bad net-dial addr: {e}"))?;
        match req.op_id {
            None => {
                // Direct (non-journaled) dial — no dedup, like a no-op_id input.
                let (conn_id, remote) = host.dial(addr).map_err(|e| e.to_string())?;
                let observed = host.conn_remote_addr_json(conn_id);
                send_frame(
                    send,
                    &net_dialed_envelope(conn_id, remote, None, true, observed),
                );
                Ok(())
            }
            Some(op) => {
                let outcome = self
                    .journal
                    .apply_once((NET_EFFECT_SESSION, op), EffectKind::NetDial, || {
                        let (conn_id, _) = host.dial(addr)?;
                        host.record_dial_op(op, conn_id);
                        Ok(())
                    })
                    .map_err(|e| format!("net-dial failed: {e}"))?;
                let conn_id = host.dial_op_conn(op).ok_or(
                    "net-dial op already applied but its connection is no longer held \
                     (broker restarted); retry with a fresh op_id",
                )?;
                let remote = host
                    .conn_remote_hex(conn_id)
                    .ok_or("dialed connection missing from the conn table")?;
                let applied_now = outcome == Outcome::Applied;
                let observed = host.conn_remote_addr_json(conn_id);
                send_frame(
                    send,
                    &net_dialed_envelope(conn_id, remote, Some(op), applied_now, observed),
                );
                Ok(())
            }
        }
    }

    /// Mint (or reuse) the broker's single in-process loopback connection
    /// (M12 W1.5, B1) and answer with `net-dialed`. The transport a LOCAL
    /// `spt rc` rides — QUIC self-dial is refused by iroh, so local attach
    /// uses a broker-minted loopback conn whose `remote_id_hex` is this node's
    /// own id (the served stream then gates same-node, access short-circuit
    /// Allow). Not journaled: the singleton is idempotent by reuse, and there
    /// is no network leg a crash-replay could double.
    // [impl->REQ-RC-1]
    fn dispatch_net_dial_loopback(&self, send: &SharedSend) -> Result<(), String> {
        let host = self
            .net
            .get()
            .ok_or("net disabled: this broker was bound without a network host")?;
        let (conn_id, remote) = host.dial_loopback();
        send_frame(
            send,
            &net_dialed_envelope(conn_id, remote, None, true, serde_json::Value::Null),
        );
        Ok(())
    }

    /// Run the joiner leg of the subnet pairing ceremony (M7 D3,
    /// REQ-SUBNET-2) on the broker-owned endpoint — it binds and pins the
    /// REAL node pubkey, so it cannot run anywhere else. Not journaled: a
    /// repeated join is idempotent at the stores (re-pair keeps the existing
    /// trust row + seed), and the TOTP code in the request must never land
    /// in a durable journal. Blocks this client's channel for the ceremony's
    /// (bounded) life; the reply is always a `pair-joined` frame, with
    /// failures classified for the CLI's guided UX.
    // [impl->REQ-SUBNET-2]
    fn dispatch_pair_join(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {
        let req: PairJoinReq = serde_json::from_value(env.payload)
            .map_err(|e| format!("bad pair-join payload: {e}"))?;
        let host = self
            .net
            .get()
            .ok_or("net disabled: this broker was bound without a network host")?;
        // The optional explicit responder address (the rig seam) decodes
        // strictly — a malformed address is a usage error, not a dial flop.
        let responder_addr = match req.responder_addr {
            Some(v) => Some(
                serde_json::from_value::<EndpointAddr>(v)
                    .map_err(|e| format!("bad pair-join responder_addr: {e}"))?,
            ),
            None => None,
        };
        let reply = match host.pair_join(&req.subnet, &req.code, responder_addr) {
            Ok(outcome) => PairJoinReply {
                ok: true,
                class: None,
                retry_after_secs: None,
                detail: outcome,
            },
            Err(fail) => {
                use crate::pairhost::JoinFail;
                let (class, retry) = match &fail {
                    JoinFail::NoSeedHolder(_) => ("no-seed-holder", None),
                    JoinFail::WrongCode => ("wrong-code", None),
                    JoinFail::RateLimited(retry) => ("rate-limited", *retry),
                    JoinFail::UnknownSubnet(_) => ("unknown-subnet", None),
                    JoinFail::ClockSkew => ("clock-skew", None),
                    JoinFail::Other(_) => ("other", None),
                };
                PairJoinReply {
                    ok: false,
                    class: Some(class.to_string()),
                    retry_after_secs: retry,
                    detail: fail.to_string(),
                }
            }
        };
        let frame = Envelope::new(
            KIND_PAIR_JOINED,
            serde_json::to_value(reply).expect("PairJoinReply serializes"),
        );
        send_frame(send, &frame);
        Ok(())
    }

    /// Open a bidi stream on a held connection (D4b). Journaled exactly-once
    /// under the same discipline as [`Self::dispatch_net_dial`]: the minted
    /// `stream_id` is recorded inside the `apply_once` closure, and a deduped
    /// replay is answered with the stream the first apply opened.
    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]
    fn dispatch_net_stream_open(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {
        let req: NetStreamOpenReq = serde_json::from_value(env.payload)
            .map_err(|e| format!("bad net-stream-open payload: {e}"))?;
        let host = self.net.get().ok_or("net disabled")?;
        let build = |stream_id: u64, conn_id: u64, op_id: Option<u64>, applied_now: bool| {
            Envelope::new(
                KIND_NET_STREAM_OPENED,
                serde_json::to_value(NetStreamOpened {
                    stream_id,
                    conn_id,
                    op_id,
                    applied_now,
                })
                .expect("NetStreamOpened serializes"),
            )
        };
        match req.op_id {
            None => {
                let stream_id = host.open_stream(req.conn_id).map_err(|e| e.to_string())?;
                send_frame(send, &build(stream_id, req.conn_id, None, true));
                Ok(())
            }
            Some(op) => {
                let outcome = self
                    .journal
                    .apply_once((NET_EFFECT_SESSION, op), EffectKind::NetSend, || {
                        let stream_id = host.open_stream(req.conn_id)?;
                        host.record_stream_op(op, stream_id);
                        Ok(())
                    })
                    .map_err(|e| format!("net-stream-open failed: {e}"))?;
                let stream_id = host.stream_op_id(op).ok_or(
                    "stream-open op already applied but its stream is no longer held \
                     (broker restarted); retry with a fresh op_id",
                )?;
                let applied_now = outcome == Outcome::Applied;
                send_frame(send, &build(stream_id, req.conn_id, Some(op), applied_now));
                Ok(())
            }
        }
    }

    /// Write bytes to a held stream's send side (D4b). With an `op_id` the
    /// write is exactly-once through the broker-owned journal (a replayed
    /// chunk after a brain crash is not written twice — Spike #3 invariant D
    /// on the send path) and acked with `net-sent`; without one it is a direct
    /// write (no dedup, no ack), mirroring the PTY `input` contract.
    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]
    fn dispatch_net_stream_send(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {
        let req: NetStreamSendReq = serde_json::from_value(env.payload)
            .map_err(|e| format!("bad net-stream-send payload: {e}"))?;
        let bytes =
            decode_bytes(&req.data_b64).map_err(|e| format!("bad net-stream-send base64: {e}"))?;
        let host = self.net.get().ok_or("net disabled")?;
        match req.op_id {
            None => host
                .send_stream(req.stream_id, &bytes, req.finish)
                .map_err(|e| e.to_string()),
            Some(op) => {
                let outcome = self
                    .journal
                    .apply_once((NET_EFFECT_SESSION, op), EffectKind::NetSend, || {
                        host.send_stream(req.stream_id, &bytes, req.finish)
                    })
                    .map_err(|e| format!("net-stream-send failed: {e}"))?;
                let frame = Envelope::new(
                    KIND_NET_SENT,
                    serde_json::to_value(NetSent {
                        stream_id: req.stream_id,
                        op_id: op,
                        applied_now: outcome == Outcome::Applied,
                    })
                    .expect("NetSent serializes"),
                );
                send_frame(send, &frame);
                Ok(())
            }
        }
    }


Dispatch sites clear: QUIC awaits are inside `host.dial/open_stream/send_stream`, called within `apply_once` closures (timeout there → closure Errs → not recorded → no phantom id ✓). Bounding inside nethost is cleanest. Read nethost.rs.