    // [unit->REQ-SUBNET-3] `@node` qualifiers accept a label or key-prefix
    // (case-insensitive), not just the full hex; a qualifier matching several
    // distinct nodes refuses with paste-able candidate key prefixes —
    // machines sharing a hostname are never guessed between.
    #[test]
    fn node_qualifier_accepts_label_and_prefix_refuses_shared() {
        let labeled = |node: &str, label: &str| Instance {
            node_label: Some(label.to_string()),
            ..inst(node, Status::Active, 1)
        };
        let mut reg = SubnetRegistry::new();
        reg.merge_instance("ling", labeled("bcead52b00112233", "HFENDULEAM"));
        reg.merge_instance("ling", labeled("77aa11ff00112233", "GRAVITY"));

        // Label, any case.
        let r = reg.resolve(&addr("ling@hfenduleam"), "local");
        assert!(
            matches!(&r, Resolution::Resolved { instance, .. } if instance.node.starts_with("bcead52b")),
            "label resolves case-insensitively: {r:?}"
        );
        // Key-prefix.
        let r = reg.resolve(&addr("ling@77aa11ff"), "local");
        assert!(
            matches!(&r, Resolution::Resolved { instance, .. } if instance.node.starts_with("77aa11ff")),
            "key-prefix resolves: {r:?}"
        );
        // Full hex still pins exactly.
        let r = reg.resolve(&addr("ling@bcead52b00112233"), "local");
        assert!(matches!(&r, Resolution::Resolved { .. }), "full hex resolves: {r:?}");
        // Unknown qualifier finds nothing.
        assert_eq!(reg.resolve(&addr("ling@nosuch"), "local"), Resolution::NotFound);

        // A shared label refuses with candidate key prefixes — never a guess.
        let mut shared = SubnetRegistry::new();
        shared.merge_instance("ling", labeled("bcead52b00112233", "TWIN"));
        shared.merge_instance("ling", labeled("77aa11ff00112233", "TWIN"));
        match shared.resolve(&addr("ling@twin"), "local") {
            Resolution::Ambiguous(Ambiguity::NodeQualifier { qualifier, candidates }) => {
                assert_eq!(qualifier, "twin");
                assert_eq!(candidates, vec!["bcead52b".to_string(), "77aa11ff".to_string()]);
            }
            other => panic!("expected NodeQualifier refusal, got {other:?}"),
        }
        // …and each candidate prefix then resolves uniquely.
        assert!(matches!(
            shared.resolve(&addr("ling@bcead52b"), "local"),
            Resolution::Resolved { .. }
        ));
    }

    // [unit->REQ-PRES-1] the presence datum is a FIELD under the lease, not a

assert_eq!(old.node_label, None, "absent field parses as no label");

+    // [unit->REQ-SUBNET-3] `@node` qualifiers accept a label or key-prefix

+    // (case-insensitive), not just the full hex; a qualifier matching several

+    // distinct nodes refuses with paste-able candidate key prefixes —

+    // machines sharing a hostname are never guessed between.

