                            brain.attach(session_id, from_seq)?;
                            attached = true;
                            // Detection is a daemon fact (REQ-REACH-1): the
                            // served endpoint is now driven from origin_node.
                            // Re-stamped by a successor's re-serve after a
                            // brain crash (the same replayed Request that
                            // re-attaches); cleared when the viewport ends.
                            // [impl->REQ-REACH-1]
                            mark_driven_by(endpoint, Some(origin_node));

//! Remote terminal attach (M4-D5b, REQ-INST-8) — serve a byte-stream viewport
//! onto a local session for an operator on another node.
//!
//! The operator's daemon opened a bidi QUIC stream to this node (D4) and sent
//! [`AttachRecord::Request`]; this module is the target-side pump between that
//! stream and the local broker-hosted session:
//!
//! - session `Output` chunks → [`AttachRecord::Output`] lines onto the wire
//!   (fire-and-forget sends; `Output.seq` carries the dedup authority);
//! - inbound [`AttachRecord::Input`] → [`Brain::send_effect`] with the
//!   operator-minted `op_id` — exactly-once at the PTY write through the B5
//!   journal (a replayed input record cannot double-type);
//! - session `Exit` → [`AttachRecord::Exit`] + stream finish.
//!
//! ## Exactly-once lives at the EDGES, not the wire sends
//!
//! Stream sends here are deliberately **unjournaled** (`op_id: None`): a
//! journaled [`Brain::net_stream_send`] blocks reading events until its ack
//! and *discards* everything interleaved — inside a full-duplex pump loop that
//! eats the operator's `Input` records (both sides starve; found the hard way
//! in `tests/attach.rs`). Idempotency holds without it because each direction
//! dedups at its **effect**: the PTY write is journaled by the operator-minted
//! `Input.op_id`, and the operator's viewport dedups `Output` records by their
//! session-log `seq` (the render cursor). A post-restart re-serve may
//! re-transmit output records on the wire — duplicates are dropped at the
//! render cursor, the same discipline the local `OutputLog` replay uses
//! (REQ-HAZARD-RESTART-IDEMPOTENT at the boundary that matters).
//!
//! **Compute + files stay here**; the operator is a viewport (CONTEXT
//! §Remote-control vs local operation). The access gate runs before anything
//! is served: the [`AttachRecord::Request`] is *unsolicited wire inbound*, so
//! [`access_check`] sees the **handshake-proven** origin from the broker's
//! stream table — never payload bytes (KNOWN-HAZARDS 7.5). Remote-drive of
//! your own running instance is ungated by default (ADR-0009 default-open);
//! the D5d whitelist drops in at this same call.
//!
//! Restart survival composes from D4/B5 with **no new machinery**: the broker
//! owns the QUIC stream, the session, its `OutputLog`, and the effect journal.
//! A restarted target brain re-attaches the session from its output cursor,
//! resubscribes the stream from its net cursor, and re-drives journaled sends;
//! a restarted operator brain resubscribes and dedups rendered chunks by
//! `Output.seq`.

use std::io;

use spt_net::net::attach::{AttachDecoder, AttachRecord};
use spt_net::net::ndjson;
use spt_store::perch::{resolve_perch_path, ParentHint};

use crate::access::{access_check, AccessDecision, InboundClass};
use crate::brain::{Brain, BrokerEvent};
use crate::msg::{decode_bytes, encode_bytes};

/// Stamp (or clear) the endpoint's `driven_by` remote-drive marker — the
/// daemon fact REQ-REACH-1 turns detection on: the agent (and the `spt`
/// binary) read it off the perch's `info.json` rather than guessing from
/// session heuristics. Best-effort: a session served without a perch (raw
/// broker tests) simply has nowhere to record it.
fn mark_driven_by(endpoint: &str, origin_node: Option<&str>) {
    let perch = resolve_perch_path(endpoint, ParentHint::Infer);
    let _ = spt_store::info::set_driven_by(&perch, origin_node);
}

/// Why a serve loop returned.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AttachServeOutcome {
    /// The access gate refused the requesting origin — nothing was served.
    Refused,
    /// The session's child exited; the `Exit` record was sent and the stream
    /// finished.
    Exited(Option<i32>),
    /// The operator closed its send side (detached); the viewport ended with
    /// the session still running.
    Detached,
}

