54	    evicted_envelope, net_dialed_envelope, output_envelope,
55	    size_envelope, subscribed_envelope, AdapterApplyReq, BrainRestarted, EndpointInputReq,
56	    ErrorEvent, ExitEvent, InputReq, KillReq, NetDialReq, NetPresenceSubscribeReq, NetSent,
57	    NetStatusReply, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq,
58	    NetStreamsReply, PairJoinReply, PairJoinReq, ResizeReq, SessionInfo, SessionsReply, SpawnReq,
59	    Spawned, SubscribeOutcome, SubscribeReq, UnsubscribeReq, KIND_BRAIN_RESTART, KIND_BRAIN_RESTARTED, KIND_ENDPOINT_INPUT, KIND_ERROR, KIND_EXIT,
60	    KIND_INPUT, KIND_KILL, KIND_NET_DIAL, KIND_NET_DIAL_LOOPBACK, KIND_NET_PRESENCE_SUBSCRIBE,
61	    KIND_NET_SENT,
62	    KIND_ADAPTER_APPLY, KIND_APPLIED,
63	    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,
64	    KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE,
65	    KIND_PAIR_JOIN, KIND_PAIR_JOINED, KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,
66	    KIND_SPAWNED, KIND_SUBSCRIBE, KIND_UNSUBSCRIBE,
67	};

    evicted_envelope, net_dialed_envelope, output_envelope,
    size_envelope, subscribed_envelope, AdapterApplyReq, BrainRestarted, EndpointInputReq,
    ErrorEvent, ExitEvent, InputReq, KillReq, NetDialReq, NetPresenceSubscribeReq, NetSent,
    NetStatusReply, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq,
    NetStreamsReply, PairJoinReply, PairJoinReq, ResizeReq, SessionInfo, SessionsReply, SpawnReq,
    Spawned, SubscribeOutcome, SubscribeReq, UnsubscribeReq, KIND_BRAIN_RESTART, KIND_BRAIN_RESTARTED, KIND_ENDPOINT_INPUT, KIND_ERROR, KIND_EXIT,
    KIND_INPUT, KIND_KILL, KIND_NET_DIAL, KIND_NET_DIAL_LOOPBACK, KIND_NET_PRESENCE_SUBSCRIBE,
    KIND_NET_SENT,
    KIND_ADAPTER_APPLY, KIND_APPLIED,
    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,
    KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE,
    KIND_PAIR_JOIN, KIND_PAIR_JOINED, KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,
    KIND_SPAWNED, KIND_SUBSCRIBE, KIND_UNSUBSCRIBE,
};


Edit

NetStreamsReply, PairJoinReply, PairJoinReq, ResizeReq, SessionInfo, SessionsReply, SpawnReq,

NetStreamsReply, MetMember, PairCodeSubmit, PairJoinReply, PairJoinReq, PairMeetReq, ResizeReq, SessionInfo, SessionsReply, SpawnReq,


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

size_envelope, subscribed_envelope, AdapterApplyReq, BrainRestarted, EndpointInputReq,

ErrorEvent, ExitEvent, InputReq, KillReq, NetDialReq, NetPresenceSubscribeReq, NetSent,

NetStatusReply, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq,

-    NetStreamsReply, PairJoinReply, PairJoinReq, ResizeReq, SessionInfo, SessionsReply, SpawnReq,

+    NetStreamsReply, MetMember, PairCodeSubmit, PairJoinReply, PairJoinReq, PairMeetReq, ResizeReq, SessionInfo, SessionsReply, SpawnReq,

Spawned, SubscribeOutcome, SubscribeReq, UnsubscribeReq, KIND_BRAIN_RESTART, KIND_BRAIN_RESTARTED, KIND_ENDPOINT_INPUT, KIND_ERROR, KIND_EXIT,

KIND_INPUT, KIND_KILL, KIND_NET_DIAL, KIND_NET_DIAL_LOOPBACK, KIND_NET_PRESENCE_SUBSCRIBE,


KIND_PAIR_JOIN, KIND_PAIR_JOINED, KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,

KIND_MET_MEMBER, KIND_PAIR_CODE_SUBMIT, KIND_PAIR_JOIN, KIND_PAIR_JOINED, KIND_PAIR_MEET, KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,


KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,

KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE,

-    KIND_PAIR_JOIN, KIND_PAIR_JOINED, KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,

+    KIND_MET_MEMBER, KIND_PAIR_CODE_SUBMIT, KIND_PAIR_JOIN, KIND_PAIR_JOINED, KIND_PAIR_MEET, KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SPAWN,


Now the routing arms in `handle_conn`.


                KIND_PAIR_JOIN => {
                    if let Err(msg) = self.dispatch_pair_join(env, &send) {
                        send_error(&send, &msg);
                    }
                }

                KIND_PAIR_JOIN => {
                    if let Err(msg) = self.dispatch_pair_join(env, &send) {
                        send_error(&send, &msg);
                    }
                }
                KIND_PAIR_MEET => {
                    if let Err(msg) = self.dispatch_pair_meet(env, &send) {
                        send_error(&send, &msg);
                    }
                }
                KIND_PAIR_CODE_SUBMIT => {
                    if let Err(msg) = self.dispatch_pair_code_submit(env, &send) {
                        send_error(&send, &msg);
                    }
                }


                KIND_PAIR_JOIN => {
                    if let Err(msg) = self.dispatch_pair_join(env, &send) {
                        send_error(&send, &msg);
                    }
                }
                KIND_PAIR_MEET => {
                    if let Err(msg) = self.dispatch_pair_meet(env, &send) {
                        send_error(&send, &msg);
                    }
                }
                KIND_PAIR_CODE_SUBMIT => {
                    if let Err(msg) = self.dispatch_pair_code_submit(env, &send) {
                        send_error(&send, &msg);
                    }
                }

