140	}
141
142	/// Sanitize a wire-supplied sync id into a filesystem-safe scratch-file stem.
143	fn safe_stem(sync_id: &str) -> String {
144	    sync_id
145	        .chars()
146	        .map(|c| if c.is_ascii_alphanumeric() || c == '-' || c == '_' { c } else { '_' })
147	        .collect()
148	}
149
150	/// Serve one sync pull against this node's context store, blocking until the
151	/// pull completes, the requester detaches, or the request fails.
152	///
153	/// `origin_node` MUST be the handshake-proven remote id from the broker's
154	/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes
155	/// (REQ-HAZARD-WAN-ORIGIN-AUTH). Every requested ref is re-checked against
156	/// `policy` **here**, fail-closed (REQ-INST-5: a peer must not be able to pull
157	/// a hidden/unsynced mind by naming it). `scratch_dir` holds the transient
158	/// bundle file.
159	// [impl->REQ-NET-3]
160	// [impl->REQ-INST-5]
161	// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]
162	pub fn serve_sync(
163	    brain: &mut Brain,
164	    stream_id: u64,
165	    origin_node: &str,
166	    net_from_seq: u64,
167	    cs: &ContextStore,
168	    policy: &SyncPolicy,
169	    scratch_dir: &Path,
170	) -> io::Result<SyncServeOutcome> {
171	    brain.net_stream_subscribe(stream_id, net_from_seq)?;
172	    let mut decoder = SyncDecoder::new();
173
174	    loop {
175	        match brain.read_event()? {
176	            BrokerEvent::NetStreamData { stream_id: sid, bytes, .. } if sid == stream_id => {
177	                let Some(rec) = decoder.push(&bytes).into_iter().next() else { continue };
178	                let SyncRecord::Request { sync_id, refs, have_tips } = rec else {
179	                    continue; // not a request — protocol noise, keep waiting
180	                };
181
182	                // The gate: partition requested refs under the proven origin.
183	                let (allowed, denied): (Vec<String>, Vec<String>) =
184	                    refs.into_iter().partition(|r| policy.allows(r, origin_node));
185	                if !denied.is_empty() {
186	                    let line = SyncRecord::Deny {
187	                        sync_id: sync_id.clone(),
188	                        refs: denied.clone(),
189	                        message: "refused: not synced/visible to this origin".to_string(),
190	                    }
191	                    .encode_line();
192	                    brain.net_stream_send(stream_id, &line, None, false)?;
193	                }
194
195	                // Incremental bundle for the allowed refs (absent branches and
196	                // up-to-date tips are skipped inside create_bundle).
197	                let wants: Vec<(String, Option<String>)> = allowed
198	                    .iter()
199	                    .map(|r| (r.clone(), have_tips.get(r).cloned()))
200	                    .collect();
201	                std::fs::create_dir_all(scratch_dir)?;
202	                let bundle = scratch_dir.join(format!("serve-{}.bundle", safe_stem(&sync_id)));
203	                let included = match cs.branch_store().create_bundle(&bundle, &wants) {
204	                    Ok(i) => i,
205	                    Err(e) => {
206	                        let line = SyncRecord::Err {
207	                            sync_id: sync_id.clone(),
208	                            message: format!("bundle failed: {e}"),
209	                        }
210	                        .encode_line();
211	                        brain.net_stream_send(stream_id, &line, None, true)?;
212	                        return Ok(SyncServeOutcome::Failed(e.to_string()));
213	                    }
214	                };
215	                if included.is_empty() {
216	                    let line = SyncRecord::Done { sync_id, total: 0, refs: Vec::new() }
217	                        .encode_line();
218	                    brain.net_stream_send(stream_id, &line, None, true)?;
219	                    return Ok(SyncServeOutcome::UpToDate { denied });
220	                }
221
222	                // Pump the bundle file as positional chunks (fire-and-forget;
223	                // the offset is the dedup, not a send journal — D5b lesson).
224	                let bytes_total = {
225	                    let mut file = std::fs::File::open(&bundle)?;
226	                    let total = file.metadata()?.len();
227	                    let mut offset = 0u64;
228	                    let mut buf = vec![0u8; CHUNK_BYTES];
229	                    loop {
230	                        use std::io::Read;
231	                        let n = file.read(&mut buf)?;
232	                        if n == 0 {
233	                            break;
234	                        }
235	                        let line = SyncRecord::Chunk {
236	                            sync_id: sync_id.clone(),
237	                            offset,
238	                            data_b64: encode_bytes(&buf[..n]),
239	                        }
240	                        .encode_line();
241	                        brain.net_stream_send(stream_id, &line, None, false)?;
242	                        offset += n as u64;
243	                    }
244	                    total
245	                };
246	                let _ = std::fs::remove_file(&bundle);
247	                let done = SyncRecord::Done {
248	                    sync_id,
249	                    total: bytes_total,
250	                    refs: included.clone(),
251	                }
252	                .encode_line();
253	                brain.net_stream_send(stream_id, &done, None, true)?;
254	                return Ok(SyncServeOutcome::Sent { bytes: bytes_total, refs: included, denied });
255	            }
256	            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {
257	                brain.net_stream_send(stream_id, &[], None, true)?;
258	                return Ok(SyncServeOutcome::Detached);
259	            }
260	            BrokerEvent::Error { message } => return Err(io::Error::other(message)),
261	            _ => {}
262	        }
263	    }
264	}
265
266	/// The refs this node should request from `peer_node`: every local `a-<id>`
267	/// branch the policy allows toward that peer (symmetric scoping — don't ask
268	/// for a mind the peer's own gate would refuse to receive elsewhere) plus
269	/// every hosted `p-<project>` branch. The pull model's requester half:
270	/// two-tier scoping with no new registry fields (ADR-0013).
271	// [impl->REQ-INST-5]
272	pub fn select_refs(
273	    policy: &SyncPolicy,
274	    cs: &ContextStore,
275	    peer_node: &str,
276	) -> io::Result<Vec<String>> {
277	    Ok(cs
278	        .branch_store()
279	        .branches()?
280	        .into_iter()
281	        .filter(|b| policy.allows(b, peer_node))
282	        .collect())
283	}
284
285	/// Requester side: pull `refs` from the peer over `conn_id`, apply each
286	/// fetched tip with the vector driver, and report per-branch verdicts.
287	/// `open_op` is the durable id for the exactly-once stream open; everything
288	/// after rides fire-and-forget. A torn pull errors — recovery is a fresh
289	/// `request_sync` (apply is idempotent; FAULT-MATRIX stream-loss row).
290	// [impl->REQ-NET-3]
291	pub fn request_sync(
292	    brain: &mut Brain,
293	    conn_id: u64,
294	    refs: &[String],
295	    open_op: u64,
296	    cs: &ContextStore,
297	    scratch_dir: &Path,
298	) -> io::Result<SyncPullReport> {
299	    let opened = brain.net_open_stream(conn_id, Some(open_op))?;
300	    let stream_id = opened.stream_id;
301	    brain.net_stream_subscribe(stream_id, 0)?;
302
303	    let sync_id = format!("pull-{open_op}");
304	    let mut have_tips = BTreeMap::new();
305	    for r in refs {
306	        if let Some(tip) = cs.branch_store().tip(r)? {
307	            have_tips.insert(r.clone(), tip);
308	        }
309	    }
310	    let line = SyncRecord::Request {
311	        sync_id: sync_id.clone(),
312	        refs: refs.to_vec(),
313	        have_tips,
314	    }
315	    .encode_line();
316	    brain.net_stream_send(stream_id, &line, None, true)?;
317
318	    std::fs::create_dir_all(scratch_dir)?;
319	    let bundle: PathBuf = scratch_dir.join(format!("recv-{}.bundle", safe_stem(&sync_id)));

}

/// Sanitize a wire-supplied sync id into a filesystem-safe scratch-file stem.
fn safe_stem(sync_id: &str) -> String {
    sync_id
        .chars()
        .map(|c| if c.is_ascii_alphanumeric() || c == '-' || c == '_' { c } else { '_' })
        .collect()
}

/// Serve one sync pull against this node's context store, blocking until the
/// pull completes, the requester detaches, or the request fails.
///
/// `origin_node` MUST be the handshake-proven remote id from the broker's
/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes
/// (REQ-HAZARD-WAN-ORIGIN-AUTH). Every requested ref is re-checked against
/// `policy` **here**, fail-closed (REQ-INST-5: a peer must not be able to pull
/// a hidden/unsynced mind by naming it). `scratch_dir` holds the transient
/// bundle file.
// [impl->REQ-NET-3]
// [impl->REQ-INST-5]
// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]
pub fn serve_sync(
    brain: &mut Brain,
    stream_id: u64,
    origin_node: &str,
    net_from_seq: u64,
    cs: &ContextStore,
    policy: &SyncPolicy,
    scratch_dir: &Path,
) -> io::Result<SyncServeOutcome> {
    brain.net_stream_subscribe(stream_id, net_from_seq)?;
    let mut decoder = SyncDecoder::new();

    loop {
        match brain.read_event()? {
            BrokerEvent::NetStreamData { stream_id: sid, bytes, .. } if sid == stream_id => {
                let Some(rec) = decoder.push(&bytes).into_iter().next() else { continue };
                let SyncRecord::Request { sync_id, refs, have_tips } = rec else {
                    continue; // not a request — protocol noise, keep waiting
                };

                // The gate: partition requested refs under the proven origin.
                let (allowed, denied): (Vec<String>, Vec<String>) =
                    refs.into_iter().partition(|r| policy.allows(r, origin_node));
                if !denied.is_empty() {
                    let line = SyncRecord::Deny {
                        sync_id: sync_id.clone(),
                        refs: denied.clone(),
                        message: "refused: not synced/visible to this origin".to_string(),
                    }
                    .encode_line();
                    brain.net_stream_send(stream_id, &line, None, false)?;
                }

                // Incremental bundle for the allowed refs (absent branches and
                // up-to-date tips are skipped inside create_bundle).
                let wants: Vec<(String, Option<String>)> = allowed
                    .iter()
                    .map(|r| (r.clone(), have_tips.get(r).cloned()))
                    .collect();
                std::fs::create_dir_all(scratch_dir)?;
                let bundle = scratch_dir.join(format!("serve-{}.bundle", safe_stem(&sync_id)));
                let included = match cs.branch_store().create_bundle(&bundle, &wants) {
                    Ok(i) => i,
                    Err(e) => {
                        let line = SyncRecord::Err {
                            sync_id: sync_id.clone(),
                            message: format!("bundle failed: {e}"),
                        }
                        .encode_line();
                        brain.net_stream_send(stream_id, &line, None, true)?;
                        return Ok(SyncServeOutcome::Failed(e.to_string()));
                    }
                };
                if included.is_empty() {
                    let line = SyncRecord::Done { sync_id, total: 0, refs: Vec::new() }
                        .encode_line();
                    brain.net_stream_send(stream_id, &line, None, true)?;
                    return Ok(SyncServeOutcome::UpToDate { denied });
                }

                // Pump the bundle file as positional chunks (fire-and-forget;
                // the offset is the dedup, not a send journal — D5b lesson).
                let bytes_total = {
                    let mut file = std::fs::File::open(&bundle)?;
                    let total = file.metadata()?.len();
                    let mut offset = 0u64;
                    let mut buf = vec![0u8; CHUNK_BYTES];
                    loop {
                        use std::io::Read;
                        let n = file.read(&mut buf)?;
                        if n == 0 {
                            break;
                        }
                        let line = SyncRecord::Chunk {
                            sync_id: sync_id.clone(),
                            offset,
                            data_b64: encode_bytes(&buf[..n]),
                        }
                        .encode_line();
                        brain.net_stream_send(stream_id, &line, None, false)?;
                        offset += n as u64;
                    }
                    total
                };
                let _ = std::fs::remove_file(&bundle);
                let done = SyncRecord::Done {
                    sync_id,
                    total: bytes_total,
                    refs: included.clone(),
                }
                .encode_line();
                brain.net_stream_send(stream_id, &done, None, true)?;
                return Ok(SyncServeOutcome::Sent { bytes: bytes_total, refs: included, denied });
            }
            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {
                brain.net_stream_send(stream_id, &[], None, true)?;
                return Ok(SyncServeOutcome::Detached);
            }
            BrokerEvent::Error { message } => return Err(io::Error::other(message)),
            _ => {}
        }
    }
}

