/// `spt pair show-totp` — surface a subnet's current code off this node, behind the
/// OS-elevation gate (REQ-PAIR-3/6). Wires the real store, clock, and elevation probe
/// into the unit-tested [`resolve`]/[`decide_show_totp`] core.
// [impl->REQ-PAIR-3] [impl->REQ-PAIR-6]
fn cmd_pair_show_totp(subnet: Option<String>, create_new: Option<String>) -> i32 {
    let mut store = SubnetStore::load();
    let resolved = resolve(&mut store, subnet, create_new);
    let result = decide_show_totp(resolved, now_unix(), elevation::current());

    if result.save {
        if let Err(e) = store.save() {
            eprintln!("SAVE_FAIL: freshly minted subnet not persisted: {e}");
            return 1;
        }
        // First-join home adoption (REQ-INST-15): a fresh subnet on a
        // previously-unpaired node homes every local-only Self perch.
        let mut vis = spt_store::visibility::VisibilityStore::load();
        let adopted =
            spt_store::home::adopt_for_unset(&perch::owlery_dir(), &store, &mut vis);
        if !adopted.is_empty() {
            let _ = vis.save();
            eprintln!("HOME_ADOPTED:{}", adopted.join(","));
        }
    }
    if let Some(out) = result.out {
        print!("{out}");
        let _ = std::io::stdout().flush();
    }
    eprintln!("{}", result.msg);
    result.code
}

/// Decide `subnet create`'s rendered outcome from a resolution + clock + elevation.
/// Same gate-last shape as [`decide_show_totp`] — a refused mint persists nothing and
/// leaks nothing — but the elevated output is the full joining material (M7 plan
/// decision 11): code, `otpauth://` URI, terminal QR, and the join hint.
// [impl->REQ-SUBNET-1]
fn decide_create(resolved: Resolved, now: u64, elevation: Elevation) -> ShowResult {
    match resolved {
        Resolved::Subnet { name, seed, minted } => match elevation::gate_for(elevation) {
            Gate::Show => {
                let totp = TotpSeed::from_bytes(seed);
                let uri = totp.otpauth_uri(ISSUER, &name);
                let mut out = format!("{}\n{}\n", totp.code_at(now), uri);
                // Terminal QR of the provisioning URI — scan it into an
                // authenticator app from the other machine's side of the desk.
                if let Some(qr) = qr_unicode(&uri) {
                    out.push_str(&qr);
                    out.push('\n');
                }
                out.push_str(&format!(
                    "Run `spt subnet join {name}` on the other machine to pair it in.\n"
                ));
                ShowResult {
                    code: 0,
                    out: Some(out),
                    msg: format!("SUBNET_CREATED:{name} (this node is the sole seed-holder)"),
                    save: minted,
                }
            }
            // Refused mint: nothing persists, nothing leaks (the seed-reveal
            // path needs confirmed privilege — same posture as show-totp).
            Gate::Fallback => ShowResult {
                code: EXIT_NOT_ELEVATED,
                out: None,
                msg: format!(
                    "ELEVATION_REQUIRED: creating subnet '{name}' reveals its joining secret — \
                     re-run elevated (run as administrator / root)"
                ),
                save: false,
            },
        },
        other => decide_show_totp(other, now, elevation),
    }
}

/// Render the provisioning URI as a terminal QR (unicode half-blocks). `None` when
/// encoding fails (URI too long for the QR alphabet) — the URI text above it is the
/// fallback, so create degrades gracefully instead of refusing.
fn qr_unicode(uri: &str) -> Option<String> {
    let code = qrcode::QrCode::new(uri.as_bytes()).ok()?;
    Some(code.render::<qrcode::render::unicode::Dense1x2>().quiet_zone(true).build())
}

/// Persist a freshly-minted subnet + fire first-join home adoption (REQ-INST-15):
/// a fresh subnet on a previously-unpaired node homes every local-only Self perch.
fn save_minted(store: &SubnetStore) -> i32 {
    if let Err(e) = store.save() {
        eprintln!("SAVE_FAIL: freshly minted subnet not persisted: {e}");
        return 1;
    }
    let mut vis = spt_store::visibility::VisibilityStore::load();
    let adopted = spt_store::home::adopt_for_unset(&perch::owlery_dir(), store, &mut vis);
    if !adopted.is_empty() {
        let _ = vis.save();
        eprintln!("HOME_ADOPTED:{}", adopted.join(","));
    }
    0
}

/// Print a [`ShowResult`]: stdout block (if any), stderr status, exit code.
fn finish_show(result: ShowResult, store: &SubnetStore) -> i32 {
    if result.save {
        let code = save_minted(store);
        if code != 0 {
            return code;
        }
    }
    if let Some(out) = result.out {
        print!("{out}");
        let _ = std::io::stdout().flush();
    }
    eprintln!("{}", result.msg);
    result.code
}

/// `spt subnet show-totp [NAME]` — surface a subnet's current code off this node,
/// behind the OS-elevation gate (REQ-PAIR-3/6; ported off `spt pair show-totp`, M7
/// plan decision 3). Wires the real store, clock, and elevation probe into the
/// unit-tested [`resolve`]/[`decide_show_totp`] core.
// [impl->REQ-PAIR-3] [impl->REQ-PAIR-6] [impl->REQ-SUBNET-1]
fn cmd_subnet_show_totp(name: Option<String>) -> i32 {
    let mut store = SubnetStore::load();
    let resolved = resolve(&mut store, name, None);
    let result = decide_show_totp(resolved, now_unix(), elevation::current());
    finish_show(result, &store)
}

/// `spt subnet create <NAME>` — mint a fresh subnet (this node becomes the sole
/// seed-holder) and print its joining material (REQ-SUBNET-1; the verb-as-flag
/// `show-totp --create-new` un-buried, M7 plan decisions 3 + 11). Elevation-gated:
/// the output reveals the subnet's joining secret.
// [impl->REQ-SUBNET-1]
fn cmd_subnet_create(name: String) -> i32 {
    let mut store = SubnetStore::load();
    let resolved = resolve(&mut store, None, Some(name));
    let result = decide_create(resolved, now_unix(), elevation::current());
    finish_show(result, &store)
}

/// One subnet's status-view row: membership shape only — never seeds, epochs,
/// or codes (M7 plan decision 5).
struct SubnetStatusRow {
    name: String,
    /// Paired nodes known for the subnet (this node included).
    nodes: usize,
    /// Endpoints visible in the subnet's registry snapshot.
    endpoints: usize,
}

/// Build the status rows from the subnet store + registry snapshots. Pure over
/// its inputs so the view is unit-tested hermetically. Counts are
/// gossip-derived and stale-tolerant: a missing snapshot renders as this node
/// alone with no advertised endpoints (counts catch up as gossip lands).
// [impl->REQ-SUBNET-1]
fn subnet_status_rows(
    store: &SubnetStore,
    regs: &std::collections::BTreeMap<String, spt_net::net::registry::SubnetRegistry>,
    only: Option<&str>,
) -> Vec<SubnetStatusRow> {
    store
        .subnets
        .iter()
        .filter(|s| only.is_none_or(|n| n == s.name))
        .map(|s| {
            let (mut nodes, mut endpoints) = (std::collections::BTreeSet::new(), 0usize);
            if let Some(reg) = regs.get(&s.name) {
                for id in reg.endpoint_ids() {
                    endpoints += 1;
                    for inst in reg.instances(id) {
                        nodes.insert(inst.node.clone());
                    }
                }
            }
            // This node is a member even before its first row gossips in.
            SubnetStatusRow { name: s.name.clone(), nodes: nodes.len().max(1), endpoints }
        })
        .collect()
}

/// Render the status view (M7 plan decision 5): aligned rows, the hint footer
/// always, explanatory standalone-node text on zero subnets. D2 adds
/// `--nodes` per-node rows + hybrid liveness; D3 adds the join hint.
// [impl->REQ-SUBNET-1]
fn render_subnet_status(rows: &[SubnetStatusRow]) -> String {
    let mut out = String::new();
    if rows.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",
        );
    } else {
        let pad = rows.iter().map(|r| r.name.len()).max().unwrap_or(0).max("SUBNET".len());
        out.push_str(&format!("{:<pad$}  NODES  ENDPOINTS\n", "SUBNET"));
        for r in rows {
            out.push_str(&format!("{:<pad$}  {:<5}  {}\n", r.name, r.nodes, r.endpoints));
        }
    }
    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",
    );
    out
}

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

-/// `spt pair show-totp` — surface a subnet's current code off this node, behind the

-/// OS-elevation gate (REQ-PAIR-3/6). Wires the real store, clock, and elevation probe

-/// into the unit-tested [`resolve`]/[`decide_show_totp`] core.