+                    if let Err(msg) = self.dispatch_pair_meet(env, &send) {

+                        send_error(&send, &msg);

+                KIND_PAIR_CODE_SUBMIT => {

+                    if let Err(msg) = self.dispatch_pair_code_submit(env, &send) {

if let Err(msg) = self.dispatch_net_stream_open(env, &send) {


Step 6 — brain.rs. Reading the BrokerEvent enum, read_event mapping, and imports.


140	    NetStreamOpened(NetStreamOpened),
141	    /// A journaled stream send durably landed — fresh or deduped (D4b).
142	    NetSent(NetSent),
143	    /// The broker's held-streams listing (D4b).
144	    NetStreams(NetStreamsReply),
145	    /// The broker's hosted-sessions listing (D9-1: session → endpoint).
146	    Sessions(SessionsReply),
147	    /// The subnet-join ceremony's verdict (a `pair-joined` reply, M7 D3).
148	    PairJoined(PairJoinReply),
149	    /// A contiguous chunk of a stream's reads (the cursor has advanced past it).
150	    NetStreamData {
151	        stream_id: u64,
152	        seq: u64,
153	        bytes: Vec<u8>,

    NetStreamOpened(NetStreamOpened),
    /// A journaled stream send durably landed — fresh or deduped (D4b).
    NetSent(NetSent),
    /// The broker's held-streams listing (D4b).
    NetStreams(NetStreamsReply),
    /// The broker's hosted-sessions listing (D9-1: session → endpoint).
    Sessions(SessionsReply),
    /// The subnet-join ceremony's verdict (a `pair-joined` reply, M7 D3).
    PairJoined(PairJoinReply),
    /// A contiguous chunk of a stream's reads (the cursor has advanced past it).
    NetStreamData {
        stream_id: u64,
        seq: u64,
        bytes: Vec<u8>,


724	                }
725	                KIND_SESSIONS_REPLY => {
726	                    let ev: SessionsReply = serde_json::from_value(env.payload)
727	                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
728	                    return Ok(BrokerEvent::Sessions(ev));
729	                }
730	                KIND_PAIR_JOINED => {
731	                    let ev: PairJoinReply = serde_json::from_value(env.payload)
732	                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
733	                    return Ok(BrokerEvent::PairJoined(ev));
734	                }
735	                KIND_NET_STREAM_DATA => {
736	                    let ev: NetStreamData = serde_json::from_value(env.payload)
737	                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
738	                    // Same exactly-once discipline as PTY output, per stream:
739	                    // accept the contiguous next seq, drop a replay duplicate,

                }
                KIND_SESSIONS_REPLY => {
                    let ev: SessionsReply = serde_json::from_value(env.payload)
                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
                    return Ok(BrokerEvent::Sessions(ev));
                }
                KIND_PAIR_JOINED => {
                    let ev: PairJoinReply = serde_json::from_value(env.payload)
                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
                    return Ok(BrokerEvent::PairJoined(ev));
                }
                KIND_NET_STREAM_DATA => {
                    let ev: NetStreamData = serde_json::from_value(env.payload)
                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
                    // Same exactly-once discipline as PTY output, per stream:
                    // accept the contiguous next seq, drop a replay duplicate,


36	use crate::msg::{
37	    decode_bytes, encode_bytes, AdapterApplyReq, AppliedEvent, BrainRestarted, DisplacedEvent,
38	    EndpointInjected, EndpointInputReq, ErrorEvent, ExitEvent,
39	    InputReq,
40	    KillReq, NetDialReq, NetDialed, NetPresenceEvent, NetPresenceSubscribeReq, NetSent,
41	    NetStatusReply, NetStreamData, NetStreamEof, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq,
42	    NetStreamSubscribeReq, NetStreamsReply, OutputEvent, PairJoinReply, PairJoinReq, ResizeReq,
43	    SessionsReply, SizeEvent, SpawnReq, Spawned, SubscribeOutcome, SubscribeReq, SubscribedReply,
44	    ViewerEvictedEvent,
45	    KIND_ADAPTER_APPLY, KIND_APPLIED, KIND_BRAIN_RESTART, KIND_VIEWER_EVICTED,
46	    KIND_BRAIN_RESTARTED, KIND_DISPLACED, KIND_ENDPOINT_INJECTED, KIND_ENDPOINT_INPUT, KIND_ERROR, KIND_EXIT, KIND_INPUT, KIND_KILL, KIND_NET_DIAL,
47	    KIND_NET_DIALED, KIND_NET_DIAL_LOOPBACK, KIND_NET_PRESENCE_EVENT, KIND_NET_PRESENCE_SUBSCRIBE,

use crate::msg::{
    decode_bytes, encode_bytes, AdapterApplyReq, AppliedEvent, BrainRestarted, DisplacedEvent,
    EndpointInjected, EndpointInputReq, ErrorEvent, ExitEvent,
    InputReq,
    KillReq, NetDialReq, NetDialed, NetPresenceEvent, NetPresenceSubscribeReq, NetSent,
    NetStatusReply, NetStreamData, NetStreamEof, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq,
    NetStreamSubscribeReq, NetStreamsReply, OutputEvent, PairJoinReply, PairJoinReq, ResizeReq,
    SessionsReply, SizeEvent, SpawnReq, Spawned, SubscribeOutcome, SubscribeReq, SubscribedReply,
    ViewerEvictedEvent,
    KIND_ADAPTER_APPLY, KIND_APPLIED, KIND_BRAIN_RESTART, KIND_VIEWER_EVICTED,
    KIND_BRAIN_RESTARTED, KIND_DISPLACED, KIND_ENDPOINT_INJECTED, KIND_ENDPOINT_INPUT, KIND_ERROR, KIND_EXIT, KIND_INPUT, KIND_KILL, KIND_NET_DIAL,
    KIND_NET_DIALED, KIND_NET_DIAL_LOOPBACK, KIND_NET_PRESENCE_EVENT, KIND_NET_PRESENCE_SUBSCRIBE,


47	    KIND_NET_DIALED, KIND_NET_DIAL_LOOPBACK, KIND_NET_PRESENCE_EVENT, KIND_NET_PRESENCE_SUBSCRIBE,
48	    KIND_NET_SENT,
49	    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,
50	    KIND_NET_STREAM_DATA, KIND_NET_STREAM_EOF, KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED,
51	    KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE, KIND_OUTPUT, KIND_PAIR_JOIN, KIND_PAIR_JOINED,
52	    KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SIZE, KIND_SPAWN, KIND_SPAWNED,
53	    KIND_SUBSCRIBE, KIND_SUBSCRIBED, KIND_UNSUBSCRIBE, UnsubscribeReq,
54	};

    KIND_NET_DIALED, KIND_NET_DIAL_LOOPBACK, KIND_NET_PRESENCE_EVENT, KIND_NET_PRESENCE_SUBSCRIBE,
    KIND_NET_SENT,
    KIND_NET_STATUS, KIND_NET_STATUS_REPLY, KIND_NET_STREAMS, KIND_NET_STREAMS_REPLY,
    KIND_NET_STREAM_DATA, KIND_NET_STREAM_EOF, KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED,
    KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE, KIND_OUTPUT, KIND_PAIR_JOIN, KIND_PAIR_JOINED,
    KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SIZE, KIND_SPAWN, KIND_SPAWNED,
    KIND_SUBSCRIBE, KIND_SUBSCRIBED, KIND_UNSUBSCRIBE, UnsubscribeReq,
};


NetStreamSubscribeReq, NetStreamsReply, OutputEvent, PairJoinReply, PairJoinReq, ResizeReq,

NetStreamSubscribeReq, NetStreamsReply, OutputEvent, MetMember, PairCodeSubmit, PairJoinReply, PairJoinReq, PairMeetReq, ResizeReq,


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

KillReq, NetDialReq, NetDialed, NetPresenceEvent, NetPresenceSubscribeReq, NetSent,

NetStatusReply, NetStreamData, NetStreamEof, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq,

-    NetStreamSubscribeReq, NetStreamsReply, OutputEvent, PairJoinReply, PairJoinReq, ResizeReq,

+    NetStreamSubscribeReq, NetStreamsReply, OutputEvent, MetMember, PairCodeSubmit, PairJoinReply, PairJoinReq, PairMeetReq, ResizeReq,

SessionsReply, SizeEvent, SpawnReq, Spawned, SubscribeOutcome, SubscribeReq, SubscribedReply,

KIND_ADAPTER_APPLY, KIND_APPLIED, KIND_BRAIN_RESTART, KIND_VIEWER_EVICTED,


KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE, KIND_OUTPUT, KIND_PAIR_JOIN, KIND_PAIR_JOINED,

KIND_MET_MEMBER, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE, KIND_OUTPUT, KIND_PAIR_CODE_SUBMIT, KIND_PAIR_JOIN, KIND_PAIR_JOINED, KIND_PAIR_MEET,


KIND_NET_STREAM_DATA, KIND_NET_STREAM_EOF, KIND_NET_STREAM_OPEN, KIND_NET_STREAM_OPENED,

-    KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE, KIND_OUTPUT, KIND_PAIR_JOIN, KIND_PAIR_JOINED,

+    KIND_MET_MEMBER, KIND_NET_STREAM_SEND, KIND_NET_STREAM_SUBSCRIBE, KIND_OUTPUT, KIND_PAIR_CODE_SUBMIT, KIND_PAIR_JOIN, KIND_PAIR_JOINED, KIND_PAIR_MEET,

KIND_RESIZE, KIND_SESSIONS, KIND_SESSIONS_REPLY, KIND_SIZE, KIND_SPAWN, KIND_SPAWNED,

KIND_SUBSCRIBE, KIND_SUBSCRIBED, KIND_UNSUBSCRIBE, UnsubscribeReq,


Now the `BrokerEvent::MetMember` variant, the `read_event` arm, and the two brain methods.


    /// The subnet-join ceremony's verdict (a `pair-joined` reply, M7 D3).
    PairJoined(PairJoinReply),

    /// The subnet-join ceremony's verdict (a `pair-joined` reply, M7 D3).
    PairJoined(PairJoinReply),
    /// Phase 1 of the two-phase join met a seed-holder — its real address is held
    /// under a session id, awaiting the code (REQ-JOIN-TWO-PHASE).
    MetMember(MetMember),


    /// The subnet-join ceremony's verdict (a `pair-joined` reply, M7 D3).
    PairJoined(PairJoinReply),
    /// Phase 1 of the two-phase join met a seed-holder — its real address is held
    /// under a session id, awaiting the code (REQ-JOIN-TWO-PHASE).
    MetMember(MetMember),

+    /// Phase 1 of the two-phase join met a seed-holder — its real address is held

+    /// under a session id, awaiting the code (REQ-JOIN-TWO-PHASE).

/// A contiguous chunk of a stream's reads (the cursor has advanced past it).


                KIND_PAIR_JOINED => {
                    let ev: PairJoinReply = serde_json::from_value(env.payload)
                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
                    return Ok(BrokerEvent::PairJoined(ev));
                }

                KIND_PAIR_JOINED => {
                    let ev: PairJoinReply = serde_json::from_value(env.payload)
                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
                    return Ok(BrokerEvent::PairJoined(ev));
                }
                KIND_MET_MEMBER => {
                    let ev: MetMember = serde_json::from_value(env.payload)
                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
                    return Ok(BrokerEvent::MetMember(ev));
                }


                KIND_PAIR_JOINED => {
                    let ev: PairJoinReply = serde_json::from_value(env.payload)
                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
                    return Ok(BrokerEvent::PairJoined(ev));
                }
                KIND_MET_MEMBER => {
                    let ev: MetMember = serde_json::from_value(env.payload)
                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
                    return Ok(BrokerEvent::MetMember(ev));
                }

+                    let ev: MetMember = serde_json::from_value(env.payload)

+                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;

+                    return Ok(BrokerEvent::MetMember(ev));

let ev: NetStreamData = serde_json::from_value(env.payload)


Adding the two brain methods + a `MetOutcome` type. Locating the end of `BrokerEvent`.


156	        bytes: Vec<u8>,
157	    },
158	    /// A stream's read side finished cleanly; `next_seq` is the completeness
159	    /// check (the subscriber's cursor must equal it to have missed nothing).
160	    NetStreamEof { stream_id: u64, next_seq: u64 },
161	    /// A contiguous conn-liveness event (the presence cursor has advanced past
162	    /// it) — connected/disconnected on the broker's conn table (D4c).
163	    NetPresence(NetPresenceEvent),
164	    /// The broker reported a command error.
165	    Error { message: String },
166	    /// A frame of an unrecognized kind (forward-compat: tolerated, surfaced).
167	    Other(Envelope),
168	}
169
170	/// The brain's IPC carrier — either the ordinary whole [`Stream`] (every
171	/// non-pump client: blocking, unbounded reads, correct everywhere) or a
172	/// **split** carrier for the peer pump.
173	///
174	/// **Why the split exists (REQ-HAZARD-PUMP-IPC-DEADLINE).** The pump is
175	/// single-threaded; a brain-IPC read that blocks forever on a black-holed peer
176	/// wedges the whole loop (the 2.2h hfenduleam stall). The deadline must be
177	/// cross-platform, and on Windows named pipes interprocess 2.4.2 offers NEITHER
178	/// portable read timeout (`set_recv_timeout` → `no_timeouts()`; `set_nonblocking`
179	/// → deprecated `PIPE_NOWAIT`, which corrupts mid-stream — proven by the mesh
180	/// E2E). So the pump carrier instead **splits** the stream: a dedicated reader
181	/// thread does blocking [`read_frame`] on the [`RecvHalf`] and forwards each
182	/// framed result down a channel; the main thread writes on the [`SendHalf`] and
183	/// reads with [`Receiver::recv_timeout`]. Blocking reads work on both OSes; the
184	/// channel supplies the deadline. On a timeout the pump bubbles out to a
185	/// supervised restart (a fresh [`Brain`] → fresh split → fresh reader thread);
186	/// the old reader, parked in `read_frame`, is abandoned (KH 7.6 leak watch — the
187	/// old conn's drop eventually errors its read and the thread exits).
188	enum BrainConn {
189	    /// The ordinary blocking carrier — every non-pump brain.
190	    Whole(Stream),
191	    /// The pump carrier: writes go out [`SendHalf`]; framed reads arrive over
192	    /// `frames` from the reader thread (`_reader`, named `pump-ipc-reader`).
193	    Split {
194	        send: SendHalf,
195	        frames: Receiver<io::Result<Envelope>>,

        bytes: Vec<u8>,
    },
    /// A stream's read side finished cleanly; `next_seq` is the completeness
    /// check (the subscriber's cursor must equal it to have missed nothing).
    NetStreamEof { stream_id: u64, next_seq: u64 },
    /// A contiguous conn-liveness event (the presence cursor has advanced past
    /// it) — connected/disconnected on the broker's conn table (D4c).
    NetPresence(NetPresenceEvent),
    /// The broker reported a command error.
    Error { message: String },
    /// A frame of an unrecognized kind (forward-compat: tolerated, surfaced).
    Other(Envelope),
}

/// The brain's IPC carrier — either the ordinary whole [`Stream`] (every
/// non-pump client: blocking, unbounded reads, correct everywhere) or a
/// **split** carrier for the peer pump.
///
/// **Why the split exists (REQ-HAZARD-PUMP-IPC-DEADLINE).** The pump is
/// single-threaded; a brain-IPC read that blocks forever on a black-holed peer
/// wedges the whole loop (the 2.2h hfenduleam stall). The deadline must be
/// cross-platform, and on Windows named pipes interprocess 2.4.2 offers NEITHER
/// portable read timeout (`set_recv_timeout` → `no_timeouts()`; `set_nonblocking`
/// → deprecated `PIPE_NOWAIT`, which corrupts mid-stream — proven by the mesh
/// E2E). So the pump carrier instead **splits** the stream: a dedicated reader
/// thread does blocking [`read_frame`] on the [`RecvHalf`] and forwards each
/// framed result down a channel; the main thread writes on the [`SendHalf`] and
/// reads with [`Receiver::recv_timeout`]. Blocking reads work on both OSes; the
/// channel supplies the deadline. On a timeout the pump bubbles out to a
/// supervised restart (a fresh [`Brain`] → fresh split → fresh reader thread);
/// the old reader, parked in `read_frame`, is abandoned (KH 7.6 leak watch — the
/// old conn's drop eventually errors its read and the thread exits).
enum BrainConn {
    /// The ordinary blocking carrier — every non-pump brain.
    Whole(Stream),
    /// The pump carrier: writes go out [`SendHalf`]; framed reads arrive over
    /// `frames` from the reader thread (`_reader`, named `pump-ipc-reader`).
    Split {
        send: SendHalf,
        frames: Receiver<io::Result<Envelope>>,


    /// A frame of an unrecognized kind (forward-compat: tolerated, surfaced).
    Other(Envelope),
}

    /// A frame of an unrecognized kind (forward-compat: tolerated, surfaced).
    Other(Envelope),
}

/// The outcome of the two-phase join's MEET phase ([`Brain::pair_meet`],
/// REQ-JOIN-TWO-PHASE): a member was found (its real address is held under the
/// session id), or the meet failed before any code was needed (rendered for the
/// operator without ever prompting for a code).
#[derive(Debug, Clone)]
pub enum MetOutcome {
    /// A seed-holder was met — proceed to collect the code and submit it.
    Met(MetMember),
    /// The meet failed (no member, unknown subnet, …) — a classified
    /// `pair-joined` failure surfaced before the code prompt.
    Failed(PairJoinReply),
}


    /// A frame of an unrecognized kind (forward-compat: tolerated, surfaced).
    Other(Envelope),
}

/// The outcome of the two-phase join's MEET phase ([`Brain::pair_meet`],
/// REQ-JOIN-TWO-PHASE): a member was found (its real address is held under the
/// session id), or the meet failed before any code was needed (rendered for the
/// operator without ever prompting for a code).
#[derive(Debug, Clone)]
pub enum MetOutcome {
    /// A seed-holder was met — proceed to collect the code and submit it.
    Met(MetMember),
    /// The meet failed (no member, unknown subnet, …) — a classified
    /// `pair-joined` failure surfaced before the code prompt.
    Failed(PairJoinReply),
}

+/// The outcome of the two-phase join's MEET phase ([`Brain::pair_meet`],

+/// REQ-JOIN-TWO-PHASE): a member was found (its real address is held under the

+/// session id), or the meet failed before any code was needed (rendered for the

+/// operator without ever prompting for a code).

+    /// A seed-holder was met — proceed to collect the code and submit it.

+    /// The meet failed (no member, unknown subnet, …) — a classified

+    /// `pair-joined` failure surfaced before the code prompt.

/// The brain's IPC carrier — either the ordinary whole [`Stream`] (every

/// non-pump client: blocking, unbounded reads, correct everywhere) or a

/// **split** carrier for the peer pump.


        loop {
            match self.read_event()? {
                BrokerEvent::PairJoined(r) => return Ok(r),
                BrokerEvent::Error { message } => return Err(io::Error::other(message)),
                _ => continue,
            }
        }
    }

        loop {
            match self.read_event()? {
                BrokerEvent::PairJoined(r) => return Ok(r),
                BrokerEvent::Error { message } => return Err(io::Error::other(message)),
                _ => continue,
            }
        }
    }

    /// Phase 1 of the two-phase join (REQ-JOIN-TWO-PHASE): MEET a seed-holder for
    /// `subnet` — no code. The daemon resolves and holds the member's real
    /// address; the reply is either a [`MetMember`] (proceed to the code) or a
    /// classified failure ([`MetOutcome::Failed`]) surfaced before any code
    /// prompt. The code routes nothing here — the meet selector is the public
    /// `(name, TOTP-epoch)`.
    // [impl->REQ-JOIN-TWO-PHASE]
    pub fn pair_meet(&mut self, subnet: &str) -> io::Result<MetOutcome> {
        self.send(
            KIND_PAIR_MEET,
            serde_json::to_value(PairMeetReq {
                subnet: subnet.to_string(),
            })
            .expect("PairMeetReq serializes"),
        )?;
        loop {
            match self.read_event()? {
                BrokerEvent::MetMember(m) => return Ok(MetOutcome::Met(m)),
                BrokerEvent::PairJoined(r) => return Ok(MetOutcome::Failed(r)),
                BrokerEvent::Error { message } => return Err(io::Error::other(message)),
                _ => continue,
            }
        }
    }

    /// Phase 2 of the two-phase join (REQ-JOIN-TWO-PHASE): submit the code for a
    /// held meet `session_id`. The daemon dials the HELD address (no re-search)
    /// and runs the ceremony. A wrong-code retry calls this again with the SAME
    /// session id (ceremony only). The code never persists.
    // [impl->REQ-JOIN-TWO-PHASE]
    pub fn pair_submit_code(&mut self, session_id: u64, code: &str) -> io::Result<PairJoinReply> {
        self.send(
            KIND_PAIR_CODE_SUBMIT,
            serde_json::to_value(PairCodeSubmit {
                session_id,
                code: code.to_string(),
            })
            .expect("PairCodeSubmit serializes"),
        )?;
        loop {
            match self.read_event()? {
                BrokerEvent::PairJoined(r) => return Ok(r),
                BrokerEvent::Error { message } => return Err(io::Error::other(message)),
                _ => continue,
            }
        }
    }


        loop {
            match self.read_event()? {
                BrokerEvent::PairJoined(r) => return Ok(r),
                BrokerEvent::Error { message } => return Err(io::Error::other(message)),
                _ => continue,
            }
        }
    }

    /// Phase 1 of the two-phase join (REQ-JOIN-TWO-PHASE): MEET a seed-holder for
    /// `subnet` — no code. The daemon resolves and holds the member's real
    /// address; the reply is either a [`MetMember`] (proceed to the code) or a
    /// classified failure ([`MetOutcome::Failed`]) surfaced before any code
    /// prompt. The code routes nothing here — the meet selector is the public
    /// `(name, TOTP-epoch)`.
    // [impl->REQ-JOIN-TWO-PHASE]
    pub fn pair_meet(&mut self, subnet: &str) -> io::Result<MetOutcome> {
        self.send(
            KIND_PAIR_MEET,
            serde_json::to_value(PairMeetReq {
                subnet: subnet.to_string(),
            })
            .expect("PairMeetReq serializes"),
        )?;
        loop {
            match self.read_event()? {
                BrokerEvent::MetMember(m) => return Ok(MetOutcome::Met(m)),
                BrokerEvent::PairJoined(r) => return Ok(MetOutcome::Failed(r)),
                BrokerEvent::Error { message } => return Err(io::Error::other(message)),
                _ => continue,
            }
        }
    }

    /// Phase 2 of the two-phase join (REQ-JOIN-TWO-PHASE): submit the code for a
    /// held meet `session_id`. The daemon dials the HELD address (no re-search)
    /// and runs the ceremony. A wrong-code retry calls this again with the SAME
    /// session id (ceremony only). The code never persists.
    // [impl->REQ-JOIN-TWO-PHASE]
    pub fn pair_submit_code(&mut self, session_id: u64, code: &str) -> io::Result<PairJoinReply> {
        self.send(
            KIND_PAIR_CODE_SUBMIT,
            serde_json::to_value(PairCodeSubmit {
                session_id,
                code: code.to_string(),
            })
            .expect("PairCodeSubmit serializes"),
        )?;
        loop {
            match self.read_event()? {
                BrokerEvent::PairJoined(r) => return Ok(r),
                BrokerEvent::Error { message } => return Err(io::Error::other(message)),
                _ => continue,
            }
        }
    }