/// The refs this node should request from `peer_node`: every local `a-<id>`
/// branch the policy allows toward that peer (symmetric scoping — don't ask
/// for a mind the peer's own gate would refuse to receive elsewhere) plus
/// every hosted `p-<project>` branch. The pull model's requester half:
/// two-tier scoping with no new registry fields (ADR-0013).
// [impl->REQ-INST-5]
pub fn select_refs(
    policy: &SyncPolicy,
    cs: &ContextStore,
    peer_node: &str,
) -> io::Result<Vec<String>> {
    Ok(cs
        .branch_store()
        .branches()?
        .into_iter()
        .filter(|b| policy.allows(b, peer_node))
        .collect())
}

/// Requester side: pull `refs` from the peer over `conn_id`, apply each
/// fetched tip with the vector driver, and report per-branch verdicts.
/// `open_op` is the durable id for the exactly-once stream open; everything
/// after rides fire-and-forget. A torn pull errors — recovery is a fresh
/// `request_sync` (apply is idempotent; FAULT-MATRIX stream-loss row).
// [impl->REQ-NET-3]
pub fn request_sync(
    brain: &mut Brain,
    conn_id: u64,
    refs: &[String],
    open_op: u64,
    cs: &ContextStore,
    scratch_dir: &Path,
) -> io::Result<SyncPullReport> {
    let opened = brain.net_open_stream(conn_id, Some(open_op))?;
    let stream_id = opened.stream_id;
    brain.net_stream_subscribe(stream_id, 0)?;

    let sync_id = format!("pull-{open_op}");
    let mut have_tips = BTreeMap::new();
    for r in refs {
        if let Some(tip) = cs.branch_store().tip(r)? {
            have_tips.insert(r.clone(), tip);
        }
    }
    let line = SyncRecord::Request {
        sync_id: sync_id.clone(),
        refs: refs.to_vec(),
        have_tips,
    }
    .encode_line();
    brain.net_stream_send(stream_id, &line, None, true)?;

    std::fs::create_dir_all(scratch_dir)?;
    let bundle: PathBuf = scratch_dir.join(format!("recv-{}.bundle", safe_stem(&sync_id)));


Bash

sed -n '1,80p' crates/spt-daemon/src/wan.rs; echo ===; grep -n "pub fn\|pub struct\|pub enum" crates/spt-daemon/src/wan.rs

Read wan.rs head and surface


//! WAN message receive funnel (M4-D5a) — wire record → access gate → op-id
//! dedup → the existing local delivery path.
//!
//! The sending node resolved the target (D3c policy), dialed, and wrote
//! [`WanMessage`] NDJSON lines onto a broker-owned QUIC stream (D4). This
//! module is the receiving node's funnel from a decoded record to the local
//! REQ-MSG machinery — it adds exactly two things the local path doesn't have,
//! then delegates:
//!
//! 1. **the access gate** ([`crate::access`], ADR-0009): every wire-inbound
//!    record passes [`access_check`] with the **handshake-proven** origin node
//!    (the broker stream's `remote_id_hex` — never a payload field,
//!    REQ-HAZARD-WAN-ORIGIN-AUTH);
//! 2. **op-id idempotency** (REQ-HAZARD-RESTART-IDEMPOTENT): the wire feed
//!    replays — a sender's journaled redrive, or this brain restarting and
//!    resubscribing from an earlier cursor — so delivery dedups on the
//!    sender-minted durable `op_id`.
//!
//! ## Exactly-once boundaries (honest accounting)
//!
//! - **Spool path (offline target):** fully exactly-once — the op-id claim and
//!   the spool row commit in ONE SQLite transaction
//!   ([`spool::spool_wan_message_at`]); a crash on either side of the write
//!   replays to the same single row.
//! - **Live-TCP path:** check → TCP write → claim. A crash in the gap between
//!   a completed TCP write and the claim re-delivers that op on replay (an
//!   at-least-once window bounded to one in-flight record; the live listener
//!   path has no durable receipt to dedup against). Claim-before-write was
//!   rejected because a crash in ITS gap silently drops the message — for a
//!   messaging substrate, a rare duplicate beats a rare silent drop. Tracked
//!   in `docs/FAULT-MATRIX.md`.

use std::path::Path;

use spt_msg::deliver::{deliver_tcp, perch_exists};
use spt_net::net::wanmsg::WanMessage;
use spt_store::spool;

use crate::access::{access_check, classify_inbound, AccessDecision};

/// Wall-clock now (epoch ms) — the reply-window clock.
fn now_ms() -> u64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_millis() as u64)
        .unwrap_or(0)
}

/// What the funnel did with one inbound record.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WanOutcome {
    /// Delivered live over TCP to the target's listener (op claimed after).
    DeliveredTcp,
    /// Spooled for later drain, atomically with the op claim.
    Spooled,
    /// The op_id was already claimed — a replay; nothing written.
    Duplicate,
    /// Refused at the access gate (ADR-0009) — dropped before any delivery.
    Refused,
    /// No perch for the target on this node — dropped (not claimed: if the
    /// perch appears later, a replayed record may still land).
    NoPerch,
}