/// Serve one attach stream against this brain's broker, blocking until the
/// session exits, the operator detaches, or the gate refuses.
///
/// `origin_node` MUST be the handshake-proven remote id from the broker's
/// stream table (`NetStreamInfo::remote_id_hex`) — never payload bytes
/// (REQ-HAZARD-WAN-ORIGIN-AUTH). `endpoint` names the endpoint whose session
/// is being attached, for the gate. `net_from_seq` is this brain's resume
/// cursor on the attach stream (0 cold — also the safe post-restart choice:
/// the replayed `Request`/`Input` records re-gate and dedup at the PTY
/// journal, and re-pumped output dedups at the operator's render cursor).
// [impl->REQ-INST-8]
// [impl->REQ-HAZARD-WAN-ORIGIN-AUTH]
pub fn serve_attach(
    brain: &mut Brain,
    stream_id: u64,
    endpoint: &str,
    origin_node: &str,
    net_from_seq: u64,
) -> io::Result<AttachServeOutcome> {
    brain.net_stream_subscribe(stream_id, net_from_seq)?;
    let mut decoder = AttachDecoder::new();
    let mut attached = false;

    loop {
        match brain.read_event()? {
            // ── Wire → session: requests and input. ────────────────────────
            BrokerEvent::NetStreamData { stream_id: sid, bytes, .. } if sid == stream_id => {
                for rec in decoder.push(&bytes) {
                    match rec {
                        AttachRecord::Request { session_id, from_seq } => {
                            // Unsolicited wire inbound — the ADR-0009 gate,
                            // under the transport-proven origin.
                            if access_check(endpoint, origin_node, InboundClass::Unsolicited)
                                == AccessDecision::Deny
                            {
                                brain.net_stream_send(stream_id, &[], None, true)?;
                                return Ok(AttachServeOutcome::Refused);
                            }
                            brain.attach(session_id, from_seq)?;
                            attached = true;
                        }
                        AttachRecord::Input { data_b64, op_id } if attached => {
                            let bytes = decode_bytes(&data_b64)
                                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
                            // Exactly-once at the PTY: the operator-minted id
                            // rides the B5 journal.
                            brain.send_effect(op_id, &bytes)?;
                        }
                        // Input before Request, or target-direction records
                        // echoed back: protocol noise — ignore (degrade).
                        _ => {}
                    }
                }
            }
            // The operator closed its send side: the viewport is over.
            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {
                brain.net_stream_send(stream_id, &[], None, true)?;
                return Ok(AttachServeOutcome::Detached);
            }

            // ── Session → wire: output and exit. ────────────────────────────
            BrokerEvent::Output { seq, bytes, .. } if attached => {
                let line = ndjson::encode_line(&AttachRecord::Output {
                    seq,
                    data_b64: encode_bytes(&bytes),
                });
                // Unjournaled by design (see module docs): `seq` rides inside
                // the record, and the operator's render cursor is the dedup.
                brain.net_stream_send(stream_id, &line, None, false)?;
            }
            BrokerEvent::Exit { code, .. } if attached => {
                let line = ndjson::encode_line(&AttachRecord::Exit { code });
                brain.net_stream_send(stream_id, &line, None, true)?;
                return Ok(AttachServeOutcome::Exited(code));
            }

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

/// Operator-side: open an attach stream on a dialed connection and send the
/// [`AttachRecord::Request`]. Returns the stream id the caller then consumes
/// ([`AttachRecord::Output`] frames, deduped by `seq`) and writes
/// ([`AttachRecord::Input`] with operator-minted op ids). `open_op` is the
/// durable id for the exactly-once stream open (safe to block for its ack —
/// no attach traffic can be in flight before the stream exists); the Request
/// send itself is unjournaled (see module docs — a replayed Request re-gates
/// and re-attaches idempotently).
// [impl->REQ-INST-8]
pub fn request_attach(
    brain: &mut Brain,
    conn_id: u64,
    session_id: u64,
    from_seq: u64,
    open_op: u64,
) -> io::Result<u64> {
    let opened = brain.net_open_stream(conn_id, Some(open_op))?;
    let line = ndjson::encode_line(&AttachRecord::Request { session_id, from_seq });
    brain.net_stream_send(opened.stream_id, &line, None, false)?;
    Ok(opened.stream_id)
}

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

+                            // Detection is a daemon fact (REQ-REACH-1): the

+                            // served endpoint is now driven from origin_node.

+                            // Re-stamped by a successor's re-serve after a

+                            // brain crash (the same replayed Request that

+                            // re-attaches); cleared when the viewport ends.

+                            // [impl->REQ-REACH-1]

+                            mark_driven_by(endpoint, Some(origin_node));


Edit

            // The operator closed its send side: the viewport is over.
            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {
                brain.net_stream_send(stream_id, &[], None, true)?;
                return Ok(AttachServeOutcome::Detached);
            }

            // The operator closed its send side: the viewport is over.
            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {
                brain.net_stream_send(stream_id, &[], None, true)?;
                if attached {
                    mark_driven_by(endpoint, None);
                }
                return Ok(AttachServeOutcome::Detached);
            }


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

            // The operator closed its send side: the viewport is over.
            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {
                brain.net_stream_send(stream_id, &[], None, true)?;
                return Ok(AttachServeOutcome::Detached);
            }

            // The operator closed its send side: the viewport is over.
            BrokerEvent::NetStreamEof { stream_id: sid, .. } if sid == stream_id => {
                brain.net_stream_send(stream_id, &[], None, true)?;
                if attached {
                    mark_driven_by(endpoint, None);
                }
                return Ok(AttachServeOutcome::Detached);
            }

+                    mark_driven_by(endpoint, None);


            BrokerEvent::Exit { code, .. } if attached => {
                let line = ndjson::encode_line(&AttachRecord::Exit { code });
                brain.net_stream_send(stream_id, &line, None, true)?;
                return Ok(AttachServeOutcome::Exited(code));
            }

            BrokerEvent::Error { message } => return Err(io::Error::other(message)),

            BrokerEvent::Exit { code, .. } if attached => {
                let line = ndjson::encode_line(&AttachRecord::Exit { code });
                brain.net_stream_send(stream_id, &line, None, true)?;
                mark_driven_by(endpoint, None);
                return Ok(AttachServeOutcome::Exited(code));
            }

            BrokerEvent::Error { message } => {
                if attached {
                    mark_driven_by(endpoint, None);
                }
                return Err(io::Error::other(message));
            }


NOTE FROM THE USER: proceed with next steps.


            BrokerEvent::Exit { code, .. } if attached => {
                let line = ndjson::encode_line(&AttachRecord::Exit { code });
                brain.net_stream_send(stream_id, &line, None, true)?;
                return Ok(AttachServeOutcome::Exited(code));
            }

            BrokerEvent::Error { message } => return Err(io::Error::other(message)),

            BrokerEvent::Exit { code, .. } if attached => {
                let line = ndjson::encode_line(&AttachRecord::Exit { code });
                brain.net_stream_send(stream_id, &line, None, true)?;
                mark_driven_by(endpoint, None);
                return Ok(AttachServeOutcome::Exited(code));
            }

            BrokerEvent::Error { message } => {
                if attached {
                    mark_driven_by(endpoint, None);
                }
                return Err(io::Error::other(message));
            }

+                mark_driven_by(endpoint, None);

-            BrokerEvent::Error { message } => return Err(io::Error::other(message)),

+            BrokerEvent::Error { message } => {

+                return Err(io::Error::other(message));

// Applied/NetSent acks and unrelated events: not ours to handle.


D5c: wire records — spt-net xfer.rs (NDJSON, no origin field)

XferRecord {Fetch|Push|Chunk(offset)|Done|Err} + XferDecoder, unknown-kind skip, unit tests (round-trip, unknown-field tolerance, no-origin-field hazard test)

D5c: progress store + driven_by field — spt-store

xfer progress files {bytes_done,bytes_total,state} per (xfer_id, role) under spt_home/transfers, atomic writes, read helper; InfoJson.driven_by + set_driven_by; unit tests

D5c: daemon pumps — serve_xfer/fetch_file/push_file + driven_by stamping in serve_attach

spt-daemon/src/xfer.rs: target serve (gated, offset-idempotent writes, temp+rename, progress per chunk, fire-and-forget sends only); operator fetch/push; serve_attach stamps/clears driven_by on endpoint perch

D5c: api expose driven_by

Query surface so agent can detect "driven by node X" via spt api

D5c: int tests — fetch cross-daemon + push survives brain restart + detection

tests/xfer.rs (fetch w/ mid-flight progress; crash mid-push → re-serve seq 0 → byte-identical); attach.rs detection test (driven_by set on attach, cleared on detach)

D5c: docs + activation — handoff seam shape, FAULT-MATRIX row, REQ-REACH-1 [impl,unit]

Handoff seam (git pull + fresh-with-preload, remote dormant) doc'd as D6-consumed shape; FAULT-MATRIX row 13; traceable-reqs.toml activation; traceable-reqs check green; push + CI watch


Bash

cargo test -p spt-daemon --lib 2>&1 | tail -8

Build + run daemon unit tests


test relay::tests::respawn_loses_no_spooled_message ... ok
test relay::tests::run_honors_stop ... ok
test seedmap::tests::put_refreshes_existing ... ok
test seedmap::tests::put_then_take_consumes_once ... ok
test seedmap::tests::take_absent_is_none ... ok

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


cargo test -p spt-daemon --lib xfer 2>&1 | tail -6 && cargo clippy -p spt-daemon -p spt-net -p spt-store --all-targets 2>&1 | tail -5

Run xfer unit tests + clippy


running 2 tests
test xfer::tests::resolve_under_confines_to_root ... ok
test xfer::tests::recv_state_chunks_idempotent_and_commit_replays_safely ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 80 filtered out; finished in 0.01s

    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.93.0/index.html#suspicious_open_options

warning: `spt-daemon` (lib) generated 2 warnings
warning: `spt-daemon` (lib test) generated 2 warnings (2 duplicates)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.31s


cargo clippy -p spt-daemon --all-targets 2>&1 | grep -B4 -A12 "warning" | head -50

Show clippy warnings detail


warning: file opened with `create`, but `truncate` behavior not defined
   --> crates\spt-daemon\src\xfer.rs:143:48
    |
143 |         let file = std::fs::OpenOptions::new().create(true).write(true).open(&temp)?;
    |                                                ^^^^^^^^^^^^- help: add: `.truncate(true)`
    |
    = help: if you intend to overwrite an existing file entirely, call `.truncate(true)`
    = help: if you instead know that you may want to keep some parts of the old file, call `.truncate(false)`
    = help: alternatively, use `.append(true)` to append to the file instead of overwriting it
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.93.0/index.html#suspicious_open_options
    = note: `#[warn(clippy::suspicious_open_options)]` on by default

warning: file opened with `create`, but `truncate` behavior not defined
   --> crates\spt-daemon\src\xfer.rs:408:41
    |
408 |             std::fs::OpenOptions::new().create(true).write(true).open(temp_path(dest, xfer_id))?,
    |                                         ^^^^^^^^^^^^- help: add: `.truncate(true)`
    |
    = help: if you intend to overwrite an existing file entirely, call `.truncate(true)`
    = help: if you instead know that you may want to keep some parts of the old file, call `.truncate(false)`
    = help: alternatively, use `.append(true)` to append to the file instead of overwriting it
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.93.0/index.html#suspicious_open_options

warning: `spt-daemon` (lib) generated 2 warnings
warning: `spt-daemon` (lib test) generated 2 warnings (2 duplicates)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.43s


Edit

        // No truncate: a re-served (replayed) push rewrites the same bytes at
        // the same offsets over whatever the dead life already wrote.
        let file = std::fs::OpenOptions::new().create(true).write(true).open(&temp)?;

        // truncate(false): a re-served (replayed) push rewrites the same bytes
        // at the same offsets over whatever the dead life already wrote.
        let file =
            std::fs::OpenOptions::new().create(true).truncate(false).write(true).open(&temp)?;


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

        // No truncate: a re-served (replayed) push rewrites the same bytes at
        // the same offsets over whatever the dead life already wrote.
        let file = std::fs::OpenOptions::new().create(true).write(true).open(&temp)?;

        // truncate(false): a re-served (replayed) push rewrites the same bytes
        // at the same offsets over whatever the dead life already wrote.
        let file =
            std::fs::OpenOptions::new().create(true).truncate(false).write(true).open(&temp)?;

let temp = temp_path(&final_path, xfer_id);

-        // No truncate: a re-served (replayed) push rewrites the same bytes at

-        // the same offsets over whatever the dead life already wrote.

-        let file = std::fs::OpenOptions::new().create(true).write(true).open(&temp)?;

+        // truncate(false): a re-served (replayed) push rewrites the same bytes

+        // at the same offsets over whatever the dead life already wrote.

+            std::fs::OpenOptions::new().create(true).truncate(false).write(true).open(&temp)?;


        file: Some(
            std::fs::OpenOptions::new().create(true).write(true).open(temp_path(dest, xfer_id))?,
        ),

        file: Some(
            std::fs::OpenOptions::new()
                .create(true)
                .truncate(false)
                .write(true)
                .open(temp_path(dest, xfer_id))?,
        ),


        file: Some(
            std::fs::OpenOptions::new().create(true).write(true).open(temp_path(dest, xfer_id))?,
        ),

        file: Some(
            std::fs::OpenOptions::new()
                .create(true)
                .truncate(false)
                .write(true)
                .open(temp_path(dest, xfer_id))?,
        ),

-            std::fs::OpenOptions::new().create(true).write(true).open(temp_path(dest, xfer_id))?,

+            std::fs::OpenOptions::new()

+                .open(temp_path(dest, xfer_id))?,

total: 0, // unknown until Done (chunks carry offsets, not totals)


Bash

cargo clippy -p spt-daemon --all-targets 2>&1 | tail -3

Re-run clippy


    Checking spt-daemon v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.43s


Task 4: api expose.


Updated task #3 status


Updated task #4 status


1	//! Reporting + boundary surface: `api boundary`, `api session-end`,
2	//! `api presence`, `api history-log`, `api emit`, plus the M2a *declarations*
3	//! for commune/signoff file-drops and adapter env aliases.
4	//!
5	//! - **boundary** (REQ-API-2) — rebind the perch to the rotated `session_id`
6	//!   while keeping the stable identity + `parent_pid` anchor (CONTEXT §api
7	//!   boundary). The Self-resume commune authoring is M2b; M2a rebinds only.
8	//! - **session-end** (REQ-HAZARD-SOFT-CLEANUP / CASCADE-WIPE-GUARD) — soft
9	//!   teardown by default (preserve perch + spool + history); `--erase`
10	//!   hard-wipes, but never a parent that still hosts live workers.
11	//! - **presence** (REQ-API-2) — record `(endpoint, ts)`; no content (CONTEXT
12	//!   §presence is privacy-preserving).
13	//! - **history-log** (REQ-API-2) — Path-B native store: append a normalized
14	//!   record to the perch's history log.
15	//! - **emit** (REQ-API-2) — Shell sensory push; a stub here (concrete Shells M5).
16	//! - **commune/signoff are file-drops, not commands** (REQ-API-3): resolved from
17	//!   the manifest's watched dirs with spt-core-fixed filenames.
18	//! - **env aliases** (REQ-START-4): the adapter declares `$SPT`/`$OWL`/`$LIVE`
19	//!   in `[env]`; spt-core resolves the injections (it supplies the subcommands).
20	//
21	// [impl->REQ-API-2]
22
23	use std::path::PathBuf;
24
25	use spt_runtime::manifest::EnvDirection;
26	use spt_runtime::Manifest;
27	use spt_store::info::{self};
28	use spt_store::perch::{self, ParentHint};
29	use spt_store::registry;
30
31	use super::worker;
32	use super::{Ctx, EXIT_REFUSED};
33
34	/// `api boundary <clear|compact> <id> --to-session-id <sid>` — rebind the perch
35	/// to the rotated session id, preserving identity + parent_pid anchor.
36	// [impl->REQ-API-2]
37	pub fn cmd_boundary(id: &str, mode: &str, to_session: &str) -> i32 {
38	    if to_session.is_empty() {
39	        eprintln!("USAGE: boundary requires a non-empty --to-session-id");
40	        return EXIT_REFUSED;
41	    }
42	    let perch_path = perch::resolve_perch_path(id, ParentHint::Infer);
43	    let Some(mut rec) = info::read_info(&perch_path) else {
44	        eprintln!("NO_ENDPOINT:{id} has no perch to rebind");
45	        return EXIT_REFUSED;
46	    };
47	    // Keep id / started / parent_pid / cwd / state; rotate only the session id.
48	    rec.session_id = to_session.to_string();
49	    if let Err(e) = info::write_info(&perch_path, &rec) {
50	        eprintln!("BOUNDARY_FAIL:{id}: {e}");
51	        return EXIT_REFUSED;
52	    }
53	    eprintln!("BOUNDARY:{mode}:{id} -> {to_session}");
54	    0
55	}
56
57	/// `api session-end <id> [--erase]` — soft teardown by default; `--erase`
58	/// hard-wipes, guarded against cascade-wipe (6.3).
59	// [impl->REQ-HAZARD-SOFT-CLEANUP]
60	// [impl->REQ-HAZARD-CASCADE-WIPE-GUARD]

//! Reporting + boundary surface: `api boundary`, `api session-end`,
//! `api presence`, `api history-log`, `api emit`, plus the M2a *declarations*
//! for commune/signoff file-drops and adapter env aliases.
//!
//! - **boundary** (REQ-API-2) — rebind the perch to the rotated `session_id`
//!   while keeping the stable identity + `parent_pid` anchor (CONTEXT §api
//!   boundary). The Self-resume commune authoring is M2b; M2a rebinds only.
//! - **session-end** (REQ-HAZARD-SOFT-CLEANUP / CASCADE-WIPE-GUARD) — soft
//!   teardown by default (preserve perch + spool + history); `--erase`
//!   hard-wipes, but never a parent that still hosts live workers.
//! - **presence** (REQ-API-2) — record `(endpoint, ts)`; no content (CONTEXT
//!   §presence is privacy-preserving).
//! - **history-log** (REQ-API-2) — Path-B native store: append a normalized
//!   record to the perch's history log.
//! - **emit** (REQ-API-2) — Shell sensory push; a stub here (concrete Shells M5).
//! - **commune/signoff are file-drops, not commands** (REQ-API-3): resolved from
//!   the manifest's watched dirs with spt-core-fixed filenames.
//! - **env aliases** (REQ-START-4): the adapter declares `$SPT`/`$OWL`/`$LIVE`
//!   in `[env]`; spt-core resolves the injections (it supplies the subcommands).
//
// [impl->REQ-API-2]

use std::path::PathBuf;

use spt_runtime::manifest::EnvDirection;
use spt_runtime::Manifest;
use spt_store::info::{self};
use spt_store::perch::{self, ParentHint};
use spt_store::registry;

use super::worker;
use super::{Ctx, EXIT_REFUSED};

/// `api boundary <clear|compact> <id> --to-session-id <sid>` — rebind the perch
/// to the rotated session id, preserving identity + parent_pid anchor.
// [impl->REQ-API-2]
pub fn cmd_boundary(id: &str, mode: &str, to_session: &str) -> i32 {
    if to_session.is_empty() {
        eprintln!("USAGE: boundary requires a non-empty --to-session-id");
        return EXIT_REFUSED;
    }
    let perch_path = perch::resolve_perch_path(id, ParentHint::Infer);
    let Some(mut rec) = info::read_info(&perch_path) else {
        eprintln!("NO_ENDPOINT:{id} has no perch to rebind");
        return EXIT_REFUSED;
    };
    // Keep id / started / parent_pid / cwd / state; rotate only the session id.
    rec.session_id = to_session.to_string();
    if let Err(e) = info::write_info(&perch_path, &rec) {
        eprintln!("BOUNDARY_FAIL:{id}: {e}");
        return EXIT_REFUSED;
    }
    eprintln!("BOUNDARY:{mode}:{id} -> {to_session}");
    0
}

/// `api session-end <id> [--erase]` — soft teardown by default; `--erase`
/// hard-wipes, guarded against cascade-wipe (6.3).
// [impl->REQ-HAZARD-SOFT-CLEANUP]
// [impl->REQ-HAZARD-CASCADE-WIPE-GUARD]


Edit

/// Append a normalized record to the perch's Path-B native history store,

/// `api driven-by <id>` — report which node (if any) is remote-driving this
/// endpoint's session, read from the daemon-stamped `driven_by` marker on the
/// perch's `info.json` (REQ-REACH-1: detection is a daemon fact — the agent
/// queries it rather than guessing from session heuristics). Prints
/// `DRIVEN_BY:<id>:<node-hex>` while net-driven, `DRIVEN_BY:<id>:-` when
/// locally driven.
// [impl->REQ-REACH-1]
pub fn cmd_driven_by(id: &str) -> i32 {
    let perch_path = perch::resolve_perch_path(id, ParentHint::Infer);
    let Some(rec) = info::read_info(&perch_path) else {
        eprintln!("NO_ENDPOINT:{id} has no perch");
        return EXIT_REFUSED;
    };
    println!("DRIVEN_BY:{id}:{}", rec.driven_by.as_deref().unwrap_or("-"));
    0
}

/// Append a normalized record to the perch's Path-B native history store,


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

/// `api driven-by <id>` — report which node (if any) is remote-driving this
/// endpoint's session, read from the daemon-stamped `driven_by` marker on the
/// perch's `info.json` (REQ-REACH-1: detection is a daemon fact — the agent
/// queries it rather than guessing from session heuristics). Prints
/// `DRIVEN_BY:<id>:<node-hex>` while net-driven, `DRIVEN_BY:<id>:-` when
/// locally driven.
// [impl->REQ-REACH-1]
pub fn cmd_driven_by(id: &str) -> i32 {
    let perch_path = perch::resolve_perch_path(id, ParentHint::Infer);
    let Some(rec) = info::read_info(&perch_path) else {
        eprintln!("NO_ENDPOINT:{id} has no perch");
        return EXIT_REFUSED;
    };
    println!("DRIVEN_BY:{id}:{}", rec.driven_by.as_deref().unwrap_or("-"));
    0
}

/// Append a normalized record to the perch's Path-B native history store,

+/// `api driven-by <id>` — report which node (if any) is remote-driving this

+/// endpoint's session, read from the daemon-stamped `driven_by` marker on the

+/// perch's `info.json` (REQ-REACH-1: detection is a daemon fact — the agent

+/// queries it rather than guessing from session heuristics). Prints

+/// `DRIVEN_BY:<id>:<node-hex>` while net-driven, `DRIVEN_BY:<id>:-` when

+pub fn cmd_driven_by(id: &str) -> i32 {

+    let perch_path = perch::resolve_perch_path(id, ParentHint::Infer);

+    let Some(rec) = info::read_info(&perch_path) else {

+        eprintln!("NO_ENDPOINT:{id} has no perch");

+    println!("DRIVEN_BY:{id}:{}", rec.driven_by.as_deref().unwrap_or("-"));

/// returning bytes written. Thin wrapper over the store-layer primitive

/// ([`spt_store::history::append_record`]) — shared with the M3b digest's opt-in


142	    },
143	    /// Report presence (T7).
144	    Presence {
145	        id: String,
146	        #[command(flatten)]
147	        auth: AuthFlags,
148	    },
149	    /// Append to the native history store (Path-B) (T7).
150	    HistoryLog {
151	        id: String,
152	        #[command(flatten)]
153	        auth: AuthFlags,
154	    },
155	    /// Emit a Shell sensory payload (stub) (T7).
156	    Emit {
157	        id: String,
158	        #[arg(long)]
159	        r#type: String,
160	        #[command(flatten)]
161	        auth: AuthFlags,
162	    },
163	    /// Print the adapter's declared capability (`hostable_types`) (T7).
164	    Capability,
165	    /// Graceful live-agent signoff (M2b): run the final echo-commune BEFORE
166	    /// teardown (3.3), then soft-stop. The `spt shutdown` lifecycle path.
167	    Shutdown {
168	        id: String,
169	        #[command(flatten)]
170	        auth: AuthFlags,
171	    },
172	}
173
174	/// Auth proof flags shared by every mutating subcommand.
175	#[derive(Args)]
176	pub struct AuthFlags {
177	    /// Capability token proving association to the target perch.
178	    #[arg(long)]
179	    token: Option<String>,
180	    /// Session id proving association (matches the perch's info.json).
181	    #[arg(long)]
182	    session_id: Option<String>,
183	}
184
185	impl AuthFlags {
186	    fn proof(&self) -> Proof {
187	        Proof::resolve(self.token.clone(), self.session_id.clone())
188	    }
189	}
190
191	/// Resolved per-call context shared by handlers.
192	pub struct Ctx {
193	    /// The `adapter_name` carried on this call.
194	    pub adapter: String,
195	    /// Parsed manifest, when `--manifest` was supplied (and valid).
196	    pub manifest: Option<Manifest>,
197	}
198
199	/// Auto-start the per-machine daemon if absent (REQ-DAEMON-3). Best-effort: a
200	/// start failure is surfaced but does not abort commands that don't need the
201	/// daemon (e.g. `capability`); the seed/listen path re-checks and fails loudly if
202	/// the daemon is genuinely unreachable.
203	// [impl->REQ-DAEMON-3]
204	fn ensure_daemon() {
205	    if let Err(e) = spt_daemon::ensure_running() {
206	        eprintln!("DAEMON_START_WARN: {e}");
207	    }
208	}
209
210	/// Parse + dispatch the `api` group. Returns the process exit code.
211	pub fn run(args: ApiArgs) -> i32 {
212	    // Any api invocation brings the daemon up if absent — the reliable anchor
213	    // every harness hook shares (REQ-DAEMON-3).
214	    ensure_daemon();
215
216	    // Resolve the manifest up front so a bad path/content fails fast and
217	    // uniformly, before any state mutation.
218	    let manifest = match &args.manifest {
219	        Some(path) => match load_manifest(path, &args.adapter) {
220	            Ok(m) => Some(m),
221	            Err(code) => return code,
222	        },
223	        None => None,
224	    };
225	    let ctx = Ctx { adapter: args.adapter, manifest };
226
227	    match args.cmd {
228	        ApiCmd::Capability => reporting::cmd_capability(&ctx),
229	        // Harness-hosted startup (T4). Seed establishes; listen consumes+binds;
230	        // bind is the post-spawn establishing call (intrinsic auth).
231	        ApiCmd::Seed { pid, session_id } => startup::cmd_seed(&ctx, pid, session_id),
232	        ApiCmd::Listen { id, parent_pid, once } => {
233	            startup::cmd_listen(&id, parent_pid, once, ctx.manifest.as_ref())
234	        }
235	        ApiCmd::Bind { id, bind_session, auth } => {
236	            startup::cmd_bind(&id, bind_session, auth.session_id)
237	        }
238	        // Delivery + activity (T5), gated by auth.
239	        ApiCmd::State { state, id, no_gate, auth } => {
240	            gated(&id, &auth, |id| delivery::cmd_state(id, &state, no_gate))
241	        }
242	        ApiCmd::EchoGate { action, id, auth } => {
243	            gated(&id, &auth, |id| delivery::cmd_echo_gate(id, &action))
244	        }
245	        ApiCmd::Poll { id, include_deferred, auth } => gated(&id, &auth, |id| {
246	            delivery::cmd_poll(id, include_deferred, ctx.manifest.as_ref())
247	        }),
248	        // Worker perches (T6). worker-start authenticates against the parent.
249	        ApiCmd::WorkerStart { parent, id, auth } => {
250	            gated(&parent, &auth, |parent| worker::cmd_worker_start(parent, &id))
251	        }
252	        ApiCmd::WorkerStop { id, auth } => gated(&id, &auth, worker::cmd_worker_stop),
253	        ApiCmd::WorkerPoll { id, auth } => {
254	            gated(&id, &auth, |id| worker::cmd_worker_poll(id, false))
255	        }
256	        // Reporting + boundary (T7), gated by auth.
257	        ApiCmd::Boundary { mode, id, to_session, auth } => {
258	            gated(&id, &auth, |id| reporting::cmd_boundary(id, &mode, &to_session))
259	        }
260	        ApiCmd::SessionEnd { id, erase, auth } => {
261	            gated(&id, &auth, |id| reporting::cmd_session_end(id, erase))
262	        }
263	        ApiCmd::Presence { id, auth } => gated(&id, &auth, reporting::cmd_presence),
264	        ApiCmd::HistoryLog { id, auth } => gated(&id, &auth, reporting::cmd_history_log),
265	        ApiCmd::Shutdown { id, auth } => {
266	            let manifest = ctx.manifest.as_ref();
267	            gated(&id, &auth, |id| live::cmd_shutdown(id, manifest))
268	        }
269	        ApiCmd::Emit { id, r#type, auth } => {
270	            gated(&id, &auth, |id| reporting::cmd_emit(id, &r#type))
271	        }
272	    }
273	}
274
275	/// Load + validate a manifest, checking it matches the declared adapter_name.
276	/// Returns an exit code on failure (already reported to stderr).
277	fn load_manifest(path: &PathBuf, adapter: &str) -> Result<Manifest, i32> {
278	    let text = std::fs::read_to_string(path).map_err(|e| {
279	        eprintln!("MANIFEST_READ:{}: {e}", path.display());
280	        EXIT_REFUSED
281	    })?;

    },
    /// Report presence (T7).
    Presence {
        id: String,
        #[command(flatten)]
        auth: AuthFlags,
    },
    /// Append to the native history store (Path-B) (T7).
    HistoryLog {
        id: String,
        #[command(flatten)]
        auth: AuthFlags,
    },
    /// Emit a Shell sensory payload (stub) (T7).
    Emit {
        id: String,
        #[arg(long)]
        r#type: String,
        #[command(flatten)]
        auth: AuthFlags,
    },
    /// Print the adapter's declared capability (`hostable_types`) (T7).
    Capability,
    /// Graceful live-agent signoff (M2b): run the final echo-commune BEFORE
    /// teardown (3.3), then soft-stop. The `spt shutdown` lifecycle path.
    Shutdown {
        id: String,
        #[command(flatten)]
        auth: AuthFlags,
    },
}

/// Auth proof flags shared by every mutating subcommand.
#[derive(Args)]
pub struct AuthFlags {
    /// Capability token proving association to the target perch.
    #[arg(long)]
    token: Option<String>,
    /// Session id proving association (matches the perch's info.json).
    #[arg(long)]
    session_id: Option<String>,
}

impl AuthFlags {
    fn proof(&self) -> Proof {
        Proof::resolve(self.token.clone(), self.session_id.clone())
    }
}

/// Resolved per-call context shared by handlers.
pub struct Ctx {
    /// The `adapter_name` carried on this call.
    pub adapter: String,
    /// Parsed manifest, when `--manifest` was supplied (and valid).
    pub manifest: Option<Manifest>,
}

/// Auto-start the per-machine daemon if absent (REQ-DAEMON-3). Best-effort: a
/// start failure is surfaced but does not abort commands that don't need the
/// daemon (e.g. `capability`); the seed/listen path re-checks and fails loudly if
/// the daemon is genuinely unreachable.
// [impl->REQ-DAEMON-3]
fn ensure_daemon() {
    if let Err(e) = spt_daemon::ensure_running() {
        eprintln!("DAEMON_START_WARN: {e}");
    }
}

/// Parse + dispatch the `api` group. Returns the process exit code.
pub fn run(args: ApiArgs) -> i32 {
    // Any api invocation brings the daemon up if absent — the reliable anchor
    // every harness hook shares (REQ-DAEMON-3).
    ensure_daemon();

    // Resolve the manifest up front so a bad path/content fails fast and
    // uniformly, before any state mutation.
    let manifest = match &args.manifest {
        Some(path) => match load_manifest(path, &args.adapter) {
            Ok(m) => Some(m),
            Err(code) => return code,
        },
        None => None,
    };
    let ctx = Ctx { adapter: args.adapter, manifest };

    match args.cmd {
        ApiCmd::Capability => reporting::cmd_capability(&ctx),
        // Harness-hosted startup (T4). Seed establishes; listen consumes+binds;
        // bind is the post-spawn establishing call (intrinsic auth).
        ApiCmd::Seed { pid, session_id } => startup::cmd_seed(&ctx, pid, session_id),
        ApiCmd::Listen { id, parent_pid, once } => {
            startup::cmd_listen(&id, parent_pid, once, ctx.manifest.as_ref())
        }
        ApiCmd::Bind { id, bind_session, auth } => {
            startup::cmd_bind(&id, bind_session, auth.session_id)
        }
        // Delivery + activity (T5), gated by auth.
        ApiCmd::State { state, id, no_gate, auth } => {
            gated(&id, &auth, |id| delivery::cmd_state(id, &state, no_gate))
        }
        ApiCmd::EchoGate { action, id, auth } => {
            gated(&id, &auth, |id| delivery::cmd_echo_gate(id, &action))
        }
        ApiCmd::Poll { id, include_deferred, auth } => gated(&id, &auth, |id| {
            delivery::cmd_poll(id, include_deferred, ctx.manifest.as_ref())
        }),
        // Worker perches (T6). worker-start authenticates against the parent.
        ApiCmd::WorkerStart { parent, id, auth } => {
            gated(&parent, &auth, |parent| worker::cmd_worker_start(parent, &id))
        }
        ApiCmd::WorkerStop { id, auth } => gated(&id, &auth, worker::cmd_worker_stop),
        ApiCmd::WorkerPoll { id, auth } => {
            gated(&id, &auth, |id| worker::cmd_worker_poll(id, false))
        }
        // Reporting + boundary (T7), gated by auth.
        ApiCmd::Boundary { mode, id, to_session, auth } => {
            gated(&id, &auth, |id| reporting::cmd_boundary(id, &mode, &to_session))
        }
        ApiCmd::SessionEnd { id, erase, auth } => {
            gated(&id, &auth, |id| reporting::cmd_session_end(id, erase))
        }
        ApiCmd::Presence { id, auth } => gated(&id, &auth, reporting::cmd_presence),
        ApiCmd::HistoryLog { id, auth } => gated(&id, &auth, reporting::cmd_history_log),
        ApiCmd::Shutdown { id, auth } => {
            let manifest = ctx.manifest.as_ref();
            gated(&id, &auth, |id| live::cmd_shutdown(id, manifest))
        }
        ApiCmd::Emit { id, r#type, auth } => {
            gated(&id, &auth, |id| reporting::cmd_emit(id, &r#type))
        }
    }
}

/// Load + validate a manifest, checking it matches the declared adapter_name.
/// Returns an exit code on failure (already reported to stderr).
fn load_manifest(path: &PathBuf, adapter: &str) -> Result<Manifest, i32> {
    let text = std::fs::read_to_string(path).map_err(|e| {
        eprintln!("MANIFEST_READ:{}: {e}", path.display());
        EXIT_REFUSED
    })?;


    /// Report presence (T7).
    Presence {
        id: String,
        #[command(flatten)]
        auth: AuthFlags,
    },

    /// Report presence (T7).
    Presence {
        id: String,
        #[command(flatten)]
        auth: AuthFlags,
    },
    /// Report which node (if any) is remote-driving this endpoint (M4-D5c).
    DrivenBy {
        id: String,
        #[command(flatten)]
        auth: AuthFlags,
    },


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

    /// Report presence (T7).
    Presence {
        id: String,
        #[command(flatten)]
        auth: AuthFlags,
    },
    /// Report which node (if any) is remote-driving this endpoint (M4-D5c).
    DrivenBy {
        id: String,
        #[command(flatten)]
        auth: AuthFlags,
    },

+    /// Report which node (if any) is remote-driving this endpoint (M4-D5c).

/// Append to the native history store (Path-B) (T7).


ApiCmd::Presence { id, auth } => gated(&id, &auth, reporting::cmd_presence),

        ApiCmd::Presence { id, auth } => gated(&id, &auth, reporting::cmd_presence),
        ApiCmd::DrivenBy { id, auth } => gated(&id, &auth, reporting::cmd_driven_by),


        ApiCmd::Presence { id, auth } => gated(&id, &auth, reporting::cmd_presence),
        ApiCmd::DrivenBy { id, auth } => gated(&id, &auth, reporting::cmd_driven_by),

gated(&id, &auth, |id| reporting::cmd_session_end(id, erase))

+        ApiCmd::DrivenBy { id, auth } => gated(&id, &auth, reporting::cmd_driven_by),

ApiCmd::HistoryLog { id, auth } => gated(&id, &auth, reporting::cmd_history_log),


Bash

grep -rn "cmd_presence\|Presence" crates/spt/src --include=*.rs | grep -i test | head; ls crates/spt/tests 2>/dev/null

Find api test coverage pattern


grep -n "presence\|driven" crates/spt/tests/contract_e2e.rs | head -10 && grep -c "" crates/spt/tests/contract_e2e.rs

Check contract e2e coverage of presence


grep -n "mod tests\|#\[test\]" crates/spt/src/api/reporting.rs | head

Check reporting.rs test module


223:mod tests {
237:    #[test] // [unit->REQ-API-2] boundary rebinds the session id, keeps identity+anchor.
248:    #[test] // [unit->REQ-API-2] boundary on a missing perch is refused.
254:    #[test] // [unit->REQ-HAZARD-SOFT-CLEANUP] soft session-end preserves info+spool.
264:    #[test] // [unit->REQ-HAZARD-SOFT-CLEANUP] --erase hard-wipes a childless perch.
272:    #[test] // [unit->REQ-HAZARD-CASCADE-WIPE-GUARD] --erase refuses a parent with
284:    #[test] // [unit->REQ-API-2] history-log appends to the Path-B native store.
296:    #[test] // [unit->REQ-API-2] presence records the endpoint, no content.
305:    #[test] // [unit->REQ-API-3] commune/signoff resolve as file-drops with fixed names.
318:    #[test] // [unit->REQ-SEAM-CAPABILITY] capability lists the adapter's hostable_types.


223	mod tests {
224	    use super::*;
225	    use crate::testutil::isolated_home;
226	    use spt_store::info::InfoJson;
227
228	    fn establish(id: &str, session: &str) {
229	        let path = perch::resolve_perch_path(id, ParentHint::Infer);
230	        std::fs::create_dir_all(&path).unwrap();
231	        let mut rec = InfoJson::new(id, "0", std::process::id(), session, "live_agent");
232	        rec.parent_pid = Some(4242);
233	        info::write_info(&path, &rec).unwrap();
234	        std::fs::write(perch::resolve_ready_file(id, ParentHint::Infer), "").unwrap();
235	    }
236
237	    #[test] // [unit->REQ-API-2] boundary rebinds the session id, keeps identity+anchor.
238	    fn boundary_rebinds_keeping_anchor() {
239	        let _h = isolated_home();
240	        establish("alice", "sid-old");
241	        assert_eq!(cmd_boundary("alice", "compact", "sid-new"), 0);
242	        let rec = info::read_info(&perch::resolve_perch_path("alice", ParentHint::Infer)).unwrap();
243	        assert_eq!(rec.session_id, "sid-new");
244	        assert_eq!(rec.parent_pid, Some(4242), "parent anchor preserved");
245	        assert_eq!(rec.id, "alice");
246	    }
247
248	    #[test] // [unit->REQ-API-2] boundary on a missing perch is refused.
249	    fn boundary_missing_perch_refused() {
250	        let _h = isolated_home();
251	        assert_eq!(cmd_boundary("ghost", "clear", "sid"), EXIT_REFUSED);
252	    }
253
254	    #[test] // [unit->REQ-HAZARD-SOFT-CLEANUP] soft session-end preserves info+spool.
255	    fn session_end_soft_preserves_state() {
256	        let _h = isolated_home();
257	        establish("alice", "sid");
258	        assert_eq!(cmd_session_end("alice", false), 0);
259	        let perch_path = perch::resolve_perch_path("alice", ParentHint::Infer);
260	        assert!(perch_path.join("info.json").exists(), "info preserved");
261	        assert!(!perch::resolve_ready_file("alice", ParentHint::Infer).exists(), "ready removed");
262	    }
263
264	    #[test] // [unit->REQ-HAZARD-SOFT-CLEANUP] --erase hard-wipes a childless perch.
265	    fn session_end_erase_wipes_childless_perch() {
266	        let _h = isolated_home();
267	        establish("alice", "sid");
268	        assert_eq!(cmd_session_end("alice", true), 0);
269	        assert!(!perch::resolve_perch_path("alice", ParentHint::Infer).exists());
270	    }
271
272	    #[test] // [unit->REQ-HAZARD-CASCADE-WIPE-GUARD] --erase refuses a parent with
273	    // live workers; soft-cleans instead.
274	    fn session_end_erase_refused_with_live_children() {
275	        let _h = isolated_home();
276	        establish("alice", "sid");
277	        worker::cmd_worker_start("alice", "alice-w1");
278	        assert_eq!(cmd_session_end("alice", true), EXIT_REFUSED);
279	        // Parent dir (and the live child) survive the refused erase.
280	        assert!(perch::resolve_perch_path("alice", ParentHint::Infer).join("info.json").exists());
281	        assert!(perch::resolve_perch_path("alice-w1", ParentHint::Infer).join("info.json").exists());
282	    }
283
284	    #[test] // [unit->REQ-API-2] history-log appends to the Path-B native store.
285	    fn history_log_appends() {
286	        let _h = isolated_home();
287	        establish("alice", "sid");
288	        append_history("alice", "{\"role\":\"user\",\"text\":\"hi\"}").unwrap();
289	        append_history("alice", "{\"role\":\"assistant\",\"text\":\"yo\"}").unwrap();
290	        let log = perch::resolve_perch_path("alice", ParentHint::Infer).join("history.log");
291	        let content = std::fs::read_to_string(log).unwrap();
292	        assert_eq!(content.lines().count(), 2);
293	        assert!(content.contains("\"hi\""));
294	    }
295
296	    #[test] // [unit->REQ-API-2] presence records the endpoint, no content.
297	    fn presence_records_endpoint() {
298	        let _h = isolated_home();
299	        assert_eq!(cmd_presence("alice"), 0);
300	        let datum = std::fs::read_to_string(presence_path()).unwrap();
301	        assert!(datum.contains("alice"));
302	        assert!(datum.contains("ts"));
303	    }
304
305	    #[test] // [unit->REQ-API-3] commune/signoff resolve as file-drops with fixed names.
306	    fn filedrops_are_fixed_named_files() {
307	        let m = Manifest::from_toml_str(
308	            "[adapter]\nname=\"m\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n[session]\ncommune_dir=\".claude\"\nsignoff_dir=\".claude\"\n",
309	        )
310	        .unwrap();
311	        let (commune, signoff) = resolve_filedrops(Some(&m), "alice");
312	        assert_eq!(commune.unwrap(), PathBuf::from(".claude").join("alice-commune.md"));
313	        assert_eq!(signoff.unwrap(), PathBuf::from(".claude").join("alice-signoff.md"));
314	        // No manifest → no file-drops resolved.
315	        assert_eq!(resolve_filedrops(None, "alice"), (None, None));
316	    }
317

mod tests {
    use super::*;
    use crate::testutil::isolated_home;
    use spt_store::info::InfoJson;

    fn establish(id: &str, session: &str) {
        let path = perch::resolve_perch_path(id, ParentHint::Infer);
        std::fs::create_dir_all(&path).unwrap();
        let mut rec = InfoJson::new(id, "0", std::process::id(), session, "live_agent");
        rec.parent_pid = Some(4242);
        info::write_info(&path, &rec).unwrap();
        std::fs::write(perch::resolve_ready_file(id, ParentHint::Infer), "").unwrap();
    }

    #[test] // [unit->REQ-API-2] boundary rebinds the session id, keeps identity+anchor.
    fn boundary_rebinds_keeping_anchor() {
        let _h = isolated_home();
        establish("alice", "sid-old");
        assert_eq!(cmd_boundary("alice", "compact", "sid-new"), 0);
        let rec = info::read_info(&perch::resolve_perch_path("alice", ParentHint::Infer)).unwrap();
        assert_eq!(rec.session_id, "sid-new");
        assert_eq!(rec.parent_pid, Some(4242), "parent anchor preserved");
        assert_eq!(rec.id, "alice");
    }

    #[test] // [unit->REQ-API-2] boundary on a missing perch is refused.
    fn boundary_missing_perch_refused() {
        let _h = isolated_home();
        assert_eq!(cmd_boundary("ghost", "clear", "sid"), EXIT_REFUSED);
    }

    #[test] // [unit->REQ-HAZARD-SOFT-CLEANUP] soft session-end preserves info+spool.
    fn session_end_soft_preserves_state() {
        let _h = isolated_home();
        establish("alice", "sid");
        assert_eq!(cmd_session_end("alice", false), 0);
        let perch_path = perch::resolve_perch_path("alice", ParentHint::Infer);
        assert!(perch_path.join("info.json").exists(), "info preserved");
        assert!(!perch::resolve_ready_file("alice", ParentHint::Infer).exists(), "ready removed");
    }

    #[test] // [unit->REQ-HAZARD-SOFT-CLEANUP] --erase hard-wipes a childless perch.
    fn session_end_erase_wipes_childless_perch() {
        let _h = isolated_home();
        establish("alice", "sid");
        assert_eq!(cmd_session_end("alice", true), 0);
        assert!(!perch::resolve_perch_path("alice", ParentHint::Infer).exists());
    }

    #[test] // [unit->REQ-HAZARD-CASCADE-WIPE-GUARD] --erase refuses a parent with
    // live workers; soft-cleans instead.
    fn session_end_erase_refused_with_live_children() {
        let _h = isolated_home();
        establish("alice", "sid");
        worker::cmd_worker_start("alice", "alice-w1");
        assert_eq!(cmd_session_end("alice", true), EXIT_REFUSED);
        // Parent dir (and the live child) survive the refused erase.
        assert!(perch::resolve_perch_path("alice", ParentHint::Infer).join("info.json").exists());
        assert!(perch::resolve_perch_path("alice-w1", ParentHint::Infer).join("info.json").exists());
    }

    #[test] // [unit->REQ-API-2] history-log appends to the Path-B native store.
    fn history_log_appends() {
        let _h = isolated_home();
        establish("alice", "sid");
        append_history("alice", "{\"role\":\"user\",\"text\":\"hi\"}").unwrap();
        append_history("alice", "{\"role\":\"assistant\",\"text\":\"yo\"}").unwrap();
        let log = perch::resolve_perch_path("alice", ParentHint::Infer).join("history.log");
        let content = std::fs::read_to_string(log).unwrap();
        assert_eq!(content.lines().count(), 2);
        assert!(content.contains("\"hi\""));
    }

    #[test] // [unit->REQ-API-2] presence records the endpoint, no content.
    fn presence_records_endpoint() {
        let _h = isolated_home();
        assert_eq!(cmd_presence("alice"), 0);
        let datum = std::fs::read_to_string(presence_path()).unwrap();
        assert!(datum.contains("alice"));
        assert!(datum.contains("ts"));
    }

    #[test] // [unit->REQ-API-3] commune/signoff resolve as file-drops with fixed names.
    fn filedrops_are_fixed_named_files() {
        let m = Manifest::from_toml_str(
            "[adapter]\nname=\"m\"\nversion=\"1\"\nmin_spt_core_version=\"1\"\n\n[session]\ncommune_dir=\".claude\"\nsignoff_dir=\".claude\"\n",
        )
        .unwrap();
        let (commune, signoff) = resolve_filedrops(Some(&m), "alice");
        assert_eq!(commune.unwrap(), PathBuf::from(".claude").join("alice-commune.md"));
        assert_eq!(signoff.unwrap(), PathBuf::from(".claude").join("alice-signoff.md"));
        // No manifest → no file-drops resolved.
        assert_eq!(resolve_filedrops(None, "alice"), (None, None));
    }


Edit

#[test] // [unit->REQ-API-3] commune/signoff resolve as file-drops with fixed names.

    #[test] // [unit->REQ-REACH-1] driven-by reads the daemon-stamped remote-drive
    // marker: "-" while locally driven, the node hex while net-driven, refused
    // without a perch (the agent's detection query, not a guess).
    fn driven_by_reads_daemon_marker() {
        let _h = isolated_home();
        assert_eq!(cmd_driven_by("ghost"), EXIT_REFUSED, "no perch → refused");

        establish("alice", "sid");
        assert_eq!(cmd_driven_by("alice"), 0, "locally driven reads clean");

        let perch_path = perch::resolve_perch_path("alice", ParentHint::Infer);
        info::set_driven_by(&perch_path, Some("cafe1234")).unwrap();
        assert_eq!(cmd_driven_by("alice"), 0);
        assert_eq!(
            info::read_info(&perch_path).unwrap().driven_by.as_deref(),
            Some("cafe1234"),
            "the marker the command reports"
        );
    }

    #[test] // [unit->REQ-API-3] commune/signoff resolve as file-drops with fixed names.


    #[test] // [unit->REQ-REACH-1] driven-by reads the daemon-stamped remote-drive
    // marker: "-" while locally driven, the node hex while net-driven, refused
    // without a perch (the agent's detection query, not a guess).
    fn driven_by_reads_daemon_marker() {
        let _h = isolated_home();
        assert_eq!(cmd_driven_by("ghost"), EXIT_REFUSED, "no perch → refused");

        establish("alice", "sid");
        assert_eq!(cmd_driven_by("alice"), 0, "locally driven reads clean");

        let perch_path = perch::resolve_perch_path("alice", ParentHint::Infer);
        info::set_driven_by(&perch_path, Some("cafe1234")).unwrap();
        assert_eq!(cmd_driven_by("alice"), 0);
        assert_eq!(
            info::read_info(&perch_path).unwrap().driven_by.as_deref(),
            Some("cafe1234"),
            "the marker the command reports"
        );
    }

    #[test] // [unit->REQ-API-3] commune/signoff resolve as file-drops with fixed names.

+    #[test] // [unit->REQ-REACH-1] driven-by reads the daemon-stamped remote-drive

+    // marker: "-" while locally driven, the node hex while net-driven, refused

+    // without a perch (the agent's detection query, not a guess).

+    fn driven_by_reads_daemon_marker() {

+        assert_eq!(cmd_driven_by("ghost"), EXIT_REFUSED, "no perch → refused");

+        assert_eq!(cmd_driven_by("alice"), 0, "locally driven reads clean");

+        let perch_path = perch::resolve_perch_path("alice", ParentHint::Infer);

+        info::set_driven_by(&perch_path, Some("cafe1234")).unwrap();

+        assert_eq!(cmd_driven_by("alice"), 0);

+            info::read_info(&perch_path).unwrap().driven_by.as_deref(),

+            "the marker the command reports"


D5c: api expose driven_by


Bash

Run spt crate unit tests


error: no library targets found in package `spt`


cargo test -p spt --bin spt driven 2>&1 | tail -8

Run new driven-by test


    Finished `test` profile [unoptimized + debuginfo] target(s) in 9.26s
     Running unittests src\main.rs (target\debug\deps\spt-bf411dc9a50e797b.exe)

running 1 test
test api::reporting::tests::driven_by_reads_daemon_marker ... ok

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


Updated task #5 status


Write

//! D5c regression — off-node file transfer (REQ-REACH-1): pull a file off the
//! target node and push one onto it over the broker-owned wire, chunked,
//! progress-queryable, and exactly-once across a target-brain restart.
//!
//! Two in-process brokers (hermetic loopback endpoints, relays disabled) play
//! the two daemons. The invariants:
//!
//! - **fetch**: a multi-chunk file on the target lands byte-identical at the
//!   operator; both ends' durable progress records read `done == total` after
//!   commit (the mid-flight query surface, CONTEXT §file-transfer progress);
//! - **path confinement**: wire paths resolve under the target's transfer
//!   root — a traversal attempt is refused with an `Err` record, not served;
//! - **restart survival** (REQ-HAZARD-RESTART-IDEMPOTENT): the target brain
//!   dies mid-push with a partial temp file on disk; the successor re-serves
//!   the SAME stream from seq 0 — the replayed chunks rewrite in place
//!   (positional idempotency), the commit renames atomically, and the file is
//!   byte-identical with no stray temp left behind.

use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;

use spt_daemon::brain::{Brain, BrokerEvent};
use spt_daemon::nethost::{NetConfig, NetHost};
use spt_daemon::xfer::{fetch_file, push_file, serve_xfer, XferServeOutcome};
use spt_daemon::Broker;
use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};
use spt_net::net::xfer::{XferDecoder, XferRecord};
use spt_proto::identity::Identity;
use spt_store::xfer::{read_progress_at, XferRole};

static SEQ: AtomicU32 = AtomicU32::new(0);
fn unique_name() -> String {
    let n = SEQ.fetch_add(1, Ordering::Relaxed);
    format!("spt-daemon-d5c-{}-{}.sock", std::process::id(), n)
}

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

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

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

/// Poll the brain's `net-streams` until a stream appears; return
/// `(stream_id, remote_id_hex)`.
fn wait_for_stream(brain: &mut Brain) -> (u64, String) {
    for _ in 0..400 {
        let reply = brain.net_streams().expect("net-streams");
        if let Some(s) = reply.streams.first() {
            return (s.stream_id, s.remote_id_hex.clone());
        }
        thread::sleep(Duration::from_millis(5));
    }
    panic!("transfer stream never appeared at the target's broker");
}

/// A deterministic multi-chunk payload (~3 chunks at the 64KiB chunk size),
/// varied enough that any offset mix-up corrupts a comparison.
fn payload() -> Vec<u8> {
    (0..150_000u32).map(|i| (i % 251) as u8).collect()
}

// [unit->REQ-REACH-1] pull a multi-chunk file off the target node: it lands
// byte-identical at the operator, both ends' durable progress records read
// done == total (the mid-flight query surface), and a traversal attempt is
// refused with an Err record under the handshake-proven origin gate.
#[test]
fn fetch_lands_byte_identical_with_progress_both_ends() {
    let dir = tempfile::tempdir().expect("tempdir");
    let (name_a, name_b) = (unique_name(), unique_name());
    let _broker_a = net_broker(&name_a, &dir.path().join("a"));
    let _broker_b = net_broker(&name_b, &dir.path().join("b"));

    // Target A: a real file under its transfer root.
    let root = dir.path().join("root");
    std::fs::create_dir_all(root.join("notes")).expect("root");
    let bytes = payload();
    std::fs::write(root.join("notes").join("plan.md"), &bytes).expect("seed file");
    let progress_a = dir.path().join("progress-a");
    let progress_b = dir.path().join("progress-b");

    let mut target = connect_retry(&name_a);
    let a_addr = target.net_status().expect("a status").addr;

    // Operator B fetches on its own thread (the call blocks until commit).
    let mut operator = connect_retry(&name_b);
    let b_node = operator.net_status().expect("b status").node_id_hex.expect("node id");
    let conn = operator.net_dial(a_addr, Some(1)).expect("dial");
    let dest = dir.path().join("fetched").join("plan.md");
    let dest_clone = dest.clone();
    let progress_b_clone = progress_b.clone();
    let fetcher = thread::spawn(move || {
        let n = fetch_file(
            &mut operator,
            conn.conn_id,
            "notes/plan.md",
            &dest_clone,
            "xfer1",
            2,
            &progress_b_clone,
        )
        .expect("fetch");
        (n, operator)
    });

    // Target A: serve the arriving stream under the handshake-proven origin.
    let (stream_a, origin) = wait_for_stream(&mut target);
    assert_eq!(origin, b_node, "serve gate subject IS the QUIC handshake identity");
    let outcome =
        serve_xfer(&mut target, stream_a, "ling", &origin, 0, &root, &progress_a).expect("serve");
    assert_eq!(outcome, XferServeOutcome::Sent { bytes: bytes.len() as u64 });

    let (n, mut operator) = fetcher.join().expect("fetch thread");
    assert_eq!(n, bytes.len() as u64);
    assert_eq!(std::fs::read(&dest).expect("dest"), bytes, "byte-identical across the wire");

    // Both ends' durable progress records: done == total, state done — the
    // record the agent/GUI polls mid-flight, settled at commit.
    let send = read_progress_at(&progress_a, "xfer1", XferRole::Send).expect("send progress");
    let recv = read_progress_at(&progress_b, "xfer1", XferRole::Recv).expect("recv progress");
    assert_eq!((send.bytes_done, send.bytes_total, send.state.as_str()),
        (bytes.len() as u64, bytes.len() as u64, "done"));
    assert_eq!((recv.bytes_done, recv.bytes_total, recv.state.as_str()),
        (bytes.len() as u64, bytes.len() as u64, "done"));

    // Confinement: a traversal fetch is refused with an Err record (the
    // operator's fetch errors; nothing outside the root is served).
    let conn2 = operator.net_dial(target.net_status().expect("a").addr, Some(3)).expect("dial2");
    let dest2 = dir.path().join("stolen.txt");
    let fetcher2 = thread::spawn(move || {
        let err = fetch_file(
            &mut operator, conn2.conn_id, "../effects.log", &dest2, "xfer2", 4, &progress_b,
        )
        .expect_err("traversal must be refused");
        err.to_string()
    });
    let (stream2, origin2) = wait_for_stream(&mut target);
    let outcome2 =
        serve_xfer(&mut target, stream2, "ling", &origin2, 0, &root, &progress_a).expect("serve2");
    assert!(matches!(outcome2, XferServeOutcome::Failed(_)), "refused, not served: {outcome2:?}");
    let msg = fetcher2.join().expect("fetch2 thread");
    assert!(msg.contains("refused"), "operator sees the refusal: {msg}");
}

// [unit->REQ-REACH-1] push survives a target-brain crash mid-receive exactly
// once: life 1 dies with a partial temp file on disk; the successor re-serves
// the SAME broker-held stream from seq 0 — the replayed Push re-opens the
// temp, the replayed chunks rewrite the same bytes at the same offsets
// (positional idempotency), the commit renames atomically, and the operator's
// blocked push observes the durable commit echo.
// [unit->REQ-HAZARD-RESTART-IDEMPOTENT]
#[test]
fn push_survives_target_brain_restart_exactly_once() {
    let dir = tempfile::tempdir().expect("tempdir");
    let (name_a, name_b) = (unique_name(), unique_name());
    let _broker_a = net_broker(&name_a, &dir.path().join("a"));
    let _broker_b = net_broker(&name_b, &dir.path().join("b"));

    let root = dir.path().join("root");
    std::fs::create_dir_all(&root).expect("root");
    let progress_a = dir.path().join("progress-a");
    let progress_b = dir.path().join("progress-b");

    // The local file the operator pushes.
    let bytes = payload();
    let src = dir.path().join("local.bin");
    std::fs::write(&src, &bytes).expect("src");

    let mut life1 = connect_retry(&name_a);
    let a_addr = life1.net_status().expect("a status").addr;

    // Operator B: push on its own thread; it blocks until the commit echo —
    // which only the successor brain will send.
    let mut operator = connect_retry(&name_b);
    let conn = operator.net_dial(a_addr, Some(1)).expect("dial");
    let total = bytes.len() as u64;
    let pusher = thread::spawn(move || {
        let n = push_file(&mut operator, conn.conn_id, &src, "in/drop.bin", "xferR", 2, &progress_b)
            .expect("push");
        n
    });

    // Life 1 receives manually (the serve loop's exact write path, stepped so
    // the crash point is deterministic): open the temp the protocol names,
    // write the FIRST chunk only — then die with a partial temp on disk.
    let (stream_a, origin) = wait_for_stream(&mut life1);
    life1.net_stream_subscribe(stream_a, 0).expect("life1 subscribe");
    let temp = root.join("in").join("drop.bin.xferR.part"); // xfer_id is alnum → safe name verbatim
    let mut decoder = XferDecoder::new();
    let mut wrote_first = false;
    'life1: loop {
        match life1.read_event().expect("life1 event") {
            BrokerEvent::NetStreamData { stream_id, bytes: data, .. } if stream_id == stream_a => {
                for rec in decoder.push(&data) {
                    match rec {
                        XferRecord::Push { path, total: t, .. } => {
                            assert_eq!((path.as_str(), t), ("in/drop.bin", total));
                            std::fs::create_dir_all(root.join("in")).expect("dir");
                        }
                        XferRecord::Chunk { offset, data_b64, .. } => {
                            use std::io::{Seek, SeekFrom, Write};
                            let chunk =
                                spt_daemon::msg::decode_bytes(&data_b64).expect("chunk b64");
                            let mut f = std::fs::OpenOptions::new()
                                .create(true)
                                .truncate(false)
                                .write(true)
                                .open(&temp)
                                .expect("temp");
                            f.seek(SeekFrom::Start(offset)).expect("seek");
                            f.write_all(&chunk).expect("write");
                            wrote_first = true;
                            break 'life1; // crash with a partial temp on disk
                        }
                        _ => {}
                    }
                }
            }
            BrokerEvent::Error { message } => panic!("life1 broker error: {message}"),
            _ => {}
        }
    }
    assert!(wrote_first, "the dead life left a partial temp behind");
    assert!(temp.exists(), "partial temp on disk at the crash point");
    let state = life1.snapshot();
    drop(life1); // the crash — broker A, stream, ring all survive

    // Life 2: handoff, then the REAL serve loop re-serves the SAME stream from
    // seq 0 — the worst-case full replay over the partial temp.
    let mut life2 = Brain::handoff(&name_a, state, 2).expect("handoff");
    let outcome = serve_xfer(&mut life2, stream_a, "ling", &origin, 0, &root, &progress_a)
        .expect("re-serve");
    assert_eq!(outcome, XferServeOutcome::Received { bytes: total });

    // The operator's blocked push observed the durable commit.
    assert_eq!(pusher.join().expect("push thread"), total);

    // Exactly once at the boundary that matters: the committed file is
    // byte-identical, and no temp is left behind.
    assert_eq!(
        std::fs::read(root.join("in").join("drop.bin")).expect("final"),
        bytes,
        "file byte-identical across the restart"
    );
    assert!(!temp.exists(), "the commit consumed the temp");

    // Receiver-end durable progress settled at done == total.
    let recv = read_progress_at(&progress_a, "xferR", XferRole::Recv).expect("recv progress");
    assert_eq!((recv.bytes_done, recv.bytes_total, recv.state.as_str()), (total, total, "done"));
}


File created successfully at: C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\tests\xfer.rs (file state is current in your context — no need to Read it back)

//! D5c regression — off-node file transfer (REQ-REACH-1): pull a file off the
//! target node and push one onto it over the broker-owned wire, chunked,
//! progress-queryable, and exactly-once across a target-brain restart.
//!
//! Two in-process brokers (hermetic loopback endpoints, relays disabled) play
//! the two daemons. The invariants:
//!
//! - **fetch**: a multi-chunk file on the target lands byte-identical at the
//!   operator; both ends' durable progress records read `done == total` after
//!   commit (the mid-flight query surface, CONTEXT §file-transfer progress);
//! - **path confinement**: wire paths resolve under the target's transfer
//!   root — a traversal attempt is refused with an `Err` record, not served;
//! - **restart survival** (REQ-HAZARD-RESTART-IDEMPOTENT): the target brain
//!   dies mid-push with a partial temp file on disk; the successor re-serves
//!   the SAME stream from seq 0 — the replayed chunks rewrite in place
//!   (positional idempotency), the commit renames atomically, and the file is
//!   byte-identical with no stray temp left behind.

use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;

use spt_daemon::brain::{Brain, BrokerEvent};
use spt_daemon::nethost::{NetConfig, NetHost};
use spt_daemon::xfer::{fetch_file, push_file, serve_xfer, XferServeOutcome};
use spt_daemon::Broker;
use spt_net::net::endpoint::{BindScope, LocalDiscovery, RelayPolicy};
use spt_net::net::xfer::{XferDecoder, XferRecord};
use spt_proto::identity::Identity;
use spt_store::xfer::{read_progress_at, XferRole};

static SEQ: AtomicU32 = AtomicU32::new(0);
fn unique_name() -> String {
    let n = SEQ.fetch_add(1, Ordering::Relaxed);
    format!("spt-daemon-d5c-{}-{}.sock", std::process::id(), n)
}

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

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

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

/// Poll the brain's `net-streams` until a stream appears; return
/// `(stream_id, remote_id_hex)`.
fn wait_for_stream(brain: &mut Brain) -> (u64, String) {
    for _ in 0..400 {
        let reply = brain.net_streams().expect("net-streams");
        if let Some(s) = reply.streams.first() {
            return (s.stream_id, s.remote_id_hex.clone());
        }
        thread::sleep(Duration::from_millis(5));
    }
    panic!("transfer stream never appeared at the target's broker");
}

/// A deterministic multi-chunk payload (~3 chunks at the 64KiB chunk size),
/// varied enough that any offset mix-up corrupts a comparison.
fn payload() -> Vec<u8> {
    (0..150_000u32).map(|i| (i % 251) as u8).collect()
}

// [unit->REQ-REACH-1] pull a multi-chunk file off the target node: it lands
// byte-identical at the operator, both ends' durable progress records read
// done == total (the mid-flight query surface), and a traversal attempt is
// refused with an Err record under the handshake-proven origin gate.
#[test]
fn fetch_lands_byte_identical_with_progress_both_ends() {
    let dir = tempfile::tempdir().expect("tempdir");
    let (name_a, name_b) = (unique_name(), unique_name());
    let _broker_a = net_broker(&name_a, &dir.path().join("a"));
    let _broker_b = net_broker(&name_b, &dir.path().join("b"));

    // Target A: a real file under its transfer root.
    let root = dir.path().join("root");
    std::fs::create_dir_all(root.join("notes")).expect("root");
    let bytes = payload();
    std::fs::write(root.join("notes").join("plan.md"), &bytes).expect("seed file");
    let progress_a = dir.path().join("progress-a");
    let progress_b = dir.path().join("progress-b");

    let mut target = connect_retry(&name_a);
    let a_addr = target.net_status().expect("a status").addr;

    // Operator B fetches on its own thread (the call blocks until commit).
    let mut operator = connect_retry(&name_b);
    let b_node = operator.net_status().expect("b status").node_id_hex.expect("node id");
    let conn = operator.net_dial(a_addr, Some(1)).expect("dial");
    let dest = dir.path().join("fetched").join("plan.md");
    let dest_clone = dest.clone();
    let progress_b_clone = progress_b.clone();
    let fetcher = thread::spawn(move || {
        let n = fetch_file(
            &mut operator,
            conn.conn_id,
            "notes/plan.md",
            &dest_clone,
            "xfer1",
            2,
            &progress_b_clone,
        )
        .expect("fetch");
        (n, operator)
    });

    // Target A: serve the arriving stream under the handshake-proven origin.
    let (stream_a, origin) = wait_for_stream(&mut target);
    assert_eq!(origin, b_node, "serve gate subject IS the QUIC handshake identity");
    let outcome =
        serve_xfer(&mut target, stream_a, "ling", &origin, 0, &root, &progress_a).expect("serve");
    assert_eq!(outcome, XferServeOutcome::Sent { bytes: bytes.len() as u64 });

    let (n, mut operator) = fetcher.join().expect("fetch thread");
    assert_eq!(n, bytes.len() as u64);
    assert_eq!(std::fs::read(&dest).expect("dest"), bytes, "byte-identical across the wire");

    // Both ends' durable progress records: done == total, state done — the
    // record the agent/GUI polls mid-flight, settled at commit.
    let send = read_progress_at(&progress_a, "xfer1", XferRole::Send).expect("send progress");
    let recv = read_progress_at(&progress_b, "xfer1", XferRole::Recv).expect("recv progress");
    assert_eq!((send.bytes_done, send.bytes_total, send.state.as_str()),
        (bytes.len() as u64, bytes.len() as u64, "done"));
    assert_eq!((recv.bytes_done, recv.bytes_total, recv.state.as_str()),
        (bytes.len() as u64, bytes.len() as u64, "done"));

    // Confinement: a traversal fetch is refused with an Err record (the
    // operator's fetch errors; nothing outside the root is served).
    let conn2 = operator.net_dial(target.net_status().expect("a").addr, Some(3)).expect("dial2");
    let dest2 = dir.path().join("stolen.txt");
    let fetcher2 = thread::spawn(move || {
        let err = fetch_file(
            &mut operator, conn2.conn_id, "../effects.log", &dest2, "xfer2", 4, &progress_b,
        )
        .expect_err("traversal must be refused");
        err.to_string()
    });
    let (stream2, origin2) = wait_for_stream(&mut target);
    let outcome2 =
        serve_xfer(&mut target, stream2, "ling", &origin2, 0, &root, &progress_a).expect("serve2");
    assert!(matches!(outcome2, XferServeOutcome::Failed(_)), "refused, not served: {outcome2:?}");
    let msg = fetcher2.join().expect("fetch2 thread");
    assert!(msg.contains("refused"), "operator sees the refusal: {msg}");
}

// [unit->REQ-REACH-1] push survives a target-brain crash mid-receive exactly
// once: life 1 dies with a partial temp file on disk; the successor re-serves
// the SAME broker-held stream from seq 0 — the replayed Push re-opens the
// temp, the replayed chunks rewrite the same bytes at the same offsets
// (positional idempotency), the commit renames atomically, and the operator's
// blocked push observes the durable commit echo.
// [unit->REQ-HAZARD-RESTART-IDEMPOTENT]
#[test]
fn push_survives_target_brain_restart_exactly_once() {
    let dir = tempfile::tempdir().expect("tempdir");
    let (name_a, name_b) = (unique_name(), unique_name());
    let _broker_a = net_broker(&name_a, &dir.path().join("a"));
    let _broker_b = net_broker(&name_b, &dir.path().join("b"));

    let root = dir.path().join("root");
    std::fs::create_dir_all(&root).expect("root");
    let progress_a = dir.path().join("progress-a");
    let progress_b = dir.path().join("progress-b");

    // The local file the operator pushes.
    let bytes = payload();
    let src = dir.path().join("local.bin");
    std::fs::write(&src, &bytes).expect("src");

    let mut life1 = connect_retry(&name_a);
    let a_addr = life1.net_status().expect("a status").addr;

    // Operator B: push on its own thread; it blocks until the commit echo —
    // which only the successor brain will send.
    let mut operator = connect_retry(&name_b);
    let conn = operator.net_dial(a_addr, Some(1)).expect("dial");
    let total = bytes.len() as u64;
    let pusher = thread::spawn(move || {
        let n = push_file(&mut operator, conn.conn_id, &src, "in/drop.bin", "xferR", 2, &progress_b)
            .expect("push");
        n
    });

    // Life 1 receives manually (the serve loop's exact write path, stepped so
    // the crash point is deterministic): open the temp the protocol names,
    // write the FIRST chunk only — then die with a partial temp on disk.
    let (stream_a, origin) = wait_for_stream(&mut life1);
    life1.net_stream_subscribe(stream_a, 0).expect("life1 subscribe");
    let temp = root.join("in").join("drop.bin.xferR.part"); // xfer_id is alnum → safe name verbatim
    let mut decoder = XferDecoder::new();
    let mut wrote_first = false;
    'life1: loop {
        match life1.read_event().expect("life1 event") {
            BrokerEvent::NetStreamData { stream_id, bytes: data, .. } if stream_id == stream_a => {
                for rec in decoder.push(&data) {
                    match rec {
                        XferRecord::Push { path, total: t, .. } => {
                            assert_eq!((path.as_str(), t), ("in/drop.bin", total));
                            std::fs::create_dir_all(root.join("in")).expect("dir");
                        }
                        XferRecord::Chunk { offset, data_b64, .. } => {
                            use std::io::{Seek, SeekFrom, Write};
                            let chunk =
                                spt_daemon::msg::decode_bytes(&data_b64).expect("chunk b64");
                            let mut f = std::fs::OpenOptions::new()
                                .create(true)
                                .truncate(false)
                                .write(true)
                                .open(&temp)
                                .expect("temp");
                            f.seek(SeekFrom::Start(offset)).expect("seek");
                            f.write_all(&chunk).expect("write");
                            wrote_first = true;
                            break 'life1; // crash with a partial temp on disk
                        }
                        _ => {}
                    }
                }
            }
            BrokerEvent::Error { message } => panic!("life1 broker error: {message}"),
            _ => {}
        }
    }
    assert!(wrote_first, "the dead life left a partial temp behind");
    assert!(temp.exists(), "partial temp on disk at the crash point");
    let state = life1.snapshot();
    drop(life1); // the crash — broker A, stream, ring all survive

    // Life 2: handoff, then the REAL serve loop re-serves the SAME stream from
    // seq 0 — the worst-case full replay over the partial temp.
    let mut life2 = Brain::handoff(&name_a, state, 2).expect("handoff");
    let outcome = serve_xfer(&mut life2, stream_a, "ling", &origin, 0, &root, &progress_a)
        .expect("re-serve");
    assert_eq!(outcome, XferServeOutcome::Received { bytes: total });

    // The operator's blocked push observed the durable commit.
    assert_eq!(pusher.join().expect("push thread"), total);

    // Exactly once at the boundary that matters: the committed file is
    // byte-identical, and no temp is left behind.
    assert_eq!(
        std::fs::read(root.join("in").join("drop.bin")).expect("final"),
        bytes,
        "file byte-identical across the restart"
    );
    assert!(!temp.exists(), "the commit consumed the temp");

    // Receiver-end durable progress settled at done == total.
    let recv = read_progress_at(&progress_a, "xferR", XferRole::Recv).expect("recv progress");
    assert_eq!((recv.bytes_done, recv.bytes_total, recv.state.as_str()), (total, total, "done"));
}


Bash

cargo test -p spt-daemon --test xfer 2>&1 | tail -12

Run xfer integration tests


thread 'fetch_lands_byte_identical_with_progress_both_ends' (62548) panicked at crates\spt-daemon\tests\xfer.rs:165:5:
refused, not served: Sent { bytes: 150000 }

failures:
    fetch_lands_byte_identical_with_progress_both_ends
    push_survives_target_brain_restart_exactly_once

test result: FAILED. 0 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.18s

error: test failed, to rerun pass `-p spt-daemon --test xfer`


cargo test -p spt-daemon --test xfer push_survives 2>&1 | grep -A6 "panicked" | head -20

See push test failure detail


thread 'push_survives_target_brain_restart_exactly_once' (64340) panicked at crates\spt-daemon\src\brain.rs:191:41:
snapshot before a session exists
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

failures:
    push_survives_target_brain_restart_exactly_once


126	    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.
127	    /// `None` until subscribed (or the first event arrives).
128	    presence_cursor: Option<u64>,
129	}
130
131	impl Brain {
132	    /// Cold start: connect, handshake, fresh generation 0, `gen_start = now`.
133	    pub fn cold_start(name: &str, now_ms: u64) -> io::Result<Self> {
134	        let mut conn = LocalSocketTransport::connect(name)?;
135	        send_hello(&mut conn, Role::Brain)?;
136	        Ok(Brain {
137	            conn,
138	            generation: 0,
139	            gen_start_ms: now_ms,
140	            session_id: None,
141	            next_seq: 0,
142	            net_cursors: std::collections::HashMap::new(),
143	            presence_cursor: None,
144	        })
145	    }
146
147	    /// Handoff: connect, handshake, rehydrate the resume cursor + generation
148	    /// from `prior`, but stamp `gen_start = now()` (NOT `prior.gen_start_ms` —
149	    /// the 2.4 invariant), and re-subscribe to the session from the cursor for a
150	    /// gapless resume.
151	    pub fn handoff(name: &str, prior: BrainState, now_ms: u64) -> io::Result<Self> {
152	        let mut conn = LocalSocketTransport::connect(name)?;
153	        send_hello(&mut conn, Role::Brain)?;
154	        let mut brain = Brain {
155	            conn,
156	            generation: prior.generation + 1,
157	            gen_start_ms: now_ms, // fresh — prior.gen_start_ms is intentionally discarded
158	            session_id: Some(prior.session_id),
159	            next_seq: prior.next_seq,
160	            net_cursors: std::collections::HashMap::new(),
161	            presence_cursor: None,
162	        };
163	        brain.subscribe(prior.session_id, prior.next_seq)?;
164	        Ok(brain)
165	    }
166
167	    /// The wall-clock generation start (epoch ms) — always this generation's own
168	    /// start time, never a rehydrated value.
169	    pub fn gen_start_ms(&self) -> u64 {
170	        self.gen_start_ms
171	    }
172
173	    /// The generation counter (0 on cold start, +1 per handoff).
174	    pub fn generation(&self) -> u64 {
175	        self.generation
176	    }
177
178	    /// The next output sequence still needed (the resume cursor).
179	    pub fn next_seq(&self) -> u64 {
180	        self.next_seq
181	    }
182
183	    /// The session id the brain is driving, if any.
184	    pub fn session_id(&self) -> Option<u64> {
185	        self.session_id
186	    }
187
188	    /// Snapshot durable state for a future handoff.
189	    pub fn snapshot(&self) -> BrainState {
190	        BrainState {
191	            session_id: self.session_id.expect("snapshot before a session exists"),
192	            generation: self.generation,
193	            next_seq: self.next_seq,
194	            gen_start_ms: self.gen_start_ms,
195	        }
196	    }
197
198	    /// Spawn a session under a broker PTY; the broker auto-subscribes this
199	    /// connection. Reads until the `spawned` reply (consuming any output that
200	    /// races ahead).
201	    pub fn spawn_session(&mut self, req: SpawnReq) -> io::Result<u64> {
202	        self.send(KIND_SPAWN, serde_json::to_value(req).expect("SpawnReq serializes"))?;
203	        loop {
204	            match self.read_event()? {
205	                BrokerEvent::Spawned { session_id, .. } => {

    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.
    /// `None` until subscribed (or the first event arrives).
    presence_cursor: Option<u64>,
}

impl Brain {
    /// Cold start: connect, handshake, fresh generation 0, `gen_start = now`.
    pub fn cold_start(name: &str, now_ms: u64) -> io::Result<Self> {
        let mut conn = LocalSocketTransport::connect(name)?;
        send_hello(&mut conn, Role::Brain)?;
        Ok(Brain {
            conn,
            generation: 0,
            gen_start_ms: now_ms,
            session_id: None,
            next_seq: 0,
            net_cursors: std::collections::HashMap::new(),
            presence_cursor: None,
        })
    }

    /// Handoff: connect, handshake, rehydrate the resume cursor + generation
    /// from `prior`, but stamp `gen_start = now()` (NOT `prior.gen_start_ms` —
    /// the 2.4 invariant), and re-subscribe to the session from the cursor for a
    /// gapless resume.
    pub fn handoff(name: &str, prior: BrainState, now_ms: u64) -> io::Result<Self> {
        let mut conn = LocalSocketTransport::connect(name)?;
        send_hello(&mut conn, Role::Brain)?;
        let mut brain = Brain {
            conn,
            generation: prior.generation + 1,
            gen_start_ms: now_ms, // fresh — prior.gen_start_ms is intentionally discarded
            session_id: Some(prior.session_id),
            next_seq: prior.next_seq,
            net_cursors: std::collections::HashMap::new(),
            presence_cursor: None,
        };
        brain.subscribe(prior.session_id, prior.next_seq)?;
        Ok(brain)
    }

    /// The wall-clock generation start (epoch ms) — always this generation's own
    /// start time, never a rehydrated value.
    pub fn gen_start_ms(&self) -> u64 {
        self.gen_start_ms
    }

    /// The generation counter (0 on cold start, +1 per handoff).
    pub fn generation(&self) -> u64 {
        self.generation
    }

    /// The next output sequence still needed (the resume cursor).
    pub fn next_seq(&self) -> u64 {
        self.next_seq
    }

    /// The session id the brain is driving, if any.
    pub fn session_id(&self) -> Option<u64> {
        self.session_id
    }

    /// Snapshot durable state for a future handoff.
    pub fn snapshot(&self) -> BrainState {
        BrainState {
            session_id: self.session_id.expect("snapshot before a session exists"),
            generation: self.generation,
            next_seq: self.next_seq,
            gen_start_ms: self.gen_start_ms,
        }
    }

    /// Spawn a session under a broker PTY; the broker auto-subscribes this
    /// connection. Reads until the `spawned` reply (consuming any output that
    /// races ahead).
    pub fn spawn_session(&mut self, req: SpawnReq) -> io::Result<u64> {
        self.send(KIND_SPAWN, serde_json::to_value(req).expect("SpawnReq serializes"))?;
        loop {
            match self.read_event()? {
                BrokerEvent::Spawned { session_id, .. } => {


Two fixes: handoff needs PTY session — use fresh cold-start like wanmsg test; and second-stream wait must skip the first.


Edit

/// Poll the brain's `net-streams` until a stream appears; return
/// `(stream_id, remote_id_hex)`.
fn wait_for_stream(brain: &mut Brain) -> (u64, String) {
    for _ in 0..400 {
        let reply = brain.net_streams().expect("net-streams");
        if let Some(s) = reply.streams.first() {
            return (s.stream_id, s.remote_id_hex.clone());
        }
        thread::sleep(Duration::from_millis(5));
    }
    panic!("transfer stream never appeared at the target's broker");
}

/// Poll the brain's `net-streams` until a stream NOT in `skip` appears; return
/// `(stream_id, remote_id_hex)`.
fn wait_for_stream_except(brain: &mut Brain, skip: &[u64]) -> (u64, String) {
    for _ in 0..400 {
        let reply = brain.net_streams().expect("net-streams");
        if let Some(s) = reply.streams.iter().find(|s| !skip.contains(&s.stream_id)) {
            return (s.stream_id, s.remote_id_hex.clone());
        }
        thread::sleep(Duration::from_millis(5));
    }
    panic!("transfer stream never appeared at the target's broker");
}

fn wait_for_stream(brain: &mut Brain) -> (u64, String) {
    wait_for_stream_except(brain, &[])
}


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

/// Poll the brain's `net-streams` until a stream appears; return
/// `(stream_id, remote_id_hex)`.
fn wait_for_stream(brain: &mut Brain) -> (u64, String) {
    for _ in 0..400 {
        let reply = brain.net_streams().expect("net-streams");
        if let Some(s) = reply.streams.first() {
            return (s.stream_id, s.remote_id_hex.clone());
        }
        thread::sleep(Duration::from_millis(5));
    }
    panic!("transfer stream never appeared at the target's broker");
}

/// Poll the brain's `net-streams` until a stream NOT in `skip` appears; return
/// `(stream_id, remote_id_hex)`.
fn wait_for_stream_except(brain: &mut Brain, skip: &[u64]) -> (u64, String) {
    for _ in 0..400 {
        let reply = brain.net_streams().expect("net-streams");
        if let Some(s) = reply.streams.iter().find(|s| !skip.contains(&s.stream_id)) {
            return (s.stream_id, s.remote_id_hex.clone());
        }
        thread::sleep(Duration::from_millis(5));
    }
    panic!("transfer stream never appeared at the target's broker");
}

fn wait_for_stream(brain: &mut Brain) -> (u64, String) {
    wait_for_stream_except(brain, &[])
}

-/// Poll the brain's `net-streams` until a stream appears; return

+/// Poll the brain's `net-streams` until a stream NOT in `skip` appears; return

-fn wait_for_stream(brain: &mut Brain) -> (u64, String) {

+fn wait_for_stream_except(brain: &mut Brain, skip: &[u64]) -> (u64, String) {

-        if let Some(s) = reply.streams.first() {

+        if let Some(s) = reply.streams.iter().find(|s| !skip.contains(&s.stream_id)) {

+fn wait_for_stream(brain: &mut Brain) -> (u64, String) {

/// A deterministic multi-chunk payload (~3 chunks at the 64KiB chunk size),

/// varied enough that any offset mix-up corrupts a comparison.


let (stream2, origin2) = wait_for_stream(&mut target);

let (stream2, origin2) = wait_for_stream_except(&mut target, &[stream_a]);


.expect_err("traversal must be refused");

-    let (stream2, origin2) = wait_for_stream(&mut target);

+    let (stream2, origin2) = wait_for_stream_except(&mut target, &[stream_a]);

serve_xfer(&mut target, stream2, "ling", &origin2, 0, &root, &progress_a).expect("serve2");

assert!(matches!(outcome2, XferServeOutcome::Failed(_)), "refused, not served: {outcome2:?}");


    assert!(wrote_first, "the dead life left a partial temp behind");
    assert!(temp.exists(), "partial temp on disk at the crash point");
    let state = life1.snapshot();
    drop(life1); // the crash — broker A, stream, ring all survive

    // Life 2: handoff, then the REAL serve loop re-serves the SAME stream from
    // seq 0 — the worst-case full replay over the partial temp.
    let mut life2 = Brain::handoff(&name_a, state, 2).expect("handoff");

    assert!(wrote_first, "the dead life left a partial temp behind");
    assert!(temp.exists(), "partial temp on disk at the crash point");
    drop(life1); // the crash — broker A, the stream, and its ring all survive

    // Life 2: a successor brain connects fresh, then the REAL serve loop
    // re-serves the SAME broker-held stream from seq 0 — the worst-case full
    // replay over the partial temp.
    let mut life2 = connect_retry(&name_a);


    assert!(wrote_first, "the dead life left a partial temp behind");
    assert!(temp.exists(), "partial temp on disk at the crash point");
    let state = life1.snapshot();
    drop(life1); // the crash — broker A, stream, ring all survive

    // Life 2: handoff, then the REAL serve loop re-serves the SAME stream from
    // seq 0 — the worst-case full replay over the partial temp.
    let mut life2 = Brain::handoff(&name_a, state, 2).expect("handoff");

    assert!(wrote_first, "the dead life left a partial temp behind");
    assert!(temp.exists(), "partial temp on disk at the crash point");
    drop(life1); // the crash — broker A, the stream, and its ring all survive

    // Life 2: a successor brain connects fresh, then the REAL serve loop
    // re-serves the SAME broker-held stream from seq 0 — the worst-case full
    // replay over the partial temp.
    let mut life2 = connect_retry(&name_a);

-    drop(life1); // the crash — broker A, stream, ring all survive

+    drop(life1); // the crash — broker A, the stream, and its ring all survive

-    // Life 2: handoff, then the REAL serve loop re-serves the SAME stream from

-    // seq 0 — the worst-case full replay over the partial temp.

-    let mut life2 = Brain::handoff(&name_a, state, 2).expect("handoff");

+    // Life 2: a successor brain connects fresh, then the REAL serve loop

+    // re-serves the SAME broker-held stream from seq 0 — the worst-case full

+    let mut life2 = connect_retry(&name_a);

let outcome = serve_xfer(&mut life2, stream_a, "ling", &origin, 0, &root, &progress_a)

assert_eq!(outcome, XferServeOutcome::Received { bytes: total });


Bash

cargo test -p spt-daemon --test xfer 2>&1 | tail -8

Re-run xfer integration tests


     Running tests\xfer.rs (target\debug\deps\xfer-5a84a27ceeefb82d.exe)

running 2 tests
test push_survives_target_brain_restart_exactly_once ... ok
test fetch_lands_byte_identical_with_progress_both_ends ... ok

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


Xfer tests green. Now detection test in attach.rs:


Edit

use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;

use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::{Arc, OnceLock};
use std::thread;
use std::time::Duration;


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

use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;

use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::{Arc, OnceLock};
use std::thread;
use std::time::Duration;

//!   input B sent INTO the dead window.


static SEQ: AtomicU32 = AtomicU32::new(0);
fn unique_name() -> String {
    let n = SEQ.fetch_add(1, Ordering::Relaxed);
    format!("spt-daemon-d5b-{}-{}.sock", std::process::id(), n)
}

/// Hold the temp home for the whole test binary so it isn't deleted mid-run.
/// Every test inits it: serve_attach best-effort-stamps the endpoint perch's
/// `driven_by` marker, and that resolution must never touch a real home.
static HOME: OnceLock<tempfile::TempDir> = OnceLock::new();

fn init_home() {
    HOME.get_or_init(|| {
        let dir = tempfile::TempDir::new().expect("temp dir");
        std::env::set_var("SPT_HOME", dir.path());
        dir
    });
}

static SEQ: AtomicU32 = AtomicU32::new(0);
fn unique_name() -> String {
    let n = SEQ.fetch_add(1, Ordering::Relaxed);
    format!("spt-daemon-d5b-{}-{}.sock", std::process::id(), n)
}


/// Hold the temp home for the whole test binary so it isn't deleted mid-run.
/// Every test inits it: serve_attach best-effort-stamps the endpoint perch's
/// `driven_by` marker, and that resolution must never touch a real home.
static HOME: OnceLock<tempfile::TempDir> = OnceLock::new();

fn init_home() {
    HOME.get_or_init(|| {
        let dir = tempfile::TempDir::new().expect("temp dir");
        std::env::set_var("SPT_HOME", dir.path());
        dir
    });
}

static SEQ: AtomicU32 = AtomicU32::new(0);
fn unique_name() -> String {
    let n = SEQ.fetch_add(1, Ordering::Relaxed);
    format!("spt-daemon-d5b-{}-{}.sock", std::process::id(), n)
}

+/// Hold the temp home for the whole test binary so it isn't deleted mid-run.

+/// Every test inits it: serve_attach best-effort-stamps the endpoint perch's

+/// `driven_by` marker, and that resolution must never touch a real home.

+static HOME: OnceLock<tempfile::TempDir> = OnceLock::new();

+        let dir = tempfile::TempDir::new().expect("temp dir");

+        std::env::set_var("SPT_HOME", dir.path());


Add `init_home()` to existing tests + new detection test:


#[test]
fn remote_attach_drives_a_real_pty_cross_daemon() {
    let dir = tempfile::tempdir().expect("tempdir");

#[test]
fn remote_attach_drives_a_real_pty_cross_daemon() {
    init_home();
    let dir = tempfile::tempdir().expect("tempdir");


#[test]
fn remote_attach_drives_a_real_pty_cross_daemon() {
    let dir = tempfile::tempdir().expect("tempdir");

#[test]
fn remote_attach_drives_a_real_pty_cross_daemon() {
    init_home();
    let dir = tempfile::tempdir().expect("tempdir");

// origin; B detaching ends the viewport with the session still running on A.

let (name_a, name_b) = (unique_name(), unique_name());

let broker_a = net_broker(&name_a, &dir.path().join("a"));