+    /// Phase 1 of the two-phase join (REQ-JOIN-TWO-PHASE): MEET a seed-holder for

+    /// `subnet` — no code. The daemon resolves and holds the member's real

+    /// address; the reply is either a [`MetMember`] (proceed to the code) or a

+    /// classified failure ([`MetOutcome::Failed`]) surfaced before any code

+    /// prompt. The code routes nothing here — the meet selector is the public

+    pub fn pair_meet(&mut self, subnet: &str) -> io::Result<MetOutcome> {

+            serde_json::to_value(PairMeetReq {

+                subnet: subnet.to_string(),

+            .expect("PairMeetReq serializes"),

+                BrokerEvent::MetMember(m) => return Ok(MetOutcome::Met(m)),

+                BrokerEvent::PairJoined(r) => return Ok(MetOutcome::Failed(r)),

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

+    /// Phase 2 of the two-phase join (REQ-JOIN-TWO-PHASE): submit the code for a

+    /// held meet `session_id`. The daemon dials the HELD address (no re-search)

+    /// and runs the ceremony. A wrong-code retry calls this again with the SAME

+    /// session id (ceremony only). The code never persists.

+    pub fn pair_submit_code(&mut self, session_id: u64, code: &str) -> io::Result<PairJoinReply> {

+            serde_json::to_value(PairCodeSubmit {

+                code: code.to_string(),

+            .expect("PairCodeSubmit serializes"),

+                BrokerEvent::PairJoined(r) => return Ok(r),

/// Dial a peer over the broker-owned endpoint (D4a). `addr` is the peer's

/// serialized `EndpointAddr` (as another node's `net-status` reply carries).

/// With `op_id` the dial is exactly-once: a replay of the same durable id


Step 7 — the CLI. Reading the rest of `cmd_subnet_join` to rewrite the flow cleanly.


6335	                        eprintln!("JOIN_FAIL:{name}: wrong code");
6336	                        return 1;
6337	                    }
6338	                }
6339	            }
6340	            Some("wrong-code") => {
6341	                eprintln!(
6342	                    "JOIN_FAIL:{name}: wrong code — read the CURRENT code off the member \
6343	                     machine (`spt subnet show-code`) and retry"
6344	                );
6345	                return 1;
6346	            }
6347	            // The rate limiter speaks for itself — relay it honestly, never
6348	            // retry into it automatically.
6349	            Some("rate-limited") => {
6350	                match reply.retry_after_secs {
6351	                    Some(s) => eprintln!(
6352	                        "JOIN_RATE_LIMITED:{name}: too many failed attempts — retry in {s}s"
6353	                    ),
6354	                    None => eprintln!(
6355	                        "JOIN_BUSY:{name}: the member is mid-ceremony with another machine — \
6356	                         retry in a moment"
6357	                    ),
6358	                }
6359	                return 1;
6360	            }
6361	            Some("no-seed-holder") => {
6362	                eprintln!(
6363	                    "NO_SEED_HOLDER:{name}: no member answered the rendezvous.\n\
6364	                     - Is a member machine online (its spt daemon running)?\n\
6365	                     - Same subnet name on both sides? (names are exact)\n\
6366	                     - Are both clocks within ±30s? (the rendezvous derives from the time)\n\
6367	                     detail: {}",
6368	                    reply.detail
6369	                );
6370	                return 1;
6371	            }
6372	            Some("clock-skew") => {
6373	                eprintln!(
6374	                    "JOIN_FAIL:{name}: the member rejected this machine's clock — sync time \
6375	                     (within ±30s) and retry"
6376	                );
6377	                return 1;
6378	            }
6379	            Some("unknown-subnet") => {
6380	                eprintln!("JOIN_FAIL:{name}: the answering machine holds no subnet by that name");
6381	                return 1;
6382	            }
6383	            _ => {
6384	                eprintln!("JOIN_FAIL:{name}: {}", reply.detail);
6385	                return 1;
6386	            }
6387	        }
6388	    }
6389	    eprintln!("JOIN_FAIL:{name}: attempts exhausted");
6390	    1
6391	}
6392
6393	// ------------------------------------------------------------------ how-to
6394
6395	/// `spt how-to ready` — listener guidance for agents.
6396	const HOW_TO_READY: &str = "\
6397	# how-to: ready — receive messages
6398
6399	Pick a short lowercase perch id for yourself (`<id>` below). `spt whoami`
6400	shows your session's endpoint (id, state, description) if it already has one.
6401
6402	Preferred — your harness can host a long-running background task:
6403
6404	    spt ready <id>
6405
6406	- Blocks for the session's life: run it as a background task and read its stdout.
6407	- `READY:<id>` on stderr is the online signal.
6408	- The spooled backlog drains first; then each delivery prints to stdout as it
6409	  arrives.

                        eprintln!("JOIN_FAIL:{name}: wrong code");
                        return 1;
                    }
                }
            }
            Some("wrong-code") => {
                eprintln!(
                    "JOIN_FAIL:{name}: wrong code — read the CURRENT code off the member \
                     machine (`spt subnet show-code`) and retry"
                );
                return 1;
            }
            // The rate limiter speaks for itself — relay it honestly, never
            // retry into it automatically.
            Some("rate-limited") => {
                match reply.retry_after_secs {
                    Some(s) => eprintln!(
                        "JOIN_RATE_LIMITED:{name}: too many failed attempts — retry in {s}s"
                    ),
                    None => eprintln!(
                        "JOIN_BUSY:{name}: the member is mid-ceremony with another machine — \
                         retry in a moment"
                    ),
                }
                return 1;
            }
            Some("no-seed-holder") => {
                eprintln!(
                    "NO_SEED_HOLDER:{name}: no member answered the rendezvous.\n\
                     - Is a member machine online (its spt daemon running)?\n\
                     - Same subnet name on both sides? (names are exact)\n\
                     - Are both clocks within ±30s? (the rendezvous derives from the time)\n\
                     detail: {}",
                    reply.detail
                );
                return 1;
            }
            Some("clock-skew") => {
                eprintln!(
                    "JOIN_FAIL:{name}: the member rejected this machine's clock — sync time \
                     (within ±30s) and retry"
                );
                return 1;
            }
            Some("unknown-subnet") => {
                eprintln!("JOIN_FAIL:{name}: the answering machine holds no subnet by that name");
                return 1;
            }
            _ => {
                eprintln!("JOIN_FAIL:{name}: {}", reply.detail);
                return 1;
            }
        }
    }
    eprintln!("JOIN_FAIL:{name}: attempts exhausted");
    1
}

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

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