/// Funnel one decoded wire record into local delivery. `origin_node` MUST be
/// the QUIC handshake-proven remote node id (the broker's
/// `NetStreamInfo::remote_id_hex` for the stream the record arrived on) —
/// never anything read out of record bytes (REQ-HAZARD-WAN-ORIGIN-AUTH).
// [impl->REQ-NET-1]
// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]
// [impl->REQ-HAZARD-RESTART-IDEMPOTENT]
pub fn receive_wan(msg: &WanMessage, origin_node: &str, owlery: &Path) -> WanOutcome {
    // Gate 2 of ADR-0009 (visibility ran at the sender's resolution; grants
    // gate capabilities, not messages). Messages classify reply-vs-unsolicited
    // against the target's own recent outbound (the stateful firewall): a
    // correlated reply passes a whitelist that would refuse the same node
    // unsolicited.
    let class = classify_inbound(&msg.target, origin_node, now_ms());
    if access_check(&msg.target, origin_node, class) == AccessDecision::Deny {
        return WanOutcome::Refused;
===
51:pub enum WanOutcome {
72:pub fn receive_wan(msg: &WanMessage, origin_node: &str, owlery: &Path) -> WanOutcome {


grep -n "fn \|wait_for_stream\|net_streams\|net_dial\|KIND" crates/spt-daemon/tests/wanmsg.rs | head -40; echo ===; grep -n "pub fn net_\|pub fn read_event\|pub fn cold_start\|pub struct Brain" crates/spt-daemon/src/brain.rs | head -30

Survey wanmsg test harness and Brain net API


41:fn init_home() {
50:fn unique_name() -> String {
55:fn unique_id(prefix: &str) -> String {
60:fn hermetic() -> NetConfig {
69:fn net_broker(name: &str, dir: &std::path::Path) -> Arc<Broker> {
80:fn connect_retry(name: &str) -> Brain {
92:fn wait_for_stream(brain: &mut Brain) -> (u64, String) {
94:        let reply = brain.net_streams().expect("net-streams");
105:fn offline_perch(id: &str) -> std::path::PathBuf {
113:fn msg(target: &str, body: &str, op_id: &str) -> WanMessage {
119:fn funnel_until_eof(
152:fn wan_message_lands_exactly_once_under_transport_origin() {
168:    let conn = sender.net_dial(addr, Some(1)).expect("dial");
187:    let (stream, remote) = wait_for_stream(&mut b_probe);
209:fn receiver_restart_replays_feed_without_double_delivery() {
223:    let conn = sender.net_dial(addr, Some(1)).expect("dial");
233:    let (stream, remote) = wait_for_stream(&mut b_probe);
===
62:pub struct BrainState {
111:pub struct Brain {
133:    pub fn cold_start(name: &str, now_ms: u64) -> io::Result<Self> {
296:    pub fn read_event(&mut self) -> io::Result<BrokerEvent> {
475:    pub fn net_status(&mut self) -> io::Result<NetStatusReply> {
492:    pub fn net_dial(
515:    pub fn net_open_stream(
539:    pub fn net_stream_send(
570:    pub fn net_streams(&mut self) -> io::Result<NetStreamsReply> {
585:    pub fn net_stream_subscribe(&mut self, stream_id: u64, from_seq: u64) -> io::Result<()> {
596:    pub fn net_cursor(&self, stream_id: u64) -> Option<u64> {
604:    pub fn net_presence_subscribe(&mut self, from_seq: u64) -> io::Result<()> {


grep -rn "\"kind\"\|kind =\|#\[serde(tag" crates/spt-net/src/net/*.rs | grep -i "tag\|rename" | head; echo ===; grep -rn "serde(tag = \"kind\"" crates/spt-net/src/net/*.rs; echo ===; grep -rn "rename = " crates/spt-net/src/net/sync.rs crates/spt-net/src/net/update.rs crates/spt-net/src/net/notif.rs crates/spt-net/src/net/wanmsg.rs crates/spt-net/src/net/replicate.rs | head -30

Find wire record kind tags across families


crates/spt-net/src/net/attach.rs:37:#[serde(tag = "kind", rename_all = "snake_case")]
crates/spt-net/src/net/notif.rs:32:#[serde(tag = "kind", rename_all = "snake_case")]
crates/spt-net/src/net/sync.rs:39:#[serde(tag = "kind", rename_all = "snake_case")]
crates/spt-net/src/net/update.rs:40:#[serde(tag = "kind", rename_all = "snake_case")]
crates/spt-net/src/net/xfer.rs:42:#[serde(tag = "kind", rename_all = "snake_case")]
===
crates/spt-net/src/net/attach.rs:37:#[serde(tag = "kind", rename_all = "snake_case")]
crates/spt-net/src/net/notif.rs:32:#[serde(tag = "kind", rename_all = "snake_case")]
crates/spt-net/src/net/sync.rs:39:#[serde(tag = "kind", rename_all = "snake_case")]
crates/spt-net/src/net/update.rs:40:#[serde(tag = "kind", rename_all = "snake_case")]
crates/spt-net/src/net/xfer.rs:42:#[serde(tag = "kind", rename_all = "snake_case")]
===


grep -rn "enum SyncRecord" -A 12 crates/spt-net/src/net/sync.rs | grep -E "enum|^\s+[A-Z]" | head; echo ===; grep -n "enum UpdRecord" -A 10 crates/spt-net/src/net/update.rs | grep -E "enum|[A-Z][a-z]+ \{|[A-Z][a-z]+," ; echo ===; grep -n "enum NotifRecord" -A 6 crates/spt-net/src/net/notif.rs; echo ===; grep -n "struct WanMessage" -B 6 crates/spt-net/src/net/wanmsg.rs | head -12; echo ===; sed -n '30,60p' crates/spt-net/src/net/replicate.rs

Enumerate wire record variants per family


40:pub enum SyncRecord {
===
41:pub enum UpdRecord {
45-    Query {
46-        upd_id: String,
48-        channel: String,
===
33:pub enum NotifRecord {
34-    /// The full semilattice row, as currently held by the sender. The receiver
35-    /// joins it ([`NotifStore::merge_row`](spt_store::notif::NotifStore::merge_row));
36-    /// re-presenting an already-joined row is a no-op by construction.
37-    Row { row: NotifRow },
38-}
39-
===
32-/// delivering, so a replayed feed (sender redrive, receiver brain restart
33-/// resubscribing from an earlier cursor) delivers exactly once
34-/// (REQ-HAZARD-RESTART-IDEMPOTENT, now wire-fed).
35-// [impl->REQ-NET-1]
36-// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]
37-#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38:pub struct WanMessage {
===

use serde::{Deserialize, Serialize};

use crate::net::ndjson::{self, NdjsonDecoder};
use crate::net::registry::{Instance, MergeOutcome, SubnetRegistry};

/// One registry replication record: an endpoint's instance state in one subnet,
/// as authored by the node it runs on (the `instance.epoch` is that node's
/// monotonic [`spt_store::epoch::EpochSource`] stamp — the merge-precedence key).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RegistryUpdate {
    /// The subnet whose registry this update targets.
    pub subnet: String,
    /// The endpoint the instance belongs to.
    pub endpoint_id: String,
    /// The instance state (node, status, author epoch).
    pub instance: Instance,
}

impl RegistryUpdate {
    /// Encode as one NDJSON line (the JSON document + `\n`).
    pub fn encode_line(&self) -> Vec<u8> {
        ndjson::encode_line(self)
    }
}

/// The registry feed's NDJSON reassembler — the shared
/// [`NdjsonDecoder`](crate::net::ndjson::NdjsonDecoder) typed to
/// [`RegistryUpdate`] records. One decoder per stream.
// [impl->REQ-INST-7]
pub type LineDecoder = NdjsonDecoder<RegistryUpdate>;


grep -n "enum AttachRecord" -A 8 crates/spt-net/src/net/attach.rs | head -12; echo ===; grep -n "enum XferRecord" -A 8 crates/spt-net/src/net/xfer.rs | head -12; echo ===; grep -rn "RegistryUpdate\|replicate::" crates/spt-daemon/src/*.rs | grep -v "^.*///" | head -15; echo ===; grep -n "pub fn" crates/spt-daemon/src/relay.rs crates/spt-daemon/src/transport.rs crates/spt-daemon/src/xfer.rs crates/spt-daemon/src/attach.rs 2>/dev/null | head -25

Survey attach/xfer records and daemon replicate drivers


38:pub enum AttachRecord {
39-    /// Operator → target, first record on the stream: attach to `session_id`,
40-    /// replaying output from `from_seq` (the operator's resume cursor — 0 for
41-    /// a fresh attach, the last-rendered seq + 1 on re-attach).
42-    Request { session_id: u64, from_seq: u64 },
43-    /// Target → operator: one session output chunk. `seq` is the target
44-    /// session's `OutputLog` sequence verbatim (the operator's dedup cursor).
45-    Output { seq: u64, data_b64: String },
46-    /// Operator → target: input bytes for the session's PTY. `op_id` is the
===
43:pub enum XferRecord {
44-    /// Operator → target, first record on a pull stream: send me the file at
45-    /// `path` (resolved under the target's transfer root — never absolute,
46-    /// never traversing). `xfer_id` is the operator-minted durable transfer id.
47-    Fetch { path: String, xfer_id: String },
48-    /// Operator → target, first record on a push stream: an incoming file of
49-    /// `total` bytes for `path` (same root-relative rules as `Fetch`).
50-    Push { path: String, total: u64, xfer_id: String },
51-    /// One chunk of file bytes at absolute byte `offset` — positionally
===
===
crates/spt-daemon/src/relay.rs:39:    pub fn bind(id: &str, perch_path: &Path) -> std::io::Result<Self> {
crates/spt-daemon/src/relay.rs:44:    pub fn bind_with(id: &str, perch_path: &Path, poll: Duration) -> std::io::Result<Self> {
crates/spt-daemon/src/relay.rs:51:    pub fn id(&self) -> &str {
crates/spt-daemon/src/relay.rs:58:    pub fn drain_backlog<F: FnMut(&str)>(&self, mut sink: F) -> usize {
crates/spt-daemon/src/relay.rs:69:    pub fn pump_once<F: FnMut(&str)>(&self, mut sink: F) {
crates/spt-daemon/src/relay.rs:79:    pub fn run<F: FnMut(&str)>(&self, stop: &AtomicBool, mut sink: F) {
crates/spt-daemon/src/transport.rs:47:    pub fn bind(name: &str) -> io::Result<Self> {
crates/spt-daemon/src/transport.rs:54:    pub fn connect(name: &str) -> io::Result<Stream> {
crates/spt-daemon/src/transport.rs:71:pub fn send_hello<W: Write>(w: &mut W, role: Role) -> io::Result<()> {
crates/spt-daemon/src/transport.rs:83:pub fn recv_hello<R: Read>(r: &mut R, expected_peer: Role) -> io::Result<Hello> {
crates/spt-daemon/src/xfer.rs:211:pub fn serve_xfer(
crates/spt-daemon/src/xfer.rs:384:pub fn fetch_file(
crates/spt-daemon/src/xfer.rs:476:pub fn push_file(
crates/spt-daemon/src/attach.rs:89:pub fn serve_attach(
crates/spt-daemon/src/attach.rs:185:pub fn request_attach(
crates/spt-daemon/src/attach.rs:203:pub fn send_attach_input(


grep -n "pub struct EndpointAddr" -A 15 crates/spt-net/src/net/endpoint.rs; echo ===; grep -n "fn dial\|discovery\|NodeId\|node_id" crates/spt-net/src/net/endpoint.rs | head -25; echo ===; grep -rn "trust" crates/spt-store/src/trust.rs | grep -n "pub fn\|pub struct" | head; grep -n "pub fn\|pub struct" crates/spt-store/src/trust.rs | head -20

EndpointAddr shape, dial path, trust store surface


===
61:/// Whether the endpoint also runs **mDNS** LAN discovery on top of the preset's n0
62:/// DNS discovery. With mDNS on, a node advertises itself and resolves peers by id
68:    /// Advertise + resolve over mDNS (swarm-discovery) in addition to n0 DNS.
70:    /// No mDNS — WAN discovery only (and direct addresses). Used by hermetic tests.
126:        discovery: LocalDiscovery,
128:        Self::bind_with_alpns(identity, relay, discovery, vec![SPT_NET_ALPN.to_vec()]).await
136:    /// "Why a dedicated pre-trust ALPN"). All relay/discovery handling is identical to
142:        discovery: LocalDiscovery,
145:        Self::bind_scoped(identity, relay, discovery, alpns, BindScope::All).await
157:        discovery: LocalDiscovery,
190:        // mDNS rides alongside the preset's n0 DNS discovery as an extra address
192:        let builder = match discovery {
208:    pub fn node_id(&self) -> PublicKey {
214:    pub fn node_id_hex(&self) -> String {
215:        self.node_id().to_hex()
266:    /// Dial a peer by its node id (public key) alone, letting discovery (mDNS on the
288:    /// Bind a fully hermetic endpoint: relays disabled, no discovery, loopback
338:            ep.node_id(),
342:        assert_eq!(ep.node_id_hex(), id.public_key().to_hex());
346:    // QUIC with relays DISABLED, no discovery, and loopback-only sockets — fully
364:    // [unit->REQ-NET-1] mDNS LAN discovery: two endpoints, each with mDNS on, connect
366:    // over mDNS. #[ignore]'d because it needs working LAN multicast (swarm-discovery),
371:    async fn mdns_discovery_connects_by_id() {
379:        let server_pk = server.node_id();
===
27:152:    pub fn is_trusted(&self, subnet: &str, pubkey_hex: &str) -> bool {
90:pub struct TrustedPeer {
108:pub struct TrustStore {
117:    pub fn load() -> TrustStore {
125:    pub fn load_from(path: &Path) -> TrustStore {
133:    pub fn save(&self) -> Result<(), TrustError> {
140:    pub fn save_to(&self, path: &Path) -> Result<(), TrustError> {
152:    pub fn is_trusted(&self, subnet: &str, pubkey_hex: &str) -> bool {
159:    pub fn peers_in<'a>(&'a self, subnet: &'a str) -> impl Iterator<Item = &'a TrustedPeer> {
172:    pub fn record(
210:    pub fn repin(
233:    pub fn revoke(&mut self, subnet: &str, pubkey_hex: &str) -> bool {


sed -n '486,540p' crates/spt-daemon/src/brain.rs; echo ===; grep -n "EndpointAddr" crates/spt-net/src/net/endpoint.rs | head -8; grep -n "pub enum EndpointAddr\|pub struct EndpointAddr\|type EndpointAddr" -A 12 crates/spt-net/src/net/endpoint.rs | head -20

Brain net_dial signature + EndpointAddr definition


    /// Dial a peer over the broker-owned endpoint (D4a). `addr` is the peer's
    /// serialized `EndpointAddr` (as another node's `net-status` reply carries).
    /// With `op_id` the dial is exactly-once: a replay of the same durable id
    /// after a brain crash is deduped at the broker and answered with the
    /// already-open connection ([`NetDialed::applied_now`] distinguishes).
    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]
    pub fn net_dial(
        &mut self,
        addr: serde_json::Value,
        op_id: Option<u64>,
    ) -> io::Result<NetDialed> {
        self.send(
            KIND_NET_DIAL,
            serde_json::to_value(NetDialReq { addr, op_id }).expect("NetDialReq serializes"),
        )?;
        loop {
            match self.read_event()? {
                BrokerEvent::NetDialed(d) => return Ok(d),
                BrokerEvent::Error { message } => return Err(io::Error::other(message)),
                _ => continue,
            }
        }
    }

    /// Open a bidi stream on a held connection (D4b). With `op_id` the open is
    /// exactly-once (a replay is answered with the original stream). NOTE all
    /// net op ids — dials, opens, sends — share ONE durable namespace; mint
    /// them from a single durable counter.
    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]
    pub fn net_open_stream(
        &mut self,
        conn_id: u64,
        op_id: Option<u64>,
    ) -> io::Result<NetStreamOpened> {
        self.send(
            KIND_NET_STREAM_OPEN,
            serde_json::to_value(NetStreamOpenReq { conn_id, op_id })
                .expect("NetStreamOpenReq serializes"),
        )?;
        loop {
            match self.read_event()? {
                BrokerEvent::NetStreamOpened(ev) => return Ok(ev),
                BrokerEvent::Error { message } => return Err(io::Error::other(message)),
                _ => continue,
            }
        }
    }

    /// Write bytes to a held stream over the broker (D4b). With `op_id` the
    /// write is exactly-once and this blocks for the `net-sent` ack (returned);
    /// without one it is a direct fire-and-forget write (`None` returned).
    /// `finish` closes the send side after the write.
    // [impl->REQ-HAZARD-RESTART-IDEMPOTENT]
    pub fn net_stream_send(
        &mut self,
===
29:pub use iroh::EndpointAddr;
220:    pub fn addr(&self) -> EndpointAddr {
249:    pub async fn connect(&self, addr: EndpointAddr) -> Result<Connection, NetError> {
257:        addr: EndpointAddr,
274:        self.connect(EndpointAddr::new(id)).await


D9-1: production trigger loops + VerifyPolicy + spt send WAN CLI

peerloop.rs pump host (sync/update/notif/reconcile thin calls over existing drivers), DaemonConfig knobs, production VerifyPolicy assembly, spt send WAN leg, loopback E2E proving loops fire undriven. Gotchas #1/#9.

D9-2: resting-state model + transition echo + wake-edge resurface

State machine (active→dormant→suspended, wake), Status::Suspended, transition echo commune (KH 3.3), resurface_at_boundary(Wake), freshness pull trigger. REQ-INST-3/4 → [impl,unit]; KH 7.4 if multi-agent echo scheduling.

D9-3: dormancy budget measurement + warm/cold policy lock

SPT_BUDGET=1 harness on real adapters (both rig hosts), docs/DORMANCY-BUDGET.md, policy lock, ADR-0003 #9 closed. doc→REQ-INST-3.

D9-4: REQ-INST-14 resource advertisement

resources field on Instance, spt resources set/show/list projection (visibility-gated). [doc,impl,unit].

D9-5: REQ-INST-15 immutable home subnet + spt fork

Home-at-creation (auto/ask/local-until-join, no setter), sync_subnets=[home] seed, adapter-at-creation rule, spt fork copy-then-diverge (--delete-source; same-node v1). [doc,impl,unit].

D9-6: two-host E2E + runbook + gh-repo interim retirement

SPT_TWO_HOST=1 int tests (full ladder), docs/TWO-HOST-RUNBOOK.md, run on rig, int stage activations, retire gh-repo-sync interim.

D9-7: closeout sweep

required_stages audit, traceable-reqs green, ROADMAP M4-delivered, CONTEXT amendments, plan-status COMPLETE, CI matrix FINAL green.


grep -rn "fn cmd_send\|Cmd::Send" crates/spt/src/cli.rs | head -5; grep -rn "pub struct VerifyPolicy" -A 12 crates/spt-runtime/src/*.rs crates/spt-daemon/src/*.rs 2>/dev/null | head -20; echo ===; grep -rn "VerifyPolicy" crates/spt-daemon/src/propagate.rs | head -5; grep -rn "trusted_keys\|TrustedKeys\|signing key" crates/spt-daemon/src/update.rs crates/spt-daemon/src/release.rs | head -10

spt send + VerifyPolicy + trusted keys recon


226:        Cmd::Send { target, from, reply_to, deferred } => cmd_send(target, from, reply_to, deferred),
503:fn cmd_send(target: Option<String>, from: Option<String>, reply_to: Option<String>, deferred: bool) -> i32 {
1021:        assert!(matches!(parse(&["spt", "send", "bob", "--from", "alice"]).unwrap().cmd, Cmd::Send { .. }));
1022:        assert!(matches!(parse(&["spt", "send", "bob", "--deferred"]).unwrap().cmd, Cmd::Send { deferred: true, .. }));
1139:            Cmd::Send { target, reply_to, .. } => {
crates/spt-daemon/src/release.rs:87:pub struct VerifyPolicy {
crates/spt-daemon/src/release.rs-88-    /// Trusted release keys by `key_id`. Rotation = add a new id; old ids may
crates/spt-daemon/src/release.rs-89-    /// linger until retired.
crates/spt-daemon/src/release.rs-90-    pub trusted_keys: BTreeMap<String, VerifyingKey>,
crates/spt-daemon/src/release.rs-91-    /// Explicitly revoked `key_id`s — rejected even with a valid signature.
crates/spt-daemon/src/release.rs-92-    pub revoked_keys: BTreeSet<String>,
crates/spt-daemon/src/release.rs-93-    /// The channel this node is pinned to.
crates/spt-daemon/src/release.rs-94-    pub pinned_channel: String,
crates/spt-daemon/src/release.rs-95-    /// The running build's version — an update must exceed it (monotonic).
crates/spt-daemon/src/release.rs-96-    pub current_version: u64,
crates/spt-daemon/src/release.rs-97-    /// Wall-clock now (epoch ms) for the expiry check (caller-supplied).
crates/spt-daemon/src/release.rs-98-    pub now_ms: u64,
crates/spt-daemon/src/release.rs-99-}
===
40:use crate::release::{verify_metadata, RejectReason, ReleaseMetadata, SignedRelease, VerifyPolicy};
216:    policy: &VerifyPolicy,
crates/spt-daemon/src/update.rs:318:            trusted_keys: trusted,
crates/spt-daemon/src/release.rs:16://! - **key rotation + revocation** — the signing key is selected by `key_id` from
crates/spt-daemon/src/release.rs:90:    pub trusted_keys: BTreeMap<String, VerifyingKey>,
crates/spt-daemon/src/release.rs:127:            RejectReason::UnknownKey(k) => write!(f, "unknown signing key id {k:?}"),
crates/spt-daemon/src/release.rs:128:            RejectReason::RevokedKey(k) => write!(f, "revoked signing key id {k:?}"),
crates/spt-daemon/src/release.rs:171:        .trusted_keys
crates/spt-daemon/src/release.rs:299:    /// A deterministic signing key (no RNG — test-stable). Real release tooling
crates/spt-daemon/src/release.rs:328:            trusted_keys: trusted,


sed -n '290,340p' crates/spt-daemon/src/update.rs; echo ===; sed -n '500,560p' crates/spt/src/cli.rs

Update policy assembly context + cmd_send body


    // ── C1 gate (plan_verified): no plan is produced for an unverified release. ──
    use crate::release::{ReleaseMetadata, SignedRelease, VerifyPolicy};
    use ed25519_dalek::{Signer, SigningKey};
    use std::collections::{BTreeMap, BTreeSet};

    fn signed_for(sk: &SigningKey, meta: &ReleaseMetadata) -> SignedRelease {
        let metadata_json = serde_json::to_string(meta).unwrap();
        let sig = sk.sign(metadata_json.as_bytes());
        let signature_hex = sig.to_bytes().iter().map(|b| format!("{b:02x}")).collect();
        SignedRelease { metadata_json, signature_hex }
    }

    fn gate_meta(version: u64, artifact: &[u8]) -> ReleaseMetadata {
        ReleaseMetadata {
            version,
            channel: "stable".to_string(),
            expires_at_ms: 10_000,
            key_id: "k".to_string(),
            artifact_sha256: crate::release::sha256_hex(artifact),
            brain_ipc_version: IPC_PROTOCOL_VERSION,
            broker_resource_abi: BROKER_RESOURCE_ABI,
        }
    }

    fn gate_policy(sk: &SigningKey) -> VerifyPolicy {
        let mut trusted = BTreeMap::new();
        trusted.insert("k".to_string(), sk.verifying_key());
        VerifyPolicy {
            trusted_keys: trusted,
            revoked_keys: BTreeSet::new(),
            pinned_channel: "stable".to_string(),
            current_version: 5,
            now_ms: 1_000,
        }
    }

    // [unit->REQ-UPD-2] a verified release yields a plan; an apply can then run.
    // The verified release's spec classifies brain-only (same broker ABI, hostable
    // brain) — the routine path.
    #[test]
    fn plan_verified_yields_a_plan_for_a_valid_release() {
        let sk = SigningKey::from_bytes(&[3u8; 32]);
        let artifact = b"new binary";
        let signed = signed_for(&sk, &gate_meta(6, artifact));
        let plan = plan_verified(&BrokerAbi::current(), &signed, artifact, &gate_policy(&sk))
            .expect("a valid release produces a plan");
        assert_eq!(plan.class, UpdateClass::BrainOnly);
    }

    // [unit->REQ-UPD-2] a release that fails verification produces NO plan — there
    // is nothing for apply_brain_only to act on, so an unverified binary can never
===
    from.or_else(roster::detect_self_id).unwrap_or_default()
}

fn cmd_send(target: Option<String>, from: Option<String>, reply_to: Option<String>, deferred: bool) -> i32 {
    let is_reply = reply_to.is_some();
    // --reply-to provides the target when no positional target is given.
    let target = match target.or_else(|| reply_to.clone()) {
        Some(t) => t,
        None => {
            eprintln!("USAGE: a target (or --reply-to <sender>) is required");
            return 2;
        }
    };
    let from = resolve_from(from);
    let body = read_stdin();
    let owlery = perch::owlery_dir();

    let outcome = if deferred {
        deliver::send_deferred(&target, &from, &body)
    } else {
        deliver::send(&target, &from, &body, &owlery)
    };

    match outcome {
        SendOutcome::Sent => {
            eprintln!("{}:{}", if is_reply { "REPLIED" } else { "SENT" }, target);
            0
        }
        SendOutcome::Queued => {
            let label = if deferred {
                "DEFERRED"
            } else if is_reply {
                "REPLIED(QUEUED)"
            } else {
                "QUEUED"
            };
            eprintln!("{}:{}", label, target);
            0
        }
        SendOutcome::NoPerch => {
            eprintln!("NO_PERCH:{} is not listening", target);
            1
        }
        SendOutcome::Empty => {
            eprintln!("EMPTY_MSG: refusing to send empty message to {}", target);
            1
        }
    }
}

fn cmd_ring(target: String, from: Option<String>, timeout: u64) -> i32 {
    let from = resolve_from(from);
    if from.is_empty() {
        eprintln!("NO_SELF: ring needs a sender id (--from <id> or $OWL_SESSION_ID)");
        return 1;
    }
    let body = read_stdin();
    let owlery = perch::owlery_dir();

    match ring::ring(&target, &from, &body, Duration::from_secs(timeout), &owlery) {
        RingOutcome::Replied(reply) => {


grep -n "pub struct NetStreamInfo" -A 15 crates/spt-daemon/src/msg.rs; echo ===; grep -n "inbound\|initiated\|accepted\|dir" crates/spt-daemon/src/nethost.rs | head -15

NetStreamInfo fields — inbound flag check


318:pub struct NetStreamInfo {
319-    /// The broker-minted stream id.
320-    pub stream_id: u64,
321-    /// The connection it rides.
322-    pub conn_id: u64,
323-    /// The remote node's stable name (hex pubkey).
324-    pub remote_id_hex: String,
325-    /// The next read sequence the broker will assign (= chunks buffered so far).
326-    pub next_seq: u64,
327-    /// Whether the read side finished cleanly.
328-    #[serde(default)]
329-    pub finished: bool,
330-}
331-
332-/// `net-streams-reply` payload — every stream the broker currently holds.
333-#[derive(Debug, Clone, Serialize, Deserialize)]
===
5://! accepted/dialed QUIC connection. A live cross-node connection is exactly the
278:/// Register a live connection: mint its broker id and spawn its inbound-stream
324:/// Register a live stream (opened or accepted): mint its broker id and spawn
382:    /// accept loop (every inbound `SPT_NET_ALPN` connection joins the table).
432:    /// The endpoint's dialable address (id + currently-known direct/relay paths).
442:    /// Live connections (accepted + dialed) currently held.
550:    /// streams (including peer-initiated ones that arrived while it was down).
663:        // The acceptor's loop registers the inbound connection (async — poll).
670:        assert_eq!(b.conn_count(), 1, "accept loop registered the inbound conn");


270	            next_stream_id: AtomicU64::new(1),
271	            streams: Mutex::new(HashMap::new()),
272	            ring_cap: DEFAULT_STREAM_RING_CHUNKS,
273	            presence: Mutex::new(PresenceLog::new(DEFAULT_STREAM_RING_CHUNKS)),
274	        }
275	    }
276	}
277
278	/// Register a live connection: mint its broker id and spawn its inbound-stream
279	/// acceptor. Must run inside the host's runtime context (the accept loop, or a
280	/// `block_on` future) — it spawns tasks.
281	fn register_conn(shared: &Arc<NetShared>, conn: Connection) -> (u64, String) {
282	    let remote_id_hex = PublicKey::from_bytes(conn.remote_id().as_bytes())
283	        .expect("an iroh EndpointId is always a valid ed25519 public key")
284	        .to_hex();
285	    let id = shared.next_conn_id.fetch_add(1, Ordering::Relaxed);
286	    shared
287	        .conns
288	        .lock()
289	        .unwrap()
290	        .insert(id, ConnEntry { conn: conn.clone(), remote_id_hex: remote_id_hex.clone() });
291
292	    // Presence: announce the row BEFORE spawning the closed-watcher, so the
293	    // `connected` event can never race behind its own `disconnected` (D4c).
294	    shared.presence.lock().unwrap().append(PRESENCE_CONNECTED, id, &remote_id_hex);
295
296	    // Closed-watcher: the row's exit path. Awaits the connection's end (clean
297	    // close, conn loss, or our own endpoint close), removes the row — conns
298	    // finally LEAVE the table — then appends the `disconnected` event (row
299	    // gone before the event is observable).
300	    // [impl->REQ-EP-4]
301	    let sh_close = Arc::clone(shared);
302	    let conn_close = conn.clone();
303	    let hex_close = remote_id_hex.clone();
304	    tokio::spawn(async move {
305	        conn_close.closed().await;
306	        sh_close.conns.lock().unwrap().remove(&id);
307	        sh_close.presence.lock().unwrap().append(PRESENCE_DISCONNECTED, id, &hex_close);
308	    });
309
310	    // Inbound-stream acceptor: every peer-opened bidi stream joins the table
311	    // and gets a read pump. (QUIC streams are lazy — the peer's open becomes
312	    // visible here on its first write.) Ends when the connection does.
313	    let sh = Arc::clone(shared);
314	    let hex = remote_id_hex.clone();
315	    tokio::spawn(async move {
316	        while let Ok((send, recv)) = conn.accept_bi().await {
317	            register_stream(&sh, id, &hex, send, recv);
318	        }
319	    });
320
321	    (id, remote_id_hex)
322	}
323
324	/// Register a live stream (opened or accepted): mint its broker id and spawn
325	/// its read pump. Must run inside the host's runtime context.
326	fn register_stream(
327	    shared: &Arc<NetShared>,
328	    conn_id: u64,
329	    remote_id_hex: &str,
330	    send: SendStream,
331	    recv: RecvStream,
332	) -> u64 {
333	    let id = shared.next_stream_id.fetch_add(1, Ordering::Relaxed);
334	    let log = Arc::new(Mutex::new(StreamLog::new(id, shared.ring_cap)));
335	    shared.streams.lock().unwrap().insert(
336	        id,
337	        Arc::new(StreamEntry {
338	            conn_id,
339	            remote_id_hex: remote_id_hex.to_string(),
340	            send: tokio::sync::Mutex::new(send),
341	            log: Arc::clone(&log),
342	        }),
343	    );
344
345	    // The read pump — the stream's single producer. A clean end (peer finish)
346	    // and a torn end (conn lost) both finish the log; the buffered chunks stay
347	    // replayable either way.
348	    let mut recv = recv;
349	    tokio::spawn(async move {
350	        loop {
351	            match recv.read_chunk(STREAM_READ_CHUNK).await {
352	                Ok(Some(chunk)) => log.lock().unwrap().append(&chunk.bytes),
353	                Ok(None) | Err(_) => {
354	                    log.lock().unwrap().finish();
355	                    break;
356	                }
357	            }
358	        }
359	    });
360
361	    id
362	}
363
364	/// The broker-owned net endpoint + live connection/stream tables (see module
365	/// docs).
366	pub struct NetHost {
367	    /// The dedicated tokio runtime hosting the endpoint and its accept loop.
368	    /// Dropping the host drops the runtime, stopping every net task with it.
369	    runtime: tokio::runtime::Runtime,
370	    endpoint: Arc<NetEndpoint>,
371	    shared: Arc<NetShared>,
372	    /// `op_id → conn_id` for exactly-once dial replies: a deduped replay of a
373	    /// journaled dial is answered with the connection the first apply opened.
374	    dial_ops: Mutex<HashMap<u64, u64>>,
375	    /// `op_id → stream_id` for exactly-once stream-open replies (D4b), same
376	    /// discipline as `dial_ops`. Shares the one net op-id namespace.
377	    stream_ops: Mutex<HashMap<u64, u64>>,
378	}
379
380	impl NetHost {
381	    /// Bind the broker's endpoint on a fresh dedicated runtime and start the
382	    /// accept loop (every inbound `SPT_NET_ALPN` connection joins the table).
383	    // [impl->REQ-DAEMON-2]
384	    pub fn start(cfg: NetConfig) -> io::Result<NetHost> {
385	        let runtime = tokio::runtime::Builder::new_multi_thread()
386	            .worker_threads(2)
387	            .thread_name("spt-broker-net")
388	            .enable_all()
389	            .build()?;
390	        let endpoint = runtime
391	            .block_on(NetEndpoint::bind_scoped(
392	                &cfg.identity,
393	                cfg.relay,
394	                cfg.discovery,
395	                vec![SPT_NET_ALPN.to_vec()],
396	                cfg.scope,
397	            ))
398	            .map_err(|e| io::Error::other(e.to_string()))?;
399	        let endpoint = Arc::new(endpoint);
400	        let shared = Arc::new(NetShared::new());
401
402	        // Accept loop: lives for the endpoint's lifetime on the host's runtime.
403	        // Each handshake completes on its own task so a slow peer can't stall
404	        // the loop; a failed handshake is simply not registered.
405	        let ep = Arc::clone(&endpoint);
406	        let sh = Arc::clone(&shared);
407	        runtime.spawn(async move {
408	            while let Some(incoming) = ep.accept().await {
409	                let sh = Arc::clone(&sh);
410	                tokio::spawn(async move {
411	                    let Ok(connecting) = incoming.accept() else { return };
412	                    let Ok(conn) = connecting.await else { return };
413	                    register_conn(&sh, conn);
414	                });
415	            }
416	        });
417
418	        Ok(NetHost {
419	            runtime,
420	            endpoint,
421	            shared,
422	            dial_ops: Mutex::new(HashMap::new()),
423	            stream_ops: Mutex::new(HashMap::new()),
424	        })
425	    }
426
427	    /// The node's stable name (lowercase-hex pubkey == iroh EndpointId).
428	    pub fn node_id_hex(&self) -> String {
429	        self.endpoint.node_id_hex()
430	    }
431
432	    /// The endpoint's dialable address (id + currently-known direct/relay paths).
433	    pub fn addr(&self) -> EndpointAddr {
434	        self.endpoint.addr()
435	    }
436
437	    /// [`Self::addr`] as the opaque JSON the IPC status reply carries.
438	    pub fn addr_json(&self) -> serde_json::Value {
439	        serde_json::to_value(self.endpoint.addr()).unwrap_or(serde_json::Value::Null)
440	    }
441
442	    /// Live connections (accepted + dialed) currently held.
443	    pub fn conn_count(&self) -> u64 {
444	        self.shared.conns.lock().unwrap().len() as u64
445	    }
446
447	    /// Dial a peer over `SPT_NET_ALPN`, registering the connection in the table.
448	    /// Bridges sync→async via the host's own runtime (the broker's dispatch
449	    /// threads block here; the future runs on the net worker threads).
450	    // [impl->REQ-DAEMON-2]
451	    pub fn dial(&self, addr: EndpointAddr) -> io::Result<(u64, String)> {
452	        let endpoint = Arc::clone(&self.endpoint);
453	        let shared = Arc::clone(&self.shared);
454	        self.runtime.handle().block_on(async move {
455	            let conn = endpoint
456	                .connect(addr)
457	                .await
458	                .map_err(|e| io::Error::other(e.to_string()))?;
459	            Ok(register_conn(&shared, conn))

            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) -> (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 { conn: conn.clone(), remote_id_hex: remote_id_hex.clone() });

    // Presence: announce the row BEFORE spawning the closed-watcher, so the
    // `connected` event can never race behind its own `disconnected` (D4c).
    shared.presence.lock().unwrap().append(PRESENCE_CONNECTED, id, &remote_id_hex);

    // Closed-watcher: the row's exit path. Awaits the connection's end (clean
    // close, conn loss, or our own endpoint close), removes the row — conns
    // finally LEAVE the table — then appends the `disconnected` event (row
    // gone before the event is observable).
    // [impl->REQ-EP-4]
    let sh_close = Arc::clone(shared);
    let conn_close = conn.clone();
    let hex_close = remote_id_hex.clone();
    tokio::spawn(async move {
        conn_close.closed().await;
        sh_close.conns.lock().unwrap().remove(&id);
        sh_close.presence.lock().unwrap().append(PRESENCE_DISCONNECTED, id, &hex_close);
    });

    // Inbound-stream acceptor: every peer-opened bidi stream joins the table
    // and gets a read pump. (QUIC streams are lazy — the peer's open becomes
    // visible here on its first write.) Ends when the connection does.
    let sh = Arc::clone(shared);
    let hex = remote_id_hex.clone();
    tokio::spawn(async move {
        while let Ok((send, recv)) = conn.accept_bi().await {
            register_stream(&sh, id, &hex, send, recv);
        }
    });

    (id, remote_id_hex)
}

/// Register a live stream (opened or accepted): mint its broker id and spawn
/// its read pump. Must run inside the host's runtime context.
fn register_stream(
    shared: &Arc<NetShared>,
    conn_id: u64,
    remote_id_hex: &str,
    send: SendStream,
    recv: RecvStream,
) -> u64 {
    let id = shared.next_stream_id.fetch_add(1, Ordering::Relaxed);
    let log = Arc::new(Mutex::new(StreamLog::new(id, shared.ring_cap)));
    shared.streams.lock().unwrap().insert(
        id,
        Arc::new(StreamEntry {
            conn_id,
            remote_id_hex: remote_id_hex.to_string(),
            send: tokio::sync::Mutex::new(send),
            log: Arc::clone(&log),
        }),
    );

    // 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.
    let mut recv = recv;
    tokio::spawn(async move {
        loop {
            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;
                }
            }
        }
    });

    id
}

/// 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>>,
}

impl NetHost {
    /// Bind the broker's endpoint on a fresh dedicated runtime and start the
    /// accept loop (every inbound `SPT_NET_ALPN` connection joins the table).
    // [impl->REQ-DAEMON-2]
    pub fn start(cfg: NetConfig) -> io::Result<NetHost> {
        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,
                cfg.discovery,
                vec![SPT_NET_ALPN.to_vec()],
                cfg.scope,
            ))
            .map_err(|e| io::Error::other(e.to_string()))?;
        let endpoint = Arc::new(endpoint);
        let shared = Arc::new(NetShared::new());

        // Accept loop: lives for the endpoint's lifetime on the host's runtime.
        // Each handshake completes on its own task so a slow peer can't stall
        // the loop; a failed handshake is simply not registered.
        let ep = Arc::clone(&endpoint);
        let sh = Arc::clone(&shared);
        runtime.spawn(async move {
            while let Some(incoming) = ep.accept().await {
                let sh = Arc::clone(&sh);
                tokio::spawn(async move {
                    let Ok(connecting) = incoming.accept() else { return };
                    let Ok(conn) = connecting.await else { return };
                    register_conn(&sh, conn);
                });
            }
        });

        Ok(NetHost {
            runtime,
            endpoint,
            shared,
            dial_ops: Mutex::new(HashMap::new()),
            stream_ops: Mutex::new(HashMap::new()),
        })
    }

    /// The node's stable name (lowercase-hex pubkey == iroh EndpointId).
    pub fn node_id_hex(&self) -> String {
        self.endpoint.node_id_hex()
    }

    /// The endpoint's dialable address (id + currently-known direct/relay paths).
    pub fn addr(&self) -> EndpointAddr {
        self.endpoint.addr()
    }

    /// [`Self::addr`] as the opaque JSON the IPC status reply carries.
    pub fn addr_json(&self) -> serde_json::Value {
        serde_json::to_value(self.endpoint.addr()).unwrap_or(serde_json::Value::Null)
    }

    /// Live connections (accepted + dialed) currently held.
    pub fn conn_count(&self) -> u64 {
        self.shared.conns.lock().unwrap().len() as u64
    }

    /// Dial a peer over `SPT_NET_ALPN`, registering the connection in the table.
    /// Bridges sync→async via the host's own runtime (the broker's dispatch
    /// threads block here; the future runs on the net worker threads).
    // [impl->REQ-DAEMON-2]
    pub fn dial(&self, addr: EndpointAddr) -> io::Result<(u64, String)> {
        let endpoint = Arc::clone(&self.endpoint);
        let shared = Arc::clone(&self.shared);
        self.runtime.handle().block_on(async move {
            let conn = endpoint
                .connect(addr)
                .await
                .map_err(|e| io::Error::other(e.to_string()))?;
            Ok(register_conn(&shared, conn))


`register_stream` serves both directions — flag rides there. Gathering remaining serve-fn signatures for the dispatcher deps:


grep -rn "net-streams\|NetStreamsReply\|fn net_streams\|NetStreamInfo {" crates/spt-daemon/src/broker.rs | head -8; echo ===; sed -n '85,100p' crates/spt-daemon/src/notifsync.rs; echo ===; sed -n '205,220p' crates/spt-daemon/src/xfer.rs; sed -n '85,100p' crates/spt-daemon/src/attach.rs; echo ===; grep -n "fn open_stream\|register_stream(" crates/spt-daemon/src/nethost.rs

Broker streams reply + serve fn signatures + register_stream callers


51:    NetStreamOpenReq, NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq, NetStreamsReply,
645:            serde_json::to_value(NetStreamsReply { streams: host.stream_infos() })
646:                .expect("NetStreamsReply serializes"),
===
/// Apply decoded inbound records under the gate (module docs). `origin_node`
/// MUST be the handshake-proven identity from the broker's stream table —
/// never anything read from the payload.
// [impl->REQ-NOTIF-1]
// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]
pub fn apply_notif_feed(
    store: &NotifStore,
    origin_node: &str,
    records: &[NotifRecord],
    policy: &NotifPolicy,
) -> Result<Vec<NotifApplyVerdict>, String> {
    records
        .iter()
        .map(|record| {
            let NotifRecord::Row { row } = record;
            if !policy.subnets.iter().any(|s| s == &row.subnet) {
===
/// ([`spt_store::xfer::transfers_dir`] in production; tests inject).
/// `net_from_seq` is this brain's resume cursor on the stream (0 cold — also
/// the safe post-restart choice: the replayed feed re-gates, chunks rewrite
/// in place, and the commit dedups).
// [impl->REQ-REACH-1]
// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]
pub fn serve_xfer(
    brain: &mut Brain,
    stream_id: u64,
    endpoint: &str,
    origin_node: &str,
    net_from_seq: u64,
    root: &Path,
    progress_dir: &Path,
) -> io::Result<XferServeOutcome> {
    brain.net_stream_subscribe(stream_id, net_from_seq)?;
/// the replayed `Request`/`Input` records re-gate and dedup at the PTY
/// journal, and re-pumped output dedups at the operator's render cursor).
// [impl->REQ-INST-8]
// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]
pub fn serve_attach(
    brain: &mut Brain,
    stream_id: u64,
    endpoint: &str,
    origin_node: &str,
    net_from_seq: u64,
) -> io::Result<AttachServeOutcome> {
    brain.net_stream_subscribe(stream_id, net_from_seq)?;
    let mut decoder = AttachDecoder::new();
    let mut attached = false;

    loop {
===
317:            register_stream(&sh, id, &hex, send, recv);
326:fn register_stream(
485:    pub fn open_stream(&self, conn_id: u64) -> io::Result<u64> {
499:            Ok(register_stream(&shared, conn_id, &remote_id_hex, send, recv))


grep -n "stream_infos" -A 20 crates/spt-daemon/src/nethost.rs | head -30; echo ===; grep -n "struct StreamEntry" -A 10 crates/spt-daemon/src/nethost.rs

stream_infos + StreamEntry definitions


551:    pub fn stream_infos(&self) -> Vec<NetStreamInfo> {
552-        let streams = self.shared.streams.lock().unwrap();
553-        streams
554-            .iter()
555-            .map(|(id, e)| {
556-                let log = e.log.lock().unwrap();
557-                NetStreamInfo {
558-                    stream_id: *id,
559-                    conn_id: e.conn_id,
560-                    remote_id_hex: e.remote_id_hex.clone(),
561-                    next_seq: log.next_seq,
562-                    finished: log.finished,
563-                }
564-            })
565-            .collect()
566-    }
567-
568-    /// Attach `sub` as a stream's read subscriber, replaying from `from_seq`
569-    /// (gapless resume — the PTY `subscribe` discipline on a QUIC stream).
570-    // [impl->REQ-DAEMON-2]
571-    pub(crate) fn subscribe_stream(
===
244:struct StreamEntry {
245-    conn_id: u64,
246-    remote_id_hex: String,
247-    /// The send half, serialized behind an async mutex (writes happen on the
248-    /// host's runtime via `block_on`).
249-    send: tokio::sync::Mutex<SendStream>,
250-    log: Arc<Mutex<StreamLog>>,
251-}
252-
253-/// Tables shared between the host's sync surface and its runtime tasks
254-/// (accept loop, per-conn stream acceptors, per-stream read pumps).


240	}
241
242	/// One live QUIC stream the broker holds: the send half (brain writes ride it
243	/// over IPC) + the sequenced read log.
244	struct StreamEntry {
245	    conn_id: u64,
246	    remote_id_hex: String,
247	    /// The send half, serialized behind an async mutex (writes happen on the
248	    /// host's runtime via `block_on`).
249	    send: tokio::sync::Mutex<SendStream>,
250	    log: Arc<Mutex<StreamLog>>,
251	}
252
253	/// Tables shared between the host's sync surface and its runtime tasks

}

/// One live QUIC stream the broker holds: the send half (brain writes ride it
/// over IPC) + the sequenced read log.
struct StreamEntry {
    conn_id: u64,
    remote_id_hex: String,
    /// The send half, serialized behind an async mutex (writes happen on the
    /// host's runtime via `block_on`).
    send: tokio::sync::Mutex<SendStream>,
    log: Arc<Mutex<StreamLog>>,
}

/// Tables shared between the host's sync surface and its runtime tasks


Edit

struct StreamEntry {
    conn_id: u64,
    remote_id_hex: String,
    /// The send half, serialized behind an async mutex (writes happen on the
    /// host's runtime via `block_on`).
    send: tokio::sync::Mutex<SendStream>,
    log: Arc<Mutex<StreamLog>>,
}

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<SendStream>,
    log: Arc<Mutex<StreamLog>>,
}


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)

struct StreamEntry {
    conn_id: u64,
    remote_id_hex: String,
    /// The send half, serialized behind an async mutex (writes happen on the
    /// host's runtime via `block_on`).
    send: tokio::sync::Mutex<SendStream>,
    log: Arc<Mutex<StreamLog>>,
}

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<SendStream>,
    log: Arc<Mutex<StreamLog>>,
}

+    /// 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).


    let sh = Arc::clone(shared);
    let hex = remote_id_hex.clone();
    tokio::spawn(async move {
        while let Ok((send, recv)) = conn.accept_bi().await {
            register_stream(&sh, id, &hex, send, recv);
        }
    });

    let sh = Arc::clone(shared);
    let hex = remote_id_hex.clone();
    tokio::spawn(async move {
        while let Ok((send, recv)) = conn.accept_bi().await {
            register_stream(&sh, id, &hex, send, recv, false);
        }
    });


    let sh = Arc::clone(shared);
    let hex = remote_id_hex.clone();
    tokio::spawn(async move {
        while let Ok((send, recv)) = conn.accept_bi().await {
            register_stream(&sh, id, &hex, send, recv);
        }
    });

    let sh = Arc::clone(shared);
    let hex = remote_id_hex.clone();
    tokio::spawn(async move {
        while let Ok((send, recv)) = conn.accept_bi().await {
            register_stream(&sh, id, &hex, send, recv, false);
        }
    });

-            register_stream(&sh, id, &hex, send, recv);

+            register_stream(&sh, id, &hex, send, recv, false);


/// Register a live stream (opened or accepted): mint its broker id and spawn
/// its read pump. Must run inside the host's runtime context.
fn register_stream(
    shared: &Arc<NetShared>,
    conn_id: u64,
    remote_id_hex: &str,
    send: SendStream,
    recv: RecvStream,
) -> u64 {
    let id = shared.next_stream_id.fetch_add(1, Ordering::Relaxed);
    let log = Arc::new(Mutex::new(StreamLog::new(id, shared.ring_cap)));
    shared.streams.lock().unwrap().insert(
        id,
        Arc::new(StreamEntry {
            conn_id,
            remote_id_hex: remote_id_hex.to_string(),
            send: tokio::sync::Mutex::new(send),
            log: Arc::clone(&log),
        }),
    );

/// Register a live stream (opened or accepted): mint its broker id and spawn
/// its read pump. Must run inside the host's runtime context.
/// `initiated_locally` records the direction fact for the table (true only
/// from [`NetHost::open_stream`]).
fn register_stream(
    shared: &Arc<NetShared>,
    conn_id: u64,
    remote_id_hex: &str,
    send: SendStream,
    recv: RecvStream,
    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)));
    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),
        }),
    );


/// Register a live stream (opened or accepted): mint its broker id and spawn
/// its read pump. Must run inside the host's runtime context.
fn register_stream(
    shared: &Arc<NetShared>,
    conn_id: u64,
    remote_id_hex: &str,
    send: SendStream,
    recv: RecvStream,
) -> u64 {
    let id = shared.next_stream_id.fetch_add(1, Ordering::Relaxed);
    let log = Arc::new(Mutex::new(StreamLog::new(id, shared.ring_cap)));
    shared.streams.lock().unwrap().insert(
        id,
        Arc::new(StreamEntry {
            conn_id,
            remote_id_hex: remote_id_hex.to_string(),
            send: tokio::sync::Mutex::new(send),
            log: Arc::clone(&log),
        }),
    );

/// Register a live stream (opened or accepted): mint its broker id and spawn
/// its read pump. Must run inside the host's runtime context.
/// `initiated_locally` records the direction fact for the table (true only
/// from [`NetHost::open_stream`]).
fn register_stream(
    shared: &Arc<NetShared>,
    conn_id: u64,
    remote_id_hex: &str,
    send: SendStream,
    recv: RecvStream,
    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)));
    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),
        }),
    );

+/// `initiated_locally` records the direction fact for the table (true only


D9-7: closeout sweep


490
491	    /// Open a bidi stream on a held connection, registering it (with its read
492	    /// pump) in the stream table (D4b). Note QUIC streams are lazy: the peer
493	    /// sees the stream on its first write.
494	    // [impl->REQ-DAEMON-2]
495	    pub fn open_stream(&self, conn_id: u64) -> io::Result<u64> {
496	        let (conn, remote_id_hex) = {
497	            let conns = self.shared.conns.lock().unwrap();
498	            let e = conns.get(&conn_id).ok_or_else(|| {
499	                io::Error::new(io::ErrorKind::NotFound, format!("no such conn {conn_id}"))
500	            })?;
501	            (e.conn.clone(), e.remote_id_hex.clone())
502	        };
503	        let shared = Arc::clone(&self.shared);
504	        self.runtime.handle().block_on(async move {
505	            let (send, recv) = conn
506	                .open_bi()
507	                .await
508	                .map_err(|e| io::Error::other(e.to_string()))?;
509	            Ok(register_stream(&shared, conn_id, &remote_id_hex, send, recv))
510	        })
511	    }
512
513	    /// Record that journaled open `op_id` minted `stream_id` (inside the
514	    /// `apply_once` closure — same discipline as [`Self::record_dial_op`]).
515	    pub fn record_stream_op(&self, op_id: u64, stream_id: u64) {
516	        self.stream_ops.lock().unwrap().insert(op_id, stream_id);
517	    }
518
519	    /// The stream a journaled open `op_id` minted, if still held.

    /// Open a bidi stream on a held connection, registering it (with its read
    /// pump) in the stream table (D4b). Note QUIC streams are lazy: the peer
    /// sees the stream on its first write.
    // [impl->REQ-DAEMON-2]
    pub fn open_stream(&self, conn_id: u64) -> io::Result<u64> {
        let (conn, 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}"))
            })?;
            (e.conn.clone(), e.remote_id_hex.clone())
        };
        let shared = Arc::clone(&self.shared);
        self.runtime.handle().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, send, recv))
        })
    }

    /// 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.


Ok(register_stream(&shared, conn_id, &remote_id_hex, send, recv))

Ok(register_stream(&shared, conn_id, &remote_id_hex, send, recv, true))


-            Ok(register_stream(&shared, conn_id, &remote_id_hex, send, recv))

+            Ok(register_stream(&shared, conn_id, &remote_id_hex, send, recv, true))


                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,
                }

                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,
                }


                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,
                }

                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,
                }

+                    initiated_locally: e.initiated_locally,


    /// Whether the read side finished cleanly.
    #[serde(default)]
    pub finished: bool,
}

    /// Whether the read side finished cleanly.
    #[serde(default)]
    pub finished: bool,
    /// Whether THIS node opened the stream (vs the peer). The inbound
    /// dispatcher (D9-1) claims only `false` rows — the table holds
    /// locally-opened outbound streams too.
    #[serde(default)]
    pub initiated_locally: bool,
}


<tool_use_error>File has not been read yet. Read it first before writing to it.</tool_use_error>

Error: File has not been read yet. Read it first before writing to it.


310	    pub op_id: u64,
311	    /// Fresh apply (`true`) vs deduped replay (`false`).
312	    #[serde(default)]
313	    pub applied_now: bool,
314	}
315
316	/// One held stream in a `net-streams-reply` (D4b).
317	#[derive(Debug, Clone, Serialize, Deserialize)]
318	pub struct NetStreamInfo {
319	    /// The broker-minted stream id.
320	    pub stream_id: u64,
321	    /// The connection it rides.
322	    pub conn_id: u64,
323	    /// The remote node's stable name (hex pubkey).
324	    pub remote_id_hex: String,
325	    /// The next read sequence the broker will assign (= chunks buffered so far).
326	    pub next_seq: u64,
327	    /// Whether the read side finished cleanly.
328	    #[serde(default)]
329	    pub finished: bool,
330	}
331
332	/// `net-streams-reply` payload — every stream the broker currently holds.
333	#[derive(Debug, Clone, Serialize, Deserialize)]
334	pub struct NetStreamsReply {
335	    /// Held streams (opened + peer-initiated), unordered.
336	    #[serde(default)]
337	    pub streams: Vec<NetStreamInfo>,
338	}
339

    pub op_id: u64,
    /// Fresh apply (`true`) vs deduped replay (`false`).
    #[serde(default)]
    pub applied_now: bool,
}

/// One held stream in a `net-streams-reply` (D4b).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetStreamInfo {
    /// The broker-minted stream id.
    pub stream_id: u64,
    /// The connection it rides.
    pub conn_id: u64,
    /// The remote node's stable name (hex pubkey).
    pub remote_id_hex: String,
    /// The next read sequence the broker will assign (= chunks buffered so far).
    pub next_seq: u64,
    /// Whether the read side finished cleanly.
    #[serde(default)]
    pub finished: bool,
}

/// `net-streams-reply` payload — every stream the broker currently holds.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetStreamsReply {
    /// Held streams (opened + peer-initiated), unordered.
    #[serde(default)]
    pub streams: Vec<NetStreamInfo>,
}


    /// Whether the read side finished cleanly.
    #[serde(default)]
    pub finished: bool,
}

/// `net-streams-reply` payload — every stream the broker currently holds.

    /// Whether the read side finished cleanly.
    #[serde(default)]
    pub finished: bool,
    /// Whether THIS node opened the stream (vs the peer). The inbound
    /// dispatcher (D9-1) claims only `false` rows — the table holds
    /// locally-opened outbound streams too.
    #[serde(default)]
    pub initiated_locally: bool,
}

/// `net-streams-reply` payload — every stream the broker currently holds.


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

    /// Whether the read side finished cleanly.
    #[serde(default)]
    pub finished: bool,
}

/// `net-streams-reply` payload — every stream the broker currently holds.

    /// Whether the read side finished cleanly.
    #[serde(default)]
    pub finished: bool,
    /// Whether THIS node opened the stream (vs the peer). The inbound
    /// dispatcher (D9-1) claims only `false` rows — the table holds
    /// locally-opened outbound streams too.
    #[serde(default)]
    pub initiated_locally: bool,
}

/// `net-streams-reply` payload — every stream the broker currently holds.

+    /// Whether THIS node opened the stream (vs the peer). The inbound

+    /// dispatcher (D9-1) claims only `false` rows — the table holds

+    /// locally-opened outbound streams too.


Bash

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

Check daemon compiles after flag plumbing


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


Plumbing compiles. Dispatcher next — need Brain constructor + registry persistence facts:


grep -n "pub fn connect\|fn connect_retry" -A 6 crates/spt-daemon/src/brain.rs | head -20; echo ===; grep -rn "SubnetRegistry" crates/spt-daemon/src/ crates/spt-store/src/ crates/spt/src/ | head -10; echo ===; sed -n '80,92p' crates/spt-daemon/src/attach.rs

Brain connect + registry persistence + attach endpoint param


===
===
/// `origin_node` MUST be the handshake-proven remote id from the broker's
/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes
/// (REQ-HAZARD-WAN-ORIGIN-AUTH). `endpoint` names the endpoint whose session
/// is being attached, for the gate. `net_from_seq` is this brain's resume
/// cursor on the attach stream (0 cold — also the safe post-restart choice:
/// the replayed `Request`/`Input` records re-gate and dedup at the PTY
/// journal, and re-pumped output dedups at the operator's render cursor).
// [impl->REQ-INST-8]
// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]
pub fn serve_attach(
    brain: &mut Brain,
    stream_id: u64,
    endpoint: &str,


sed -n '60,105p' crates/spt-daemon/tests/wanmsg.rs; echo ===; grep -rn "registry" crates/spt-net/src/net/*.rs | grep -in "persist\|save\|load\|path\|file" | head; echo ===; grep -rn "resolve_across_visible\|resolve_visible" crates/spt-net/src/net/registry.rs crates/spt/src crates/spt-daemon/src 2>/dev/null | grep -v "^crates.spt-net" | head

Test Brain constructor + registry persistence/consumers


fn hermetic() -> NetConfig {
    NetConfig {
        identity: Identity::generate(),
        relay: RelayPolicy::Disabled,
        discovery: LocalDiscovery::Off,
        scope: BindScope::Loopback,
    }
}

fn net_broker(name: &str, dir: &std::path::Path) -> Arc<Broker> {
    let host = NetHost::start(hermetic()).expect("net host start");
    let broker =
        Broker::bind_in_with_net(name, dir.join("effects.log"), Some(host)).expect("bind broker");
    let serve = Arc::clone(&broker);
    thread::spawn(move || {
        let _ = serve.serve();
    });
    broker
}

fn connect_retry(name: &str) -> Brain {
    for _ in 0..300 {
        if let Ok(b) = Brain::cold_start(name, 1) {
            return b;
        }
        thread::sleep(Duration::from_millis(10));
    }
    panic!("brain could not connect");
}

/// Poll the receiving brain's `net-streams` until a stream appears; return
/// `(stream_id, remote_id_hex)` — the handshake-proven origin the funnel uses.
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.first() {
            return (s.stream_id, s.remote_id_hex.clone());
        }
        thread::sleep(Duration::from_millis(5));
    }
    panic!("stream never appeared at the receiver's broker");
}

/// Create an offline perch (dir + info.json, no listener) for `id` so the
/// funnel takes the atomic spool path.
fn offline_perch(id: &str) -> std::path::PathBuf {
===
===


sed -n '89,180p' crates/spt-daemon/src/attach.rs

serve_attach body — endpoint param use


pub fn serve_attach(
    brain: &mut Brain,
    stream_id: u64,
    endpoint: &str,
    origin_node: &str,
    net_from_seq: u64,
) -> io::Result<AttachServeOutcome> {
    brain.net_stream_subscribe(stream_id, net_from_seq)?;
    let mut decoder = AttachDecoder::new();
    let mut attached = false;

    loop {
        match brain.read_event()? {
            // ── Wire → session: requests and input. ────────────────────────
            BrokerEvent::NetStreamData { stream_id: sid, bytes, .. } if sid == stream_id => {
                for rec in decoder.push(&bytes) {
                    match rec {
                        AttachRecord::Request { session_id, from_seq } => {
                            // Unsolicited wire inbound — the ADR-0009 gate,
                            // under the transport-proven origin.
                            if access_check(endpoint, origin_node, InboundClass::Unsolicited)
                                == AccessDecision::Deny
                            {
                                brain.net_stream_send(stream_id, &[], None, true)?;
                                return Ok(AttachServeOutcome::Refused);
                            }
                            brain.attach(session_id, from_seq)?;
                            attached = true;
                            // Detection is a daemon fact (REQ-REACH-1): the
                            // served endpoint is now driven from origin_node.
                            // Re-stamped by a successor's re-serve after a
                            // brain crash (the same replayed Request that
                            // re-attaches); cleared when the viewport ends.
                            // [impl->REQ-REACH-1]
                            mark_driven_by(endpoint, Some(origin_node));
                        }
                        AttachRecord::Input { data_b64, op_id } if attached => {
                            let bytes = decode_bytes(&data_b64)
                                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
                            // Exactly-once at the PTY: the operator-minted id
                            // rides the B5 journal.
                            brain.send_effect(op_id, &bytes)?;
                        }
                        // Input before Request, or target-direction records
                        // echoed back: protocol noise — ignore (degrade).
                        _ => {}
                    }
                }
            }
            // The operator closed its send side: the viewport is over.
            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {
                brain.net_stream_send(stream_id, &[], None, true)?;
                if attached {
                    mark_driven_by(endpoint, None);
                }
                return Ok(AttachServeOutcome::Detached);
            }

            // ── Session → wire: output and exit. ────────────────────────────
            BrokerEvent::Output { seq, bytes, .. } if attached => {
                let line = ndjson::encode_line(&AttachRecord::Output {
                    seq,
                    data_b64: encode_bytes(&bytes),
                });
                // 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)?;
            }
            BrokerEvent::Exit { code, .. } if attached => {
                let line = ndjson::encode_line(&AttachRecord::Exit { code });
                brain.net_stream_send(stream_id, &line, None, true)?;
                mark_driven_by(endpoint, None);
                return Ok(AttachServeOutcome::Exited(code));
            }

            BrokerEvent::Error { message } => {
                if attached {
                    mark_driven_by(endpoint, None);
                }
                return Err(io::Error::other(message));
            }
            // Applied/NetSent acks and unrelated events: not ours to handle.
            _ => {}
        }
    }
}

/// Operator-side: open an attach stream on a dialed connection and send the
/// [`AttachRecord::Request`]. Returns the stream id the caller then consumes
/// ([`AttachRecord::Output`] frames, deduped by `seq`) and writes
/// ([`AttachRecord::Input`] with operator-minted op ids). `open_op` is the
/// durable id for the exactly-once stream open (safe to block for its ack —


grep -n "KIND_" crates/spt-daemon/src/msg.rs | head -40; echo ===; grep -rn "endpoint" crates/spt-daemon/src/msg.rs | grep -i "session\|spawn" | head

IPC verb list + session/endpoint linkage


22:pub const KIND_SPAWN: &str = "spawn";
24:pub const KIND_SPAWNED: &str = "spawned";
26:pub const KIND_SUBSCRIBE: &str = "subscribe";
28:pub const KIND_INPUT: &str = "input";
30:pub const KIND_RESIZE: &str = "resize";
32:pub const KIND_KILL: &str = "kill";
34:pub const KIND_OUTPUT: &str = "output";
36:pub const KIND_EXIT: &str = "exit";
41:pub const KIND_APPLIED: &str = "applied";
43:pub const KIND_ERROR: &str = "error";
47:pub const KIND_NET_STATUS: &str = "net-status";
49:pub const KIND_NET_STATUS_REPLY: &str = "net-status-reply";
52:pub const KIND_NET_DIAL: &str = "net-dial";
54:/// distinguishes, mirroring [`KIND_APPLIED`]). Carries the broker-minted conn id.
55:pub const KIND_NET_DIALED: &str = "net-dialed";
58:pub const KIND_NET_STREAM_OPEN: &str = "net-stream-open";
60:pub const KIND_NET_STREAM_OPENED: &str = "net-stream-opened";
62:/// `op_id` the write is exactly-once (journaled; acked with [`KIND_NET_SENT`]).
63:pub const KIND_NET_STREAM_SEND: &str = "net-stream-send";
65:pub const KIND_NET_SENT: &str = "net-sent";
69:pub const KIND_NET_STREAMS: &str = "net-streams";
71:pub const KIND_NET_STREAMS_REPLY: &str = "net-streams-reply";
74:pub const KIND_NET_STREAM_SUBSCRIBE: &str = "net-stream-subscribe";
76:pub const KIND_NET_STREAM_DATA: &str = "net-stream-data";
79:pub const KIND_NET_STREAM_EOF: &str = "net-stream-eof";
84:pub const KIND_NET_PRESENCE_SUBSCRIBE: &str = "net-presence-subscribe";
86:pub const KIND_NET_PRESENCE_EVENT: &str = "net-presence-event";
140:    /// replies [`KIND_APPLIED`]. `None` is the legacy direct write (no dedup) —
299:    /// peer's read pump sees a clean end and emits [`KIND_NET_STREAM_EOF`].
411:        KIND_OUTPUT,
421:        KIND_APPLIED,
431:        KIND_NET_STREAM_DATA,
440:        KIND_NET_STREAM_EOF,
451:        KIND_NET_PRESENCE_EVENT,
465:        KIND_NET_DIALED,
515:        assert_eq!(env.kind, KIND_NET_DIALED);
532:        assert_eq!(env.kind, KIND_NET_PRESENCE_EVENT);
550:        assert_eq!(env.kind, KIND_OUTPUT);
===


grep -n "pub fn\|pub struct\|session_id\|endpoint" crates/spt-daemon/src/digesthub.rs | head -30

DigestHub endpoint-session mapping check


5://! snapshot per endpoint and fans every change out to live subscribers. The
36:/// Client→daemon: pull the current structured digest for an endpoint.
40:/// Client→daemon: subscribe to an endpoint's delta-stream from a version floor.
47:pub struct DigestReq {
48:    /// The (local) endpoint id whose digest to read.
58:pub struct DigestResult {
59:    /// The current structured digest, absent if the endpoint has no live digest.
67:/// One endpoint's live digest state in the hub: the latest snapshot + version
76:/// The daemon's per-endpoint live-digest registry. The brain publishes; the
82:pub struct DigestHub {
88:    pub fn new() -> Self {
96:    pub fn publish(&self, id: &str, snapshot: Digest, update: DigestUpdate) {
105:    pub fn snapshot(&self, id: &str) -> Option<(Digest, u64)> {
122:    pub fn subscriber_count(&self, id: &str) -> usize {
136:pub fn serve_digest_control(name: &str, hub: Arc<DigestHub>) -> io::Result<()> {
211:/// `Ok(None)` ⇒ the daemon holds no live digest for that endpoint.
213:pub fn pull_snapshot(name: &str, id: &str) -> io::Result<Option<(Digest, u64)>> {
227:pub fn follow<F: FnMut(&DigestUpdate) -> bool>(
264:pub fn render_digest(digest: &Digest) -> String {
270:pub fn render_update(update: &DigestUpdate) -> String {
275:pub fn digest_to_json(digest: &Digest) -> String {
281:pub fn update_to_json(update: &DigestUpdate) -> String {


grep -rn "fn spawn" crates/spt-daemon/src/brain.rs | head -5; grep -rn "spawn" crates/spt/src/api/worker.rs | head -10; echo ===; grep -rn "session_endpoint\|owner\|endpoint" crates/spt-daemon/src/brain.rs | grep -i session | head; echo ===; grep -rn "driven_by\|mark_driven_by" crates/spt-daemon/src/attach.rs | head -5; grep -n "fn mark_driven_by" -A 15 crates/spt-daemon/src/attach.rs

Session spawn paths + driven_by mechanics


201:    pub fn spawn_session(&mut self, req: SpawnReq) -> io::Result<u64> {
===
440:    /// Run the **PTY-digest feed** for `endpoint_id` (B8): read this session's
===
54:/// Stamp (or clear) the endpoint's `driven_by` remote-drive marker — the
59:fn mark_driven_by(endpoint: &str, origin_node: Option<&str>) {
61:    let _ = spt_store::info::set_driven_by(&perch, origin_node);
123:                            mark_driven_by(endpoint, Some(origin_node));
142:                    mark_driven_by(endpoint, None);
59:fn mark_driven_by(endpoint: &str, origin_node: Option<&str>) {
60-    let perch = resolve_perch_path(endpoint, ParentHint::Infer);
61-    let _ = spt_store::info::set_driven_by(&perch, origin_node);
62-}
63-
64-/// Why a serve loop returned.
65-#[derive(Debug, Clone, PartialEq, Eq)]
66-pub enum AttachServeOutcome {
67-    /// The access gate refused the requesting origin — nothing was served.
68-    Refused,
69-    /// The session's child exited; the `Exit` record was sent and the stream
70-    /// finished.
71-    Exited(Option<i32>),
72-    /// The operator closed its send side (detached); the viewport ended with
73-    /// the session still running.
74-    Detached,


grep -n "pub struct SpawnReq" -A 15 crates/spt-daemon/src/msg.rs; echo ===; grep -rn "SpawnReq {" crates/spt-daemon/src crates/spt/src | head -8

SpawnReq fields — endpoint label at spawn?


96:pub struct SpawnReq {
97-    /// The program to exec (resolved on `PATH` by the OS spawn).
98-    pub program: String,
99-    /// Its arguments, verbatim.
100-    #[serde(default)]
101-    pub args: Vec<String>,
102-    /// Initial PTY rows.
103-    pub rows: u16,
104-    /// Initial PTY cols.
105-    pub cols: u16,
106-}
107-
108-/// `spawned` payload — the broker-minted session id + the child's OS pid.
109-#[derive(Debug, Clone, Serialize, Deserialize)]
110-pub struct Spawned {
111-    /// The id the brain uses to address this session in later commands.
===
crates/spt-daemon/src/msg.rs:96:pub struct SpawnReq {


grep -rn "spawn_session" crates/ --include=*.rs | grep -v "fn spawn_session\|//" | head -12

Find spawn_session callers


crates/spt-daemon/src/notif.rs:227:    runtime.spawn_session(NOTIF_ROLE, &notif_keys(row)).map(|s| s.pid).map_err(|e| e.to_string())
crates/spt-daemon/tests/attach.rs:187:    let sid = target.spawn_session(echo_spawn_req()).expect("spawn echo child");
crates/spt-daemon/tests/attach.rs:237:    let sid = life1.spawn_session(echo_spawn_req()).expect("spawn echo child");
crates/spt-daemon/tests/attach.rs:359:    let sid = target.spawn_session(echo_spawn_req()).expect("spawn echo child");
crates/spt-daemon/tests/brain_swap.rs:90:    let sid = brain.spawn_session(echo_spawn_req()).expect("spawn session");
crates/spt-daemon/tests/daemon_e2e.rs:139:    let sid = brain.spawn_session(echo_req()).expect("spawn echo child");
crates/spt-daemon/tests/digest.rs:106:    let _sid = brain.spawn_session(echo_req()).expect("spawn echo child");
crates/spt-daemon/tests/handoff.rs:81:    let sid = brain.spawn_session(echo_spawn_req()).expect("spawn session");
crates/spt-daemon/tests/idempotent.rs:153:    let sid = brain.spawn_session(echo_spawn_req()).expect("spawn session");
crates/spt-live/src/psyche.rs:132:    let spawned = runtime.spawn_session(role, &k)?;
crates/spt-runtime/src/runtime.rs:427:        let e = rt.spawn_session("self", &keys(&[])).unwrap_err();
crates/spt-runtime/src/runtime.rs:479:        let spawned = rt.spawn_session("self", &keys(&[])).expect("spawn");


sed -n '60,90p' crates/spt-daemon/src/propagate.rs; echo ===; sed -n '30,62p' crates/spt-daemon/src/notifsync.rs; echo ===; grep -n "pub fn open\|pub fn load\|fn open_or_init" crates/spt-store/src/notif.rs crates/spt-store/src/contextstore.rs | head; echo ===; sed -n '60,95p' crates/spt-daemon/src/sync.rs

serve_update sig + policies + store constructors