-fn cmd_pair_show_totp(subnet: Option<String>, create_new: Option<String>) -> i32 {

-    let mut store = SubnetStore::load();

-    let resolved = resolve(&mut store, subnet, create_new);

-    let result = decide_show_totp(resolved, now_unix(), elevation::current());

+/// Decide `subnet create`'s rendered outcome from a resolution + clock + elevation.

+/// Same gate-last shape as [`decide_show_totp`] — a refused mint persists nothing and

+/// leaks nothing — but the elevated output is the full joining material (M7 plan

+/// decision 11): code, `otpauth://` URI, terminal QR, and the join hint.

+fn decide_create(resolved: Resolved, now: u64, elevation: Elevation) -> ShowResult {

+        Resolved::Subnet { name, seed, minted } => match elevation::gate_for(elevation) {

+                let totp = TotpSeed::from_bytes(seed);

+                let uri = totp.otpauth_uri(ISSUER, &name);

+                let mut out = format!("{}\n{}\n", totp.code_at(now), uri);

+                // Terminal QR of the provisioning URI — scan it into an

+                // authenticator app from the other machine's side of the desk.

+                if let Some(qr) = qr_unicode(&uri) {

+                    "Run `spt subnet join {name}` on the other machine to pair it in.\n"

+                    msg: format!("SUBNET_CREATED:{name} (this node is the sole seed-holder)"),

+            // Refused mint: nothing persists, nothing leaks (the seed-reveal

+            // path needs confirmed privilege — same posture as show-totp).

+            Gate::Fallback => ShowResult {

+                code: EXIT_NOT_ELEVATED,

+                    "ELEVATION_REQUIRED: creating subnet '{name}' reveals its joining secret — \

+                     re-run elevated (run as administrator / root)"

+        other => decide_show_totp(other, now, elevation),

+/// Render the provisioning URI as a terminal QR (unicode half-blocks). `None` when

+/// encoding fails (URI too long for the QR alphabet) — the URI text above it is the

+/// fallback, so create degrades gracefully instead of refusing.

+fn qr_unicode(uri: &str) -> Option<String> {

+    let code = qrcode::QrCode::new(uri.as_bytes()).ok()?;

+    Some(code.render::<qrcode::render::unicode::Dense1x2>().quiet_zone(true).build())

+/// Persist a freshly-minted subnet + fire first-join home adoption (REQ-INST-15):

+/// a fresh subnet on a previously-unpaired node homes every local-only Self perch.

+fn save_minted(store: &SubnetStore) -> i32 {

+        eprintln!("SAVE_FAIL: freshly minted subnet not persisted: {e}");

+    let mut vis = spt_store::visibility::VisibilityStore::load();

+    let adopted = spt_store::home::adopt_for_unset(&perch::owlery_dir(), store, &mut vis);

+        eprintln!("HOME_ADOPTED:{}", adopted.join(","));

+/// Print a [`ShowResult`]: stdout block (if any), stderr status, exit code.

+fn finish_show(result: ShowResult, store: &SubnetStore) -> i32 {

-            eprintln!("SAVE_FAIL: freshly minted subnet not persisted: {e}");

-        // First-join home adoption (REQ-INST-15): a fresh subnet on a

-        // previously-unpaired node homes every local-only Self perch.

-        let mut vis = spt_store::visibility::VisibilityStore::load();

-            spt_store::home::adopt_for_unset(&perch::owlery_dir(), &store, &mut vis);

-            eprintln!("HOME_ADOPTED:{}", adopted.join(","));

+/// `spt subnet show-totp [NAME]` — surface a subnet's current code off this node,

+/// behind the OS-elevation gate (REQ-PAIR-3/6; ported off `spt pair show-totp`, M7

+/// plan decision 3). Wires the real store, clock, and elevation probe into the

+/// unit-tested [`resolve`]/[`decide_show_totp`] core.

+// [impl->REQ-PAIR-3] [impl->REQ-PAIR-6] [impl->REQ-SUBNET-1]

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

+    let mut store = SubnetStore::load();

+    let resolved = resolve(&mut store, name, None);

+    let result = decide_show_totp(resolved, now_unix(), elevation::current());

+/// `spt subnet create <NAME>` — mint a fresh subnet (this node becomes the sole

+/// seed-holder) and print its joining material (REQ-SUBNET-1; the verb-as-flag

+/// `show-totp --create-new` un-buried, M7 plan decisions 3 + 11). Elevation-gated:

+/// the output reveals the subnet's joining secret.

+fn cmd_subnet_create(name: String) -> i32 {

+    let resolved = resolve(&mut store, None, Some(name));

+    let result = decide_create(resolved, now_unix(), elevation::current());

+/// One subnet's status-view row: membership shape only — never seeds, epochs,

+    /// Paired nodes known for the subnet (this node included).

+    /// Endpoints visible in the subnet's registry snapshot.

+/// Build the status rows from the subnet store + registry snapshots. Pure over

+/// its inputs so the view is unit-tested hermetically. Counts are

+/// gossip-derived and stale-tolerant: a missing snapshot renders as this node

+/// alone with no advertised endpoints (counts catch up as gossip lands).

+    regs: &std::collections::BTreeMap<String, spt_net::net::registry::SubnetRegistry>,

+        .filter(|s| only.is_none_or(|n| n == s.name))

+            let (mut nodes, mut endpoints) = (std::collections::BTreeSet::new(), 0usize);

+            if let Some(reg) = regs.get(&s.name) {

+                for id in reg.endpoint_ids() {

+                    for inst in reg.instances(id) {

+                        nodes.insert(inst.node.clone());

+            // This node is a member even before its first row gossips in.

+            SubnetStatusRow { name: s.name.clone(), nodes: nodes.len().max(1), endpoints }

+/// Render the status view (M7 plan decision 5): aligned rows, the hint footer

+/// always, explanatory standalone-node text on zero subnets. D2 adds

+/// `--nodes` per-node rows + hybrid liveness; D3 adds the join hint.

+fn render_subnet_status(rows: &[SubnetStatusRow]) -> String {

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

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

+        let pad = rows.iter().map(|r| r.name.len()).max().unwrap_or(0).max("SUBNET".len());

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

+            out.push_str(&format!("{:<pad$}  {:<5}  {}\n", r.name, r.nodes, r.endpoints));

+        "\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",

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

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

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

+            eprintln!("NO_SUCH_SUBNET:{n}");

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

/// Current unix time in whole seconds (the TOTP clock input). A pre-epoch clock

/// degrades to step 0 rather than panicking.

/// `spt rename <old> <new>` — the on-disk rename ripple (REQ-INST-11). Validates


crates\spt-store\src\contextstore.rs:220:            if prefix.as_ref().is_none_or(|p| name.starts_with(p.as_str())) {
crates\spt\src\cli.rs:1708:        .filter(|s| only.is_none_or(|n| n == s.name))
crates\spt\src\cli.rs:2774:                .filter(|r| agent.as_deref().is_none_or(|a| r.agent == a))


Now the how-to command — append after the subnet status block:


Edit

    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
}

    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
}

// ------------------------------------------------------------------ how-to

/// `spt how-to ready` — listener guidance for agents.
const HOW_TO_READY: &str = "\
# how-to: ready — receive messages

Pick a short lowercase perch id for yourself (`<id>` below). `spt whoami`
prints it if your session already has one.

Preferred — your harness can host a long-running background task:

    spt ready <id>

- Blocks for the session's life: run it as a background task and read its stdout.
- `READY:<id>` on stderr is the online signal.
- The spooled backlog drains first; then each delivery prints to stdout as it
  arrives.
- Delivery shape on stdout: a `__REPLY_TO__:<sender>` header line, then the body.

Fallback — no long-running tasks available (the one-shot loop):

    spt ready <id> --once

- Drains the backlog, waits for one live delivery, then exits.
- Re-run it after every exit to stay reachable.

Replying: pipe the body to `spt send --reply-to <sender>` — `<sender>` is the id
from the `__REPLY_TO__` header.

Going offline cleanly: `spt stop <id>` (the spool is preserved; messages sent
while you are away queue and drain on your next `ready`).
";

/// `spt how-to send` — delivery guidance for agents.
const HOW_TO_SEND: &str = "\
# how-to: send — deliver messages

Fire-and-forget (the body is read from stdin):

    spt send <target>

- `SENT:<target>`   — delivered live to a listening agent.
- `QUEUED:<target>` — the target is not listening; the message spooled and
  drains when it next runs `spt ready`. QUEUED is success, not an error — do
  not retry.

Reply to a message you received (body from stdin; prints `REPLIED:<sender>`):

    spt send --reply-to <sender>

Send and block for the reply (body from stdin; the reply body prints to stdout):

    spt ring <target> --timeout 60

- `TIMEOUT` after the wait is exit 0 — the message still landed; the reply will
  arrive on your listener instead.

Your own sender id is auto-detected in most sessions; `--from <id>` overrides.
";

/// The `how-to` topic registry: (name, one-line summary, full text). v1 topics
/// per M7 plan decision 12; bare `how-to` lists this table.
const HOW_TO_TOPICS: &[(&str, &str, &str)] = &[
    ("ready", "receive messages: listener guidance + the --once fallback loop", HOW_TO_READY),
    ("send", "deliver messages: send vs ring, replies, SENT vs QUEUED", HOW_TO_SEND),
];

/// `spt how-to [topic]` — in-binary, task-oriented agent instructions
/// (REQ-DOCS-6). Single source = this binary: the docs-site points here and
/// never duplicates the texts (the DOCS-STRATEGY anti-drift posture applied
/// to agent guidance), so what an agent reads cannot disagree with the
/// binary it runs.
// [impl->REQ-DOCS-6]
fn cmd_how_to(topic: Option<&str>) -> i32 {
    let list = || {
        let pad = HOW_TO_TOPICS.iter().map(|(n, ..)| n.len()).max().unwrap_or(0);
        HOW_TO_TOPICS
            .iter()
            .map(|(n, summary, _)| format!("  {n:<pad$}  {summary}\n"))
            .collect::<String>()
    };
    match topic {
        None => {
            print!("Topics (`spt how-to <topic>`):\n{}", list());
            let _ = std::io::stdout().flush();
            0
        }
        Some(t) => match HOW_TO_TOPICS.iter().find(|(name, ..)| *name == t) {
            Some((_, _, text)) => {
                print!("{text}");
                let _ = std::io::stdout().flush();
                0
            }
            None => {
                eprintln!("NO_SUCH_TOPIC:{t} — topics:\n{}", list());
                2
            }
        },
    }
}


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)

    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
}

// ------------------------------------------------------------------ how-to

/// `spt how-to ready` — listener guidance for agents.
const HOW_TO_READY: &str = "\
# how-to: ready — receive messages

Pick a short lowercase perch id for yourself (`<id>` below). `spt whoami`
prints it if your session already has one.

Preferred — your harness can host a long-running background task:

    spt ready <id>

- Blocks for the session's life: run it as a background task and read its stdout.
- `READY:<id>` on stderr is the online signal.
- The spooled backlog drains first; then each delivery prints to stdout as it
  arrives.
- Delivery shape on stdout: a `__REPLY_TO__:<sender>` header line, then the body.

Fallback — no long-running tasks available (the one-shot loop):

    spt ready <id> --once

- Drains the backlog, waits for one live delivery, then exits.
- Re-run it after every exit to stay reachable.

Replying: pipe the body to `spt send --reply-to <sender>` — `<sender>` is the id
from the `__REPLY_TO__` header.

Going offline cleanly: `spt stop <id>` (the spool is preserved; messages sent
while you are away queue and drain on your next `ready`).
";

/// `spt how-to send` — delivery guidance for agents.
const HOW_TO_SEND: &str = "\
# how-to: send — deliver messages

Fire-and-forget (the body is read from stdin):

    spt send <target>

- `SENT:<target>`   — delivered live to a listening agent.
- `QUEUED:<target>` — the target is not listening; the message spooled and
  drains when it next runs `spt ready`. QUEUED is success, not an error — do
  not retry.

Reply to a message you received (body from stdin; prints `REPLIED:<sender>`):

    spt send --reply-to <sender>

Send and block for the reply (body from stdin; the reply body prints to stdout):

    spt ring <target> --timeout 60

- `TIMEOUT` after the wait is exit 0 — the message still landed; the reply will
  arrive on your listener instead.

Your own sender id is auto-detected in most sessions; `--from <id>` overrides.
";

/// The `how-to` topic registry: (name, one-line summary, full text). v1 topics
/// per M7 plan decision 12; bare `how-to` lists this table.
const HOW_TO_TOPICS: &[(&str, &str, &str)] = &[
    ("ready", "receive messages: listener guidance + the --once fallback loop", HOW_TO_READY),
    ("send", "deliver messages: send vs ring, replies, SENT vs QUEUED", HOW_TO_SEND),
];

/// `spt how-to [topic]` — in-binary, task-oriented agent instructions
/// (REQ-DOCS-6). Single source = this binary: the docs-site points here and
/// never duplicates the texts (the DOCS-STRATEGY anti-drift posture applied
/// to agent guidance), so what an agent reads cannot disagree with the
/// binary it runs.
// [impl->REQ-DOCS-6]
fn cmd_how_to(topic: Option<&str>) -> i32 {
    let list = || {
        let pad = HOW_TO_TOPICS.iter().map(|(n, ..)| n.len()).max().unwrap_or(0);
        HOW_TO_TOPICS
            .iter()
            .map(|(n, summary, _)| format!("  {n:<pad$}  {summary}\n"))
            .collect::<String>()
    };
    match topic {
        None => {
            print!("Topics (`spt how-to <topic>`):\n{}", list());
            let _ = std::io::stdout().flush();
            0
        }
        Some(t) => match HOW_TO_TOPICS.iter().find(|(name, ..)| *name == t) {
            Some((_, _, text)) => {
                print!("{text}");
                let _ = std::io::stdout().flush();
                0
            }
            None => {
                eprintln!("NO_SUCH_TOPIC:{t} — topics:\n{}", list());
                2
            }
        },
    }
}

+/// `spt how-to ready` — listener guidance for agents.

+Pick a short lowercase perch id for yourself (`<id>` below). `spt whoami`

+prints it if your session already has one.

+Preferred — your harness can host a long-running background task:

+- Blocks for the session's life: run it as a background task and read its stdout.

+- `READY:<id>` on stderr is the online signal.

+- The spooled backlog drains first; then each delivery prints to stdout as it

+- Delivery shape on stdout: a `__REPLY_TO__:<sender>` header line, then the body.

+Fallback — no long-running tasks available (the one-shot loop):

+- Drains the backlog, waits for one live delivery, then exits.

+- Re-run it after every exit to stay reachable.

+Replying: pipe the body to `spt send --reply-to <sender>` — `<sender>` is the id

+Going offline cleanly: `spt stop <id>` (the spool is preserved; messages sent

+while you are away queue and drain on your next `ready`).

+/// `spt how-to send` — delivery guidance for agents.

+Fire-and-forget (the body is read from stdin):

+- `SENT:<target>`   — delivered live to a listening agent.

+- `QUEUED:<target>` — the target is not listening; the message spooled and

+  drains when it next runs `spt ready`. QUEUED is success, not an error — do

+Reply to a message you received (body from stdin; prints `REPLIED:<sender>`):

+Send and block for the reply (body from stdin; the reply body prints to stdout):

+- `TIMEOUT` after the wait is exit 0 — the message still landed; the reply will

+Your own sender id is auto-detected in most sessions; `--from <id>` overrides.

+/// The `how-to` topic registry: (name, one-line summary, full text). v1 topics

+/// per M7 plan decision 12; bare `how-to` lists this table.

+const HOW_TO_TOPICS: &[(&str, &str, &str)] = &[

+    ("ready", "receive messages: listener guidance + the --once fallback loop", HOW_TO_READY),

+    ("send", "deliver messages: send vs ring, replies, SENT vs QUEUED", HOW_TO_SEND),

+/// `spt how-to [topic]` — in-binary, task-oriented agent instructions

+/// (REQ-DOCS-6). Single source = this binary: the docs-site points here and

+/// never duplicates the texts (the DOCS-STRATEGY anti-drift posture applied

+/// to agent guidance), so what an agent reads cannot disagree with the

+fn cmd_how_to(topic: Option<&str>) -> i32 {

+        let pad = HOW_TO_TOPICS.iter().map(|(n, ..)| n.len()).max().unwrap_or(0);

+            .map(|(n, summary, _)| format!("  {n:<pad$}  {summary}\n"))

+            print!("Topics (`spt how-to <topic>`):\n{}", list());

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

+        Some(t) => match HOW_TO_TOPICS.iter().find(|(name, ..)| *name == t) {

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

+                eprintln!("NO_SUCH_TOPIC:{t} — topics:\n{}", list());

/// Current unix time in whole seconds (the TOTP clock input). A pre-epoch clock

/// degrades to step 0 rather than panicking.

/// `spt rename <old> <new>` — the on-disk rename ripple (REQ-INST-11). Validates


    /// A subnet to surface: its name + raw seed bytes. `minted` = freshly created by
    /// `--create-new` (persist it only once the code is actually shown).

    /// A subnet to surface: its name + raw seed bytes. `minted` = freshly created by
    /// `subnet create` (persist it only once the code is actually shown).


    /// A subnet to surface: its name + raw seed bytes. `minted` = freshly created by
    /// `--create-new` (persist it only once the code is actually shown).

    /// A subnet to surface: its name + raw seed bytes. `minted` = freshly created by
    /// `subnet create` (persist it only once the code is actually shown).

-    /// `--create-new` (persist it only once the code is actually shown).

+    /// `subnet create` (persist it only once the code is actually shown).

Subnet { name: String, seed: [u8; spt_store::subnet::TOTP_SEED_LEN], minted: bool },

/// Several subnets and no `--subnet` — refuse rather than guess.


    /// A named subnet (`--subnet`) is not in the store.
    NotFound(String),
    /// `--create-new <name>` for a subnet that already exists.
    Exists(String),

    /// A named subnet is not in the store.
    NotFound(String),
    /// `subnet create <name>` for a subnet that already exists.
    Exists(String),


    /// A named subnet (`--subnet`) is not in the store.
    NotFound(String),
    /// `--create-new <name>` for a subnet that already exists.
    Exists(String),

    /// A named subnet is not in the store.
    NotFound(String),
    /// `subnet create <name>` for a subnet that already exists.
    Exists(String),

/// The node belongs to no subnet at all.

-    /// A named subnet (`--subnet`) is not in the store.

+    /// A named subnet is not in the store.

-    /// `--create-new <name>` for a subnet that already exists.

+    /// `subnet create <name>` for a subnet that already exists.

/// The stored seed is unreadable (corrupt hex/length) — re-pair to re-provision.


/// Resolve `show-totp` arguments against the store (mutating it iff `--create-new`
/// mints). The mint is in-memory only; the caller persists it just on the show path.

/// Resolve `show-totp`/`create` arguments against the store (mutating it iff a create
/// mints). The mint is in-memory only; the caller persists it just on the show path.


/// Resolve `show-totp` arguments against the store (mutating it iff `--create-new`
/// mints). The mint is in-memory only; the caller persists it just on the show path.

/// Resolve `show-totp`/`create` arguments against the store (mutating it iff a create
/// mints). The mint is in-memory only; the caller persists it just on the show path.

-/// Resolve `show-totp` arguments against the store (mutating it iff `--create-new`

+/// Resolve `show-totp`/`create` arguments against the store (mutating it iff a create

fn resolve(store: &mut SubnetStore, subnet: Option<String>, create_new: Option<String>) -> Resolved {


Cargo.toml — qrcode dep + description sweep:


description = "spt-core CLI: the single `spt` binary. Thin command layer over spt-msg (send/ring/ready/poll/list/stop/whoami) + the harness-contract `api` surface (spt-runtime)."

description = "spt-core CLI: the single `spt` binary. Thin command layer over spt-msg (send/ring/ready/list/stop/whoami) + the harness-contract `api` surface (spt-runtime)."


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

[package]
name = "spt"
version.workspace = true
edition = "2021"
publish = false
description = "spt-core CLI: the single `spt` binary. Thin command layer over spt-msg (send/ring/ready/poll/list/stop/whoami) + the harness-contract `api` surface (spt-runtime)."

[[bin]]
name = "spt"
path = "src/main.rs"

[dependencies]
spt-proto = { path = "../spt-proto" }
spt-store = { path = "../spt-store" }
spt-msg = { path = "../spt-msg" }
spt-runtime = { path = "../spt-runtime" }
spt-live = { path = "../spt-live" }
spt-daemon = { path = "../spt-daemon" }
# The reference binary ships the WAN transport on by default (ADR-0002). D2g reuses its
# RFC-6238 TotpSeed (spt_net::net::pairing::totp) to render a subnet's local pairing
# code — reused, not reimplemented, so the CLI code matches the ceremony's exactly.
spt-net = { path = "../spt-net" }
# `wrap_help`: wrap long help text to the terminal width with a hanging indent
# (without it clap emits unwrapped lines and the terminal hard-wraps to col 0).
clap = { version = "4", features = ["derive", "wrap_help"] }
# The WAN send leg (D9-1-4) serializes the dial address + reads registry
# snapshots - same pinned shape as spt-daemon's.
serde_json = { version = "1", features = ["preserve_order"] }

# OS privilege-elevation probe for the node-bound code fetch (REQ-PAIR-6). Only the
# binary pulls the OS deps; the core stays dep-minimal. Matches the workspace's existing
# libc/windows-sys usage (spt-msg, spt-store) rather than the heavyweight `windows`.
[target.'cfg(unix)'.dependencies]
libc = "0.2"

[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.59", features = [
    "Win32_Foundation",
    "Win32_Security",
    "Win32_System_Threading",
] }

[dev-dependencies]
tempfile = "3"
toml = "0.8"
# install_e2e stages the built binary as a release asset (SHA256SUMS) for the
# D2 installer-script rung — same sha2 the workspace already carries.
sha2 = "0.10"

-description = "spt-core CLI: the single `spt` binary. Thin command layer over spt-msg (send/ring/ready/poll/list/stop/whoami) + the harness-contract `api` surface (spt-runtime)."

+description = "spt-core CLI: the single `spt` binary. Thin command layer over spt-msg (send/ring/ready/list/stop/whoami) + the harness-contract `api` surface (spt-runtime)."


# The WAN send leg (D9-1-4) serializes the dial address + reads registry
# snapshots - same pinned shape as spt-daemon's.
serde_json = { version = "1", features = ["preserve_order"] }

# The WAN send leg (D9-1-4) serializes the dial address + reads registry
# snapshots - same pinned shape as spt-daemon's.
serde_json = { version = "1", features = ["preserve_order"] }
# Terminal QR render for `spt subnet create` (M7 plan decision 11): the
# otpauth:// URI as unicode half-blocks. default-features off drops the
# image/svg renderers — the unicode renderer is all the CLI needs.
qrcode = { version = "0.14", default-features = false }


# The WAN send leg (D9-1-4) serializes the dial address + reads registry
# snapshots - same pinned shape as spt-daemon's.
serde_json = { version = "1", features = ["preserve_order"] }
# Terminal QR render for `spt subnet create` (M7 plan decision 11): the
# otpauth:// URI as unicode half-blocks. default-features off drops the
# image/svg renderers — the unicode renderer is all the CLI needs.
qrcode = { version = "0.14", default-features = false }

[package]
name = "spt"
version.workspace = true
edition = "2021"
publish = false
description = "spt-core CLI: the single `spt` binary. Thin command layer over spt-msg (send/ring/ready/list/stop/whoami) + the harness-contract `api` surface (spt-runtime)."

[[bin]]
name = "spt"
path = "src/main.rs"

[dependencies]
spt-proto = { path = "../spt-proto" }
spt-store = { path = "../spt-store" }
spt-msg = { path = "../spt-msg" }
spt-runtime = { path = "../spt-runtime" }
spt-live = { path = "../spt-live" }
spt-daemon = { path = "../spt-daemon" }
# The reference binary ships the WAN transport on by default (ADR-0002). D2g reuses its
# RFC-6238 TotpSeed (spt_net::net::pairing::totp) to render a subnet's local pairing
# code — reused, not reimplemented, so the CLI code matches the ceremony's exactly.
spt-net = { path = "../spt-net" }
# `wrap_help`: wrap long help text to the terminal width with a hanging indent
# (without it clap emits unwrapped lines and the terminal hard-wraps to col 0).
clap = { version = "4", features = ["derive", "wrap_help"] }
# The WAN send leg (D9-1-4) serializes the dial address + reads registry
# snapshots - same pinned shape as spt-daemon's.
serde_json = { version = "1", features = ["preserve_order"] }

# OS privilege-elevation probe for the node-bound code fetch (REQ-PAIR-6). Only the
# binary pulls the OS deps; the core stays dep-minimal. Matches the workspace's existing
# libc/windows-sys usage (spt-msg, spt-store) rather than the heavyweight `windows`.
[target.'cfg(unix)'.dependencies]
libc = "0.2"

[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.59", features = [
    "Win32_Foundation",
    "Win32_Security",
    "Win32_System_Threading",
] }

[dev-dependencies]
tempfile = "3"
toml = "0.8"
# install_e2e stages the built binary as a release asset (SHA256SUMS) for the
# D2 installer-script rung — same sha2 the workspace already carries.
sha2 = "0.10"

+# Terminal QR render for `spt subnet create` (M7 plan decision 11): the

+# otpauth:// URI as unicode half-blocks. default-features off drops the

+# image/svg renderers — the unicode renderer is all the CLI needs.

+qrcode = { version = "0.14", default-features = false }


Activate REQ-SUBNET-1 + REQ-DOCS-6 stages in traceable-reqs.toml

Set required_stages = ["impl","unit"] (rule 5 activation at M7 D1 start); int stages added later (D5).

Build spt subnet namespace: status (basic), create (+QR), show-totp; delete pair

cli.rs: Subnet cmd with optional subcommand (bare = status view). create <NAME> elevation-gated mint + code + otpauth + unicode QR + join hint. show-totp [NAME] positional, REQ-PAIR-3/6 semantics. Basic status render (names/node/endpoint counts, hint footer, zero-subnet text — hybrid liveness + --nodes is D2). Remove Cmd::Pair/PairCmd. Add qrcode crate dep.

Remove spt poll; add ready --once

Drop Cmd::Poll, add --once to Ready (run_listen absorbs). Update AGENT_COMMANDS. quickstart_e2e.rs switches poll→ready (full rewrite is D5).

Add spt how-to command with ready/send topics

In-binary agent instructions (REQ-DOCS-6). Bare how-to lists topics; topics: ready (background-task guidance, --once fallback loop, reply mechanics), send (send/ring, reply-to, SENT vs QUEUED). Agent-commands help group.

Update unit tests + regen cli/reference.md + green checks

Update subcommands_parse, pair→subnet tests, how-to parse tests; help_groups_cover_every_command passes. cargo test -p spt, xtask gen, traceable-reqs check, commit.


Now tests. Update parse tests + pair→subnet test block:


        assert!(matches!(parse(&["spt", "ready", "bob"]).unwrap().cmd, Cmd::Ready { .. }));
        assert!(matches!(parse(&["spt", "poll", "bob", "--once"]).unwrap().cmd, Cmd::Poll { once: true, .. }));

        assert!(matches!(parse(&["spt", "ready", "bob"]).unwrap().cmd, Cmd::Ready { once: false, .. }));
        // [unit->REQ-MSG-2] `ready --once` absorbs the removed `poll`'s
        // drain-then-exit semantics (M7 plan decision 2); `poll` itself is gone.
        assert!(matches!(parse(&["spt", "ready", "bob", "--once"]).unwrap().cmd, Cmd::Ready { once: true, .. }));
        assert!(parse(&["spt", "poll", "bob"]).is_err(), "poll is removed (ready --once)");


        assert!(matches!(parse(&["spt", "ready", "bob"]).unwrap().cmd, Cmd::Ready { .. }));
        assert!(matches!(parse(&["spt", "poll", "bob", "--once"]).unwrap().cmd, Cmd::Poll { once: true, .. }));

        assert!(matches!(parse(&["spt", "ready", "bob"]).unwrap().cmd, Cmd::Ready { once: false, .. }));
        // [unit->REQ-MSG-2] `ready --once` absorbs the removed `poll`'s
        // drain-then-exit semantics (M7 plan decision 2); `poll` itself is gone.
        assert!(matches!(parse(&["spt", "ready", "bob", "--once"]).unwrap().cmd, Cmd::Ready { once: true, .. }));
        assert!(parse(&["spt", "poll", "bob"]).is_err(), "poll is removed (ready --once)");

assert!(matches!(parse(&["spt", "send", "bob", "--from", "alice"]).unwrap().cmd, Cmd::Send { .. }));

assert!(matches!(parse(&["spt", "send", "bob", "--deferred"]).unwrap().cmd, Cmd::Send { deferred: true, .. }));

assert!(matches!(parse(&["spt", "ring", "bob", "--timeout", "5"]).unwrap().cmd, Cmd::Ring { .. }));

-        assert!(matches!(parse(&["spt", "ready", "bob"]).unwrap().cmd, Cmd::Ready { .. }));

-        assert!(matches!(parse(&["spt", "poll", "bob", "--once"]).unwrap().cmd, Cmd::Poll { once: true, .. }));

+        assert!(matches!(parse(&["spt", "ready", "bob"]).unwrap().cmd, Cmd::Ready { once: false, .. }));

+        // [unit->REQ-MSG-2] `ready --once` absorbs the removed `poll`'s

+        // drain-then-exit semantics (M7 plan decision 2); `poll` itself is gone.

+        assert!(matches!(parse(&["spt", "ready", "bob", "--once"]).unwrap().cmd, Cmd::Ready { once: true, .. }));

+        assert!(parse(&["spt", "poll", "bob"]).is_err(), "poll is removed (ready --once)");

assert!(matches!(parse(&["spt", "stop", "bob"]).unwrap().cmd, Cmd::Stop { .. }));


    // ── pair show-totp ─────────────────────────────────────────────────────────

    fn show_totp(args: &[&str]) -> PairCmd {
        match parse(args).unwrap().cmd {
            Cmd::Pair { action } => action,
            _ => panic!("expected Pair"),
        }
    }

    // [unit->REQ-PAIR-3] the show-totp arg shapes parse: bare, --subnet, --create-new.
    #[test]
    fn pair_show_totp_arg_shapes_parse() {
        assert!(matches!(
            show_totp(&["spt", "pair", "show-totp"]),
            PairCmd::ShowTotp { subnet: None, create_new: None }
        ));
        assert!(matches!(
            show_totp(&["spt", "pair", "show-totp", "--subnet", "home"]),
            PairCmd::ShowTotp { subnet: Some(s), .. } if s == "home"
        ));
        assert!(matches!(
            show_totp(&["spt", "pair", "show-totp", "--create-new", "home"]),
            PairCmd::ShowTotp { create_new: Some(s), .. } if s == "home"
        ));
    }

    // [unit->REQ-PAIR-3] --subnet and --create-new are mutually exclusive.
    #[test]
    fn pair_show_totp_subnet_and_create_new_conflict() {
        assert!(parse(&["spt", "pair", "show-totp", "--subnet", "a", "--create-new", "b"]).is_err());
    }

    // ── subnet namespace ────────────────────────────────────────────────────────

    fn subnet_cmd(args: &[&str]) -> Option<SubnetCmd> {
        match parse(args).unwrap().cmd {
            Cmd::Subnet { action } => action,
            _ => panic!("expected Subnet"),
        }
    }

    // [unit->REQ-SUBNET-1] [unit->REQ-PAIR-3] the subnet arg shapes parse:
    // bare (= status), status [NAME], create <NAME>, show-totp [NAME] — all
    // positional names, no --subnet stutter (M7 plan decision 3).
    #[test]
    fn subnet_arg_shapes_parse() {
        assert!(subnet_cmd(&["spt", "subnet"]).is_none(), "bare subnet = status view");
        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", "create", "home"]),
            Some(SubnetCmd::Create { name }) if name == "home"
        ));
        assert!(parse(&["spt", "subnet", "create"]).is_err(), "create needs a name");
        assert!(matches!(
            subnet_cmd(&["spt", "subnet", "show-totp"]),
            Some(SubnetCmd::ShowTotp { name: None })
        ));
        assert!(matches!(
            subnet_cmd(&["spt", "subnet", "show-totp", "home"]),
            Some(SubnetCmd::ShowTotp { name: Some(n) }) if n == "home"
        ));
    }

    // [unit->REQ-SUBNET-1] the `pair` namespace is deleted outright (no shim).
    #[test]
    fn pair_namespace_is_gone() {
        assert!(parse(&["spt", "pair", "show-totp"]).is_err());
        assert!(parse(&["spt", "pair"]).is_err());
    }

    // [unit->REQ-SUBNET-1] elevated create prints the full joining material —
    // code, otpauth URI, terminal QR, join hint — and requests the persist.
    #[test]
    fn create_prints_joining_material_when_elevated() {
        let mut store = SubnetStore::default();
        let now = 1_111_111_109;
        let r = decide_create(resolve(&mut store, None, Some("home".into())), now, Elevation::Elevated);
        assert_eq!(r.code, 0);
        assert!(r.save, "a freshly minted subnet must be persisted on show");
        let out = r.out.expect("elevated create prints");
        let seed = store.find("home").unwrap().seed_bytes().unwrap();
        assert!(out.starts_with(&TotpSeed::from_bytes(seed).code_at(now)), "current code first");
        assert!(out.contains("otpauth://"), "provisioning URI present");
        assert!(out.contains('█'), "terminal QR rendered (unicode blocks)");
        assert!(out.contains("spt subnet join home"), "join hint names the subnet");
        assert!(r.msg.contains("SUBNET_CREATED:home"));
    }

    // [unit->REQ-SUBNET-1] an unelevated create refuses without minting or
    // leaking (the seed-reveal posture; exit 3 like show-totp).
    #[test]
    fn create_not_elevated_refuses_without_saving() {
        let mut store = SubnetStore::default();
        let r = decide_create(resolve(&mut store, None, Some("home".into())), 0, Elevation::NotElevated);
        assert_eq!(r.code, EXIT_NOT_ELEVATED);
        assert!(r.out.is_none(), "no material leaks on the refused path");
        assert!(!r.save, "a refused create persists no mint");
        assert!(r.msg.contains("ELEVATION_REQUIRED"));
    }

    // [unit->REQ-SUBNET-1] creating an existing name is a usage error (the
    // shared resolution core's Exists arm rides through decide_create).
    #[test]
    fn create_existing_name_is_usage_error() {
        let mut store = store_with(&["home"]);
        let r = decide_create(resolve(&mut store, None, Some("home".into())), 0, Elevation::Elevated);
        assert_eq!(r.code, 2);
        assert!(r.msg.contains("SUBNET_EXISTS"));
    }

    // [unit->REQ-SUBNET-1] the status view: zero subnets renders the
    // standalone-node explanation; members render name/node/endpoint rows;
    // the hint footer always prints; secrets never appear (decision 5).
    #[test]
    fn subnet_status_renders_rows_and_hints_never_secrets() {
        use spt_net::net::registry::{Instance, Status, SubnetRegistry};

        // Zero subnets: explanatory standalone text + hints.
        let empty = render_subnet_status(&[]);
        assert!(empty.contains("standalone node"));
        assert!(empty.contains("spt subnet create"));
        assert!(empty.contains("show-totp"));

        // A member subnet with gossip: counts come from the snapshot.
        let store = store_with(&["home"]);
        let mut reg = SubnetRegistry::new();
        for (id, node) in [("lea", "aa"), ("sergey", "bb"), ("lea2", "aa")] {
            reg.merge_instance(
                id,
                Instance { node: node.into(), status: Status::Active, epoch: 1, ..Default::default() },
            );
        }
        let regs = std::collections::BTreeMap::from([("home".to_string(), reg)]);
        let rows = subnet_status_rows(&store, &regs, None);
        assert_eq!(rows.len(), 1);
        assert_eq!((rows[0].nodes, rows[0].endpoints), (2, 3));
        let view = render_subnet_status(&rows);
        assert!(view.contains("home"));
        assert!(view.contains("ENDPOINTS"));
        assert!(view.contains("hint:"), "hint footer always printed");
        let seed_hex = {
            use std::fmt::Write as _;
            store.find("home").unwrap().seed_bytes().unwrap().iter().fold(
                String::new(),
                |mut s, b| {
                    let _ = write!(s, "{b:02x}");
                    s
                },
            )
        };
        assert!(!view.contains(&seed_hex), "the status view never carries seed material");

        // A missing snapshot degrades to this-node-alone, not a panic/zero.
        let no_gossip = subnet_status_rows(&store, &std::collections::BTreeMap::new(), None);
        assert_eq!((no_gossip[0].nodes, no_gossip[0].endpoints), (1, 0));
    }

    // [unit->REQ-DOCS-6] how-to: bare lists every topic; each topic prints
    // text naming the commands the guidance relies on; unknown is usage error.
    #[test]
    fn how_to_topics_cover_ready_and_send() {
        assert!(matches!(parse(&["spt", "how-to"]).unwrap().cmd, Cmd::HowTo { topic: None }));
        assert!(matches!(
            parse(&["spt", "how-to", "ready"]).unwrap().cmd,
            Cmd::HowTo { topic: Some(t) } if t == "ready"
        ));
        assert_eq!(cmd_how_to(Some("nonsense")), 2, "unknown topic is a usage error");

        let names: Vec<&str> = HOW_TO_TOPICS.iter().map(|(n, ..)| *n).collect();
        assert_eq!(names, vec!["ready", "send"], "v1 topics per plan decision 12");
        let ready = HOW_TO_TOPICS[0].2;
        assert!(ready.contains("spt ready <id>"));
        assert!(ready.contains("--once"), "the one-shot fallback loop is documented");
        assert!(ready.contains("__REPLY_TO__"), "reply mechanics documented");
        let send = HOW_TO_TOPICS[1].2;
        assert!(send.contains("spt send <target>"));
        assert!(send.contains("QUEUED"), "SENT vs QUEUED semantics documented");
        assert!(send.contains("spt ring"));
        assert!(send.contains("--reply-to"));
    }


    // ── pair show-totp ─────────────────────────────────────────────────────────

    fn show_totp(args: &[&str]) -> PairCmd {
        match parse(args).unwrap().cmd {
            Cmd::Pair { action } => action,
            _ => panic!("expected Pair"),
        }
    }

    // [unit->REQ-PAIR-3] the show-totp arg shapes parse: bare, --subnet, --create-new.
    #[test]
    fn pair_show_totp_arg_shapes_parse() {
        assert!(matches!(
            show_totp(&["spt", "pair", "show-totp"]),
            PairCmd::ShowTotp { subnet: None, create_new: None }
        ));
        assert!(matches!(
            show_totp(&["spt", "pair", "show-totp", "--subnet", "home"]),
            PairCmd::ShowTotp { subnet: Some(s), .. } if s == "home"
        ));
        assert!(matches!(
            show_totp(&["spt", "pair", "show-totp", "--create-new", "home"]),
            PairCmd::ShowTotp { create_new: Some(s), .. } if s == "home"
        ));
    }

    // [unit->REQ-PAIR-3] --subnet and --create-new are mutually exclusive.
    #[test]
    fn pair_show_totp_subnet_and_create_new_conflict() {
        assert!(parse(&["spt", "pair", "show-totp", "--subnet", "a", "--create-new", "b"]).is_err());
    }

    // ── subnet namespace ────────────────────────────────────────────────────────

    fn subnet_cmd(args: &[&str]) -> Option<SubnetCmd> {
        match parse(args).unwrap().cmd {
            Cmd::Subnet { action } => action,
            _ => panic!("expected Subnet"),
        }
    }

    // [unit->REQ-SUBNET-1] [unit->REQ-PAIR-3] the subnet arg shapes parse:
    // bare (= status), status [NAME], create <NAME>, show-totp [NAME] — all
    // positional names, no --subnet stutter (M7 plan decision 3).
    #[test]
    fn subnet_arg_shapes_parse() {
        assert!(subnet_cmd(&["spt", "subnet"]).is_none(), "bare subnet = status view");
        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", "create", "home"]),
            Some(SubnetCmd::Create { name }) if name == "home"
        ));
        assert!(parse(&["spt", "subnet", "create"]).is_err(), "create needs a name");
        assert!(matches!(
            subnet_cmd(&["spt", "subnet", "show-totp"]),
            Some(SubnetCmd::ShowTotp { name: None })
        ));
        assert!(matches!(
            subnet_cmd(&["spt", "subnet", "show-totp", "home"]),
            Some(SubnetCmd::ShowTotp { name: Some(n) }) if n == "home"
        ));
    }

    // [unit->REQ-SUBNET-1] the `pair` namespace is deleted outright (no shim).
    #[test]
    fn pair_namespace_is_gone() {
        assert!(parse(&["spt", "pair", "show-totp"]).is_err());
        assert!(parse(&["spt", "pair"]).is_err());
    }

    // [unit->REQ-SUBNET-1] elevated create prints the full joining material —
    // code, otpauth URI, terminal QR, join hint — and requests the persist.
    #[test]
    fn create_prints_joining_material_when_elevated() {
        let mut store = SubnetStore::default();
        let now = 1_111_111_109;
        let r = decide_create(resolve(&mut store, None, Some("home".into())), now, Elevation::Elevated);
        assert_eq!(r.code, 0);
        assert!(r.save, "a freshly minted subnet must be persisted on show");
        let out = r.out.expect("elevated create prints");
        let seed = store.find("home").unwrap().seed_bytes().unwrap();
        assert!(out.starts_with(&TotpSeed::from_bytes(seed).code_at(now)), "current code first");
        assert!(out.contains("otpauth://"), "provisioning URI present");
        assert!(out.contains('█'), "terminal QR rendered (unicode blocks)");
        assert!(out.contains("spt subnet join home"), "join hint names the subnet");
        assert!(r.msg.contains("SUBNET_CREATED:home"));
    }

    // [unit->REQ-SUBNET-1] an unelevated create refuses without minting or
    // leaking (the seed-reveal posture; exit 3 like show-totp).
    #[test]
    fn create_not_elevated_refuses_without_saving() {
        let mut store = SubnetStore::default();
        let r = decide_create(resolve(&mut store, None, Some("home".into())), 0, Elevation::NotElevated);
        assert_eq!(r.code, EXIT_NOT_ELEVATED);
        assert!(r.out.is_none(), "no material leaks on the refused path");
        assert!(!r.save, "a refused create persists no mint");
        assert!(r.msg.contains("ELEVATION_REQUIRED"));
    }

    // [unit->REQ-SUBNET-1] creating an existing name is a usage error (the
    // shared resolution core's Exists arm rides through decide_create).
    #[test]
    fn create_existing_name_is_usage_error() {
        let mut store = store_with(&["home"]);
        let r = decide_create(resolve(&mut store, None, Some("home".into())), 0, Elevation::Elevated);
        assert_eq!(r.code, 2);
        assert!(r.msg.contains("SUBNET_EXISTS"));
    }

    // [unit->REQ-SUBNET-1] the status view: zero subnets renders the
    // standalone-node explanation; members render name/node/endpoint rows;
    // the hint footer always prints; secrets never appear (decision 5).
    #[test]
    fn subnet_status_renders_rows_and_hints_never_secrets() {
        use spt_net::net::registry::{Instance, Status, SubnetRegistry};

        // Zero subnets: explanatory standalone text + hints.
        let empty = render_subnet_status(&[]);
        assert!(empty.contains("standalone node"));
        assert!(empty.contains("spt subnet create"));
        assert!(empty.contains("show-totp"));

        // A member subnet with gossip: counts come from the snapshot.
        let store = store_with(&["home"]);
        let mut reg = SubnetRegistry::new();
        for (id, node) in [("lea", "aa"), ("sergey", "bb"), ("lea2", "aa")] {
            reg.merge_instance(
                id,
                Instance { node: node.into(), status: Status::Active, epoch: 1, ..Default::default() },
            );
        }
        let regs = std::collections::BTreeMap::from([("home".to_string(), reg)]);
        let rows = subnet_status_rows(&store, &regs, None);
        assert_eq!(rows.len(), 1);
        assert_eq!((rows[0].nodes, rows[0].endpoints), (2, 3));
        let view = render_subnet_status(&rows);
        assert!(view.contains("home"));
        assert!(view.contains("ENDPOINTS"));
        assert!(view.contains("hint:"), "hint footer always printed");
        let seed_hex = {
            use std::fmt::Write as _;
            store.find("home").unwrap().seed_bytes().unwrap().iter().fold(
                String::new(),
                |mut s, b| {
                    let _ = write!(s, "{b:02x}");
                    s
                },
            )
        };
        assert!(!view.contains(&seed_hex), "the status view never carries seed material");

        // A missing snapshot degrades to this-node-alone, not a panic/zero.
        let no_gossip = subnet_status_rows(&store, &std::collections::BTreeMap::new(), None);
        assert_eq!((no_gossip[0].nodes, no_gossip[0].endpoints), (1, 0));
    }

    // [unit->REQ-DOCS-6] how-to: bare lists every topic; each topic prints
    // text naming the commands the guidance relies on; unknown is usage error.
    #[test]
    fn how_to_topics_cover_ready_and_send() {
        assert!(matches!(parse(&["spt", "how-to"]).unwrap().cmd, Cmd::HowTo { topic: None }));
        assert!(matches!(
            parse(&["spt", "how-to", "ready"]).unwrap().cmd,
            Cmd::HowTo { topic: Some(t) } if t == "ready"
        ));
        assert_eq!(cmd_how_to(Some("nonsense")), 2, "unknown topic is a usage error");

        let names: Vec<&str> = HOW_TO_TOPICS.iter().map(|(n, ..)| *n).collect();
        assert_eq!(names, vec!["ready", "send"], "v1 topics per plan decision 12");
        let ready = HOW_TO_TOPICS[0].2;
        assert!(ready.contains("spt ready <id>"));
        assert!(ready.contains("--once"), "the one-shot fallback loop is documented");
        assert!(ready.contains("__REPLY_TO__"), "reply mechanics documented");
        let send = HOW_TO_TOPICS[1].2;
        assert!(send.contains("spt send <target>"));
        assert!(send.contains("QUEUED"), "SENT vs QUEUED semantics documented");
        assert!(send.contains("spt ring"));
        assert!(send.contains("--reply-to"));
    }

-    // ── pair show-totp ─────────────────────────────────────────────────────────

+    // ── subnet namespace ────────────────────────────────────────────────────────

-    fn show_totp(args: &[&str]) -> PairCmd {

+    fn subnet_cmd(args: &[&str]) -> Option<SubnetCmd> {

-            Cmd::Pair { action } => action,

-            _ => panic!("expected Pair"),

+            Cmd::Subnet { action } => action,

+            _ => panic!("expected Subnet"),

-    // [unit->REQ-PAIR-3] the show-totp arg shapes parse: bare, --subnet, --create-new.

+    // [unit->REQ-SUBNET-1] [unit->REQ-PAIR-3] the subnet arg shapes parse:

+    // bare (= status), status [NAME], create <NAME>, show-totp [NAME] — all

+    // positional names, no --subnet stutter (M7 plan decision 3).

-    fn pair_show_totp_arg_shapes_parse() {

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

-            show_totp(&["spt", "pair", "show-totp"]),

-            PairCmd::ShowTotp { subnet: None, create_new: None }

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

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

-            show_totp(&["spt", "pair", "show-totp", "--subnet", "home"]),

-            PairCmd::ShowTotp { subnet: Some(s), .. } if s == "home"

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

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

-            show_totp(&["spt", "pair", "show-totp", "--create-new", "home"]),

-            PairCmd::ShowTotp { create_new: Some(s), .. } if s == "home"

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

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

+        assert!(parse(&["spt", "subnet", "create"]).is_err(), "create needs a name");

+            subnet_cmd(&["spt", "subnet", "show-totp"]),

+            Some(SubnetCmd::ShowTotp { name: None })

+            subnet_cmd(&["spt", "subnet", "show-totp", "home"]),

+            Some(SubnetCmd::ShowTotp { name: Some(n) }) if n == "home"

-    // [unit->REQ-PAIR-3] --subnet and --create-new are mutually exclusive.

+    // [unit->REQ-SUBNET-1] the `pair` namespace is deleted outright (no shim).

-    fn pair_show_totp_subnet_and_create_new_conflict() {

-        assert!(parse(&["spt", "pair", "show-totp", "--subnet", "a", "--create-new", "b"]).is_err());

+        assert!(parse(&["spt", "pair", "show-totp"]).is_err());

+        assert!(parse(&["spt", "pair"]).is_err());

+    // [unit->REQ-SUBNET-1] elevated create prints the full joining material —

+    // code, otpauth URI, terminal QR, join hint — and requests the persist.

+    fn create_prints_joining_material_when_elevated() {

+        let mut store = SubnetStore::default();

+        let r = decide_create(resolve(&mut store, None, Some("home".into())), now, Elevation::Elevated);

+        assert!(r.save, "a freshly minted subnet must be persisted on show");

+        let out = r.out.expect("elevated create prints");

+        let seed = store.find("home").unwrap().seed_bytes().unwrap();

+        assert!(out.starts_with(&TotpSeed::from_bytes(seed).code_at(now)), "current code first");

+        assert!(out.contains("otpauth://"), "provisioning URI present");

+        assert!(out.contains('█'), "terminal QR rendered (unicode blocks)");

+        assert!(out.contains("spt subnet join home"), "join hint names the subnet");

+        assert!(r.msg.contains("SUBNET_CREATED:home"));

+    // [unit->REQ-SUBNET-1] an unelevated create refuses without minting or

+    // leaking (the seed-reveal posture; exit 3 like show-totp).

+    fn create_not_elevated_refuses_without_saving() {

+        let r = decide_create(resolve(&mut store, None, Some("home".into())), 0, Elevation::NotElevated);

+        assert_eq!(r.code, EXIT_NOT_ELEVATED);

+        assert!(r.out.is_none(), "no material leaks on the refused path");

+        assert!(!r.save, "a refused create persists no mint");

+        assert!(r.msg.contains("ELEVATION_REQUIRED"));

+    // [unit->REQ-SUBNET-1] creating an existing name is a usage error (the

+    // shared resolution core's Exists arm rides through decide_create).

+    fn create_existing_name_is_usage_error() {

+        let mut store = store_with(&["home"]);

+        let r = decide_create(resolve(&mut store, None, Some("home".into())), 0, Elevation::Elevated);

+        assert!(r.msg.contains("SUBNET_EXISTS"));

+    // [unit->REQ-SUBNET-1] the status view: zero subnets renders the

+    // standalone-node explanation; members render name/node/endpoint rows;

+    // the hint footer always prints; secrets never appear (decision 5).

+    fn subnet_status_renders_rows_and_hints_never_secrets() {

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

+        // Zero subnets: explanatory standalone text + hints.

+        let empty = render_subnet_status(&[]);

+        assert!(empty.contains("standalone node"));

+        assert!(empty.contains("spt subnet create"));

+        assert!(empty.contains("show-totp"));

+        // A member subnet with gossip: counts come from the snapshot.

+        let store = store_with(&["home"]);

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

+                Instance { node: node.into(), status: Status::Active, epoch: 1, ..Default::default() },

+        let regs = std::collections::BTreeMap::from([("home".to_string(), reg)]);

+        let rows = subnet_status_rows(&store, &regs, None);

+        assert_eq!((rows[0].nodes, rows[0].endpoints), (2, 3));

+        let view = render_subnet_status(&rows);

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

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

+        assert!(view.contains("hint:"), "hint footer always printed");

+            store.find("home").unwrap().seed_bytes().unwrap().iter().fold(

+        assert!(!view.contains(&seed_hex), "the status view never carries seed material");

+        // A missing snapshot degrades to this-node-alone, not a panic/zero.

+        let no_gossip = subnet_status_rows(&store, &std::collections::BTreeMap::new(), None);

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

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

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

+    fn how_to_topics_cover_ready_and_send() {

+        assert!(matches!(parse(&["spt", "how-to"]).unwrap().cmd, Cmd::HowTo { topic: None }));

+            parse(&["spt", "how-to", "ready"]).unwrap().cmd,

+            Cmd::HowTo { topic: Some(t) } if t == "ready"

+        assert_eq!(cmd_how_to(Some("nonsense")), 2, "unknown topic is a usage error");

+        let names: Vec<&str> = HOW_TO_TOPICS.iter().map(|(n, ..)| *n).collect();

+        assert_eq!(names, vec!["ready", "send"], "v1 topics per plan decision 12");

+        let ready = HOW_TO_TOPICS[0].2;

+        assert!(ready.contains("spt ready <id>"));

+        assert!(ready.contains("--once"), "the one-shot fallback loop is documented");

+        assert!(ready.contains("__REPLY_TO__"), "reply mechanics documented");

+        assert!(send.contains("spt send <target>"));

+        assert!(send.contains("QUEUED"), "SENT vs QUEUED semantics documented");

+        assert!(send.contains("spt ring"));

+        assert!(send.contains("--reply-to"));

fn store_with(names: &[&str]) -> SubnetStore {


40	//! one subnet, visible in another resolves cleanly in the visible one.
41	// [impl->REQ-INST-7]
42
43	use std::collections::BTreeMap;
44
45	use serde::{Deserialize, Serialize};
46	use spt_proto::addr::Address;
47	use spt_store::visibility::VisibilityStore;
48
49	/// An endpoint instance's liveness within a subnet. Ordering carries no precedence —
50	/// precedence is the epoch's job; status is just the advertised state.
51	#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
52	pub enum Status {
53	    /// Live and currently reachable.
54	    Active,
55	    /// Known but idle (e.g. a paused live agent) — addressable, not currently serving.
56	    Dormant,
57	    /// Resting **cold** (D9-2, REQ-INST-3): the harness session is closed,
58	    /// resumed-on-wake. Still addressable (`spt wake ling@desktop` must route) —
59	    /// only `Offline` is unroutable. Additive variant on a pre-release fleet;
60	    /// the NDJSON decode posture (unknown-kind skip) is the registry record's
61	    /// forward-compat story.
62	    // [impl->REQ-INST-3]
63	    Suspended,
64	    /// Believed down. Retained (not dropped) so a newer-epoch revival or a stale-Active
65	    /// suppression can be reasoned about; the resolver skips it.
66	    Offline,
67	}
68
69	/// One endpoint as seen on one node: the node it runs on, its advertised [`Status`], and
70	/// the author node's monotonic [`epoch`](Self::epoch) (the merge-precedence key).
71	#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
72	pub struct Instance {
73	    /// The node this instance runs on (node-qualifier in `id@node` addressing).
74	    pub node: String,
75	    /// Advertised liveness.
76	    pub status: Status,
77	    /// The author node's per-node monotonic epoch at the time of this update. The sole
78	    /// precedence key in [`merge_instance`] — strictly-greater wins.
79	    pub epoch: u64,
80	    /// The resource-advertisement blurb (D9-4, REQ-INST-14 — CONTEXT §resource
81	    /// advertisement): a free-text "yellow-pages" line describing what this
82	    /// endpoint can serve. **Not a separate registry** — a field on the
83	    /// instance row, riding the existing epoch lease + `RegistryUpdate`
84	    /// replication (a blurb update is ordered like any instance update);
85	    /// the "subnet resource registry" is a [projection](resource_projection)
86	    /// of these rows. Additive serde-default: absent = no blurb, and a
87	    /// pre-D9-4 record parses clean.
88	    #[serde(default, skip_serializing_if = "Option::is_none")]
89	    pub resources: Option<String>,
90	    /// The presence datum (M5-D6a, REQ-PRES-1 — CONTEXT §presence): this
91	    /// instance's `last_active_ms` recency stamp as of the authoring node's
92	    /// advertisement scan. **Not a separate gossip channel** — a field on the
93	    /// instance row riding the existing epoch lease + `RegistryUpdate`
94	    /// replication, exactly like [`resources`](Self::resources); subnet-wide
95	    /// `(last_active_node, last_active_endpoint, ts)` is a projection of
96	    /// these rows. Wall-clock ms, cross-compared ONLY as a routing heuristic
97	    /// (most-recently-active resolution) — the epoch stays the sole merge-
98	    /// precedence key; clock skew can cost a suboptimal surface target, never
99	    /// a wrong merge. Additive serde-default: a pre-D6 row parses clean.
100	    // [impl->REQ-PRES-1]
101	    #[serde(default, skip_serializing_if = "Option::is_none")]
102	    pub last_active_ms: Option<u64>,
103	    /// The authoring node's subnet-broadcast shell adapters (M5-D8c — CONTEXT
104	    /// §Shell model "Discovery scope": shell adapters on *other* subnet nodes
105	    /// with `broadcast = subnet` surface to agents). **Not a shell-instance
106	    /// registry** (shell perches stay private to the owner link, CONTEXT
107	    /// "Not in the subnet registry") — the names of `kind="shell"` adapters
108	    /// REGISTERED on the authoring node that chose subnet-wide broadcast,
109	    /// riding the instance row like [`resources`](Self::resources) does
110	    /// (same epoch lease, same visibility gate, no new channel; node-level
111	    /// data carried per-row because the rows are the distribution).
112	    /// Discovery only: spawning one rides the deferred instantiate-anywhere
113	    /// gate. Additive serde-default: a pre-D8 row parses clean.
114	    // [impl->REQ-EP-5]
115	    #[serde(default, skip_serializing_if = "Vec::is_empty")]
116	    pub shell_adapters: Vec<String>,
117	}

//! one subnet, visible in another resolves cleanly in the visible one.
// [impl->REQ-INST-7]

use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};
use spt_proto::addr::Address;
use spt_store::visibility::VisibilityStore;

/// An endpoint instance's liveness within a subnet. Ordering carries no precedence —
/// precedence is the epoch's job; status is just the advertised state.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Status {
    /// Live and currently reachable.
    Active,
    /// Known but idle (e.g. a paused live agent) — addressable, not currently serving.
    Dormant,
    /// Resting **cold** (D9-2, REQ-INST-3): the harness session is closed,
    /// resumed-on-wake. Still addressable (`spt wake ling@desktop` must route) —
    /// only `Offline` is unroutable. Additive variant on a pre-release fleet;
    /// the NDJSON decode posture (unknown-kind skip) is the registry record's
    /// forward-compat story.
    // [impl->REQ-INST-3]
    Suspended,
    /// Believed down. Retained (not dropped) so a newer-epoch revival or a stale-Active
    /// suppression can be reasoned about; the resolver skips it.
    Offline,
}

/// One endpoint as seen on one node: the node it runs on, its advertised [`Status`], and
/// the author node's monotonic [`epoch`](Self::epoch) (the merge-precedence key).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Instance {
    /// The node this instance runs on (node-qualifier in `id@node` addressing).
    pub node: String,
    /// Advertised liveness.
    pub status: Status,
    /// The author node's per-node monotonic epoch at the time of this update. The sole
    /// precedence key in [`merge_instance`] — strictly-greater wins.
    pub epoch: u64,
    /// The resource-advertisement blurb (D9-4, REQ-INST-14 — CONTEXT §resource
    /// advertisement): a free-text "yellow-pages" line describing what this
    /// endpoint can serve. **Not a separate registry** — a field on the
    /// instance row, riding the existing epoch lease + `RegistryUpdate`
    /// replication (a blurb update is ordered like any instance update);
    /// the "subnet resource registry" is a [projection](resource_projection)
    /// of these rows. Additive serde-default: absent = no blurb, and a
    /// pre-D9-4 record parses clean.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub resources: Option<String>,
    /// The presence datum (M5-D6a, REQ-PRES-1 — CONTEXT §presence): this
    /// instance's `last_active_ms` recency stamp as of the authoring node's
    /// advertisement scan. **Not a separate gossip channel** — a field on the
    /// instance row riding the existing epoch lease + `RegistryUpdate`
    /// replication, exactly like [`resources`](Self::resources); subnet-wide
    /// `(last_active_node, last_active_endpoint, ts)` is a projection of
    /// these rows. Wall-clock ms, cross-compared ONLY as a routing heuristic
    /// (most-recently-active resolution) — the epoch stays the sole merge-
    /// precedence key; clock skew can cost a suboptimal surface target, never
    /// a wrong merge. Additive serde-default: a pre-D6 row parses clean.
    // [impl->REQ-PRES-1]
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub last_active_ms: Option<u64>,
    /// The authoring node's subnet-broadcast shell adapters (M5-D8c — CONTEXT
    /// §Shell model "Discovery scope": shell adapters on *other* subnet nodes
    /// with `broadcast = subnet` surface to agents). **Not a shell-instance
    /// registry** (shell perches stay private to the owner link, CONTEXT
    /// "Not in the subnet registry") — the names of `kind="shell"` adapters
    /// REGISTERED on the authoring node that chose subnet-wide broadcast,
    /// riding the instance row like [`resources`](Self::resources) does
    /// (same epoch lease, same visibility gate, no new channel; node-level
    /// data carried per-row because the rows are the distribution).
    /// Discovery only: spawning one rides the deferred instantiate-anywhere
    /// gate. Additive serde-default: a pre-D8 row parses clean.
    // [impl->REQ-EP-5]
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub shell_adapters: Vec<String>,
}


            reg.merge_instance(
                id,
                Instance { node: node.into(), status: Status::Active, epoch: 1, ..Default::default() },
            );

            reg.merge_instance(
                id,
                Instance {
                    node: node.into(),
                    status: Status::Active,
                    epoch: 1,
                    resources: None,
                    last_active_ms: None,
                    shell_adapters: Vec::new(),
                },
            );


            reg.merge_instance(
                id,
                Instance { node: node.into(), status: Status::Active, epoch: 1, ..Default::default() },
            );

            reg.merge_instance(
                id,
                Instance {
                    node: node.into(),
                    status: Status::Active,
                    epoch: 1,
                    resources: None,
                    last_active_ms: None,
                    shell_adapters: Vec::new(),
                },
            );

-                Instance { node: node.into(), status: Status::Active, epoch: 1, ..Default::default() },

+                    status: Status::Active,

+                    last_active_ms: None,

+                    shell_adapters: Vec::new(),

let regs = std::collections::BTreeMap::from([("home".to_string(), reg)]);


quickstart_e2e poll→ready swap:


//! M6-D4 quickstart rung — the published human quickstart
//! (docs-site/src/quickstart/messaging.md) executed step for step against the
//! real binary: poll brings bob online → live send delivers (`SENT`) and the
//! body + `__REPLY_TO__` header land on bob's stdout → bob goes away → send
//! spools (`QUEUED`) → `poll --once` drains the backlog. If the documented
//! flow stops working, this fails — the doc cannot silently rot (REQ-DOCS-2's
//! "runnable, deterministic" promise, CI-run on both platforms).

//! M6-D4 quickstart rung — the published human quickstart
//! (docs-site/src/quickstart/messaging.md) executed step for step against the
//! real binary: ready brings bob online → live send delivers (`SENT`) and the
//! body + `__REPLY_TO__` header land on bob's stdout → bob goes away → send
//! spools (`QUEUED`) → `ready --once` drains the backlog. If the documented
//! flow stops working, this fails — the doc cannot silently rot (REQ-DOCS-2's
//! "runnable, deterministic" promise, CI-run on both platforms).
//! (M7 D1: `poll` merged into `ready --once`; the full lea/sergey + how-to
//! rewrite is the D5 deliverable.)


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

//! M6-D4 quickstart rung — the published human quickstart
//! (docs-site/src/quickstart/messaging.md) executed step for step against the
//! real binary: ready brings bob online → live send delivers (`SENT`) and the
//! body + `__REPLY_TO__` header land on bob's stdout → bob goes away → send
//! spools (`QUEUED`) → `ready --once` drains the backlog. If the documented
//! flow stops working, this fails — the doc cannot silently rot (REQ-DOCS-2's
//! "runnable, deterministic" promise, CI-run on both platforms).
//! (M7 D1: `poll` merged into `ready --once`; the full lea/sergey + how-to
//! rewrite is the D5 deliverable.)

//! M6-D4 quickstart rung — the published human quickstart
//! (docs-site/src/quickstart/messaging.md) executed step for step against the
//! real binary: poll brings bob online → live send delivers (`SENT`) and the
//! body + `__REPLY_TO__` header land on bob's stdout → bob goes away → send
//! spools (`QUEUED`) → `poll --once` drains the backlog. If the documented
//! flow stops working, this fails — the doc cannot silently rot (REQ-DOCS-2's
//! "runnable, deterministic" promise, CI-run on both platforms).
//!
//! Hermetic: isolated SPT_HOME per run; not env-gated (cheap, local-only).
//
// [int->REQ-DOCS-2]

use std::io::{BufRead, BufReader, Read};
use std::process::{Command, Stdio};
use std::time::Duration;

fn spt() -> Command {
    Command::new(env!("CARGO_BIN_EXE_spt"))
}

#[test]
fn published_messaging_quickstart_runs_as_written() {
    let home = tempfile::tempdir().expect("tempdir");
    let home = home.path();

    // ---- step 2: bring an agent online (`spt poll bob`) ----
    let mut bob = spt()
        .args(["poll", "bob"])
        .env("SPT_HOME", home)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("spawn poll bob");
    // READY:bob is the online signal (stderr).
    let mut bob_err = BufReader::new(bob.stderr.take().unwrap());
    let mut line = String::new();
    bob_err.read_line(&mut line).expect("read READY line");
    assert!(line.contains("READY:bob"), "expected READY:bob, got {line:?}");

    // ---- step 3: live send from alice ----
    let out = send(home, &["send", "bob", "--from", "alice"], "hello bob - alice here");
    assert!(
        out.contains("SENT:bob"),
        "live delivery must report SENT:bob, got {out:?}"
    );
    // The documented receive shape: routing header + body on bob's stdout.
    let mut bob_out = BufReader::new(bob.stdout.take().unwrap());
    let mut l1 = String::new();
    let mut l2 = String::new();
    bob_out.read_line(&mut l1).expect("read header");
    bob_out.read_line(&mut l2).expect("read body");
    assert!(l1.contains("__REPLY_TO__:alice"), "header line, got {l1:?}");
    assert!(l2.contains("hello bob - alice here"), "body line, got {l2:?}");

    // ---- step 4: bob goes away; the send spools ----
    bob.kill().expect("stop bob");
    let _ = bob.wait();
    let out = send(home, &["send", "bob", "--from", "alice"], "ping while you were away");
    assert!(
        out.contains("QUEUED:bob"),
        "offline delivery must report QUEUED:bob, got {out:?}"
    );

    // ---- bob comes back: the backlog drains ----
    let drained = spt()
        .args(["poll", "bob", "--once"])
        .env("SPT_HOME", home)
        .output()
        .expect("poll --once");
    let stdout = String::from_utf8_lossy(&drained.stdout);
    assert!(
        stdout.contains("ping while you were away"),
        "backlog must drain on return, got {stdout:?}"
    );
}

/// Run a one-shot spt command with `body` on stdin, returning stdout+stderr.
fn send(home: &std::path::Path, args: &[&str], body: &str) -> String {
    let mut child = spt()
        .args(args)
        .env("SPT_HOME", home)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("spawn spt");
    {
        use std::io::Write;
        child
            .stdin
            .take()
            .unwrap()
            .write_all(body.as_bytes())
            .expect("write body");
    }
    // Bounded read (KNOWN-HAZARDS 5.6 posture): a hang here means a detached
    // child inherited the pipe.
    let (tx, rx) = std::sync::mpsc::channel();
    std::thread::spawn(move || {
        let out = child.wait_with_output();
        let _ = tx.send(out);
    });
    let out = rx
        .recv_timeout(Duration::from_secs(30))
        .expect("spt call must complete")
        .expect("spt runs");
    let mut s = String::from_utf8_lossy(&out.stdout).into_owned();
    let mut e = String::new();
    let _ = std::io::Cursor::new(out.stderr).read_to_string(&mut e);
    s.push_str(&e);
    s
}

-//! real binary: poll brings bob online → live send delivers (`SENT`) and the

+//! real binary: ready brings bob online → live send delivers (`SENT`) and the

-//! spools (`QUEUED`) → `poll --once` drains the backlog. If the documented

+//! spools (`QUEUED`) → `ready --once` drains the backlog. If the documented

+//! (M7 D1: `poll` merged into `ready --once`; the full lea/sergey + how-to


    // ---- step 2: bring an agent online (`spt poll bob`) ----
    let mut bob = spt()
        .args(["poll", "bob"])

    // ---- step 2: bring an agent online (`spt ready bob`) ----
    let mut bob = spt()
        .args(["ready", "bob"])


    // ---- step 2: bring an agent online (`spt ready bob`) ----
    let mut bob = spt()
        .args(["ready", "bob"])

//! M6-D4 quickstart rung — the published human quickstart
//! (docs-site/src/quickstart/messaging.md) executed step for step against the
//! real binary: ready brings bob online → live send delivers (`SENT`) and the
//! body + `__REPLY_TO__` header land on bob's stdout → bob goes away → send
//! spools (`QUEUED`) → `ready --once` drains the backlog. If the documented
//! flow stops working, this fails — the doc cannot silently rot (REQ-DOCS-2's
//! "runnable, deterministic" promise, CI-run on both platforms).
//! (M7 D1: `poll` merged into `ready --once`; the full lea/sergey + how-to
//! rewrite is the D5 deliverable.)
//!
//! Hermetic: isolated SPT_HOME per run; not env-gated (cheap, local-only).
//
// [int->REQ-DOCS-2]

use std::io::{BufRead, BufReader, Read};
use std::process::{Command, Stdio};
use std::time::Duration;

fn spt() -> Command {
    Command::new(env!("CARGO_BIN_EXE_spt"))
}

#[test]
fn published_messaging_quickstart_runs_as_written() {
    let home = tempfile::tempdir().expect("tempdir");
    let home = home.path();

    // ---- step 2: bring an agent online (`spt poll bob`) ----
    let mut bob = spt()
        .args(["poll", "bob"])
        .env("SPT_HOME", home)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("spawn poll bob");
    // READY:bob is the online signal (stderr).
    let mut bob_err = BufReader::new(bob.stderr.take().unwrap());
    let mut line = String::new();
    bob_err.read_line(&mut line).expect("read READY line");
    assert!(line.contains("READY:bob"), "expected READY:bob, got {line:?}");

    // ---- step 3: live send from alice ----
    let out = send(home, &["send", "bob", "--from", "alice"], "hello bob - alice here");
    assert!(
        out.contains("SENT:bob"),
        "live delivery must report SENT:bob, got {out:?}"
    );
    // The documented receive shape: routing header + body on bob's stdout.
    let mut bob_out = BufReader::new(bob.stdout.take().unwrap());
    let mut l1 = String::new();
    let mut l2 = String::new();
    bob_out.read_line(&mut l1).expect("read header");
    bob_out.read_line(&mut l2).expect("read body");
    assert!(l1.contains("__REPLY_TO__:alice"), "header line, got {l1:?}");
    assert!(l2.contains("hello bob - alice here"), "body line, got {l2:?}");

    // ---- step 4: bob goes away; the send spools ----
    bob.kill().expect("stop bob");
    let _ = bob.wait();
    let out = send(home, &["send", "bob", "--from", "alice"], "ping while you were away");
    assert!(
        out.contains("QUEUED:bob"),
        "offline delivery must report QUEUED:bob, got {out:?}"
    );

    // ---- bob comes back: the backlog drains ----
    let drained = spt()
        .args(["poll", "bob", "--once"])
        .env("SPT_HOME", home)
        .output()
        .expect("poll --once");
    let stdout = String::from_utf8_lossy(&drained.stdout);
    assert!(
        stdout.contains("ping while you were away"),
        "backlog must drain on return, got {stdout:?}"
    );
}

/// Run a one-shot spt command with `body` on stdin, returning stdout+stderr.
fn send(home: &std::path::Path, args: &[&str], body: &str) -> String {
    let mut child = spt()
        .args(args)
        .env("SPT_HOME", home)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("spawn spt");
    {
        use std::io::Write;
        child
            .stdin
            .take()
            .unwrap()
            .write_all(body.as_bytes())
            .expect("write body");
    }
    // Bounded read (KNOWN-HAZARDS 5.6 posture): a hang here means a detached
    // child inherited the pipe.
    let (tx, rx) = std::sync::mpsc::channel();
    std::thread::spawn(move || {
        let out = child.wait_with_output();
        let _ = tx.send(out);
    });
    let out = rx
        .recv_timeout(Duration::from_secs(30))
        .expect("spt call must complete")
        .expect("spt runs");
    let mut s = String::from_utf8_lossy(&out.stdout).into_owned();
    let mut e = String::new();
    let _ = std::io::Cursor::new(out.stderr).read_to_string(&mut e);
    s.push_str(&e);
    s
}

-    // ---- step 2: bring an agent online (`spt poll bob`) ----

+    // ---- step 2: bring an agent online (`spt ready bob`) ----


//! M6-D4 quickstart rung — the published human quickstart
//! (docs-site/src/quickstart/messaging.md) executed step for step against the
//! real binary: ready brings bob online → live send delivers (`SENT`) and the
//! body + `__REPLY_TO__` header land on bob's stdout → bob goes away → send
//! spools (`QUEUED`) → `ready --once` drains the backlog. If the documented
//! flow stops working, this fails — the doc cannot silently rot (REQ-DOCS-2's
//! "runnable, deterministic" promise, CI-run on both platforms).
//! (M7 D1: `poll` merged into `ready --once`; the full lea/sergey + how-to
//! rewrite is the D5 deliverable.)
//!
//! Hermetic: isolated SPT_HOME per run; not env-gated (cheap, local-only).
//
// [int->REQ-DOCS-2]

use std::io::{BufRead, BufReader, Read};
use std::process::{Command, Stdio};
use std::time::Duration;

fn spt() -> Command {
    Command::new(env!("CARGO_BIN_EXE_spt"))
}

#[test]
fn published_messaging_quickstart_runs_as_written() {
    let home = tempfile::tempdir().expect("tempdir");
    let home = home.path();

    // ---- step 2: bring an agent online (`spt ready bob`) ----
    let mut bob = spt()
        .args(["ready", "bob"])
        .env("SPT_HOME", home)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("spawn poll bob");
    // READY:bob is the online signal (stderr).
    let mut bob_err = BufReader::new(bob.stderr.take().unwrap());
    let mut line = String::new();
    bob_err.read_line(&mut line).expect("read READY line");
    assert!(line.contains("READY:bob"), "expected READY:bob, got {line:?}");

    // ---- step 3: live send from alice ----
    let out = send(home, &["send", "bob", "--from", "alice"], "hello bob - alice here");
    assert!(
        out.contains("SENT:bob"),
        "live delivery must report SENT:bob, got {out:?}"
    );
    // The documented receive shape: routing header + body on bob's stdout.
    let mut bob_out = BufReader::new(bob.stdout.take().unwrap());
    let mut l1 = String::new();
    let mut l2 = String::new();
    bob_out.read_line(&mut l1).expect("read header");
    bob_out.read_line(&mut l2).expect("read body");
    assert!(l1.contains("__REPLY_TO__:alice"), "header line, got {l1:?}");
    assert!(l2.contains("hello bob - alice here"), "body line, got {l2:?}");

    // ---- step 4: bob goes away; the send spools ----
    bob.kill().expect("stop bob");
    let _ = bob.wait();
    let out = send(home, &["send", "bob", "--from", "alice"], "ping while you were away");
    assert!(
        out.contains("QUEUED:bob"),
        "offline delivery must report QUEUED:bob, got {out:?}"
    );

    // ---- bob comes back: the backlog drains ----
    let drained = spt()
        .args(["poll", "bob", "--once"])
        .env("SPT_HOME", home)
        .output()
        .expect("poll --once");
    let stdout = String::from_utf8_lossy(&drained.stdout);
    assert!(
        stdout.contains("ping while you were away"),
        "backlog must drain on return, got {stdout:?}"
    );
}

/// Run a one-shot spt command with `body` on stdin, returning stdout+stderr.
fn send(home: &std::path::Path, args: &[&str], body: &str) -> String {
    let mut child = spt()
        .args(args)
        .env("SPT_HOME", home)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("spawn spt");
    {
        use std::io::Write;
        child
            .stdin
            .take()
            .unwrap()
            .write_all(body.as_bytes())
            .expect("write body");
    }
    // Bounded read (KNOWN-HAZARDS 5.6 posture): a hang here means a detached
    // child inherited the pipe.
    let (tx, rx) = std::sync::mpsc::channel();
    std::thread::spawn(move || {
        let out = child.wait_with_output();
        let _ = tx.send(out);
    });
    let out = rx
        .recv_timeout(Duration::from_secs(30))
        .expect("spt call must complete")
        .expect("spt runs");
    let mut s = String::from_utf8_lossy(&out.stdout).into_owned();
    let mut e = String::new();
    let _ = std::io::Cursor::new(out.stderr).read_to_string(&mut e);
    s.push_str(&e);
    s
}


    let drained = spt()
        .args(["poll", "bob", "--once"])
        .env("SPT_HOME", home)
        .output()
        .expect("poll --once");

    let drained = spt()
        .args(["ready", "bob", "--once"])
        .env("SPT_HOME", home)
        .output()
        .expect("ready --once");


    let drained = spt()
        .args(["ready", "bob", "--once"])
        .env("SPT_HOME", home)
        .output()
        .expect("ready --once");

//! M6-D4 quickstart rung — the published human quickstart
//! (docs-site/src/quickstart/messaging.md) executed step for step against the
//! real binary: ready brings bob online → live send delivers (`SENT`) and the
//! body + `__REPLY_TO__` header land on bob's stdout → bob goes away → send
//! spools (`QUEUED`) → `ready --once` drains the backlog. If the documented
//! flow stops working, this fails — the doc cannot silently rot (REQ-DOCS-2's
//! "runnable, deterministic" promise, CI-run on both platforms).
//! (M7 D1: `poll` merged into `ready --once`; the full lea/sergey + how-to
//! rewrite is the D5 deliverable.)
//!
//! Hermetic: isolated SPT_HOME per run; not env-gated (cheap, local-only).
//
// [int->REQ-DOCS-2]

use std::io::{BufRead, BufReader, Read};
use std::process::{Command, Stdio};
use std::time::Duration;

fn spt() -> Command {
    Command::new(env!("CARGO_BIN_EXE_spt"))
}

#[test]
fn published_messaging_quickstart_runs_as_written() {
    let home = tempfile::tempdir().expect("tempdir");
    let home = home.path();

    // ---- step 2: bring an agent online (`spt ready bob`) ----
    let mut bob = spt()
        .args(["ready", "bob"])
        .env("SPT_HOME", home)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("spawn ready bob");
    // READY:bob is the online signal (stderr).
    let mut bob_err = BufReader::new(bob.stderr.take().unwrap());
    let mut line = String::new();
    bob_err.read_line(&mut line).expect("read READY line");
    assert!(line.contains("READY:bob"), "expected READY:bob, got {line:?}");

    // ---- step 3: live send from alice ----
    let out = send(home, &["send", "bob", "--from", "alice"], "hello bob - alice here");
    assert!(
        out.contains("SENT:bob"),
        "live delivery must report SENT:bob, got {out:?}"
    );
    // The documented receive shape: routing header + body on bob's stdout.
    let mut bob_out = BufReader::new(bob.stdout.take().unwrap());
    let mut l1 = String::new();
    let mut l2 = String::new();
    bob_out.read_line(&mut l1).expect("read header");
    bob_out.read_line(&mut l2).expect("read body");
    assert!(l1.contains("__REPLY_TO__:alice"), "header line, got {l1:?}");
    assert!(l2.contains("hello bob - alice here"), "body line, got {l2:?}");

    // ---- step 4: bob goes away; the send spools ----
    bob.kill().expect("stop bob");
    let _ = bob.wait();
    let out = send(home, &["send", "bob", "--from", "alice"], "ping while you were away");
    assert!(
        out.contains("QUEUED:bob"),
        "offline delivery must report QUEUED:bob, got {out:?}"
    );

    // ---- bob comes back: the backlog drains ----
    let drained = spt()
        .args(["poll", "bob", "--once"])
        .env("SPT_HOME", home)
        .output()
        .expect("poll --once");
    let stdout = String::from_utf8_lossy(&drained.stdout);
    assert!(
        stdout.contains("ping while you were away"),
        "backlog must drain on return, got {stdout:?}"
    );
}

/// Run a one-shot spt command with `body` on stdin, returning stdout+stderr.
fn send(home: &std::path::Path, args: &[&str], body: &str) -> String {
    let mut child = spt()
        .args(args)
        .env("SPT_HOME", home)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("spawn spt");
    {
        use std::io::Write;
        child
            .stdin
            .take()
            .unwrap()
            .write_all(body.as_bytes())
            .expect("write body");
    }
    // Bounded read (KNOWN-HAZARDS 5.6 posture): a hang here means a detached
    // child inherited the pipe.
    let (tx, rx) = std::sync::mpsc::channel();
    std::thread::spawn(move || {
        let out = child.wait_with_output();
        let _ = tx.send(out);
    });
    let out = rx
        .recv_timeout(Duration::from_secs(30))
        .expect("spt call must complete")
        .expect("spt runs");
    let mut s = String::from_utf8_lossy(&out.stdout).into_owned();
    let mut e = String::new();
    let _ = std::io::Cursor::new(out.stderr).read_to_string(&mut e);
    s.push_str(&e);
    s
}

+        .args(["ready", "bob", "--once"])


//!   `spt pair show-totp` screen (the wire ceremony itself is the REAL

//!   `spt subnet show-totp` screen (the wire ceremony itself is the REAL


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

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


Remove spt poll; add ready --once


18	//! env, so either side can be driven non-interactively (the gravity leg runs
19	//! under a commit-message-gated CI step; no SSH between the rig hosts):
20	//!
21	//! - node identities: `sha256(secret:"node-a"/"node-b")` → [`Identity`] seeds;
22	//! - the subnet TOTP seed: `sha256(secret:"subnet-totp")[..20]` — role A reads
23	//!   the 6-digit code off it exactly like a user reading the seed-holder's
24	//!   `spt pair show-totp` screen (the wire ceremony itself is the REAL
25	//!   SPAKE2/TOTP exchange — only the operator's eyeballs are simulated);
26	//! - the release signing key: `sha256(secret:"release")`;
27	//! - dialable addresses: derived node id + `SPT_TWO_HOST_PEER_IP` + the fixed
28	//!   ports ([`BindScope::Port`]) — id-only tickets with one static ip:port,
29	//!   relays and discovery disabled.
30	//!
31	//! Role B (seed-holder, server) seeds its stores, answers the pairing

//! env, so either side can be driven non-interactively (the gravity leg runs
//! under a commit-message-gated CI step; no SSH between the rig hosts):
//!
//! - node identities: `sha256(secret:"node-a"/"node-b")` → [`Identity`] seeds;
//! - the subnet TOTP seed: `sha256(secret:"subnet-totp")[..20]` — role A reads
//!   the 6-digit code off it exactly like a user reading the seed-holder's
//!   `spt pair show-totp` screen (the wire ceremony itself is the REAL
//!   SPAKE2/TOTP exchange — only the operator's eyeballs are simulated);
//! - the release signing key: `sha256(secret:"release")`;
//! - dialable addresses: derived node id + `SPT_TWO_HOST_PEER_IP` + the fixed
//!   ports ([`BindScope::Port`]) — id-only tickets with one static ip:port,
//!   relays and discovery disabled.
//!
//! Role B (seed-holder, server) seeds its stores, answers the pairing


//!   `spt pair show-totp` screen (the wire ceremony itself is the REAL

//!   `spt subnet show-totp` screen (the wire ceremony itself is the REAL


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

//! - node identities: `sha256(secret:"node-a"/"node-b")` → [`Identity`] seeds;

//! - the subnet TOTP seed: `sha256(secret:"subnet-totp")[..20]` — role A reads

//!   the 6-digit code off it exactly like a user reading the seed-holder's

-//!   `spt pair show-totp` screen (the wire ceremony itself is the REAL

+//!   `spt subnet show-totp` screen (the wire ceremony itself is the REAL

//!   SPAKE2/TOTP exchange — only the operator's eyeballs are simulated);

//! - the release signing key: `sha256(secret:"release")`;

//! - dialable addresses: derived node id + `SPT_TWO_HOST_PEER_IP` + the fixed


Build:


Bash

Build spt crate


    Updating crates.io index
     Locking 1 package to latest compatible version
      Adding qrcode v0.14.1
 Downloading crates ...
  Downloaded qrcode v0.14.1
   Compiling ring v0.17.14
   Compiling rustls v0.23.40
   Compiling terminal_size v0.4.4
   Compiling qrcode v0.14.1
   Compiling clap_builder v4.6.0
   Compiling clap v4.6.1
   Compiling rustls-webpki v0.103.13
   Compiling tokio-rustls v0.26.4
   Compiling noq-proto v0.17.0
   Compiling rustls-platform-verifier v0.7.0
   Compiling hyper-rustls v0.27.9
   Compiling hickory-net v0.26.0-beta.4
   Compiling tokio-websockets v0.13.2
   Compiling reqwest v0.13.4
   Compiling hickory-resolver v0.26.0-beta.4
   Compiling noq v0.18.0
   Compiling iroh-relay v0.98.0
   Compiling iroh v0.98.2
   Compiling spt-net v0.1.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
   Compiling spt-daemon v0.1.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
   Compiling spt v0.1.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 47.62s


Run spt unit tests