Pick a short lowercase perch id for yourself (`<id>` below). `spt whoami`
shows your session's endpoint (id, state, description) if it already has one.

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

    spt ready <id>

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


Reading the exact success-block bytes (6266–6326) to rewrite the function precisely.


6266	    const MAX_ATTEMPTS: usize = 3;
6267	    for attempt in 1..=MAX_ATTEMPTS {
6268	        eprintln!("Searching for a \"{name}\" member (LAN + relay)…");
6269	        let reply = match brain.pair_join(&name, &code, None) {
6270	            Ok(r) => r,
6271	            Err(e) => {
6272	                eprintln!("JOIN_FAIL:{name}: {e}");
6273	                return 1;
6274	            }
6275	        };
6276	        if reply.ok {
6277	            println!("JOINED SUBNET: {name}");
6278	            // First-join home adoption (REQ-INST-15): fires on join exactly
6279	            // as on create — the daemon saved the seed; local-only Self
6280	            // perches adopt the fresh sole subnet now rather than waiting
6281	            // one advertisement cadence.
6282	            let store = SubnetStore::load();
6283	            let mut vis = spt_store::visibility::VisibilityStore::load();
6284	            let adopted = spt_store::home::adopt_for_unset(&perch::owlery_dir(), &store, &mut vis);
6285	            if !adopted.is_empty() {
6286	                let _ = vis.save();
6287	            }
6288	            announce_sync_home(&adopted);
6289	            // Re-provision material for the joiner (ADR-0005: re-provision the QR
6290	            // from any trusted node). Now a member AND elevated (the gate passed
6291	            // first), surface this subnet's code + `otpauth://` URI + terminal QR
6292	            // so this machine can add the subnet to its OWN authenticator app —
6293	            // the same elevated Show output as `show-code`, reusing the
6294	            // unit-tested decision core. [impl->REQ-PAIR-3] [impl->REQ-SUBNET-1]
6295	            let mut qstore = SubnetStore::load();
6296	            let show = decide_show_code(
6297	                resolve(&mut qstore, Some(name.clone()), None),
6298	                now_unix(),
6299	                elevation::current(),
6300	            );
6301	            if let Some(out) = show.out {
6302	                print!("{out}");
6303	                let _ = std::io::stdout().flush();
6304	            }
6305	            // Translate the trust verdict (the wire carries the machine tag;
6306	            // "(Pinned)" means nothing to a person — grill 2026-06-06):
6307	            match reply.detail.as_str() {
6308	                "Pinned" => eprintln!(
6309	                    "This machine and the member that answered now recognize and \
6310	                     trust each other."
6311	                ),
6312	                "AlreadyTrusted" => {
6313	                    eprintln!("The machines already trusted each other — membership refreshed.")
6314	                }
6315	                _ => {}
6316	            }
6317	            eprintln!(
6318	                "Machines in \"{name}\" will see each other shortly (the first sync \
6319	                 can take a minute) — check with `spt subnet status --nodes` on \
6320	                 either machine."
6321	            );
6322	            // Hold a fresh elevated console open (Windows UAC re-launch) so the
6323	            // joiner reads the QR before the window closes; no-op otherwise.
6324	            pause_elevated_console_if_fresh();
6325	            return 0;
6326	        }
6327	        match reply.class.as_deref() {

    const MAX_ATTEMPTS: usize = 3;
    for attempt in 1..=MAX_ATTEMPTS {
        eprintln!("Searching for a \"{name}\" member (LAN + relay)…");
        let reply = match brain.pair_join(&name, &code, None) {
            Ok(r) => r,
            Err(e) => {
                eprintln!("JOIN_FAIL:{name}: {e}");
                return 1;
            }
        };
        if reply.ok {
            println!("JOINED SUBNET: {name}");
            // First-join home adoption (REQ-INST-15): fires on join exactly
            // as on create — the daemon saved the seed; local-only Self
            // perches adopt the fresh sole subnet now rather than waiting
            // one advertisement cadence.
            let store = SubnetStore::load();
            let mut vis = spt_store::visibility::VisibilityStore::load();
            let adopted = spt_store::home::adopt_for_unset(&perch::owlery_dir(), &store, &mut vis);
            if !adopted.is_empty() {
                let _ = vis.save();
            }
            announce_sync_home(&adopted);
            // Re-provision material for the joiner (ADR-0005: re-provision the QR
            // from any trusted node). Now a member AND elevated (the gate passed
            // first), surface this subnet's code + `otpauth://` URI + terminal QR
            // so this machine can add the subnet to its OWN authenticator app —
            // the same elevated Show output as `show-code`, reusing the
            // unit-tested decision core. [impl->REQ-PAIR-3] [impl->REQ-SUBNET-1]
            let mut qstore = SubnetStore::load();
            let show = decide_show_code(
                resolve(&mut qstore, Some(name.clone()), None),
                now_unix(),
                elevation::current(),
            );
            if let Some(out) = show.out {
                print!("{out}");
                let _ = std::io::stdout().flush();
            }
            // Translate the trust verdict (the wire carries the machine tag;
            // "(Pinned)" means nothing to a person — grill 2026-06-06):
            match reply.detail.as_str() {
                "Pinned" => eprintln!(
                    "This machine and the member that answered now recognize and \
                     trust each other."
                ),
                "AlreadyTrusted" => {
                    eprintln!("The machines already trusted each other — membership refreshed.")
                }
                _ => {}
            }
            eprintln!(
                "Machines in \"{name}\" will see each other shortly (the first sync \
                 can take a minute) — check with `spt subnet status --nodes` on \
                 either machine."
            );
            // Hold a fresh elevated console open (Windows UAC re-launch) so the
            // joiner reads the QR before the window closes; no-op otherwise.
            pause_elevated_console_if_fresh();
            return 0;
        }
        match reply.class.as_deref() {


Now rewriting the CLI flow. Edit A — setup + meet phase + loop header (keeps the success block intact).


    let interactive = code.is_none();
    let Some(mut code) = code.or_else(|| prompt_line("6-digit code: ")) else {
        eprintln!(
            "USAGE: the current 6-digit code is required — `--code <CODE>` (or run interactively)"
        );
        return 2;
    };

    if let Err(code) = ensure_daemon_announced() {
        return code;
    }
    let mut brain =
        match spt_daemon::brain::Brain::cold_start(&spt_daemon::broker_socket_name(), now_ms()) {
            Ok(b) => b,
            Err(e) => {
                eprintln!("BROKER_CONNECT_FAIL: {e}");
                return 1;
            }
        };

    const MAX_ATTEMPTS: usize = 3;
    for attempt in 1..=MAX_ATTEMPTS {
        eprintln!("Searching for a \"{name}\" member (LAN + relay)…");
        let reply = match brain.pair_join(&name, &code, None) {
            Ok(r) => r,
            Err(e) => {
                eprintln!("JOIN_FAIL:{name}: {e}");
                return 1;
            }
        };
        if reply.ok {

    let interactive = code.is_none();

    if let Err(code) = ensure_daemon_announced() {
        return code;
    }
    let mut brain =
        match spt_daemon::brain::Brain::cold_start(&spt_daemon::broker_socket_name(), now_ms()) {
            Ok(b) => b,
            Err(e) => {
                eprintln!("BROKER_CONNECT_FAIL: {e}");
                return 1;
            }
        };
    eprintln!("Searching for a \"{name}\" member (LAN + relay)…");

    // Two-phase join (REQ-JOIN-TWO-PHASE): MEET a member first; the code is
    // collected AFTER, so it is fresh at the ceremony regardless of how long the
    // search took. The `--code` path stays one-shot (no prompt; relies on the
    // per-family bind gate's fast discovery to keep the supplied code in window).
    let session_id = if interactive {
        match brain.pair_meet(&name) {
            Ok(spt_daemon::brain::MetOutcome::Met(m)) => {
                eprintln!(
                    "Found a \"{name}\" member ({}…) — enter its CURRENT 6-digit code.",
                    m.member_hex.chars().take(12).collect::<String>()
                );
                Some(m.session_id)
            }
            // A dead/unknown subnet fails BEFORE the code prompt — never make the
            // user fetch a code for a member that isn't there.
            Ok(spt_daemon::brain::MetOutcome::Failed(reply)) => {
                return render_join_failure(&name, &reply);
            }
            Err(e) => {
                eprintln!("JOIN_FAIL:{name}: {e}");
                return 1;
            }
        }
    } else {
        None
    };
    let Some(mut code) = code.or_else(|| prompt_line("6-digit code: ")) else {
        eprintln!(
            "USAGE: the current 6-digit code is required — `--code <CODE>` (or run interactively)"
        );
        return 2;
    };

    const MAX_ATTEMPTS: usize = 3;
    for attempt in 1..=MAX_ATTEMPTS {
        // Phase 2: submit the code. Interactive → the held meet session (ceremony
        // only, no re-search); `--code` → the one-shot meet+ceremony.
        let reply = match session_id {
            Some(sid) => brain.pair_submit_code(sid, &code),
            None => brain.pair_join(&name, &code, None),
        };
        let reply = match reply {
            Ok(r) => r,
            Err(e) => {
                eprintln!("JOIN_FAIL:{name}: {e}");
                return 1;
            }
        };
        if reply.ok {


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

    let interactive = code.is_none();
    let Some(mut code) = code.or_else(|| prompt_line("6-digit code: ")) else {
        eprintln!(
            "USAGE: the current 6-digit code is required — `--code <CODE>` (or run interactively)"
        );
        return 2;
    };

    if let Err(code) = ensure_daemon_announced() {
        return code;
    }
    let mut brain =
        match spt_daemon::brain::Brain::cold_start(&spt_daemon::broker_socket_name(), now_ms()) {
            Ok(b) => b,
            Err(e) => {
                eprintln!("BROKER_CONNECT_FAIL: {e}");
                return 1;
            }
        };

    const MAX_ATTEMPTS: usize = 3;
    for attempt in 1..=MAX_ATTEMPTS {
        eprintln!("Searching for a \"{name}\" member (LAN + relay)…");
        let reply = match brain.pair_join(&name, &code, None) {
            Ok(r) => r,
            Err(e) => {
                eprintln!("JOIN_FAIL:{name}: {e}");
                return 1;
            }
        };
        if reply.ok {

    let interactive = code.is_none();

    if let Err(code) = ensure_daemon_announced() {
        return code;
    }
    let mut brain =
        match spt_daemon::brain::Brain::cold_start(&spt_daemon::broker_socket_name(), now_ms()) {
            Ok(b) => b,
            Err(e) => {
                eprintln!("BROKER_CONNECT_FAIL: {e}");
                return 1;
            }
        };
    eprintln!("Searching for a \"{name}\" member (LAN + relay)…");

    // Two-phase join (REQ-JOIN-TWO-PHASE): MEET a member first; the code is
    // collected AFTER, so it is fresh at the ceremony regardless of how long the
    // search took. The `--code` path stays one-shot (no prompt; relies on the
    // per-family bind gate's fast discovery to keep the supplied code in window).
    let session_id = if interactive {
        match brain.pair_meet(&name) {
            Ok(spt_daemon::brain::MetOutcome::Met(m)) => {
                eprintln!(
                    "Found a \"{name}\" member ({}…) — enter its CURRENT 6-digit code.",
                    m.member_hex.chars().take(12).collect::<String>()
                );
                Some(m.session_id)
            }
            // A dead/unknown subnet fails BEFORE the code prompt — never make the
            // user fetch a code for a member that isn't there.
            Ok(spt_daemon::brain::MetOutcome::Failed(reply)) => {
                return render_join_failure(&name, &reply);
            }
            Err(e) => {
                eprintln!("JOIN_FAIL:{name}: {e}");
                return 1;
            }
        }
    } else {
        None
    };
    let Some(mut code) = code.or_else(|| prompt_line("6-digit code: ")) else {
        eprintln!(
            "USAGE: the current 6-digit code is required — `--code <CODE>` (or run interactively)"
        );
        return 2;
    };

    const MAX_ATTEMPTS: usize = 3;
    for attempt in 1..=MAX_ATTEMPTS {
        // Phase 2: submit the code. Interactive → the held meet session (ceremony
        // only, no re-search); `--code` → the one-shot meet+ceremony.
        let reply = match session_id {
            Some(sid) => brain.pair_submit_code(sid, &code),
            None => brain.pair_join(&name, &code, None),
        };
        let reply = match reply {
            Ok(r) => r,
            Err(e) => {
                eprintln!("JOIN_FAIL:{name}: {e}");
                return 1;
            }
        };
        if reply.ok {

-    let Some(mut code) = code.or_else(|| prompt_line("6-digit code: ")) else {

-            "USAGE: the current 6-digit code is required — `--code <CODE>` (or run interactively)"

+    eprintln!("Searching for a \"{name}\" member (LAN + relay)…");

+    // Two-phase join (REQ-JOIN-TWO-PHASE): MEET a member first; the code is

+    // collected AFTER, so it is fresh at the ceremony regardless of how long the

+    // search took. The `--code` path stays one-shot (no prompt; relies on the

+    // per-family bind gate's fast discovery to keep the supplied code in window).

+            Ok(spt_daemon::brain::MetOutcome::Met(m)) => {

+                    "Found a \"{name}\" member ({}…) — enter its CURRENT 6-digit code.",

+                    m.member_hex.chars().take(12).collect::<String>()

+            // A dead/unknown subnet fails BEFORE the code prompt — never make the

+            // user fetch a code for a member that isn't there.

+            Ok(spt_daemon::brain::MetOutcome::Failed(reply)) => {

+                return render_join_failure(&name, &reply);

+                eprintln!("JOIN_FAIL:{name}: {e}");

+    let Some(mut code) = code.or_else(|| prompt_line("6-digit code: ")) else {

+            "USAGE: the current 6-digit code is required — `--code <CODE>` (or run interactively)"

-        eprintln!("Searching for a \"{name}\" member (LAN + relay)…");

-        let reply = match brain.pair_join(&name, &code, None) {

+        // Phase 2: submit the code. Interactive → the held meet session (ceremony

+        // only, no re-search); `--code` → the one-shot meet+ceremony.

+            Some(sid) => brain.pair_submit_code(sid, &code),

+            None => brain.pair_join(&name, &code, None),


Edit B — compact the loop's failure tail to delegate to a shared `render_join_failure`.


        match reply.class.as_deref() {
            // Wrong code: re-prompt against a FRESH window (codes rotate
            // every 30s) — bounded so a typo can't become a guessing loop.
            Some("wrong-code") if interactive && attempt < MAX_ATTEMPTS => {
                match prompt_line("Wrong code (codes rotate every 30s) — enter the CURRENT code: ")
                {
                    Some(next) => code = next,
                    None => {
                        eprintln!("JOIN_FAIL:{name}: wrong code");
                        return 1;
                    }
                }
            }
            Some("wrong-code") => {
                eprintln!(
                    "JOIN_FAIL:{name}: wrong code — read the CURRENT code off the member \
                     machine (`spt subnet show-code`) and retry"
                );
                return 1;
            }
            // The rate limiter speaks for itself — relay it honestly, never
            // retry into it automatically.
            Some("rate-limited") => {
                match reply.retry_after_secs {
                    Some(s) => eprintln!(
                        "JOIN_RATE_LIMITED:{name}: too many failed attempts — retry in {s}s"
                    ),
                    None => eprintln!(
                        "JOIN_BUSY:{name}: the member is mid-ceremony with another machine — \
                         retry in a moment"
                    ),
                }
                return 1;
            }
            Some("no-seed-holder") => {
                eprintln!(
                    "NO_SEED_HOLDER:{name}: no member answered the rendezvous.\n\
                     - Is a member machine online (its spt daemon running)?\n\
                     - Same subnet name on both sides? (names are exact)\n\
                     - Are both clocks within ±30s? (the rendezvous derives from the time)\n\
                     detail: {}",
                    reply.detail
                );
                return 1;
            }
            Some("clock-skew") => {
                eprintln!(
                    "JOIN_FAIL:{name}: the member rejected this machine's clock — sync time \
                     (within ±30s) and retry"
                );
                return 1;
            }
            Some("unknown-subnet") => {
                eprintln!("JOIN_FAIL:{name}: the answering machine holds no subnet by that name");
                return 1;
            }
            _ => {
                eprintln!("JOIN_FAIL:{name}: {}", reply.detail);
                return 1;
            }
        }
    }
    eprintln!("JOIN_FAIL:{name}: attempts exhausted");
    1
}

        match reply.class.as_deref() {
            // Wrong code: re-prompt against a FRESH window (codes rotate every
            // 30s) — bounded so a typo can't become a guessing loop. Interactive
            // only; the re-submit hits the held meet session (ceremony only, no
            // re-search — the held address does not age within the 5-min hold).
            Some("wrong-code") if interactive && attempt < MAX_ATTEMPTS => {
                match prompt_line("Wrong code (codes rotate every 30s) — enter the CURRENT code: ")
                {
                    Some(next) => code = next,
                    None => {
                        eprintln!("JOIN_FAIL:{name}: wrong code");
                        return 1;
                    }
                }
            }
            _ => return render_join_failure(&name, &reply),
        }
    }
    eprintln!("JOIN_FAIL:{name}: attempts exhausted");
    1
}

/// Render a classified join failure ([`PairJoinReply`] with `ok == false`) as the
/// operator-facing guidance + the process exit code (always 1). Single-sourced so
/// the meet phase (fails before the code prompt) and the code phase render
/// identically (REQ-JOIN-TWO-PHASE / REQ-JOIN-DIAGNOSTICS).
fn render_join_failure(name: &str, reply: &spt_daemon::msg::PairJoinReply) -> i32 {
    match reply.class.as_deref() {
        Some("wrong-code") => {
            eprintln!(
                "JOIN_FAIL:{name}: wrong code — read the CURRENT code off the member \
                 machine (`spt subnet show-code`) and retry"
            );
        }
        // The rate limiter speaks for itself — relay it honestly, never retry
        // into it automatically.
        Some("rate-limited") => match reply.retry_after_secs {
            Some(s) => {
                eprintln!("JOIN_RATE_LIMITED:{name}: too many failed attempts — retry in {s}s")
            }
            None => eprintln!(
                "JOIN_BUSY:{name}: the member is mid-ceremony with another machine — \
                 retry in a moment"
            ),
        },
        Some("no-seed-holder") => {
            eprintln!(
                "NO_SEED_HOLDER:{name}: no member answered the rendezvous.\n\
                 - Is a member machine online (its spt daemon running)?\n\
                 - Same subnet name on both sides? (names are exact)\n\
                 - Are both clocks within ±30s? (the rendezvous derives from the time)\n\
                 detail: {}",
                reply.detail
            );
        }
        Some("clock-skew") => {
            eprintln!(
                "JOIN_FAIL:{name}: the member rejected this machine's clock — sync time \
                 (within ±30s) and retry"
            );
        }
        Some("unknown-subnet") => {
            eprintln!("JOIN_FAIL:{name}: the answering machine holds no subnet by that name");
        }
        _ => {
            eprintln!("JOIN_FAIL:{name}: {}", reply.detail);
        }
    }
    1
}


        match reply.class.as_deref() {
            // Wrong code: re-prompt against a FRESH window (codes rotate
            // every 30s) — bounded so a typo can't become a guessing loop.
            Some("wrong-code") if interactive && attempt < MAX_ATTEMPTS => {
                match prompt_line("Wrong code (codes rotate every 30s) — enter the CURRENT code: ")
                {
                    Some(next) => code = next,
                    None => {
                        eprintln!("JOIN_FAIL:{name}: wrong code");
                        return 1;
                    }
                }
            }
            Some("wrong-code") => {
                eprintln!(
                    "JOIN_FAIL:{name}: wrong code — read the CURRENT code off the member \
                     machine (`spt subnet show-code`) and retry"
                );
                return 1;
            }
            // The rate limiter speaks for itself — relay it honestly, never
            // retry into it automatically.
            Some("rate-limited") => {
                match reply.retry_after_secs {
                    Some(s) => eprintln!(
                        "JOIN_RATE_LIMITED:{name}: too many failed attempts — retry in {s}s"
                    ),
                    None => eprintln!(
                        "JOIN_BUSY:{name}: the member is mid-ceremony with another machine — \
                         retry in a moment"
                    ),
                }
                return 1;
            }
            Some("no-seed-holder") => {
                eprintln!(
                    "NO_SEED_HOLDER:{name}: no member answered the rendezvous.\n\
                     - Is a member machine online (its spt daemon running)?\n\
                     - Same subnet name on both sides? (names are exact)\n\
                     - Are both clocks within ±30s? (the rendezvous derives from the time)\n\
                     detail: {}",
                    reply.detail
                );
                return 1;
            }
            Some("clock-skew") => {
                eprintln!(
                    "JOIN_FAIL:{name}: the member rejected this machine's clock — sync time \
                     (within ±30s) and retry"
                );
                return 1;
            }
            Some("unknown-subnet") => {
                eprintln!("JOIN_FAIL:{name}: the answering machine holds no subnet by that name");
                return 1;
            }
            _ => {
                eprintln!("JOIN_FAIL:{name}: {}", reply.detail);
                return 1;
            }
        }
    }
    eprintln!("JOIN_FAIL:{name}: attempts exhausted");
    1
}

        match reply.class.as_deref() {
            // Wrong code: re-prompt against a FRESH window (codes rotate every
            // 30s) — bounded so a typo can't become a guessing loop. Interactive
            // only; the re-submit hits the held meet session (ceremony only, no
            // re-search — the held address does not age within the 5-min hold).
            Some("wrong-code") if interactive && attempt < MAX_ATTEMPTS => {
                match prompt_line("Wrong code (codes rotate every 30s) — enter the CURRENT code: ")
                {
                    Some(next) => code = next,
                    None => {
                        eprintln!("JOIN_FAIL:{name}: wrong code");
                        return 1;
                    }
                }
            }
            _ => return render_join_failure(&name, &reply),
        }
    }
    eprintln!("JOIN_FAIL:{name}: attempts exhausted");
    1
}

/// Render a classified join failure ([`PairJoinReply`] with `ok == false`) as the
/// operator-facing guidance + the process exit code (always 1). Single-sourced so
/// the meet phase (fails before the code prompt) and the code phase render
/// identically (REQ-JOIN-TWO-PHASE / REQ-JOIN-DIAGNOSTICS).
fn render_join_failure(name: &str, reply: &spt_daemon::msg::PairJoinReply) -> i32 {
    match reply.class.as_deref() {
        Some("wrong-code") => {
            eprintln!(
                "JOIN_FAIL:{name}: wrong code — read the CURRENT code off the member \
                 machine (`spt subnet show-code`) and retry"
            );
        }
        // The rate limiter speaks for itself — relay it honestly, never retry
        // into it automatically.
        Some("rate-limited") => match reply.retry_after_secs {
            Some(s) => {
                eprintln!("JOIN_RATE_LIMITED:{name}: too many failed attempts — retry in {s}s")
            }
            None => eprintln!(
                "JOIN_BUSY:{name}: the member is mid-ceremony with another machine — \
                 retry in a moment"
            ),
        },
        Some("no-seed-holder") => {
            eprintln!(
                "NO_SEED_HOLDER:{name}: no member answered the rendezvous.\n\
                 - Is a member machine online (its spt daemon running)?\n\
                 - Same subnet name on both sides? (names are exact)\n\
                 - Are both clocks within ±30s? (the rendezvous derives from the time)\n\
                 detail: {}",
                reply.detail
            );
        }
        Some("clock-skew") => {
            eprintln!(
                "JOIN_FAIL:{name}: the member rejected this machine's clock — sync time \
                 (within ±30s) and retry"
            );
        }
        Some("unknown-subnet") => {
            eprintln!("JOIN_FAIL:{name}: the answering machine holds no subnet by that name");
        }
        _ => {
            eprintln!("JOIN_FAIL:{name}: {}", reply.detail);
        }
    }
    1
}

-            // Wrong code: re-prompt against a FRESH window (codes rotate

-            // every 30s) — bounded so a typo can't become a guessing loop.

+            // Wrong code: re-prompt against a FRESH window (codes rotate every

+            // 30s) — bounded so a typo can't become a guessing loop. Interactive

+            // only; the re-submit hits the held meet session (ceremony only, no

+            // re-search — the held address does not age within the 5-min hold).

-                    "JOIN_FAIL:{name}: wrong code — read the CURRENT code off the member \

-                     machine (`spt subnet show-code`) and retry"

-            // The rate limiter speaks for itself — relay it honestly, never

-            // retry into it automatically.

-                match reply.retry_after_secs {

-                    Some(s) => eprintln!(

-                        "JOIN_RATE_LIMITED:{name}: too many failed attempts — retry in {s}s"

-                        "JOIN_BUSY:{name}: the member is mid-ceremony with another machine — \

-                         retry in a moment"

-            Some("no-seed-holder") => {

-                    "NO_SEED_HOLDER:{name}: no member answered the rendezvous.\n\

-                     - Is a member machine online (its spt daemon running)?\n\

-                     - Same subnet name on both sides? (names are exact)\n\

-                     - Are both clocks within ±30s? (the rendezvous derives from the time)\n\

-                    "JOIN_FAIL:{name}: the member rejected this machine's clock — sync time \

-                     (within ±30s) and retry"

-            Some("unknown-subnet") => {

-                eprintln!("JOIN_FAIL:{name}: the answering machine holds no subnet by that name");

-                eprintln!("JOIN_FAIL:{name}: {}", reply.detail);

+            _ => return render_join_failure(&name, &reply),

+/// Render a classified join failure ([`PairJoinReply`] with `ok == false`) as the

+/// operator-facing guidance + the process exit code (always 1). Single-sourced so

+/// the meet phase (fails before the code prompt) and the code phase render

+/// identically (REQ-JOIN-TWO-PHASE / REQ-JOIN-DIAGNOSTICS).

+fn render_join_failure(name: &str, reply: &spt_daemon::msg::PairJoinReply) -> i32 {

+                "JOIN_FAIL:{name}: wrong code — read the CURRENT code off the member \

+                 machine (`spt subnet show-code`) and retry"

+        // The rate limiter speaks for itself — relay it honestly, never retry

+        Some("rate-limited") => match reply.retry_after_secs {

+                eprintln!("JOIN_RATE_LIMITED:{name}: too many failed attempts — retry in {s}s")

+                "JOIN_BUSY:{name}: the member is mid-ceremony with another machine — \

+                "NO_SEED_HOLDER:{name}: no member answered the rendezvous.\n\

+                 - Is a member machine online (its spt daemon running)?\n\

+                 - Same subnet name on both sides? (names are exact)\n\

+                 - Are both clocks within ±30s? (the rendezvous derives from the time)\n\

+                "JOIN_FAIL:{name}: the member rejected this machine's clock — sync time \

+                 (within ±30s) and retry"

+            eprintln!("JOIN_FAIL:{name}: the answering machine holds no subnet by that name");

+            eprintln!("JOIN_FAIL:{name}: {}", reply.detail);

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


W2 wiring complete across all 6 files. Compiling (check first, per-crate, to dodge the test-codegen OOM).


PowerShell

$env:CARGO_BUILD_JOBS=2; cargo check -p spt-daemon 2>&1 | Select-Object -Last 30


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


spt-daemon checks clean. Now spt (CLI).


$env:CARGO_BUILD_JOBS=2; cargo check -p spt 2>&1 | Select-Object -Last 25


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


Both crates check clean. Reading the existing `pairjoin.rs` integration harness to add the two-phase int test.


1	//! M7 D3/D5 regression — the daemon-hosted pairing responder serves a real
2	//! joiner end to end over loopback: NetHost binds the pairing ALPN beside
3	//! the trusted surface, the accept loop routes the ceremony to
4	//! `pairhost::respond`, and `join_ceremony` (the `pair-join` broker leg)
5	//! completes against it — INCLUDING reading the final Done+Seed frames.
6	//!
7	//! That last clause is the regression: the first cross-host run of the
8	//! product-surface rung failed because the responder dropped the connection
9	//! the instant the ceremony returned — the QUIC CLOSE discarded the unacked
10	//! Done/Seed frames and the joiner read "connection lost" instead of its
11	//! seed, every attempt (`pairhost` now waits out the joiner's close under
12	//! `CLOSE_GRACE`). A loopback joiner exercises the same frame/close ordering
13	//! without the rig.
14	//!
15	//! Shared-home caveat: responder and joiner run in ONE process, so they
16	//! share SPT_HOME stores. The seed the responder serves is therefore already
17	//! "held" by the joiner's store (`add_joined` → `Exists` is the documented
18	//! idempotent re-pair path), and the trust writes race on one file — so the
19	//! assertion is the ceremony VERDICT (`Ok`), which can only happen if the
20	//! joiner read the full final exchange.
21	//
22	// [int->REQ-SUBNET-2]
23
24	use std::time::{SystemTime, UNIX_EPOCH};
25
26	use spt_daemon::nethost::{NetConfig, NetHost};
27	use spt_daemon::pairhost::join_ceremony;
28	use spt_net::net::endpoint::{BindScope, LocalDiscovery, NetEndpoint, RelayPolicy};
29	use spt_net::net::pairing::totp::TotpSeed;
30	use spt_net::net::pairing::wire::SPT_PAIR_ALPN;
31	use spt_proto::identity::Identity;
32	use spt_store::subnet::SubnetStore;
33
34	fn now_secs() -> u64 {
35	    SystemTime::now()
36	        .duration_since(UNIX_EPOCH)
37	        .expect("clock")
38	        .as_secs()
39	}
40
41	#[test]
42	fn daemon_hosted_responder_pairs_a_loopback_joiner_end_to_end() {
43	    let home = tempfile::tempdir().expect("temp home");
44	    std::env::set_var("SPT_HOME", home.path());
45	    // Hermetic ceremony clock (REQ-PAIR-8): no real NTP probe from a test —
46	    // the offset falls back to the system clock, exactly the offline path.
47	    std::env::set_var("SPT_NTP_SERVER", "off");
48
49	    // The responder side is a stock member: the subnet record exists, the
50	    // NetHost is up — nothing pairing-specific is armed (decision 8).
51	    let mut subnets = SubnetStore::load();
52	    let seed = [7u8; 20];
53	    subnets
54	        .add_joined("pairjoin", seed, 1)
55	        .expect("seed subnet");
56	    subnets.save().expect("save subnets");
57
58	    let id_b = Identity::from_seed(&[2u8; 32]);
59	    let host = NetHost::start(NetConfig {
60	        identity: id_b,
61	        relay: RelayPolicy::Disabled,
62	        discovery: LocalDiscovery::Off,
63	        scope: BindScope::Loopback,
64	        membership: None,
65	    })
66	    .expect("net host start");
67	    let addr_b = host.addr();
68
69	    // The joiner leg, exactly as the broker `pair-join` arm runs it — its
70	    // own node identity, the current code, the explicit responder address
71	    // (the same static-address seam the twohost rig uses).
72	    let id_a = Identity::from_seed(&[3u8; 32]);
73	    let rt = tokio::runtime::Runtime::new().expect("tokio rt");
74	    let outcome = rt.block_on(async {
75	        let ep_a = NetEndpoint::bind_scoped(
76	            &id_a,
77	            RelayPolicy::Disabled,
78	            LocalDiscovery::Off,
79	            vec![SPT_PAIR_ALPN.to_vec()],
80	            BindScope::Loopback,
81	        )
82	        .await
83	        .expect("joiner endpoint bind");
84	        let code = TotpSeed::from_bytes(seed).code_at(now_secs());
85	        let out = join_ceremony(&ep_a, "pairjoin", &code, Some(addr_b)).await;
86	        ep_a.close().await;
87	        out
88	    });
89	    assert!(
90	        outcome.is_ok(),
91	        "the joiner must complete the ceremony INCLUDING the final Done+Seed \
92	         frames (the close-race regression), got: {outcome:?}"
93	    );
94	}
95

//! M7 D3/D5 regression — the daemon-hosted pairing responder serves a real
//! joiner end to end over loopback: NetHost binds the pairing ALPN beside
//! the trusted surface, the accept loop routes the ceremony to
//! `pairhost::respond`, and `join_ceremony` (the `pair-join` broker leg)
//! completes against it — INCLUDING reading the final Done+Seed frames.
//!
//! That last clause is the regression: the first cross-host run of the
//! product-surface rung failed because the responder dropped the connection
//! the instant the ceremony returned — the QUIC CLOSE discarded the unacked
//! Done/Seed frames and the joiner read "connection lost" instead of its
//! seed, every attempt (`pairhost` now waits out the joiner's close under
//! `CLOSE_GRACE`). A loopback joiner exercises the same frame/close ordering
//! without the rig.
//!
//! Shared-home caveat: responder and joiner run in ONE process, so they
//! share SPT_HOME stores. The seed the responder serves is therefore already
//! "held" by the joiner's store (`add_joined` → `Exists` is the documented
//! idempotent re-pair path), and the trust writes race on one file — so the
//! assertion is the ceremony VERDICT (`Ok`), which can only happen if the
//! joiner read the full final exchange.
//
// [int->REQ-SUBNET-2]

use std::time::{SystemTime, UNIX_EPOCH};

use spt_daemon::nethost::{NetConfig, NetHost};
use spt_daemon::pairhost::join_ceremony;
use spt_net::net::endpoint::{BindScope, LocalDiscovery, NetEndpoint, RelayPolicy};
use spt_net::net::pairing::totp::TotpSeed;
use spt_net::net::pairing::wire::SPT_PAIR_ALPN;
use spt_proto::identity::Identity;
use spt_store::subnet::SubnetStore;

fn now_secs() -> u64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("clock")
        .as_secs()
}

#[test]
fn daemon_hosted_responder_pairs_a_loopback_joiner_end_to_end() {
    let home = tempfile::tempdir().expect("temp home");
    std::env::set_var("SPT_HOME", home.path());
    // Hermetic ceremony clock (REQ-PAIR-8): no real NTP probe from a test —
    // the offset falls back to the system clock, exactly the offline path.
    std::env::set_var("SPT_NTP_SERVER", "off");

    // The responder side is a stock member: the subnet record exists, the
    // NetHost is up — nothing pairing-specific is armed (decision 8).
    let mut subnets = SubnetStore::load();
    let seed = [7u8; 20];
    subnets
        .add_joined("pairjoin", seed, 1)
        .expect("seed subnet");
    subnets.save().expect("save subnets");

    let id_b = Identity::from_seed(&[2u8; 32]);
    let host = NetHost::start(NetConfig {
        identity: id_b,
        relay: RelayPolicy::Disabled,
        discovery: LocalDiscovery::Off,
        scope: BindScope::Loopback,
        membership: None,
    })
    .expect("net host start");
    let addr_b = host.addr();

    // The joiner leg, exactly as the broker `pair-join` arm runs it — its
    // own node identity, the current code, the explicit responder address
    // (the same static-address seam the twohost rig uses).
    let id_a = Identity::from_seed(&[3u8; 32]);
    let rt = tokio::runtime::Runtime::new().expect("tokio rt");
    let outcome = rt.block_on(async {
        let ep_a = NetEndpoint::bind_scoped(
            &id_a,
            RelayPolicy::Disabled,
            LocalDiscovery::Off,
            vec![SPT_PAIR_ALPN.to_vec()],
            BindScope::Loopback,
        )
        .await
        .expect("joiner endpoint bind");
        let code = TotpSeed::from_bytes(seed).code_at(now_secs());
        let out = join_ceremony(&ep_a, "pairjoin", &code, Some(addr_b)).await;
        ep_a.close().await;
        out
    });
    assert!(
        outcome.is_ok(),
        "the joiner must complete the ceremony INCLUDING the final Done+Seed \
         frames (the close-race regression), got: {outcome:?}"
    );
}


The rendezvous meet needs real discovery/relay — can't run fully hermetic. Phase-2 (ceremony against a held addr) is already int-covered by `pairjoin.rs` (it passes `Some(addr)`). Checking the meet.rs hermetic harness to cover the new `meet_via_rendezvous` split + the broker hold logic.


325	    fn key_seed_is_not_the_token() {
326	        let token = rendezvous_token("home", 100);
327	        assert_ne!(rendezvous_identity(&token).seed(), token);
328	    }
329
330	    /// Bind a hermetic (loopback, no relay/discovery) endpoint on `alpns`.
331	    async fn hermetic(id: &Identity, alpns: Vec<Vec<u8>>) -> NetEndpoint {
332	        NetEndpoint::bind_scoped(
333	            id,
334	            RelayPolicy::Disabled,
335	            LocalDiscovery::Off,
336	            alpns,
337	            BindScope::Loopback,
338	        )
339	        .await
340	        .expect("hermetic bind")
341	    }
342
343	    /// The meet listener's derived-id address with its loopback sockets
344	    /// attached — what relay/discovery resolution would produce, built
345	    /// explicitly because the hermetic test has neither. The id in the addr
346	    /// is RE-DERIVED from (name, step), not copied from the listener, so the
347	    /// dial proves the listener really holds the token-derived key (TLS
348	    /// authenticates the id).
349	    fn derived_addr(listener: &RendezvousListener, subnet: &str, step: u64) -> EndpointAddr {
350	        let pk = rendezvous_endpoint_id(subnet, step);
351	        let id = EndpointId::from_bytes(&pk.to_bytes()).expect("valid derived id");
352	        let socks: Vec<SocketAddr> = listener.endpoint().addr().ip_addrs().copied().collect();
353	        let mut addr = EndpointAddr::new(id);
354	        for s in socks {
355	            addr = addr.with_ip_addr(s);
356	        }
357	        addr
358	    }
359
360	    // [int->REQ-PAIR-5] the rendezvous meet end to end (hermetic loopback,
361	    // relays disabled — the relay leg rides the D9a rig): the responder binds
362	    // the meet listener under the token-derived identity beside its real
363	    // pairing endpoint; the joiner re-derives the public id from the two user
364	    // inputs, dials it (TLS proves the listener holds the derived key),
365	    // receives the responder's REAL address, and lands a ceremony-ALPN
366	    // connection on the real endpoint — discovery via token, ceremony surface
367	    // unchanged.
368	    #[tokio::test]
369	    async fn meet_redirects_joiner_to_the_real_pairing_endpoint() {
370	        let subnet = "home";
371	        let step = 57_037_037u64;
372
373	        // The responder's REAL pairing endpoint (its durable identity).
374	        let resp_id = Identity::generate();
375	        let real = hermetic(&resp_id, vec![SPT_PAIR_ALPN.to_vec()]).await;
376	        let real_addr = real.addr();
377	        let real_pub = resp_id.public_key();
378
379	        // The meet listener: derived identity, meet ALPN only.
380	        let listener = bind_rendezvous(
381	            subnet,
382	            step,
383	            RelayPolicy::Disabled,
384	            LocalDiscovery::Off,
385	            BindScope::Loopback,
386	        )
387	        .await
388	        .expect("bind rendezvous");
389	        let meet_addr = derived_addr(&listener, subnet, step);
390
391	        // Responder side: answer one meet, then accept the real-dial.
392	        let serve = tokio::spawn(async move {
393	            listener.serve_once(&real_addr).await.expect("serve meet");
394	            let incoming = real.accept().await.expect("real incoming");
395	            let conn = incoming
396	                .accept()
397	                .expect("accept")
398	                .await
399	                .expect("connecting");
400	            conn.closed().await;
401	        });
402
403	        // Joiner side: meet at the derived id, then dial the real address on
404	        // the ceremony ALPN.

    fn key_seed_is_not_the_token() {
        let token = rendezvous_token("home", 100);
        assert_ne!(rendezvous_identity(&token).seed(), token);
    }

    /// Bind a hermetic (loopback, no relay/discovery) endpoint on `alpns`.
    async fn hermetic(id: &Identity, alpns: Vec<Vec<u8>>) -> NetEndpoint {
        NetEndpoint::bind_scoped(
            id,
            RelayPolicy::Disabled,
            LocalDiscovery::Off,
            alpns,
            BindScope::Loopback,
        )
        .await
        .expect("hermetic bind")
    }

    /// The meet listener's derived-id address with its loopback sockets
    /// attached — what relay/discovery resolution would produce, built
    /// explicitly because the hermetic test has neither. The id in the addr
    /// is RE-DERIVED from (name, step), not copied from the listener, so the
    /// dial proves the listener really holds the token-derived key (TLS
    /// authenticates the id).
    fn derived_addr(listener: &RendezvousListener, subnet: &str, step: u64) -> EndpointAddr {
        let pk = rendezvous_endpoint_id(subnet, step);
        let id = EndpointId::from_bytes(&pk.to_bytes()).expect("valid derived id");
        let socks: Vec<SocketAddr> = listener.endpoint().addr().ip_addrs().copied().collect();
        let mut addr = EndpointAddr::new(id);
        for s in socks {
            addr = addr.with_ip_addr(s);
        }
        addr
    }

    // [int->REQ-PAIR-5] the rendezvous meet end to end (hermetic loopback,
    // relays disabled — the relay leg rides the D9a rig): the responder binds
    // the meet listener under the token-derived identity beside its real
    // pairing endpoint; the joiner re-derives the public id from the two user
    // inputs, dials it (TLS proves the listener holds the derived key),
    // receives the responder's REAL address, and lands a ceremony-ALPN
    // connection on the real endpoint — discovery via token, ceremony surface
    // unchanged.
    #[tokio::test]
    async fn meet_redirects_joiner_to_the_real_pairing_endpoint() {
        let subnet = "home";
        let step = 57_037_037u64;

        // The responder's REAL pairing endpoint (its durable identity).
        let resp_id = Identity::generate();
        let real = hermetic(&resp_id, vec![SPT_PAIR_ALPN.to_vec()]).await;
        let real_addr = real.addr();
        let real_pub = resp_id.public_key();

        // The meet listener: derived identity, meet ALPN only.
        let listener = bind_rendezvous(
            subnet,
            step,
            RelayPolicy::Disabled,
            LocalDiscovery::Off,
            BindScope::Loopback,
        )
        .await
        .expect("bind rendezvous");
        let meet_addr = derived_addr(&listener, subnet, step);

        // Responder side: answer one meet, then accept the real-dial.
        let serve = tokio::spawn(async move {
            listener.serve_once(&real_addr).await.expect("serve meet");
            let incoming = real.accept().await.expect("real incoming");
            let conn = incoming
                .accept()
                .expect("accept")
                .await
                .expect("connecting");
            conn.closed().await;
        });

        // Joiner side: meet at the derived id, then dial the real address on
        // the ceremony ALPN.


403	        // Joiner side: meet at the derived id, then dial the real address on
404	        // the ceremony ALPN.
405	        let joiner = hermetic(&Identity::generate(), vec![SPT_PAIR_ALPN.to_vec()]).await;
406	        let fetched = meet_at(&joiner, meet_addr).await.expect("meet");
407	        assert_eq!(
408	            fetched.id.as_bytes(),
409	            &real_pub.to_bytes(),
410	            "the meet reply names the responder's REAL endpoint"
411	        );
412	        let conn = joiner
413	            .connect_with_alpn(fetched, SPT_PAIR_ALPN)
414	            .await
415	            .expect("real dial");
416	        // The ceremony connection authenticates the responder's REAL key —
417	        // what run_initiator's transcript + trust pinning consume.
418	        assert_eq!(conn.remote_id().as_bytes(), &real_pub.to_bytes());
419	        conn.close(0u32.into(), b"done");
420
421	        serve.await.expect("responder task");
422	    }
423
424	    // [unit->REQ-PAIR-5] a meet listener at step N answers a joiner whose
425	    // clock sits one window behind: the joiner's ±1 window dial tries all
426	    // three derived ids, so a one-step skew still routes them together.
427	    // (Hermetic form of the window-subscription contract: the window ids are
428	    // distinct, and the listener's id is among the skewed joiner's three.)
429	    #[test]
430	    fn skewed_window_contains_the_listener_id() {
431	        let listener_id = rendezvous_endpoint_id("home", 100);
432	        for joiner_step in [99u64, 100, 101] {
433	            let window: Vec<PublicKey> = rendezvous_window("home", joiner_step)
434	                .iter()
435	                .map(|t| rendezvous_identity(t).public_key())
436	                .collect();
437	            assert!(

        // Joiner side: meet at the derived id, then dial the real address on
        // the ceremony ALPN.
        let joiner = hermetic(&Identity::generate(), vec![SPT_PAIR_ALPN.to_vec()]).await;
        let fetched = meet_at(&joiner, meet_addr).await.expect("meet");
        assert_eq!(
            fetched.id.as_bytes(),
            &real_pub.to_bytes(),
            "the meet reply names the responder's REAL endpoint"
        );
        let conn = joiner
            .connect_with_alpn(fetched, SPT_PAIR_ALPN)
            .await
            .expect("real dial");
        // The ceremony connection authenticates the responder's REAL key —
        // what run_initiator's transcript + trust pinning consume.
        assert_eq!(conn.remote_id().as_bytes(), &real_pub.to_bytes());
        conn.close(0u32.into(), b"done");

        serve.await.expect("responder task");
    }

    // [unit->REQ-PAIR-5] a meet listener at step N answers a joiner whose
    // clock sits one window behind: the joiner's ±1 window dial tries all
    // three derived ids, so a one-step skew still routes them together.
    // (Hermetic form of the window-subscription contract: the window ids are
    // distinct, and the listener's id is among the skewed joiner's three.)
    #[test]
    fn skewed_window_contains_the_listener_id() {
        let listener_id = rendezvous_endpoint_id("home", 100);
        for joiner_step in [99u64, 100, 101] {
            let window: Vec<PublicKey> = rendezvous_window("home", joiner_step)
                .iter()
                .map(|t| rendezvous_identity(t).public_key())
                .collect();
            assert!(


The rendezvous meet needs real discovery (can't be hermetic), but phase-2 (ceremony against the held addr) is exactly `join_ceremony(Some(addr))` — already exercised by `pairjoin.rs`. Plan: reorder `dispatch_pair_code_submit` so the hold-check precedes the net requirement (cleaner + unit-testable), add a broker unit test for the expired/unknown-session path, and tag the existing e2e test for the phase-2 contract. Checking the broker test harness.


2700	        // the cursor — the next chunk is NOT marked delivered, so resume replays
2701	        // it (nothing skipped). This is the exact mid-stream-socket-death case.
2702	        let frozen = advanced_cursor(c, 2, false);
2703	        assert_eq!(frozen, 2, "failed write of seq 2 -> cursor stays at 2");
2704	        // A later successful write resumes advancing from the frozen point.
2705	        assert_eq!(
2706	            advanced_cursor(frozen, 2, true),
2707	            3,
2708	            "ok re-send of seq 2 -> 3"
2709	        );
2710	        // Amendment 3: monotonic — an attach(sub, 0) rewind re-sends seq 0 (a
2711	        // successful write of an already-delivered seq) but cannot LOWER the
2712	        // cursor (max keeps it forward), preventing a resume-cursor reset.
2713	        assert_eq!(
2714	            advanced_cursor(2, 0, true),
2715	            2,
2716	            "rewind re-send never decreases"
2717	        );
2718	        assert_eq!(
2719	            advanced_cursor(2, 1, true),
2720	            2,
2721	            "boundary re-send never decreases"
2722	        );
2723	    }
2724
2725	    /// The viewer fan-out eviction decision (REQ-HAZARD-VIEWER-ISOLATION): the
2726	    /// drain `try_send`s into each viewer's bounded queue and evicts on a FULL
2727	    /// queue (the viewer fell behind the live stream) or a DISCONNECTED one (its
2728	    /// writer thread ended). It NEVER blocks — a wedged viewer backs up only its
2729	    /// own queue, never the drain thread / controller / child.
2730	    // [unit->REQ-HAZARD-VIEWER-ISOLATION]
2731	    #[test]
2732	    fn viewer_overflow_or_disconnect_evicts_never_blocks() {
2733	        // Depth-1 queue: the first send fits; a second (the viewer hasn't drained)
2734	        // is `Full` → evict.
2735	        let (tx, rx) = sync_channel::<u8>(1);
2736	        tx.try_send(1).expect("first fits");
2737	        assert!(
2738	            viewer_send_evicts(&tx.try_send(2)),
2739	            "a full viewer queue evicts (drain does not block)"
2740	        );
2741	        // Writer thread gone (its `rx` dropped) → `Disconnected` → evict.
2742	        drop(rx);
2743	        assert!(
2744	            viewer_send_evicts(&tx.try_send(3)),
2745	            "a disconnected viewer evicts"
2746	        );
2747	        // A viewer keeping up (room in the queue) is NOT evicted.
2748	        let (tx2, _rx2) = sync_channel::<u8>(1);
2749	        assert!(
2750	            !viewer_send_evicts(&tx2.try_send(9)),
2751	            "a viewer with room stays attached"
2752	        );
2753	    }
2754
2755	    // ── v0.13.0 — REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT: the broker side of the
2756	    //    skip-to-live contract. (1) `viewer_writer` emits a KIND_VIEWER_EVICTED
2757	    //    marker as its FINAL frame ONLY when the sink was evicted for overflow (the
2758	    //    `evicted` flag is true on rx-close), and writes NOTHING extra on a NORMAL
2759	    //    rx-close (a clean teardown — session end / conn drop). (2) `OutputLog::append`
2760	    //    on an overflow eviction SETS the sink's `evicted` flag (so the writer signals)
2761	    //    BEFORE dropping the sink — an undrained viewer ends up FLAGGED, not silently
2762	    //    removed. White-box over a real `controller_socket_pair()` (server SharedSend,
2763	    //    client Stream to read the wire) + a real `sync_channel` rx, exactly like the
2764	    //    existing viewer/controller writer-thread tests. No PTY, no network. ──────────
2765
2766	    /// Read at most ONE more frame off `client` with a bounded wait, returning
2767	    /// `Some(env)` if a frame arrives before `wait` elapses, `None` on a clean EOF
2768	    /// (the writer dropped its send half with nothing further) or the timeout. A
2769	    /// dedicated reader thread + a channel `recv_timeout` so we never block the test
2770	    /// forever (Windows named pipes reject native read deadlines — see
2771	    /// `drain_output_seqs`'s note). Used to PROVE the absence of a trailing marker on
2772	    /// a normal teardown: a normal `viewer_writer` exit drops its send-half clone, the
2773	    /// client read then hits EOF, and `read_frame` returns Err → `None`.
2774	    fn try_read_frame(client: Stream, wait: Duration) -> Option<Envelope> {
2775	        let (tx, rx) = sync_channel::<Option<Envelope>>(1);
2776	        let mut client = client;
2777	        thread::spawn(move || {
2778	            let r = read_frame(&mut client).ok();
2779	            let _ = tx.send(r);

        // the cursor — the next chunk is NOT marked delivered, so resume replays
        // it (nothing skipped). This is the exact mid-stream-socket-death case.
        let frozen = advanced_cursor(c, 2, false);
        assert_eq!(frozen, 2, "failed write of seq 2 -> cursor stays at 2");
        // A later successful write resumes advancing from the frozen point.
        assert_eq!(
            advanced_cursor(frozen, 2, true),
            3,
            "ok re-send of seq 2 -> 3"
        );
        // Amendment 3: monotonic — an attach(sub, 0) rewind re-sends seq 0 (a
        // successful write of an already-delivered seq) but cannot LOWER the
        // cursor (max keeps it forward), preventing a resume-cursor reset.
        assert_eq!(
            advanced_cursor(2, 0, true),
            2,
            "rewind re-send never decreases"
        );
        assert_eq!(
            advanced_cursor(2, 1, true),
            2,
            "boundary re-send never decreases"
        );
    }

    /// The viewer fan-out eviction decision (REQ-HAZARD-VIEWER-ISOLATION): the
    /// drain `try_send`s into each viewer's bounded queue and evicts on a FULL
    /// queue (the viewer fell behind the live stream) or a DISCONNECTED one (its
    /// writer thread ended). It NEVER blocks — a wedged viewer backs up only its
    /// own queue, never the drain thread / controller / child.
    // [unit->REQ-HAZARD-VIEWER-ISOLATION]
    #[test]
    fn viewer_overflow_or_disconnect_evicts_never_blocks() {
        // Depth-1 queue: the first send fits; a second (the viewer hasn't drained)
        // is `Full` → evict.
        let (tx, rx) = sync_channel::<u8>(1);
        tx.try_send(1).expect("first fits");
        assert!(
            viewer_send_evicts(&tx.try_send(2)),
            "a full viewer queue evicts (drain does not block)"
        );
        // Writer thread gone (its `rx` dropped) → `Disconnected` → evict.
        drop(rx);
        assert!(
            viewer_send_evicts(&tx.try_send(3)),
            "a disconnected viewer evicts"
        );
        // A viewer keeping up (room in the queue) is NOT evicted.
        let (tx2, _rx2) = sync_channel::<u8>(1);
        assert!(
            !viewer_send_evicts(&tx2.try_send(9)),
            "a viewer with room stays attached"
        );
    }

    // ── v0.13.0 — REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT: the broker side of the
    //    skip-to-live contract. (1) `viewer_writer` emits a KIND_VIEWER_EVICTED
    //    marker as its FINAL frame ONLY when the sink was evicted for overflow (the
    //    `evicted` flag is true on rx-close), and writes NOTHING extra on a NORMAL
    //    rx-close (a clean teardown — session end / conn drop). (2) `OutputLog::append`
    //    on an overflow eviction SETS the sink's `evicted` flag (so the writer signals)
    //    BEFORE dropping the sink — an undrained viewer ends up FLAGGED, not silently
    //    removed. White-box over a real `controller_socket_pair()` (server SharedSend,
    //    client Stream to read the wire) + a real `sync_channel` rx, exactly like the
    //    existing viewer/controller writer-thread tests. No PTY, no network. ──────────

    /// Read at most ONE more frame off `client` with a bounded wait, returning
    /// `Some(env)` if a frame arrives before `wait` elapses, `None` on a clean EOF
    /// (the writer dropped its send half with nothing further) or the timeout. A
    /// dedicated reader thread + a channel `recv_timeout` so we never block the test
    /// forever (Windows named pipes reject native read deadlines — see
    /// `drain_output_seqs`'s note). Used to PROVE the absence of a trailing marker on
    /// a normal teardown: a normal `viewer_writer` exit drops its send-half clone, the
    /// client read then hits EOF, and `read_frame` returns Err → `None`.
    fn try_read_frame(client: Stream, wait: Duration) -> Option<Envelope> {
        let (tx, rx) = sync_channel::<Option<Envelope>>(1);
        let mut client = client;
        thread::spawn(move || {
            let r = read_frame(&mut client).ok();
            let _ = tx.send(r);


W2 impl is wired and compiles. For tests: reorder `dispatch_pair_code_submit` (hold-check before net requirement — an expired meet shouldn't need net), and extract `fail_keeps_hold`.


Edit

    fn dispatch_pair_code_submit(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {
        let req: PairCodeSubmit = serde_json::from_value(env.payload)
            .map_err(|e| format!("bad pair-code-submit payload: {e}"))?;
        let host = self
            .net
            .get()
            .ok_or("net disabled: this broker was bound without a network host")?;
        // Read the held address, enforcing the wait-for-code timeout. A missing or
        // expired hold is a clean failure — the CLI re-runs the meet.
        let held = {
            let mut holds = self.pair_holds.lock().unwrap();
            match holds.get(&req.session_id) {
                Some(h) if h.deadline > Instant::now() => Some((h.subnet.clone(), h.addr.clone())),
                Some(_) => {
                    holds.remove(&req.session_id);
                    None
                }
                None => None,
            }
        };
        let Some((subnet, addr)) = held else {

    fn dispatch_pair_code_submit(&self, env: Envelope, send: &SharedSend) -> Result<(), String> {
        let req: PairCodeSubmit = serde_json::from_value(env.payload)
            .map_err(|e| format!("bad pair-code-submit payload: {e}"))?;
        // Read the held address FIRST, enforcing the wait-for-code timeout — a
        // missing or expired hold is a clean failure that needs no net (the CLI
        // re-runs the meet).
        let held = {
            let mut holds = self.pair_holds.lock().unwrap();
            match holds.get(&req.session_id) {
                Some(h) if hold_is_live(h.deadline, Instant::now()) => {
                    Some((h.subnet.clone(), h.addr.clone()))
                }
                Some(_) => {
                    holds.remove(&req.session_id);
                    None
                }
                None => None,
            }
        };
        let Some((subnet, addr)) = held else {