+    fn node_qualifier_accepts_label_and_prefix_refuses_shared() {

+        let labeled = |node: &str, label: &str| Instance {

+            node_label: Some(label.to_string()),

+            ..inst(node, Status::Active, 1)

+        let mut reg = SubnetRegistry::new();

+        reg.merge_instance("ling", labeled("bcead52b00112233", "HFENDULEAM"));

+        reg.merge_instance("ling", labeled("77aa11ff00112233", "GRAVITY"));

+        let r = reg.resolve(&addr("ling@hfenduleam"), "local");

+            matches!(&r, Resolution::Resolved { instance, .. } if instance.node.starts_with("bcead52b")),

+            "label resolves case-insensitively: {r:?}"

+        let r = reg.resolve(&addr("ling@77aa11ff"), "local");

+            matches!(&r, Resolution::Resolved { instance, .. } if instance.node.starts_with("77aa11ff")),

+            "key-prefix resolves: {r:?}"

+        // Full hex still pins exactly.

+        let r = reg.resolve(&addr("ling@bcead52b00112233"), "local");

+        assert!(matches!(&r, Resolution::Resolved { .. }), "full hex resolves: {r:?}");

+        // Unknown qualifier finds nothing.

+        assert_eq!(reg.resolve(&addr("ling@nosuch"), "local"), Resolution::NotFound);

+        // A shared label refuses with candidate key prefixes — never a guess.

+        let mut shared = SubnetRegistry::new();

+        shared.merge_instance("ling", labeled("bcead52b00112233", "TWIN"));

+        shared.merge_instance("ling", labeled("77aa11ff00112233", "TWIN"));

+        match shared.resolve(&addr("ling@twin"), "local") {

+            Resolution::Ambiguous(Ambiguity::NodeQualifier { qualifier, candidates }) => {

+                assert_eq!(qualifier, "twin");

+                assert_eq!(candidates, vec!["bcead52b".to_string(), "77aa11ff".to_string()]);

+            other => panic!("expected NodeQualifier refusal, got {other:?}"),

+        // …and each candidate prefix then resolves uniquely.

+            shared.resolve(&addr("ling@bcead52b"), "local"),

+            Resolution::Resolved { .. }

// feed: a newer-epoch row carries the fresher stamp; a lagging row drops

// stale (a partition can never roll recency backward through the merge);


Bash

cargo test -p spt-net 2>&1 | grep -E "test result|FAILED|panicked" | head

Run spt-net tests


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


cargo test -p spt-daemon 2>&1 | grep -E "test result|FAILED" | head && cargo test -p spt 2>&1 | grep -E "test result|FAILED" | head

Run spt-daemon and spt test suites


test result: ok. 176 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.28s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.20s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.31s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.15s
test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.31s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.19s
test result: ok. 85 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 29.00s
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.60s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.36s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.10s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.82s


D2b done. D2c next — check what probe machinery exists:


<tool_use_error>InputValidationError: Grep failed due to the following issue:
An unexpected parameter `-in` was provided</tool_use_error>

InputValidationError: [
  {
    "code": "unrecognized_keys",
    "keys": [
      "-in"
    ],
    "path": [],
    "message": "Unrecognized key: \"-in\""
  }
]


crates\spt-msg\src\deliver.rs:49:    /// Target unreachable — written to the target's spool for later drain.
crates\spt-msg\src\ready.rs:185:    // [unit->REQ-MSG-3] start registers a reachable, resolvable perch.
crates\spt-daemon\src\broker.rs:517:    /// the broker has no network host (`enabled: false`) so a brain can probe
crates\spt-term\tests\inject.rs:80:    session.send_line("READY-PROBE").expect("send readiness probe");
crates\spt-term\tests\inject.rs:81:    let ready = wait_until(&collected, |t| t.contains("READY-PROBE"));
crates\spt-store\src\epoch.rs:59:            .expect("epoch counter overflowed u64 — unreachable in any real lifetime");
crates\spt-daemon\src\endpoint.rs:50:/// harmless because callers probe the socket, never this file.
crates\spt-store\src\info.rs:58:    /// the per-pid probe. The single liveness resolver
crates\spt-daemon\src\dispatch.rs:4://! Every E2E test so far drove the serve half manually (a probe brain calling
crates\spt-daemon\src\dispatch.rs:47://! connection (the broker is multi-client by design; the probe-brain pattern
crates\spt-store\src\liveness.rs:21://! it stays the per-pid probe. Both decisions live **here**, behind one resolver
crates\spt-store\src\liveness.rs:62:/// interim (liveness is the per-pid probe).
crates\spt-store\src\liveness.rs:76:/// - **External/interim** (`status` absent): the per-pid probe, preserving the
crates\spt-store\src\liveness.rs:77:///   exact interim semantics — a numeric pid is probed; a `"BUSY"` sentinel or
crates\spt-store\src\liveness.rs:97:/// the endpoint `status`; external rows pid-probe the **row's** pid exactly as
crates\spt-store\src\liveness.rs:107:        None => is_process_alive(registry_pid),  // external/interim: pid-probe the row
crates\spt-store\src\liveness.rs:147:    // status) still pid-probes: a dead pid reads offline, a live pid reads online.
crates\spt-store\src\liveness.rs:149:    fn external_perch_still_pid_probes() {
crates\spt-store\src\liveness.rs:197:        // External row (no perch dir / no status): pid-probe the registered pid.
crates\spt-daemon\tests\presence.rs:141:    let mut probe = connect_retry(&name_b);
crates\spt-daemon\tests\presence.rs:143:        serde_json::from_value(probe.net_status().expect("status").addr).expect("addr decodes");
crates\spt-daemon\tests\presence.rs:151:        if probe.net_status().expect("status").conn_count == 1 {
crates\spt-daemon\tests\presence.rs:160:    probe.net_presence_subscribe(0).expect("subscribe");
crates\spt-daemon\tests\presence.rs:161:    let first = read_presence(&mut probe);
crates\spt-daemon\tests\presence.rs:162:    let second = read_presence(&mut probe);
crates\spt\src\wansend.rs:45:    /// failed, peer unreachable).
crates\spt\src\wansend.rs:153:    /// failed, peer unreachable).
crates\spt\src\wansend.rs:483:        let mut b_probe = connect_retry(&b_name);
crates\spt\src\wansend.rs:485:        let b_hex = b_probe.net_status().expect("status").node_id_hex.expect("net");
crates\spt\src\wansend.rs:486:        let b_addr = b_probe.net_status().expect("status").addr;

[Showing results with pagination = limit: 30]


1	//! `spt send`'s WAN leg (D9-1-4) — the D5 deferral: "`spt send` grows the
2	//! off-node leg transparently."
3	//!
4	//! When local delivery finds no perch, the CLI consults the daemon's
5	//! registry **snapshots** (`identity/registry/<subnet>.json`, the
6	//! [`spt_daemon::registryhost::RegistryHost`] mirror — stale-tolerant by
7	//! contract, KNOWN-HAZARDS 4.3), resolves the target under the D3c policy
8	//! ([`resolve_across_visible`]: refuse-and-qualify on ambiguity, never a
9	//! guess), and ships one [`WanMessage`] over the broker's QUIC endpoint to
10	//! the resolved node. The receiving daemon's dispatcher funnels it through
11	//! the access gate + op-id dedup into the target's local delivery
12	//! (`spt-daemon::wan::receive_wan`).
13	//!
14	//! Deferred sends stay local-only in v1: the wire record carries no
15	//! deferred class, and inventing one is a protocol change — the seam is
16	//! documented, not faked.
17	// [impl->REQ-NET-1]
18	// [impl->REQ-INST-10]
19
20	use std::collections::BTreeMap;
21	use std::path::Path;
22
23	use spt_daemon::brain::Brain;
24	use spt_daemon::endpoint::broker_socket_name;
25	use spt_net::net::endpoint::addr_for_node_hex;
26	use spt_net::net::ndjson;
27	use spt_net::net::registry::{resolve_across_visible, Resolution, SubnetRegistry};
28	use spt_net::net::wanmsg::WanMessage;
29	use spt_proto::addr::Address;
30	use spt_store::epoch::EpochSource;
31	use spt_store::perch;
32	use spt_store::subnet::SubnetStore;
33	use spt_store::visibility::VisibilityStore;
34
35	/// What the WAN leg did — the CLI's exit-path discriminant.
36	#[derive(Debug)]
37	pub enum WanSendOutcome {
38	    /// Shipped to the resolved node (hex). Delivery/spool happens there.
39	    Sent { node: String },
40	    /// Resolution refused — the message says which qualifier disambiguates.
41	    Ambiguous(String),
42	    /// No routable remote instance in any visible subnet.
43	    NotFound,
44	    /// Resolution succeeded but the wire leg failed (daemon down, dial
45	    /// failed, peer unreachable).
46	    Failed(String),
47	}
48
49	/// Production entry: resolve `target` against the canonical snapshots and
50	/// ship over the canonical broker. `target` accepts `[subnet:]id[@node]`.
51	pub fn wan_send(target: &str, from: &str, body: &str) -> WanSendOutcome {
52	    let own_hex = match spt_store::nodeid::load_or_create() {
53	        Ok(id) => id.public_key().to_hex(),
54	        Err(e) => return WanSendOutcome::Failed(format!("node identity: {e}")),
55	    };
56	    let regs = load_snapshots(&perch::identity_dir().join("registry"));
57	    if spt_daemon::daemon::ensure_running().is_err() {
58	        return WanSendOutcome::Failed("daemon did not come up".to_string());
59	    }
60	    let mut brain = match Brain::cold_start(&broker_socket_name(), now_ms()) {
61	        Ok(b) => b,
62	        Err(e) => return WanSendOutcome::Failed(format!("broker connect: {e}")),
63	    };
64	    let mut ops = EpochSource::load_from(&perch::identity_dir().join("cli-ops.json"));
65	    let op_id = match ops.next_epoch() {
66	        Ok(e) => format!("{own_hex}:cli:{e}"),
67	        Err(e) => return WanSendOutcome::Failed(format!("op counter: {e}")),
68	    };
69	    wan_send_with(&mut brain, &regs, &own_hex, target, from, body, &op_id, |hex| {
70	        addr_for_node_hex(hex).and_then(|a| serde_json::to_value(a).ok())
71	    })
72	}
73
74	/// The testable core: resolve against `regs`, dial via `resolver`, write one
75	/// `WanMessage` line, finish the stream.
76	#[allow(clippy::too_many_arguments)]
77	pub fn wan_send_with(
78	    brain: &mut Brain,
79	    regs: &BTreeMap<String, SubnetRegistry>,
80	    own_hex: &str,
81	    target: &str,
82	    from: &str,
83	    body: &str,
84	    op_id: &str,
85	    resolver: impl Fn(&str) -> Option<serde_json::Value>,
86	) -> WanSendOutcome {
87	    let address = match Address::parse(target) {
88	        Ok(a) => a,
89	        Err(e) => return WanSendOutcome::Failed(format!("bad address: {e:?}")),
90	    };
91
92	    // The same exclusion rule the registry's advertisement side enforces
93	    // (ADR-0006 §6): hidden = not routable, even via an explicit pin.
94	    let subnets = SubnetStore::load();
95	    let vis = VisibilityStore::load();
96	    let hide_new: BTreeMap<String, bool> =
97	        subnets.subnets.iter().map(|s| (s.name.clone(), s.hide_new_endpoints)).collect();
98	    let excluded =
99	        |subnet: &str, id: &str| vis.hidden(id, subnet, *hide_new.get(subnet).unwrap_or(&false));
100
101	    let instance = match resolve_across_visible(regs, &address, own_hex, excluded) {
102	        Resolution::Resolved { instance, .. } => instance,
103	        Resolution::Ambiguous(a) => return WanSendOutcome::Ambiguous(format!("{a:?}")),
104	        Resolution::NotFound => return WanSendOutcome::NotFound,
105	    };
106	    if instance.node == own_hex {
107	        // A local instance resolves here only when the local perch scan
108	        // missed it (registry staleness) — WAN-dialing ourselves helps
109	        // nobody; let the caller report the local outcome.
110	        return WanSendOutcome::NotFound;
111	    }
112
113	    let Some(addr) = resolver(&instance.node) else {
114	        return WanSendOutcome::Failed(format!("no dialable address for {}", instance.node));
115	    };
116	    let msg = WanMessage {
117	        target: address.id.clone(),
118	        from: from.to_string(),
119	        body: body.to_string(),
120	        op_id: op_id.to_string(),
121	    };
122	    let send = || -> std::io::Result<()> {
123	        let conn = brain.net_dial(addr, None)?;
124	        let opened = brain.net_open_stream(conn.conn_id, None)?;
125	        brain.net_stream_send(opened.stream_id, &ndjson::encode_line(&msg), None, true)?;
126	        Ok(())
127	    };
128	    match send() {
129	        Ok(()) => WanSendOutcome::Sent { node: instance.node },
130	        Err(e) => WanSendOutcome::Failed(e.to_string()),
131	    }
132	}
133
134	/// What the remote rest leg did — `spt suspend/wake <id@node>`'s exit-path
135	/// discriminant (M5-D5b, the lifted `CROSS_NODE_M5` refusal).
136	#[derive(Debug)]
137	pub enum WanRestOutcome {
138	    /// The target node applied a real edge (`From->To` as it rendered it).
139	    Edge { node: String, detail: String },
140	    /// The target reports no edge — already in the target state (the local
141	    /// `NO_EDGE` sibling; idempotent UX).
142	    NoEdge { node: String },
143	    /// The stream finished with no reply — the target's access gate refused
144	    /// this origin (or dropped the request). Existence is not advertised.
145	    NoReply { node: String },

//! `spt send`'s WAN leg (D9-1-4) — the D5 deferral: "`spt send` grows the
//! off-node leg transparently."
//!
//! When local delivery finds no perch, the CLI consults the daemon's
//! registry **snapshots** (`identity/registry/<subnet>.json`, the
//! [`spt_daemon::registryhost::RegistryHost`] mirror — stale-tolerant by
//! contract, KNOWN-HAZARDS 4.3), resolves the target under the D3c policy
//! ([`resolve_across_visible`]: refuse-and-qualify on ambiguity, never a
//! guess), and ships one [`WanMessage`] over the broker's QUIC endpoint to
//! the resolved node. The receiving daemon's dispatcher funnels it through
//! the access gate + op-id dedup into the target's local delivery
//! (`spt-daemon::wan::receive_wan`).
//!
//! Deferred sends stay local-only in v1: the wire record carries no
//! deferred class, and inventing one is a protocol change — the seam is
//! documented, not faked.
// [impl->REQ-NET-1]
// [impl->REQ-INST-10]

use std::collections::BTreeMap;
use std::path::Path;

use spt_daemon::brain::Brain;
use spt_daemon::endpoint::broker_socket_name;
use spt_net::net::endpoint::addr_for_node_hex;
use spt_net::net::ndjson;
use spt_net::net::registry::{resolve_across_visible, Resolution, SubnetRegistry};
use spt_net::net::wanmsg::WanMessage;
use spt_proto::addr::Address;
use spt_store::epoch::EpochSource;
use spt_store::perch;
use spt_store::subnet::SubnetStore;
use spt_store::visibility::VisibilityStore;

/// What the WAN leg did — the CLI's exit-path discriminant.
#[derive(Debug)]
pub enum WanSendOutcome {
    /// Shipped to the resolved node (hex). Delivery/spool happens there.
    Sent { node: String },
    /// Resolution refused — the message says which qualifier disambiguates.
    Ambiguous(String),
    /// No routable remote instance in any visible subnet.
    NotFound,
    /// Resolution succeeded but the wire leg failed (daemon down, dial
    /// failed, peer unreachable).
    Failed(String),
}

/// Production entry: resolve `target` against the canonical snapshots and
/// ship over the canonical broker. `target` accepts `[subnet:]id[@node]`.
pub fn wan_send(target: &str, from: &str, body: &str) -> WanSendOutcome {
    let own_hex = match spt_store::nodeid::load_or_create() {
        Ok(id) => id.public_key().to_hex(),
        Err(e) => return WanSendOutcome::Failed(format!("node identity: {e}")),
    };
    let regs = load_snapshots(&perch::identity_dir().join("registry"));
    if spt_daemon::daemon::ensure_running().is_err() {
        return WanSendOutcome::Failed("daemon did not come up".to_string());
    }
    let mut brain = match Brain::cold_start(&broker_socket_name(), now_ms()) {
        Ok(b) => b,
        Err(e) => return WanSendOutcome::Failed(format!("broker connect: {e}")),
    };
    let mut ops = EpochSource::load_from(&perch::identity_dir().join("cli-ops.json"));
    let op_id = match ops.next_epoch() {
        Ok(e) => format!("{own_hex}:cli:{e}"),
        Err(e) => return WanSendOutcome::Failed(format!("op counter: {e}")),
    };
    wan_send_with(&mut brain, &regs, &own_hex, target, from, body, &op_id, |hex| {
        addr_for_node_hex(hex).and_then(|a| serde_json::to_value(a).ok())
    })
}

/// The testable core: resolve against `regs`, dial via `resolver`, write one
/// `WanMessage` line, finish the stream.
#[allow(clippy::too_many_arguments)]
pub fn wan_send_with(
    brain: &mut Brain,
    regs: &BTreeMap<String, SubnetRegistry>,
    own_hex: &str,
    target: &str,
    from: &str,
    body: &str,
    op_id: &str,
    resolver: impl Fn(&str) -> Option<serde_json::Value>,
) -> WanSendOutcome {
    let address = match Address::parse(target) {
        Ok(a) => a,
        Err(e) => return WanSendOutcome::Failed(format!("bad address: {e:?}")),
    };

    // The same exclusion rule the registry's advertisement side enforces
    // (ADR-0006 §6): hidden = not routable, even via an explicit pin.
    let subnets = SubnetStore::load();
    let vis = VisibilityStore::load();
    let hide_new: BTreeMap<String, bool> =
        subnets.subnets.iter().map(|s| (s.name.clone(), s.hide_new_endpoints)).collect();
    let excluded =
        |subnet: &str, id: &str| vis.hidden(id, subnet, *hide_new.get(subnet).unwrap_or(&false));

    let instance = match resolve_across_visible(regs, &address, own_hex, excluded) {
        Resolution::Resolved { instance, .. } => instance,
        Resolution::Ambiguous(a) => return WanSendOutcome::Ambiguous(format!("{a:?}")),
        Resolution::NotFound => return WanSendOutcome::NotFound,
    };
    if instance.node == own_hex {
        // A local instance resolves here only when the local perch scan
        // missed it (registry staleness) — WAN-dialing ourselves helps
        // nobody; let the caller report the local outcome.
        return WanSendOutcome::NotFound;
    }

    let Some(addr) = resolver(&instance.node) else {
        return WanSendOutcome::Failed(format!("no dialable address for {}", instance.node));
    };
    let msg = WanMessage {
        target: address.id.clone(),
        from: from.to_string(),
        body: body.to_string(),
        op_id: op_id.to_string(),
    };
    let send = || -> std::io::Result<()> {
        let conn = brain.net_dial(addr, None)?;
        let opened = brain.net_open_stream(conn.conn_id, None)?;
        brain.net_stream_send(opened.stream_id, &ndjson::encode_line(&msg), None, true)?;
        Ok(())
    };
    match send() {
        Ok(()) => WanSendOutcome::Sent { node: instance.node },
        Err(e) => WanSendOutcome::Failed(e.to_string()),
    }
}

/// What the remote rest leg did — `spt suspend/wake <id@node>`'s exit-path
/// discriminant (M5-D5b, the lifted `CROSS_NODE_M5` refusal).
#[derive(Debug)]
pub enum WanRestOutcome {
    /// The target node applied a real edge (`From->To` as it rendered it).
    Edge { node: String, detail: String },
    /// The target reports no edge — already in the target state (the local
    /// `NO_EDGE` sibling; idempotent UX).
    NoEdge { node: String },
    /// The stream finished with no reply — the target's access gate refused
    /// this origin (or dropped the request). Existence is not advertised.
    NoReply { node: String },


<tool_use_error>Path does not exist: C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\src\peers.rs. Note: your current working directory is C:\Users\decid\Documents\projects\spt-core.</tool_use_error>

Error: Path does not exist: C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\src\peers.rs. Note: your current working directory is C:\Users\decid\Documents\projects\spt-core.


grep -rn "pump\|cadence" crates/spt-daemon/src/*.rs | grep -i "ms\|secs\|interval\|Duration" | head -10

Find gossip pump cadence


crates/spt-daemon/src/config.rs:76:    registry_pump_period_ms: Option<u64>,
crates/spt-daemon/src/config.rs:78:    notif_pump_period_ms: Option<u64>,
crates/spt-daemon/src/config.rs:115:    pub registry_pump_period: Duration,
crates/spt-daemon/src/config.rs:117:    pub notif_pump_period: Duration,
crates/spt-daemon/src/config.rs:202:                .registry_pump_period_ms
crates/spt-daemon/src/config.rs:206:                .notif_pump_period_ms
crates/spt-daemon/src/config.rs:237:            registry_pump_period_ms: Some(self.registry_pump_period.as_millis() as u64),
crates/spt-daemon/src/config.rs:238:            notif_pump_period_ms: Some(self.notif_pump_period.as_millis() as u64),
crates/spt-daemon/src/config.rs:294:            registry_pump_period: Duration::from_millis(11_000),
crates/spt-daemon/src/config.rs:295:            notif_pump_period: Duration::from_millis(12_000),


355	}
356
357	/// Load every per-subnet registry snapshot under `dir`. Missing dir or an
358	/// unparseable snapshot degrade to absent (the reader's stale-tolerant
359	/// contract) — resolution then simply finds nothing there.
360	pub fn load_snapshots(dir: &Path) -> BTreeMap<String, SubnetRegistry> {
361	    let mut out = BTreeMap::new();
362	    let Ok(entries) = std::fs::read_dir(dir) else {
363	        return out;
364	    };
365	    for entry in entries.flatten() {
366	        let path = entry.path();
367	        if path.extension().and_then(|e| e.to_str()) != Some("json") {
368	            continue;
369	        }
370	        let Some(stem) = path.file_stem().and_then(|s| s.to_str()) else { continue };
371	        let Ok(raw) = std::fs::read_to_string(&path) else { continue };
372	        let Ok(reg) = serde_json::from_str::<SubnetRegistry>(&raw) else { continue };
373	        out.insert(stem.to_string(), reg);
374	    }
375	    out
376	}
377
378	fn now_ms() -> u64 {
379	    std::time::SystemTime::now()
380	        .duration_since(std::time::UNIX_EPOCH)
381	        .map(|d| d.as_millis() as u64)
382	        .unwrap_or(0)
383	}
384
385	#[cfg(test)]
386	mod tests {
387	    use super::*;
388	    use crate::testutil::isolated_home;
389	    use spt_daemon::broker::Broker;
390	    use spt_daemon::dispatch::{run_dispatch_loop, DispatchPaths};
391	    use spt_daemon::nethost::{NetConfig, NetHost};
392	    use spt_daemon::registryhost::RegistryHost;
393	    use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};
394	    use spt_net::net::registry::{Instance, Status};
395	    use spt_proto::identity::Identity;
396	    use spt_store::info::{self, InfoJson};
397	    use spt_store::perch::ParentHint;
398	    use spt_store::spool;
399	    use std::sync::atomic::{AtomicBool, Ordering};

}

/// Load every per-subnet registry snapshot under `dir`. Missing dir or an
/// unparseable snapshot degrade to absent (the reader's stale-tolerant
/// contract) — resolution then simply finds nothing there.
pub fn load_snapshots(dir: &Path) -> BTreeMap<String, SubnetRegistry> {
    let mut out = BTreeMap::new();
    let Ok(entries) = std::fs::read_dir(dir) else {
        return out;
    };
    for entry in entries.flatten() {
        let path = entry.path();
        if path.extension().and_then(|e| e.to_str()) != Some("json") {
            continue;
        }
        let Some(stem) = path.file_stem().and_then(|s| s.to_str()) else { continue };
        let Ok(raw) = std::fs::read_to_string(&path) else { continue };
        let Ok(reg) = serde_json::from_str::<SubnetRegistry>(&raw) else { continue };
        out.insert(stem.to_string(), reg);
    }
    out
}

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

#[cfg(test)]
mod tests {
    use super::*;
    use crate::testutil::isolated_home;
    use spt_daemon::broker::Broker;
    use spt_daemon::dispatch::{run_dispatch_loop, DispatchPaths};
    use spt_daemon::nethost::{NetConfig, NetHost};
    use spt_daemon::registryhost::RegistryHost;
    use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};
    use spt_net::net::registry::{Instance, Status};
    use spt_proto::identity::Identity;
    use spt_store::info::{self, InfoJson};
    use spt_store::perch::ParentHint;
    use spt_store::spool;
    use std::sync::atomic::{AtomicBool, Ordering};


D2a: node_label additive gossip field + hostname detection at daemon startup

Instance.node_label (serde-default, skip-if-none; old rows parse clean). RegistryHost computes OS hostname at construction (daemon startup re-check), stamps every advertised row. Activate REQ-SUBNET-3 [impl,unit].

D2b: @node qualifier accepts label or key-prefix, refuse-on-ambiguity

resolve_visible @node arm: match full hex, hex prefix, or label (case-insensitive); >1 distinct node matched → Ambiguous with candidate key prefixes, never guess. Update wansend render.

D2c: subnet status --nodes per-node rows + hybrid liveness + label render

Per-node rows: `LABEL (keyprefix…)`, online/offline, [online endpoints/total]. Liveness = gossip-recency for table, probe only stale rows. Regen reference.


Grep

pub fn net_dial|pub fn net_status|fn net_open_stream


490:    pub fn net_status(&mut self) -> io::Result<NetStatusReply> {
491-        self.send(KIND_NET_STATUS, serde_json::Value::Null)?;
492-        loop {
493-            match self.read_event()? {
494-                BrokerEvent::NetStatus(s) => return Ok(s),
495-                BrokerEvent::Error { message } => return Err(io::Error::other(message)),
496-                _ => continue,
497-            }
498-        }
--
507:    pub fn net_dial(
508-        &mut self,
509-        addr: serde_json::Value,
510-        op_id: Option<u64>,
511-    ) -> io::Result<NetDialed> {
512-        self.send(
513-            KIND_NET_DIAL,
514-            serde_json::to_value(NetDialReq { addr, op_id }).expect("NetDialReq serializes"),
515-        )?;
--
530:    pub fn net_open_stream(
531-        &mut self,
532-        conn_id: u64,
533-        op_id: Option<u64>,
534-    ) -> io::Result<NetStreamOpened> {
535-        self.send(
536-            KIND_NET_STREAM_OPEN,
537-            serde_json::to_value(NetStreamOpenReq { conn_id, op_id })
538-                .expect("NetStreamOpenReq serializes"),


Implement heard-stamp in RegistryHost:


Edit

    node_label: Option<String>,
    regs: Mutex<BTreeMap<String, SubnetRegistry>>,

    node_label: Option<String>,
    /// When each peer node was last *heard* (an admitted inbound feed,
    /// wall-clock ms) — the gossip-recency half of the M7 D2 hybrid
    /// liveness (REQ-SUBNET-1): a recently-heard node renders online without
    /// a probe; only stale rows get dialed. Local-only (never on the wire);
    /// mirrored to `heard.meta` beside the snapshots for the CLI reader.
    heard: Mutex<BTreeMap<String, u64>>,
    regs: Mutex<BTreeMap<String, SubnetRegistry>>,


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

    node_label: Option<String>,
    regs: Mutex<BTreeMap<String, SubnetRegistry>>,

    node_label: Option<String>,
    /// When each peer node was last *heard* (an admitted inbound feed,
    /// wall-clock ms) — the gossip-recency half of the M7 D2 hybrid
    /// liveness (REQ-SUBNET-1): a recently-heard node renders online without
    /// a probe; only stale rows get dialed. Local-only (never on the wire);
    /// mirrored to `heard.meta` beside the snapshots for the CLI reader.
    heard: Mutex<BTreeMap<String, u64>>,
    regs: Mutex<BTreeMap<String, SubnetRegistry>>,

/// epoch-bumped updates. Display/addressing sugar; `node_hex` stays the

+    /// When each peer node was last *heard* (an admitted inbound feed,

+    /// wall-clock ms) — the gossip-recency half of the M7 D2 hybrid

+    /// liveness (REQ-SUBNET-1): a recently-heard node renders online without

+    /// a probe; only stale rows get dialed. Local-only (never on the wire);

+    /// mirrored to `heard.meta` beside the snapshots for the CLI reader.

+    heard: Mutex<BTreeMap<String, u64>>,

/// The node's one durable monotonic counter (the D3b lease source) — the

/// epoch on every locally-authored instance row.


        RegistryHost {
            node_hex: node_hex.to_string(),
            node_label: None,
            regs: Mutex::new(BTreeMap::new()),
            epoch: Mutex::new(epoch),
            snapshot_dir,
        }
    }

        RegistryHost {
            node_hex: node_hex.to_string(),
            node_label: None,
            heard: Mutex::new(BTreeMap::new()),
            regs: Mutex::new(BTreeMap::new()),
            epoch: Mutex::new(epoch),
            snapshot_dir,
        }
    }


        RegistryHost {
            node_hex: node_hex.to_string(),
            node_label: None,
            regs: Mutex::new(BTreeMap::new()),
            epoch: Mutex::new(epoch),
            snapshot_dir,
        }
    }

        RegistryHost {
            node_hex: node_hex.to_string(),
            node_label: None,
            heard: Mutex::new(BTreeMap::new()),
            regs: Mutex::new(BTreeMap::new()),
            epoch: Mutex::new(epoch),
            snapshot_dir,
        }
    }

+            heard: Mutex::new(BTreeMap::new()),


        let mut regs = self.regs.lock().unwrap();
        let mut flips: Vec<String> = Vec::new();
        let verdicts = updates
            .iter()
            .map(|u| {
                if !policy.subnets.iter().any(|s| s == &u.subnet) {
                    return RegistryApplyVerdict::NotMember;
                }
                if !policy.trust.is_trusted(&u.subnet, origin_node) {
                    return RegistryApplyVerdict::UntrustedOrigin;
                }

        let mut regs = self.regs.lock().unwrap();
        let mut flips: Vec<String> = Vec::new();
        let mut admitted_any = false;
        let verdicts = updates
            .iter()
            .map(|u| {
                if !policy.subnets.iter().any(|s| s == &u.subnet) {
                    return RegistryApplyVerdict::NotMember;
                }
                if !policy.trust.is_trusted(&u.subnet, origin_node) {
                    return RegistryApplyVerdict::UntrustedOrigin;
                }
                admitted_any = true;


        let mut regs = self.regs.lock().unwrap();
        let mut flips: Vec<String> = Vec::new();
        let verdicts = updates
            .iter()
            .map(|u| {
                if !policy.subnets.iter().any(|s| s == &u.subnet) {
                    return RegistryApplyVerdict::NotMember;
                }
                if !policy.trust.is_trusted(&u.subnet, origin_node) {
                    return RegistryApplyVerdict::UntrustedOrigin;
                }

        let mut regs = self.regs.lock().unwrap();
        let mut flips: Vec<String> = Vec::new();
        let mut admitted_any = false;
        let verdicts = updates
            .iter()
            .map(|u| {
                if !policy.subnets.iter().any(|s| s == &u.subnet) {
                    return RegistryApplyVerdict::NotMember;
                }
                if !policy.trust.is_trusted(&u.subnet, origin_node) {
                    return RegistryApplyVerdict::UntrustedOrigin;
                }
                admitted_any = true;

) -> (Vec<RegistryApplyVerdict>, Vec<String>) {

let reg = regs.entry(u.subnet.clone()).or_default();


                RegistryApplyVerdict::Merged(outcome)
            })
            .collect();
        drop(regs);
        self.write_snapshots();
        (verdicts, flips)
    }

                RegistryApplyVerdict::Merged(outcome)
            })
            .collect();
        drop(regs);
        // Gossip-recency stamp (M7 D2): an ADMITTED feed proves the origin
        // node is up right now — even a Stale merge verdict is a liveness
        // signal (the lease judged the row, not the sender). Refused feeds
        // stamp nothing (an untrusted sender earns no liveness credit).
        // [impl->REQ-SUBNET-1]
        if admitted_any {
            self.heard.lock().unwrap().insert(origin_node.to_string(), wall_ms());
        }
        self.write_snapshots();
        (verdicts, flips)
    }


                RegistryApplyVerdict::Merged(outcome)
            })
            .collect();
        drop(regs);
        self.write_snapshots();
        (verdicts, flips)
    }

                RegistryApplyVerdict::Merged(outcome)
            })
            .collect();
        drop(regs);
        // Gossip-recency stamp (M7 D2): an ADMITTED feed proves the origin
        // node is up right now — even a Stale merge verdict is a liveness
        // signal (the lease judged the row, not the sender). Refused feeds
        // stamp nothing (an untrusted sender earns no liveness credit).
        // [impl->REQ-SUBNET-1]
        if admitted_any {
            self.heard.lock().unwrap().insert(origin_node.to_string(), wall_ms());
        }
        self.write_snapshots();
        (verdicts, flips)
    }

+        // Gossip-recency stamp (M7 D2): an ADMITTED feed proves the origin

+        // node is up right now — even a Stale merge verdict is a liveness

+        // signal (the lease judged the row, not the sender). Refused feeds

+        // stamp nothing (an untrusted sender earns no liveness credit).

+            self.heard.lock().unwrap().insert(origin_node.to_string(), wall_ms());


Now write heard.meta in write_snapshots + path helper + wall_ms:


        for (subnet, reg) in regs.iter() {
            let Ok(json) = serde_json::to_vec_pretty(reg) else { continue };
            let _ = atomic_write(&self.snapshot_dir.join(format!("{subnet}.json")), &json);
        }
    }

    /// The snapshot path for one subnet (the CLI reader's contract).
    pub fn snapshot_path(dir: &Path, subnet: &str) -> PathBuf {
        dir.join(format!("{subnet}.json"))
    }
}

        for (subnet, reg) in regs.iter() {
            let Ok(json) = serde_json::to_vec_pretty(reg) else { continue };
            let _ = atomic_write(&self.snapshot_dir.join(format!("{subnet}.json")), &json);
        }
        drop(regs);
        // The heard-map mirror (M7 D2 hybrid liveness). `.meta`, not `.json`:
        // the snapshot reader globs `<subnet>.json`, so this can never shadow
        // a subnet (even one literally named "heard").
        let heard = self.heard.lock().unwrap();
        if !heard.is_empty() {
            if let Ok(json) = serde_json::to_vec_pretty(&*heard) {
                let _ = atomic_write(&Self::heard_path(&self.snapshot_dir), &json);
            }
        }
    }

    /// The snapshot path for one subnet (the CLI reader's contract).
    pub fn snapshot_path(dir: &Path, subnet: &str) -> PathBuf {
        dir.join(format!("{subnet}.json"))
    }

    /// The heard-map mirror's path (the CLI reader's contract): `node hex →
    /// last admitted-feed wall ms`.
    // [impl->REQ-SUBNET-1]
    pub fn heard_path(dir: &Path) -> PathBuf {
        dir.join("heard.meta")
    }
}

/// Wall-clock ms (the heard-map stamp; display/recency only — never a merge
/// key, the epoch lease owns ordering).
fn wall_ms() -> u64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_millis() as u64)
        .unwrap_or(0)
}


        for (subnet, reg) in regs.iter() {
            let Ok(json) = serde_json::to_vec_pretty(reg) else { continue };
            let _ = atomic_write(&self.snapshot_dir.join(format!("{subnet}.json")), &json);
        }
    }

    /// The snapshot path for one subnet (the CLI reader's contract).
    pub fn snapshot_path(dir: &Path, subnet: &str) -> PathBuf {
        dir.join(format!("{subnet}.json"))
    }
}

        for (subnet, reg) in regs.iter() {
            let Ok(json) = serde_json::to_vec_pretty(reg) else { continue };
            let _ = atomic_write(&self.snapshot_dir.join(format!("{subnet}.json")), &json);
        }
        drop(regs);
        // The heard-map mirror (M7 D2 hybrid liveness). `.meta`, not `.json`:
        // the snapshot reader globs `<subnet>.json`, so this can never shadow
        // a subnet (even one literally named "heard").
        let heard = self.heard.lock().unwrap();
        if !heard.is_empty() {
            if let Ok(json) = serde_json::to_vec_pretty(&*heard) {
                let _ = atomic_write(&Self::heard_path(&self.snapshot_dir), &json);
            }
        }
    }

    /// The snapshot path for one subnet (the CLI reader's contract).
    pub fn snapshot_path(dir: &Path, subnet: &str) -> PathBuf {
        dir.join(format!("{subnet}.json"))
    }

    /// The heard-map mirror's path (the CLI reader's contract): `node hex →
    /// last admitted-feed wall ms`.
    // [impl->REQ-SUBNET-1]
    pub fn heard_path(dir: &Path) -> PathBuf {
        dir.join("heard.meta")
    }
}

/// Wall-clock ms (the heard-map stamp; display/recency only — never a merge
/// key, the epoch lease owns ordering).
fn wall_ms() -> u64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_millis() as u64)
        .unwrap_or(0)
}

+        // The heard-map mirror (M7 D2 hybrid liveness). `.meta`, not `.json`:

+        // the snapshot reader globs `<subnet>.json`, so this can never shadow

+        // a subnet (even one literally named "heard").

+        let heard = self.heard.lock().unwrap();

+            if let Ok(json) = serde_json::to_vec_pretty(&*heard) {

+                let _ = atomic_write(&Self::heard_path(&self.snapshot_dir), &json);

+    /// The heard-map mirror's path (the CLI reader's contract): `node hex →

+    pub fn heard_path(dir: &Path) -> PathBuf {

+/// Wall-clock ms (the heard-map stamp; display/recency only — never a merge

+/// key, the epoch lease owns ordering).

+        .duration_since(std::time::UNIX_EPOCH)

/// tmp-write + atomic rename with the 5.2 Windows-EBUSY retry posture.

fn atomic_write(path: &Path, bytes: &[u8]) -> io::Result<()> {

let tmp = path.with_extension("json.tmp");


// [unit->REQ-SUBNET-3] the host stamps its node label onto every row it

    // [unit->REQ-SUBNET-1] the gossip-recency half of hybrid liveness: an
    // ADMITTED feed stamps the origin's heard-ms (even when the lease drops
    // the row Stale — the sender still proved alive); a REFUSED feed stamps
    // nothing; the mirror lands at heard.meta and parses back.
    #[test]
    fn admitted_feeds_stamp_the_heard_map() {
        let dir = tempfile::tempdir().unwrap();
        let h = host(dir.path());
        let p = policy("home", "bb22");

        // Refused (untrusted origin): no stamp, no mirror.
        h.apply_feed("cc33", &[upd("home", "ling", "cc33", 5)], &p);
        assert!(!RegistryHost::heard_path(&dir.path().join("registry")).exists());

        // Admitted: stamped + mirrored.
        h.apply_feed("bb22", &[upd("home", "ling", "bb22", 5)], &p);
        let heard_file = RegistryHost::heard_path(&dir.path().join("registry"));
        let heard: BTreeMap<String, u64> =
            serde_json::from_slice(&std::fs::read(&heard_file).expect("mirror written")).unwrap();
        let first = *heard.get("bb22").expect("origin stamped");
        assert!(first > 0);
        assert!(!heard.contains_key("cc33"), "refused origin earns no liveness credit");

        // A Stale verdict still refreshes the stamp (the sender is alive).
        std::thread::sleep(std::time::Duration::from_millis(15));
        let v = h.apply_feed("bb22", &[upd("home", "ling", "bb22", 5)], &p);
        assert_eq!(v, vec![RegistryApplyVerdict::Merged(MergeOutcome::Stale)]);
        let heard: BTreeMap<String, u64> =
            serde_json::from_slice(&std::fs::read(&heard_file).unwrap()).unwrap();
        assert!(*heard.get("bb22").unwrap() > first, "stale merge still refreshes recency");
    }

    // [unit->REQ-SUBNET-3] the host stamps its node label onto every row it


    // [unit->REQ-SUBNET-1] the gossip-recency half of hybrid liveness: an
    // ADMITTED feed stamps the origin's heard-ms (even when the lease drops
    // the row Stale — the sender still proved alive); a REFUSED feed stamps
    // nothing; the mirror lands at heard.meta and parses back.
    #[test]
    fn admitted_feeds_stamp_the_heard_map() {
        let dir = tempfile::tempdir().unwrap();
        let h = host(dir.path());
        let p = policy("home", "bb22");

        // Refused (untrusted origin): no stamp, no mirror.
        h.apply_feed("cc33", &[upd("home", "ling", "cc33", 5)], &p);
        assert!(!RegistryHost::heard_path(&dir.path().join("registry")).exists());

        // Admitted: stamped + mirrored.
        h.apply_feed("bb22", &[upd("home", "ling", "bb22", 5)], &p);
        let heard_file = RegistryHost::heard_path(&dir.path().join("registry"));
        let heard: BTreeMap<String, u64> =
            serde_json::from_slice(&std::fs::read(&heard_file).expect("mirror written")).unwrap();
        let first = *heard.get("bb22").expect("origin stamped");
        assert!(first > 0);
        assert!(!heard.contains_key("cc33"), "refused origin earns no liveness credit");

        // A Stale verdict still refreshes the stamp (the sender is alive).
        std::thread::sleep(std::time::Duration::from_millis(15));
        let v = h.apply_feed("bb22", &[upd("home", "ling", "bb22", 5)], &p);
        assert_eq!(v, vec![RegistryApplyVerdict::Merged(MergeOutcome::Stale)]);
        let heard: BTreeMap<String, u64> =
            serde_json::from_slice(&std::fs::read(&heard_file).unwrap()).unwrap();
        assert!(*heard.get("bb22").unwrap() > first, "stale merge still refreshes recency");
    }

    // [unit->REQ-SUBNET-3] the host stamps its node label onto every row it

+    // [unit->REQ-SUBNET-1] the gossip-recency half of hybrid liveness: an

+    // ADMITTED feed stamps the origin's heard-ms (even when the lease drops

+    // the row Stale — the sender still proved alive); a REFUSED feed stamps

+    // nothing; the mirror lands at heard.meta and parses back.

+    fn admitted_feeds_stamp_the_heard_map() {

+        let dir = tempfile::tempdir().unwrap();

+        let p = policy("home", "bb22");

+        // Refused (untrusted origin): no stamp, no mirror.

+        assert!(!RegistryHost::heard_path(&dir.path().join("registry")).exists());

+        // Admitted: stamped + mirrored.

+        let heard_file = RegistryHost::heard_path(&dir.path().join("registry"));

+        let heard: BTreeMap<String, u64> =

+            serde_json::from_slice(&std::fs::read(&heard_file).expect("mirror written")).unwrap();

+        let first = *heard.get("bb22").expect("origin stamped");

+        assert!(!heard.contains_key("cc33"), "refused origin earns no liveness credit");

+        // A Stale verdict still refreshes the stamp (the sender is alive).

+        std::thread::sleep(std::time::Duration::from_millis(15));

+        assert_eq!(v, vec![RegistryApplyVerdict::Merged(MergeOutcome::Stale)]);

+            serde_json::from_slice(&std::fs::read(&heard_file).unwrap()).unwrap();

+        assert!(*heard.get("bb22").unwrap() > first, "stale merge still refreshes recency");

// advertises (and only its own rows — peers' labels arrive on their own

// advertisements); an unlabeled host (with_node_label(None), the test


Bash

cargo test -p spt-daemon --lib registryhost 2>&1 | grep -E "test result|FAILED|panicked|error\[" | head

Run registryhost tests


test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 169 filtered out; finished in 0.13s


Updated task #7 status


Updated task #8 status


CLI `--nodes` view. Edit SubnetCmd + dispatch + view code:


Edit

    /// Show subnet membership: name, paired nodes, endpoints.
    ///
    /// Never prints seeds, epochs, or pairing codes. Bare `spt subnet` is
    /// the same view.
    Status {
        /// Limit to one subnet (all member subnets otherwise).
        name: Option<String>,
    },

    /// Show subnet membership: name, paired nodes, endpoints.
    ///
    /// Never prints seeds, epochs, or pairing codes. Bare `spt subnet` is
    /// the same view.
    Status {
        /// Limit to one subnet (all member subnets otherwise).
        name: Option<String>,
        /// Per-node rows: label, online/offline, [online endpoints/total].
        #[arg(long)]
        nodes: bool,
    },


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

    /// Show subnet membership: name, paired nodes, endpoints.
    ///
    /// Never prints seeds, epochs, or pairing codes. Bare `spt subnet` is
    /// the same view.
    Status {
        /// Limit to one subnet (all member subnets otherwise).
        name: Option<String>,
    },

    /// Show subnet membership: name, paired nodes, endpoints.
    ///
    /// Never prints seeds, epochs, or pairing codes. Bare `spt subnet` is
    /// the same view.
    Status {
        /// Limit to one subnet (all member subnets otherwise).
        name: Option<String>,
        /// Per-node rows: label, online/offline, [online endpoints/total].
        #[arg(long)]
        nodes: bool,
    },

+        /// Per-node rows: label, online/offline, [online endpoints/total].

/// Mint a fresh subnet and print its joining material.


        Cmd::Subnet { action } => match action.unwrap_or(SubnetCmd::Status { name: None }) {
            SubnetCmd::Status { name } => cmd_subnet_status(name),
            SubnetCmd::Create { name } => cmd_subnet_create(name),
            SubnetCmd::ShowTotp { name } => cmd_subnet_show_totp(name),
        },

        Cmd::Subnet { action } => {
            match action.unwrap_or(SubnetCmd::Status { name: None, nodes: false }) {
                SubnetCmd::Status { name, nodes } => cmd_subnet_status(name, nodes),
                SubnetCmd::Create { name } => cmd_subnet_create(name),
                SubnetCmd::ShowTotp { name } => cmd_subnet_show_totp(name),
            }
        }


        Cmd::Subnet { action } => match action.unwrap_or(SubnetCmd::Status { name: None }) {
            SubnetCmd::Status { name } => cmd_subnet_status(name),
            SubnetCmd::Create { name } => cmd_subnet_create(name),
            SubnetCmd::ShowTotp { name } => cmd_subnet_show_totp(name),
        },

        Cmd::Subnet { action } => {
            match action.unwrap_or(SubnetCmd::Status { name: None, nodes: false }) {
                SubnetCmd::Status { name, nodes } => cmd_subnet_status(name, nodes),
                SubnetCmd::Create { name } => cmd_subnet_create(name),
                SubnetCmd::ShowTotp { name } => cmd_subnet_show_totp(name),
            }
        }

-        Cmd::Subnet { action } => match action.unwrap_or(SubnetCmd::Status { name: None }) {

-            SubnetCmd::Status { name } => cmd_subnet_status(name),

-            SubnetCmd::Create { name } => cmd_subnet_create(name),

-            SubnetCmd::ShowTotp { name } => cmd_subnet_show_totp(name),

+            match action.unwrap_or(SubnetCmd::Status { name: None, nodes: false }) {

+                SubnetCmd::Status { name, nodes } => cmd_subnet_status(name, nodes),

+                SubnetCmd::Create { name } => cmd_subnet_create(name),

+                SubnetCmd::ShowTotp { name } => cmd_subnet_show_totp(name),

Cmd::HowTo { topic } => cmd_how_to(topic.as_deref()),

Cmd::Access { action } => cmd_access(action),

Cmd::Grant { action } => cmd_grant(action),


/// `spt subnet [status [NAME]]` — the flagless membership view (REQ-SUBNET-1, M7
/// plan decision 5). Read-only and ungated: prints membership shape, never secrets.
// [impl->REQ-SUBNET-1]
fn cmd_subnet_status(name: Option<String>) -> i32 {
    let store = SubnetStore::load();
    if let Some(n) = &name {
        if store.find(n).is_none() {
            eprintln!("NO_SUCH_SUBNET:{n}");
            return 2;
        }
    }
    let regs = crate::wansend::load_snapshots(&perch::identity_dir().join("registry"));
    let rows = subnet_status_rows(&store, &regs, name.as_deref());
    print!("{}", render_subnet_status(&rows));
    let _ = std::io::stdout().flush();
    0
}

/// One node's `status --nodes` row (M7 plan decision 6): identity, advertised
/// label, endpoint counts, and the gossip-recency liveness verdict.
struct NodeRow {
    node: String,
    label: Option<String>,
    /// Active (listening-now) endpoint rows on this node.
    active: usize,
    /// All endpoint rows on this node.
    total: usize,
    liveness: NodeLiveness,
}

/// The gossip-recency half of the hybrid liveness decision (M7 plan decision
/// 6): the cheap signals settle most rows; only [`Probe`](NodeLiveness::Probe)
/// rows cost a dial.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum NodeLiveness {
    Online,
    Offline,
    /// Gossip can't say (not heard recently, rows not all-Offline) — dial it.
    Probe,
}

/// A node's render form: `LABEL (keypfx…)` when labeled, `keypfx…` bare
/// (REQ-SUBNET-3 — e.g. `HFENDULEAM (bcead52b…)`).
fn node_display(node: &str, label: Option<&str>) -> String {
    let pfx = spt_net::net::registry::key_prefix(node);
    match label {
        Some(l) => format!("{l} ({pfx}…)"),
        None => format!("{pfx}…"),
    }
}

/// Build one subnet's per-node rows with the gossip-recency verdict. Pure
/// over its inputs (the probe is the caller's impure follow-up): own node →
/// online (we are running); all rows Offline → offline; heard within
/// `stale_after_ms` → online; otherwise → probe.
// [impl->REQ-SUBNET-1]
fn node_status_rows(
    reg: &spt_net::net::registry::SubnetRegistry,
    heard: &std::collections::BTreeMap<String, u64>,
    own_hex: &str,
    now_ms: u64,
    stale_after_ms: u64,
) -> Vec<NodeRow> {
    use spt_net::net::registry::Status;
    // node → (label, active, total, any_routable)
    let mut by_node: std::collections::BTreeMap<String, (Option<String>, usize, usize, bool)> =
        std::collections::BTreeMap::new();
    for id in reg.endpoint_ids() {
        for inst in reg.instances(id) {
            let e = by_node.entry(inst.node.clone()).or_default();
            if e.0.is_none() {
                e.0 = inst.node_label.clone();
            }
            e.1 += usize::from(inst.status == Status::Active);
            e.2 += 1;
            e.3 |= inst.status.routable();
        }
    }
    by_node
        .into_iter()
        .map(|(node, (label, active, total, any_routable))| {
            let liveness = if node == own_hex {
                NodeLiveness::Online
            } else if !any_routable {
                NodeLiveness::Offline
            } else if heard
                .get(&node)
                .is_some_and(|&at| now_ms.saturating_sub(at) <= stale_after_ms)
            {
                NodeLiveness::Online
            } else {
                NodeLiveness::Probe
            };
            NodeRow { node, label, active, total, liveness }
        })
        .collect()
}

/// Render one subnet's `--nodes` table from probe-settled rows.
// [impl->REQ-SUBNET-1] [impl->REQ-SUBNET-3]
fn render_node_rows(subnet: &str, rows: &[(NodeRow, bool)]) -> String {
    let mut out = format!("SUBNET {subnet}\n");
    if rows.is_empty() {
        out.push_str("  (no gossip yet — pair another machine, or wait one pump cadence)\n");
        return out;
    }
    let names: Vec<String> =
        rows.iter().map(|(r, _)| node_display(&r.node, r.label.as_deref())).collect();
    let pad = names.iter().map(|n| n.chars().count()).max().unwrap_or(0).max("NODE".len());
    out.push_str(&format!("  {:<pad$}  STATUS   ENDPOINTS\n", "NODE"));
    for ((row, online), name) in rows.iter().zip(&names) {
        // Pad by char count, not byte len ('…' is multi-byte).
        let fill = " ".repeat(pad - name.chars().count());
        out.push_str(&format!(
            "  {name}{fill}  {:<7}  [{}/{}]\n",
            if *online { "online" } else { "offline" },
            row.active,
            row.total,
        ));
    }
    out
}

/// Probe a stale node: dial it over the broker's QUIC endpoint (the hybrid
/// liveness's expensive half — spent only on rows gossip could not settle).
/// Any failure (daemon down, no address, dial refused/timed out) reads
/// offline — fail-quiet, this is a status view.
fn probe_node(node_hex: &str) -> bool {
    crate::wansend::probe_node(node_hex)
}

/// `spt subnet [status [NAME] [--nodes]]` — the membership view (REQ-SUBNET-1,
/// M7 plan decisions 5 + 6). Read-only and ungated: prints membership shape,
/// never secrets.
// [impl->REQ-SUBNET-1]
fn cmd_subnet_status(name: Option<String>, nodes: bool) -> i32 {
    let store = SubnetStore::load();
    if let Some(n) = &name {
        if store.find(n).is_none() {
            eprintln!("NO_SUCH_SUBNET:{n}");
            return 2;
        }
    }
    let snap_dir = perch::identity_dir().join("registry");
    let regs = crate::wansend::load_snapshots(&snap_dir);
    if !nodes {
        let rows = subnet_status_rows(&store, &regs, name.as_deref());
        print!("{}", render_subnet_status(&rows));
        let _ = std::io::stdout().flush();
        return 0;
    }

    // --nodes: per-node rows under the hybrid liveness (decision 6).
    let own_hex = spt_store::nodeid::load_or_create()
        .map(|i| i.public_key().to_hex())
        .unwrap_or_default();
    let heard: std::collections::BTreeMap<String, u64> =
        std::fs::read(spt_daemon::registryhost::RegistryHost::heard_path(&snap_dir))
            .ok()
            .and_then(|b| serde_json::from_slice(&b).ok())
            .unwrap_or_default();
    // Stale = silent for 3 advertisement cadences (the daemon re-gossips
    // every pump period; missing a couple is jitter, missing three is news).
    let stale_after_ms =
        3 * spt_daemon::DaemonConfig::load().registry_pump_period.as_millis() as u64;
    let mut out = String::new();
    for sub in &store.subnets {
        if name.as_deref().is_some_and(|n| n != sub.name) {
            continue;
        }
        let empty = spt_net::net::registry::SubnetRegistry::new();
        let reg = regs.get(&sub.name).unwrap_or(&empty);
        let rows = node_status_rows(reg, &heard, &own_hex, now_ms(), stale_after_ms);
        let settled: Vec<(NodeRow, bool)> = rows
            .into_iter()
            .map(|r| {
                let online = match r.liveness {
                    NodeLiveness::Online => true,
                    NodeLiveness::Offline => false,
                    NodeLiveness::Probe => probe_node(&r.node),
                };
                (r, online)
            })
            .collect();
        out.push_str(&render_node_rows(&sub.name, &settled));
    }
    if out.is_empty() {
        out.push_str(
            "This node belongs to no subnet — a standalone node. Local messaging \
             works without one; a subnet links your machines into one fabric.\n",
        );
    }
    out.push_str(
        "\nhint: `spt subnet create <name>` mints a new subnet on this machine\n\
         hint: `spt subnet show-totp [name]` shows a subnet's joining code (elevated)\n",
    );
    print!("{out}");
    let _ = std::io::stdout().flush();
    0
}


/// `spt subnet [status [NAME]]` — the flagless membership view (REQ-SUBNET-1, M7
/// plan decision 5). Read-only and ungated: prints membership shape, never secrets.
// [impl->REQ-SUBNET-1]
fn cmd_subnet_status(name: Option<String>) -> i32 {
    let store = SubnetStore::load();
    if let Some(n) = &name {
        if store.find(n).is_none() {
            eprintln!("NO_SUCH_SUBNET:{n}");
            return 2;
        }
    }
    let regs = crate::wansend::load_snapshots(&perch::identity_dir().join("registry"));
    let rows = subnet_status_rows(&store, &regs, name.as_deref());
    print!("{}", render_subnet_status(&rows));
    let _ = std::io::stdout().flush();
    0
}

/// One node's `status --nodes` row (M7 plan decision 6): identity, advertised
/// label, endpoint counts, and the gossip-recency liveness verdict.
struct NodeRow {
    node: String,
    label: Option<String>,
    /// Active (listening-now) endpoint rows on this node.
    active: usize,
    /// All endpoint rows on this node.
    total: usize,
    liveness: NodeLiveness,
}

/// The gossip-recency half of the hybrid liveness decision (M7 plan decision
/// 6): the cheap signals settle most rows; only [`Probe`](NodeLiveness::Probe)
/// rows cost a dial.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum NodeLiveness {
    Online,
    Offline,
    /// Gossip can't say (not heard recently, rows not all-Offline) — dial it.
    Probe,
}

/// A node's render form: `LABEL (keypfx…)` when labeled, `keypfx…` bare
/// (REQ-SUBNET-3 — e.g. `HFENDULEAM (bcead52b…)`).
fn node_display(node: &str, label: Option<&str>) -> String {
    let pfx = spt_net::net::registry::key_prefix(node);
    match label {
        Some(l) => format!("{l} ({pfx}…)"),
        None => format!("{pfx}…"),
    }
}

/// Build one subnet's per-node rows with the gossip-recency verdict. Pure
/// over its inputs (the probe is the caller's impure follow-up): own node →
/// online (we are running); all rows Offline → offline; heard within
/// `stale_after_ms` → online; otherwise → probe.
// [impl->REQ-SUBNET-1]
fn node_status_rows(
    reg: &spt_net::net::registry::SubnetRegistry,
    heard: &std::collections::BTreeMap<String, u64>,
    own_hex: &str,
    now_ms: u64,
    stale_after_ms: u64,
) -> Vec<NodeRow> {
    use spt_net::net::registry::Status;
    // node → (label, active, total, any_routable)
    let mut by_node: std::collections::BTreeMap<String, (Option<String>, usize, usize, bool)> =
        std::collections::BTreeMap::new();
    for id in reg.endpoint_ids() {
        for inst in reg.instances(id) {
            let e = by_node.entry(inst.node.clone()).or_default();
            if e.0.is_none() {
                e.0 = inst.node_label.clone();
            }
            e.1 += usize::from(inst.status == Status::Active);
            e.2 += 1;
            e.3 |= inst.status.routable();
        }
    }
    by_node
        .into_iter()
        .map(|(node, (label, active, total, any_routable))| {
            let liveness = if node == own_hex {
                NodeLiveness::Online
            } else if !any_routable {
                NodeLiveness::Offline
            } else if heard
                .get(&node)
                .is_some_and(|&at| now_ms.saturating_sub(at) <= stale_after_ms)
            {
                NodeLiveness::Online
            } else {
                NodeLiveness::Probe
            };
            NodeRow { node, label, active, total, liveness }
        })
        .collect()
}

/// Render one subnet's `--nodes` table from probe-settled rows.
// [impl->REQ-SUBNET-1] [impl->REQ-SUBNET-3]
fn render_node_rows(subnet: &str, rows: &[(NodeRow, bool)]) -> String {
    let mut out = format!("SUBNET {subnet}\n");
    if rows.is_empty() {
        out.push_str("  (no gossip yet — pair another machine, or wait one pump cadence)\n");
        return out;
    }
    let names: Vec<String> =
        rows.iter().map(|(r, _)| node_display(&r.node, r.label.as_deref())).collect();
    let pad = names.iter().map(|n| n.chars().count()).max().unwrap_or(0).max("NODE".len());
    out.push_str(&format!("  {:<pad$}  STATUS   ENDPOINTS\n", "NODE"));
    for ((row, online), name) in rows.iter().zip(&names) {
        // Pad by char count, not byte len ('…' is multi-byte).
        let fill = " ".repeat(pad - name.chars().count());
        out.push_str(&format!(
            "  {name}{fill}  {:<7}  [{}/{}]\n",
            if *online { "online" } else { "offline" },
            row.active,
            row.total,
        ));
    }
    out
}

/// Probe a stale node: dial it over the broker's QUIC endpoint (the hybrid
/// liveness's expensive half — spent only on rows gossip could not settle).
/// Any failure (daemon down, no address, dial refused/timed out) reads
/// offline — fail-quiet, this is a status view.
fn probe_node(node_hex: &str) -> bool {
    crate::wansend::probe_node(node_hex)
}

/// `spt subnet [status [NAME] [--nodes]]` — the membership view (REQ-SUBNET-1,
/// M7 plan decisions 5 + 6). Read-only and ungated: prints membership shape,
/// never secrets.
// [impl->REQ-SUBNET-1]
fn cmd_subnet_status(name: Option<String>, nodes: bool) -> i32 {
    let store = SubnetStore::load();
    if let Some(n) = &name {
        if store.find(n).is_none() {
            eprintln!("NO_SUCH_SUBNET:{n}");
            return 2;
        }
    }
    let snap_dir = perch::identity_dir().join("registry");
    let regs = crate::wansend::load_snapshots(&snap_dir);
    if !nodes {
        let rows = subnet_status_rows(&store, &regs, name.as_deref());
        print!("{}", render_subnet_status(&rows));
        let _ = std::io::stdout().flush();
        return 0;
    }

    // --nodes: per-node rows under the hybrid liveness (decision 6).
    let own_hex = spt_store::nodeid::load_or_create()
        .map(|i| i.public_key().to_hex())
        .unwrap_or_default();
    let heard: std::collections::BTreeMap<String, u64> =
        std::fs::read(spt_daemon::registryhost::RegistryHost::heard_path(&snap_dir))
            .ok()
            .and_then(|b| serde_json::from_slice(&b).ok())
            .unwrap_or_default();
    // Stale = silent for 3 advertisement cadences (the daemon re-gossips
    // every pump period; missing a couple is jitter, missing three is news).
    let stale_after_ms =
        3 * spt_daemon::DaemonConfig::load().registry_pump_period.as_millis() as u64;
    let mut out = String::new();
    for sub in &store.subnets {
        if name.as_deref().is_some_and(|n| n != sub.name) {
            continue;
        }
        let empty = spt_net::net::registry::SubnetRegistry::new();
        let reg = regs.get(&sub.name).unwrap_or(&empty);
        let rows = node_status_rows(reg, &heard, &own_hex, now_ms(), stale_after_ms);
        let settled: Vec<(NodeRow, bool)> = rows
            .into_iter()
            .map(|r| {
                let online = match r.liveness {
                    NodeLiveness::Online => true,
                    NodeLiveness::Offline => false,
                    NodeLiveness::Probe => probe_node(&r.node),
                };
                (r, online)
            })
            .collect();
        out.push_str(&render_node_rows(&sub.name, &settled));
    }
    if out.is_empty() {
        out.push_str(
            "This node belongs to no subnet — a standalone node. Local messaging \
             works without one; a subnet links your machines into one fabric.\n",
        );
    }
    out.push_str(
        "\nhint: `spt subnet create <name>` mints a new subnet on this machine\n\
         hint: `spt subnet show-totp [name]` shows a subnet's joining code (elevated)\n",
    );
    print!("{out}");
    let _ = std::io::stdout().flush();
    0
}

-/// `spt subnet [status [NAME]]` — the flagless membership view (REQ-SUBNET-1, M7

-/// plan decision 5). Read-only and ungated: prints membership shape, never secrets.

+/// One node's `status --nodes` row (M7 plan decision 6): identity, advertised

+/// label, endpoint counts, and the gossip-recency liveness verdict.

+    /// Active (listening-now) endpoint rows on this node.

+    /// All endpoint rows on this node.

+/// The gossip-recency half of the hybrid liveness decision (M7 plan decision

+/// 6): the cheap signals settle most rows; only [`Probe`](NodeLiveness::Probe)

+#[derive(Debug, Clone, Copy, PartialEq, Eq)]

+    /// Gossip can't say (not heard recently, rows not all-Offline) — dial it.

+/// A node's render form: `LABEL (keypfx…)` when labeled, `keypfx…` bare

+/// (REQ-SUBNET-3 — e.g. `HFENDULEAM (bcead52b…)`).

+fn node_display(node: &str, label: Option<&str>) -> String {

+    let pfx = spt_net::net::registry::key_prefix(node);

+/// Build one subnet's per-node rows with the gossip-recency verdict. Pure

+/// over its inputs (the probe is the caller's impure follow-up): own node →

+/// online (we are running); all rows Offline → offline; heard within

+/// `stale_after_ms` → online; otherwise → probe.

-fn cmd_subnet_status(name: Option<String>) -> i32 {

+    reg: &spt_net::net::registry::SubnetRegistry,

+    heard: &std::collections::BTreeMap<String, u64>,

+    use spt_net::net::registry::Status;

+    // node → (label, active, total, any_routable)

+    let mut by_node: std::collections::BTreeMap<String, (Option<String>, usize, usize, bool)> =

+        std::collections::BTreeMap::new();

+        for inst in reg.instances(id) {

+            let e = by_node.entry(inst.node.clone()).or_default();

+                e.0 = inst.node_label.clone();

+            e.1 += usize::from(inst.status == Status::Active);

+            e.3 |= inst.status.routable();

+        .map(|(node, (label, active, total, any_routable))| {

+            let liveness = if node == own_hex {

+                .is_some_and(|&at| now_ms.saturating_sub(at) <= stale_after_ms)

+            NodeRow { node, label, active, total, liveness }

+/// Render one subnet's `--nodes` table from probe-settled rows.

+fn render_node_rows(subnet: &str, rows: &[(NodeRow, bool)]) -> String {

+    let mut out = format!("SUBNET {subnet}\n");

+        out.push_str("  (no gossip yet — pair another machine, or wait one pump cadence)\n");

+        rows.iter().map(|(r, _)| node_display(&r.node, r.label.as_deref())).collect();

+    let pad = names.iter().map(|n| n.chars().count()).max().unwrap_or(0).max("NODE".len());

+    out.push_str(&format!("  {:<pad$}  STATUS   ENDPOINTS\n", "NODE"));

+    for ((row, online), name) in rows.iter().zip(&names) {

+        // Pad by char count, not byte len ('…' is multi-byte).

+        let fill = " ".repeat(pad - name.chars().count());

+            if *online { "online" } else { "offline" },

+/// Probe a stale node: dial it over the broker's QUIC endpoint (the hybrid

+/// liveness's expensive half — spent only on rows gossip could not settle).

+/// Any failure (daemon down, no address, dial refused/timed out) reads

+/// offline — fail-quiet, this is a status view.

+fn probe_node(node_hex: &str) -> bool {

+    crate::wansend::probe_node(node_hex)

+/// `spt subnet [status [NAME] [--nodes]]` — the membership view (REQ-SUBNET-1,

+/// M7 plan decisions 5 + 6). Read-only and ungated: prints membership shape,

+fn cmd_subnet_status(name: Option<String>, nodes: bool) -> i32 {

-    let regs = crate::wansend::load_snapshots(&perch::identity_dir().join("registry"));

-    let rows = subnet_status_rows(&store, &regs, name.as_deref());

-    print!("{}", render_subnet_status(&rows));

+    let snap_dir = perch::identity_dir().join("registry");

+    let regs = crate::wansend::load_snapshots(&snap_dir);

+        let rows = subnet_status_rows(&store, &regs, name.as_deref());

+        print!("{}", render_subnet_status(&rows));

+        let _ = std::io::stdout().flush();

+    // --nodes: per-node rows under the hybrid liveness (decision 6).

+    let own_hex = spt_store::nodeid::load_or_create()

+        .map(|i| i.public_key().to_hex())

+    let heard: std::collections::BTreeMap<String, u64> =

+        std::fs::read(spt_daemon::registryhost::RegistryHost::heard_path(&snap_dir))

+            .and_then(|b| serde_json::from_slice(&b).ok())

+    // Stale = silent for 3 advertisement cadences (the daemon re-gossips

+    // every pump period; missing a couple is jitter, missing three is news).

+        3 * spt_daemon::DaemonConfig::load().registry_pump_period.as_millis() as u64;

+        if name.as_deref().is_some_and(|n| n != sub.name) {

+        let empty = spt_net::net::registry::SubnetRegistry::new();

+        let reg = regs.get(&sub.name).unwrap_or(&empty);

+        let rows = node_status_rows(reg, &heard, &own_hex, now_ms(), stale_after_ms);

+        let settled: Vec<(NodeRow, bool)> = rows

+                let online = match r.liveness {

+                    NodeLiveness::Online => true,

+                    NodeLiveness::Offline => false,

+                    NodeLiveness::Probe => probe_node(&r.node),

+        out.push_str(&render_node_rows(&sub.name, &settled));

+            "This node belongs to no subnet — a standalone node. Local messaging \

+             works without one; a subnet links your machines into one fabric.\n",

+        "\nhint: `spt subnet create <name>` mints a new subnet on this machine\n\

+         hint: `spt subnet show-totp [name]` shows a subnet's joining code (elevated)\n",


/// Load every per-subnet registry snapshot under `dir`. Missing dir or an
/// unparseable snapshot degrade to absent (the reader's stale-tolerant
/// contract) — resolution then simply finds nothing there.

/// Dial-probe a node (M7 D2, REQ-SUBNET-1 — the hybrid liveness's expensive
/// half, spent only on rows gossip-recency could not settle): true iff the
/// broker's QUIC endpoint can connect to it right now. Every failure mode
/// (daemon down, no dialable address, refused/timed-out dial) reads `false` —
/// the caller renders offline, never errors a status view.
// [impl->REQ-SUBNET-1]
pub fn probe_node(node_hex: &str) -> bool {
    if spt_daemon::daemon::ensure_running().is_err() {
        return false;
    }
    let Ok(mut brain) = Brain::cold_start(&broker_socket_name(), now_ms()) else {
        return false;
    };
    let Some(addr) = addr_for_node_hex(node_hex).and_then(|a| serde_json::to_value(a).ok())
    else {
        return false;
    };
    brain.net_dial(addr, None).is_ok()
}

/// Load every per-subnet registry snapshot under `dir`. Missing dir or an
/// unparseable snapshot degrade to absent (the reader's stale-tolerant
/// contract) — resolution then simply finds nothing there.


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

/// Dial-probe a node (M7 D2, REQ-SUBNET-1 — the hybrid liveness's expensive
/// half, spent only on rows gossip-recency could not settle): true iff the
/// broker's QUIC endpoint can connect to it right now. Every failure mode
/// (daemon down, no dialable address, refused/timed-out dial) reads `false` —
/// the caller renders offline, never errors a status view.
// [impl->REQ-SUBNET-1]
pub fn probe_node(node_hex: &str) -> bool {
    if spt_daemon::daemon::ensure_running().is_err() {
        return false;
    }
    let Ok(mut brain) = Brain::cold_start(&broker_socket_name(), now_ms()) else {
        return false;
    };
    let Some(addr) = addr_for_node_hex(node_hex).and_then(|a| serde_json::to_value(a).ok())
    else {
        return false;
    };
    brain.net_dial(addr, None).is_ok()
}

/// Load every per-subnet registry snapshot under `dir`. Missing dir or an
/// unparseable snapshot degrade to absent (the reader's stale-tolerant
/// contract) — resolution then simply finds nothing there.

+/// Dial-probe a node (M7 D2, REQ-SUBNET-1 — the hybrid liveness's expensive

+/// half, spent only on rows gossip-recency could not settle): true iff the

+/// broker's QUIC endpoint can connect to it right now. Every failure mode

+/// (daemon down, no dialable address, refused/timed-out dial) reads `false` —

+/// the caller renders offline, never errors a status view.

+pub fn probe_node(node_hex: &str) -> bool {

+    if spt_daemon::daemon::ensure_running().is_err() {

+    let Ok(mut brain) = Brain::cold_start(&broker_socket_name(), now_ms()) else {

+    let Some(addr) = addr_for_node_hex(node_hex).and_then(|a| serde_json::to_value(a).ok())


        assert!(matches!(
            subnet_cmd(&["spt", "subnet", "status"]),
            Some(SubnetCmd::Status { name: None })
        ));
        assert!(matches!(
            subnet_cmd(&["spt", "subnet", "status", "home"]),
            Some(SubnetCmd::Status { name: Some(n) }) if n == "home"
        ));

        assert!(matches!(
            subnet_cmd(&["spt", "subnet", "status"]),
            Some(SubnetCmd::Status { name: None, nodes: false })
        ));
        assert!(matches!(
            subnet_cmd(&["spt", "subnet", "status", "home"]),
            Some(SubnetCmd::Status { name: Some(n), .. }) if n == "home"
        ));
        assert!(matches!(
            subnet_cmd(&["spt", "subnet", "status", "home", "--nodes"]),
            Some(SubnetCmd::Status { nodes: true, .. })
        ));


        assert!(matches!(
            subnet_cmd(&["spt", "subnet", "status"]),
            Some(SubnetCmd::Status { name: None })
        ));
        assert!(matches!(
            subnet_cmd(&["spt", "subnet", "status", "home"]),
            Some(SubnetCmd::Status { name: Some(n) }) if n == "home"
        ));

        assert!(matches!(
            subnet_cmd(&["spt", "subnet", "status"]),
            Some(SubnetCmd::Status { name: None, nodes: false })
        ));
        assert!(matches!(
            subnet_cmd(&["spt", "subnet", "status", "home"]),
            Some(SubnetCmd::Status { name: Some(n), .. }) if n == "home"
        ));
        assert!(matches!(
            subnet_cmd(&["spt", "subnet", "status", "home", "--nodes"]),
            Some(SubnetCmd::Status { nodes: true, .. })
        ));

assert!(subnet_cmd(&["spt", "subnet"]).is_none(), "bare subnet = status view");

-            Some(SubnetCmd::Status { name: None })

+            Some(SubnetCmd::Status { name: None, nodes: false })

-            Some(SubnetCmd::Status { name: Some(n) }) if n == "home"

+            Some(SubnetCmd::Status { name: Some(n), .. }) if n == "home"

+            subnet_cmd(&["spt", "subnet", "status", "home", "--nodes"]),

+            Some(SubnetCmd::Status { nodes: true, .. })

subnet_cmd(&["spt", "subnet", "create", "home"]),

Some(SubnetCmd::Create { name }) if name == "home"


Now D2c unit tests — append after the status render test:


// [unit->REQ-DOCS-6] how-to: bare lists every topic; each topic prints

    // [unit->REQ-SUBNET-1] [unit->REQ-SUBNET-3] the --nodes hybrid liveness
    // verdict is settled by the cheap signals first: own node online, an
    // all-Offline node offline, a recently-heard node online without a
    // probe, and ONLY the silent-but-routable remainder asks for a dial;
    // rows carry the advertised label + [active/total] endpoint counts.
    #[test]
    fn node_status_rows_settle_cheap_signals_probe_only_stale() {
        use spt_net::net::registry::{Instance, Status, SubnetRegistry};
        let inst = |node: &str, status: Status, label: Option<&str>| Instance {
            node: node.into(),
            status,
            epoch: 1,
            resources: None,
            last_active_ms: None,
            shell_adapters: Vec::new(),
            node_label: label.map(str::to_string),
        };
        let mut reg = SubnetRegistry::new();
        reg.merge_instance("a1", inst("self00aa", Status::Active, Some("ME")));
        reg.merge_instance("a2", inst("self00aa", Status::Dormant, Some("ME")));
        reg.merge_instance("b1", inst("heard0bb", Status::Active, Some("FRESH")));
        reg.merge_instance("c1", inst("dead00cc", Status::Offline, None));
        reg.merge_instance("d1", inst("stale0dd", Status::Active, Some("QUIET")));

        let now = 1_000_000u64;
        let heard =
            std::collections::BTreeMap::from([("heard0bb".to_string(), now - 10_000)]);
        let rows = node_status_rows(&reg, &heard, "self00aa", now, 33_000);
        let by: std::collections::BTreeMap<&str, &NodeRow> =
            rows.iter().map(|r| (r.node.as_str(), r)).collect();

        assert_eq!(by["self00aa"].liveness, NodeLiveness::Online, "own node never probes");
        assert_eq!((by["self00aa"].active, by["self00aa"].total), (1, 2));
        assert_eq!(by["heard0bb"].liveness, NodeLiveness::Online, "recently heard ⇒ online");
        assert_eq!(by["dead00cc"].liveness, NodeLiveness::Offline, "all-Offline ⇒ offline");
        assert_eq!(by["stale0dd"].liveness, NodeLiveness::Probe, "silent + routable ⇒ probe");
        assert_eq!(by["stale0dd"].label.as_deref(), Some("QUIET"));

        // Render: labels as `LABEL (keypfx…)`, bare prefix when unlabeled,
        // online/offline + [active/total]; never the full pubkey alone.
        let settled: Vec<(NodeRow, bool)> = rows
            .into_iter()
            .map(|r| {
                let online = r.liveness == NodeLiveness::Online;
                (r, online)
            })
            .collect();
        let view = render_node_rows("home", &settled);
        assert!(view.contains("SUBNET home"));
        assert!(view.contains("ME (self00aa…)"), "labeled node renders label + prefix: {view}");
        assert!(view.contains("dead00cc…"), "unlabeled node renders the bare prefix");
        assert!(view.contains("[1/2]"), "active/total counts render: {view}");
        assert!(view.contains("online"));
        assert!(view.contains("offline"));

        // Zero rows: an explanatory line, not an empty table.
        let none = render_node_rows("home", &[]);
        assert!(none.contains("no gossip yet"));
    }

    // [unit->REQ-DOCS-6] how-to: bare lists every topic; each topic prints


    // [unit->REQ-SUBNET-1] [unit->REQ-SUBNET-3] the --nodes hybrid liveness
    // verdict is settled by the cheap signals first: own node online, an
    // all-Offline node offline, a recently-heard node online without a
    // probe, and ONLY the silent-but-routable remainder asks for a dial;
    // rows carry the advertised label + [active/total] endpoint counts.
    #[test]
    fn node_status_rows_settle_cheap_signals_probe_only_stale() {
        use spt_net::net::registry::{Instance, Status, SubnetRegistry};
        let inst = |node: &str, status: Status, label: Option<&str>| Instance {
            node: node.into(),
            status,
            epoch: 1,
            resources: None,
            last_active_ms: None,
            shell_adapters: Vec::new(),
            node_label: label.map(str::to_string),
        };
        let mut reg = SubnetRegistry::new();
        reg.merge_instance("a1", inst("self00aa", Status::Active, Some("ME")));
        reg.merge_instance("a2", inst("self00aa", Status::Dormant, Some("ME")));
        reg.merge_instance("b1", inst("heard0bb", Status::Active, Some("FRESH")));
        reg.merge_instance("c1", inst("dead00cc", Status::Offline, None));
        reg.merge_instance("d1", inst("stale0dd", Status::Active, Some("QUIET")));

        let now = 1_000_000u64;
        let heard =
            std::collections::BTreeMap::from([("heard0bb".to_string(), now - 10_000)]);
        let rows = node_status_rows(&reg, &heard, "self00aa", now, 33_000);
        let by: std::collections::BTreeMap<&str, &NodeRow> =
            rows.iter().map(|r| (r.node.as_str(), r)).collect();

        assert_eq!(by["self00aa"].liveness, NodeLiveness::Online, "own node never probes");
        assert_eq!((by["self00aa"].active, by["self00aa"].total), (1, 2));
        assert_eq!(by["heard0bb"].liveness, NodeLiveness::Online, "recently heard ⇒ online");
        assert_eq!(by["dead00cc"].liveness, NodeLiveness::Offline, "all-Offline ⇒ offline");
        assert_eq!(by["stale0dd"].liveness, NodeLiveness::Probe, "silent + routable ⇒ probe");
        assert_eq!(by["stale0dd"].label.as_deref(), Some("QUIET"));

        // Render: labels as `LABEL (keypfx…)`, bare prefix when unlabeled,
        // online/offline + [active/total]; never the full pubkey alone.
        let settled: Vec<(NodeRow, bool)> = rows
            .into_iter()
            .map(|r| {
                let online = r.liveness == NodeLiveness::Online;
                (r, online)
            })
            .collect();
        let view = render_node_rows("home", &settled);
        assert!(view.contains("SUBNET home"));
        assert!(view.contains("ME (self00aa…)"), "labeled node renders label + prefix: {view}");
        assert!(view.contains("dead00cc…"), "unlabeled node renders the bare prefix");
        assert!(view.contains("[1/2]"), "active/total counts render: {view}");
        assert!(view.contains("online"));
        assert!(view.contains("offline"));

        // Zero rows: an explanatory line, not an empty table.
        let none = render_node_rows("home", &[]);
        assert!(none.contains("no gossip yet"));
    }

    // [unit->REQ-DOCS-6] how-to: bare lists every topic; each topic prints

assert_eq!((no_gossip[0].nodes, no_gossip[0].endpoints), (1, 0));

+    // [unit->REQ-SUBNET-1] [unit->REQ-SUBNET-3] the --nodes hybrid liveness

+    // verdict is settled by the cheap signals first: own node online, an

+    // all-Offline node offline, a recently-heard node online without a

+    // probe, and ONLY the silent-but-routable remainder asks for a dial;

+    // rows carry the advertised label + [active/total] endpoint counts.

+    fn node_status_rows_settle_cheap_signals_probe_only_stale() {

+        use spt_net::net::registry::{Instance, Status, SubnetRegistry};

+        let inst = |node: &str, status: Status, label: Option<&str>| Instance {

+            shell_adapters: Vec::new(),

+            node_label: label.map(str::to_string),

+        let mut reg = SubnetRegistry::new();

+        reg.merge_instance("a1", inst("self00aa", Status::Active, Some("ME")));

+        reg.merge_instance("a2", inst("self00aa", Status::Dormant, Some("ME")));

+        reg.merge_instance("b1", inst("heard0bb", Status::Active, Some("FRESH")));

+        reg.merge_instance("c1", inst("dead00cc", Status::Offline, None));

+        reg.merge_instance("d1", inst("stale0dd", Status::Active, Some("QUIET")));

+            std::collections::BTreeMap::from([("heard0bb".to_string(), now - 10_000)]);

+        let rows = node_status_rows(&reg, &heard, "self00aa", now, 33_000);

+        let by: std::collections::BTreeMap<&str, &NodeRow> =

+            rows.iter().map(|r| (r.node.as_str(), r)).collect();

+        assert_eq!(by["self00aa"].liveness, NodeLiveness::Online, "own node never probes");

+        assert_eq!((by["self00aa"].active, by["self00aa"].total), (1, 2));

+        assert_eq!(by["heard0bb"].liveness, NodeLiveness::Online, "recently heard ⇒ online");

+        assert_eq!(by["dead00cc"].liveness, NodeLiveness::Offline, "all-Offline ⇒ offline");

+        assert_eq!(by["stale0dd"].liveness, NodeLiveness::Probe, "silent + routable ⇒ probe");

+        assert_eq!(by["stale0dd"].label.as_deref(), Some("QUIET"));

+        // Render: labels as `LABEL (keypfx…)`, bare prefix when unlabeled,

+        // online/offline + [active/total]; never the full pubkey alone.

+        let settled: Vec<(NodeRow, bool)> = rows

+                let online = r.liveness == NodeLiveness::Online;

+        let view = render_node_rows("home", &settled);

+        assert!(view.contains("SUBNET home"));

+        assert!(view.contains("ME (self00aa…)"), "labeled node renders label + prefix: {view}");

+        assert!(view.contains("dead00cc…"), "unlabeled node renders the bare prefix");

+        assert!(view.contains("[1/2]"), "active/total counts render: {view}");

+        assert!(view.contains("online"));

+        assert!(view.contains("offline"));

+        // Zero rows: an explanatory line, not an empty table.

+        let none = render_node_rows("home", &[]);

+        assert!(none.contains("no gossip yet"));

// text naming the commands the guidance relies on; unknown is usage error.


Bash

cargo test -p spt --bin spt 2>&1 | grep -E "test result|FAILED|panicked|error\[|warning: unused" | head

Run spt unit tests


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


cargo test -p spt 2>&1 | grep -E "FAILED|failed" | grep -v "0 failed" | head; cargo test -p spt-daemon 2>&1 | grep -E "FAILED|failed" | grep -v "0 failed" | head; cargo test -p spt-net 2>&1 | grep -E "FAILED|failed" | grep -v "0 failed" | head; echo ALLDONE

Run all three crate test suites


test effect::tests::failed_effect_is_not_recorded_applied ... ok
ALLDONE


cargo build -p spt 2>&1 | tail -1 && SPT_HOME=$(mktemp -d) ./target/debug/spt.exe subnet status --nodes; echo "exit=$?"

Smoke-test subnet status --nodes


    Finished `dev` profile [unoptimized + debuginfo] target(s) in 22.72s
This node belongs to no subnet — a standalone node. Local messaging works without one; a subnet links your machines into one fabric.

hint: `spt subnet create <name>` mints a new subnet on this machine
hint: `spt subnet show-totp [name]` shows a subnet's joining code (elevated)
exit=0


cargo run -p xtask -- gen 2>&1 | tail -1 && cargo run -p xtask -- check 2>&1 | tail -1 && traceable-reqs check >/dev/null 2>&1; echo "reqs=$?"

Regen reference + drift gate + traceability


xtask gen: wrote C:\Users\decid\Documents\projects\spt-core\docs-site/src/cli/reference.md
xtask check: OK
reqs=0


git add -A && git commit -m "$(cat <<'EOF'
feat(net,daemon,cli): M7 D2 — node labels, addressable @node qualifiers, status --nodes

The node-labels & status-views deliverable (M7-PLAN.md D2, REQ-SUBNET-3 +
the REQ-SUBNET-1 status views):

- Instance.node_label: additive serde-default field riding the existing
  registry gossip (old rows parse clean — the resources/presence additive-
  field discipline). RegistryHost reads the OS hostname at daemon startup
  (new spt_store::hostlabel; COMPUTERNAME on Windows, gethostname(2) on
  unix) and stamps it onto every advertised row, so a hostname change
  converges by ordinary epoch-bumped re-advertisement.
- @node qualifiers (decision 7): full hex, key-prefix, or label — all
  case-insensitive. A qualifier matching several distinct nodes refuses
  with Ambiguity::NodeQualifier carrying paste-able 8-hex candidate
  prefixes: machines sharing a hostname are never guessed between.
- subnet status --nodes (decision 6): per-node rows rendered as
  `LABEL (keypfx…)`, online/offline, [active/total] endpoints. Hybrid
  liveness: own node + all-Offline + gossip-recency settle cheap (the
  daemon now stamps a heard-map — origin node → last admitted-feed ms —
  mirrored to identity/registry/heard.meta; admitted-but-Stale still
  counts, the sender proved alive); only silent-but-routable rows pay a
  dial probe (wansend::probe_node over the broker QUIC endpoint), and
  every probe failure reads offline, fail-quiet.
- Activated REQ-SUBNET-3 to [impl, unit] (rule 5).
- cli/reference.md regenerated; xtask check, traceable-reqs check, and
  the spt/spt-net/spt-daemon suites green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
EOF
)" && git log -1 --oneline

Commit M7 D2


warning: in the working copy of 'crates/spt-daemon/src/reconcile.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-daemon/src/registryhost.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-daemon/src/shelldisc.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-daemon/tests/peerloop.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-daemon/tests/replicate.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-daemon/tests/sync.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-net/src/net/registry.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-net/src/net/replicate.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-net/src/net/wanmsg.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-store/src/lib.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt/src/cli.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt/src/wansend.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'docs-site/src/cli/reference.md', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-store/src/hostlabel.rs', LF will be replaced by CRLF the next time Git touches it
[dev-freeform 3d909bb] feat(net,daemon,cli): M7 D2 — node labels, addressable @node qualifiers, status --nodes
 18 files changed, 622 insertions(+), 36 deletions(-)
 create mode 100644 crates/spt-store/src/hostlabel.rs
3d909bb feat(net,daemon,cli): M7 D2 — node labels, addressable @node qualifiers, status --nodes


Write

M7 D2 (node labels & status views) COMPLETE — committed 3d909bb on dev-freeform (D1 was 0f85f1d, same session).

Delivered per M7-PLAN.md D2:
- Instance.node_label additive gossip field (old rows parse clean); spt_store::hostlabel (COMPUTERNAME / gethostname) read at daemon startup, stamped on every advertised row via RegistryHost::with_node_label.
- @node qualifiers: full hex / key-prefix / label, case-insensitive; shared-label collision refuses with Ambiguity::NodeQualifier + paste-able 8-hex candidate prefixes.
- subnet status --nodes: `LABEL (keypfx…)` rows, online/offline, [active/total]. Hybrid liveness: own-node/all-Offline/heard-recency settle cheap; daemon stamps heard-map (origin → last admitted-feed ms) mirrored to identity/registry/heard.meta; only silent-but-routable rows pay wansend::probe_node dial.
- REQ-SUBNET-3 activated [impl, unit]. All green: spt/spt-net/spt-daemon suites, xtask check (reference regen), traceable-reqs check.

Next: **D3 (join machinery)** — the milestone's main new feature: always-on pre-trust pairing responder ALPN in member daemons (rate limiter + one-ceremony-per-subnet riding it), `spt subnet join [NAME] [--code]` guided CLI initiator (LAN+relay rendezvous, wrong-code re-prompt, no-seed-holder hints), elevation gates on create/join (REQ-SUBNET-4 activation + tags), seed transfer + trust pinning + home adoption on join, ADR-0005 amendment, join hint line added to status footer. Then D4 docs, D5 CI/acceptance.

Key pointers: pairing wire = spt_net::net::pairing (run_initiator/run_responder, rig-proven in twohost.rs); elevation gate = crates/spt/src/elevation.rs (EXIT_NOT_ELEVATED=3).


File created successfully at: C:\Users\decid\Documents\projects\spt-core\.claude\doyle-commune.md (file state is current in your context — no need to Read it back)

M7 D2 (node labels & status views) COMPLETE — committed 3d909bb on dev-freeform (D1 was 0f85f1d, same session).

Delivered per M7-PLAN.md D2:
- Instance.node_label additive gossip field (old rows parse clean); spt_store::hostlabel (COMPUTERNAME / gethostname) read at daemon startup, stamped on every advertised row via RegistryHost::with_node_label.
- @node qualifiers: full hex / key-prefix / label, case-insensitive; shared-label collision refuses with Ambiguity::NodeQualifier + paste-able 8-hex candidate prefixes.
- subnet status --nodes: `LABEL (keypfx…)` rows, online/offline, [active/total]. Hybrid liveness: own-node/all-Offline/heard-recency settle cheap; daemon stamps heard-map (origin → last admitted-feed ms) mirrored to identity/registry/heard.meta; only silent-but-routable rows pay wansend::probe_node dial.
- REQ-SUBNET-3 activated [impl, unit]. All green: spt/spt-net/spt-daemon suites, xtask check (reference regen), traceable-reqs check.

Next: **D3 (join machinery)** — the milestone's main new feature: always-on pre-trust pairing responder ALPN in member daemons (rate limiter + one-ceremony-per-subnet riding it), `spt subnet join [NAME] [--code]` guided CLI initiator (LAN+relay rendezvous, wrong-code re-prompt, no-seed-holder hints), elevation gates on create/join (REQ-SUBNET-4 activation + tags), seed transfer + trust pinning + home adoption on join, ADR-0005 amendment, join hint line added to status footer. Then D4 docs, D5 CI/acceptance.

Key pointers: pairing wire = spt_net::net::pairing (run_initiator/run_responder, rig-proven in twohost.rs); elevation gate = crates/spt/src/elevation.rs (EXIT_NOT_ELEVATED=3).


Bash

ls crates/spt-net/src/net/ crates/spt-net/src/net/pairing/ 2>/dev/null

List spt-net modules


crates/spt-net/src/net/:
attach.rs
endpoint.rs
ndjson.rs
notif.rs
pairing
pairing.rs
registry.rs
replicate.rs
rest.rs
shelllink.rs
sync.rs
update.rs
wanmsg.rs
xfer.rs

crates/spt-net/src/net/pairing/:
meet.rs
ratelimit.rs
rendezvous.rs
spake.rs
totp.rs
transcript.rs
wire.rs