/// One live QUIC stream the broker holds: the send half (brain writes ride it
/// over IPC) + the sequenced read log.

/// A bounded, sequenced log of connection liveness events — the broker-owned
/// PresenceChannel seam (D4c, REQ-EP-4): the StreamLog ring/cursor discipline
/// applied to the conn table itself. `register_conn` appends `connected`; each
/// connection's closed-watcher appends `disconnected` after removing the row.
/// A brain subscribes with a cursor and replays events produced while it was
/// down, so presence survives a brain restart exactly like stream reads do.
// [impl->REQ-EP-4]
struct PresenceLog {
    next_seq: u64,
    ring: VecDeque<NetPresenceEvent>,
    cap_events: usize,
    subscriber: Option<SharedSend>,
}

impl PresenceLog {
    fn new(cap_events: usize) -> Self {
        PresenceLog { next_seq: 0, ring: VecDeque::new(), cap_events, subscriber: None }
    }

    /// Append a liveness event: assign the next `seq`, retain it in the bounded
    /// ring, live-send to the current subscriber. Producers: `register_conn`
    /// (connected) and the per-conn closed-watchers (disconnected) — every
    /// append serializes on the enclosing `Mutex`, so subscriber frames arrive
    /// in strict `seq` order.
    fn append(&mut self, kind: &str, conn_id: u64, remote_id_hex: &str) {
        let ev = NetPresenceEvent {
            seq: self.next_seq,
            kind: kind.to_string(),
            conn_id,
            remote_id_hex: remote_id_hex.to_string(),
        };
        self.next_seq += 1;
        self.ring.push_back(ev.clone());
        while self.ring.len() > self.cap_events {
            self.ring.pop_front();
        }
        if let Some(sub) = &self.subscriber {
            let frame = net_presence_event_envelope(&ev);
            if let Ok(mut s) = sub.lock() {
                let _ = write_frame(&mut *s, &frame);
            }
        }
    }

    /// Attach `sub` as the subscriber, replaying every retained event with
    /// `seq >= from_seq` before live streaming (the gapless-resume attach).
    fn attach(&mut self, sub: SharedSend, from_seq: u64) {
        self.subscriber = Some(Arc::clone(&sub));
        for ev in self.ring.iter().filter(|e| e.seq >= from_seq) {
            let frame = net_presence_event_envelope(ev);
            if let Ok(mut s) = sub.lock() {
                let _ = write_frame(&mut *s, &frame);
            }
        }
    }

    /// Detach `sub` iff it is the current subscriber (a late disconnect must
    /// not unsubscribe a newer brain).
    fn detach_if(&mut self, sub: &SharedSend) {
        if let Some(cur) = &self.subscriber {
            if Arc::ptr_eq(cur, sub) {
                self.subscriber = None;
            }
        }
    }
}

/// One live QUIC stream the broker holds: the send half (brain writes ride it
/// over IPC) + the sequenced read log.


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

/// A bounded, sequenced log of connection liveness events — the broker-owned
/// PresenceChannel seam (D4c, REQ-EP-4): the StreamLog ring/cursor discipline
/// applied to the conn table itself. `register_conn` appends `connected`; each
/// connection's closed-watcher appends `disconnected` after removing the row.
/// A brain subscribes with a cursor and replays events produced while it was
/// down, so presence survives a brain restart exactly like stream reads do.
// [impl->REQ-EP-4]
struct PresenceLog {
    next_seq: u64,
    ring: VecDeque<NetPresenceEvent>,
    cap_events: usize,
    subscriber: Option<SharedSend>,
}

impl PresenceLog {
    fn new(cap_events: usize) -> Self {
        PresenceLog { next_seq: 0, ring: VecDeque::new(), cap_events, subscriber: None }
    }

    /// Append a liveness event: assign the next `seq`, retain it in the bounded
    /// ring, live-send to the current subscriber. Producers: `register_conn`
    /// (connected) and the per-conn closed-watchers (disconnected) — every
    /// append serializes on the enclosing `Mutex`, so subscriber frames arrive
    /// in strict `seq` order.
    fn append(&mut self, kind: &str, conn_id: u64, remote_id_hex: &str) {
        let ev = NetPresenceEvent {
            seq: self.next_seq,
            kind: kind.to_string(),
            conn_id,
            remote_id_hex: remote_id_hex.to_string(),
        };
        self.next_seq += 1;
        self.ring.push_back(ev.clone());
        while self.ring.len() > self.cap_events {
            self.ring.pop_front();
        }
        if let Some(sub) = &self.subscriber {
            let frame = net_presence_event_envelope(&ev);
            if let Ok(mut s) = sub.lock() {
                let _ = write_frame(&mut *s, &frame);
            }
        }
    }

    /// Attach `sub` as the subscriber, replaying every retained event with
    /// `seq >= from_seq` before live streaming (the gapless-resume attach).
    fn attach(&mut self, sub: SharedSend, from_seq: u64) {
        self.subscriber = Some(Arc::clone(&sub));
        for ev in self.ring.iter().filter(|e| e.seq >= from_seq) {
            let frame = net_presence_event_envelope(ev);
            if let Ok(mut s) = sub.lock() {
                let _ = write_frame(&mut *s, &frame);
            }
        }
    }

    /// Detach `sub` iff it is the current subscriber (a late disconnect must
    /// not unsubscribe a newer brain).
    fn detach_if(&mut self, sub: &SharedSend) {
        if let Some(cur) = &self.subscriber {
            if Arc::ptr_eq(cur, sub) {
                self.subscriber = None;
            }
        }
    }
}

/// One live QUIC stream the broker holds: the send half (brain writes ride it
/// over IPC) + the sequenced read log.

+/// A bounded, sequenced log of connection liveness events — the broker-owned

+/// PresenceChannel seam (D4c, REQ-EP-4): the StreamLog ring/cursor discipline

+/// applied to the conn table itself. `register_conn` appends `connected`; each

+/// connection's closed-watcher appends `disconnected` after removing the row.

+/// A brain subscribes with a cursor and replays events produced while it was

+/// down, so presence survives a brain restart exactly like stream reads do.

+    fn new(cap_events: usize) -> Self {

+        PresenceLog { next_seq: 0, ring: VecDeque::new(), cap_events, subscriber: None }

+    /// Append a liveness event: assign the next `seq`, retain it in the bounded

+    /// ring, live-send to the current subscriber. Producers: `register_conn`

+    /// (connected) and the per-conn closed-watchers (disconnected) — every

+    /// append serializes on the enclosing `Mutex`, so subscriber frames arrive

+    fn append(&mut self, kind: &str, conn_id: u64, remote_id_hex: &str) {

+            remote_id_hex: remote_id_hex.to_string(),

+        self.ring.push_back(ev.clone());

+        while self.ring.len() > self.cap_events {

+        if let Some(sub) = &self.subscriber {

+            let frame = net_presence_event_envelope(&ev);

+            if let Ok(mut s) = sub.lock() {

+                let _ = write_frame(&mut *s, &frame);

+    /// Attach `sub` as the subscriber, replaying every retained event with

+    /// `seq >= from_seq` before live streaming (the gapless-resume attach).

+    fn attach(&mut self, sub: SharedSend, from_seq: u64) {

+        self.subscriber = Some(Arc::clone(&sub));

+        for ev in self.ring.iter().filter(|e| e.seq >= from_seq) {

+            let frame = net_presence_event_envelope(ev);

+    /// Detach `sub` iff it is the current subscriber (a late disconnect must

+    /// not unsubscribe a newer brain).

+    fn detach_if(&mut self, sub: &SharedSend) {

+        if let Some(cur) = &self.subscriber {

+                self.subscriber = None;


Edit

struct NetShared {
    next_conn_id: AtomicU64,
    conns: Mutex<HashMap<u64, ConnEntry>>,
    next_stream_id: AtomicU64,
    streams: Mutex<HashMap<u64, Arc<StreamEntry>>>,
    ring_cap: usize,
}

impl NetShared {
    fn new() -> Self {
        NetShared {
            next_conn_id: AtomicU64::new(1),
            conns: Mutex::new(HashMap::new()),
            next_stream_id: AtomicU64::new(1),
            streams: Mutex::new(HashMap::new()),
            ring_cap: DEFAULT_STREAM_RING_CHUNKS,
        }
    }
}

struct NetShared {
    next_conn_id: AtomicU64,
    conns: Mutex<HashMap<u64, ConnEntry>>,
    next_stream_id: AtomicU64,
    streams: Mutex<HashMap<u64, Arc<StreamEntry>>>,
    ring_cap: usize,
    /// The conn-liveness event log (D4c) — one per host, all conns interleaved.
    presence: Mutex<PresenceLog>,
}

impl NetShared {
    fn new() -> Self {
        NetShared {
            next_conn_id: AtomicU64::new(1),
            conns: Mutex::new(HashMap::new()),
            next_stream_id: AtomicU64::new(1),
            streams: Mutex::new(HashMap::new()),
            ring_cap: DEFAULT_STREAM_RING_CHUNKS,
            presence: Mutex::new(PresenceLog::new(DEFAULT_STREAM_RING_CHUNKS)),
        }
    }
}


struct NetShared {
    next_conn_id: AtomicU64,
    conns: Mutex<HashMap<u64, ConnEntry>>,
    next_stream_id: AtomicU64,
    streams: Mutex<HashMap<u64, Arc<StreamEntry>>>,
    ring_cap: usize,
}

impl NetShared {
    fn new() -> Self {
        NetShared {
            next_conn_id: AtomicU64::new(1),
            conns: Mutex::new(HashMap::new()),
            next_stream_id: AtomicU64::new(1),
            streams: Mutex::new(HashMap::new()),
            ring_cap: DEFAULT_STREAM_RING_CHUNKS,
        }
    }
}

struct NetShared {
    next_conn_id: AtomicU64,
    conns: Mutex<HashMap<u64, ConnEntry>>,
    next_stream_id: AtomicU64,
    streams: Mutex<HashMap<u64, Arc<StreamEntry>>>,
    ring_cap: usize,
    /// The conn-liveness event log (D4c) — one per host, all conns interleaved.
    presence: Mutex<PresenceLog>,
}

impl NetShared {
    fn new() -> Self {
        NetShared {
            next_conn_id: AtomicU64::new(1),
            conns: Mutex::new(HashMap::new()),
            next_stream_id: AtomicU64::new(1),
            streams: Mutex::new(HashMap::new()),
            ring_cap: DEFAULT_STREAM_RING_CHUNKS,
            presence: Mutex::new(PresenceLog::new(DEFAULT_STREAM_RING_CHUNKS)),
        }
    }
}

+    /// The conn-liveness event log (D4c) — one per host, all conns interleaved.

+            presence: Mutex::new(PresenceLog::new(DEFAULT_STREAM_RING_CHUNKS)),


    let id = shared.next_conn_id.fetch_add(1, Ordering::Relaxed);
    shared
        .conns
        .lock()
        .unwrap()
        .insert(id, ConnEntry { conn: conn.clone(), remote_id_hex: remote_id_hex.clone() });

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

    (id, remote_id_hex)

    let id = shared.next_conn_id.fetch_add(1, Ordering::Relaxed);
    shared
        .conns
        .lock()
        .unwrap()
        .insert(id, ConnEntry { conn: conn.clone(), remote_id_hex: remote_id_hex.clone() });

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

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

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

    (id, remote_id_hex)


    let id = shared.next_conn_id.fetch_add(1, Ordering::Relaxed);
    shared
        .conns
        .lock()
        .unwrap()
        .insert(id, ConnEntry { conn: conn.clone(), remote_id_hex: remote_id_hex.clone() });

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

    (id, remote_id_hex)

    let id = shared.next_conn_id.fetch_add(1, Ordering::Relaxed);
    shared
        .conns
        .lock()
        .unwrap()
        .insert(id, ConnEntry { conn: conn.clone(), remote_id_hex: remote_id_hex.clone() });

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

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

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

    (id, remote_id_hex)

+    // Presence: announce the row BEFORE spawning the closed-watcher, so the

+    // `connected` event can never race behind its own `disconnected` (D4c).

+    shared.presence.lock().unwrap().append(PRESENCE_CONNECTED, id, &remote_id_hex);

+    // Closed-watcher: the row's exit path. Awaits the connection's end (clean

+    // close, conn loss, or our own endpoint close), removes the row — conns

+    // finally LEAVE the table — then appends the `disconnected` event (row

+    // gone before the event is observable).

+    let hex_close = remote_id_hex.clone();

+        sh_close.conns.lock().unwrap().remove(&id);

+        sh_close.presence.lock().unwrap().append(PRESENCE_DISCONNECTED, id, &hex_close);


    /// Detach `sub` from a stream iff it is the current subscriber (brain
    /// disconnect cleanup; the stream + its ring persist).
    pub(crate) fn detach_stream_sub(&self, stream_id: u64, sub: &SharedSend) {
        let log = {
            let streams = self.shared.streams.lock().unwrap();
            streams.get(&stream_id).map(|e| Arc::clone(&e.log))
        };
        if let Some(log) = log {
            log.lock().unwrap().detach_if(sub);
        }
    }
}

    /// Detach `sub` from a stream iff it is the current subscriber (brain
    /// disconnect cleanup; the stream + its ring persist).
    pub(crate) fn detach_stream_sub(&self, stream_id: u64, sub: &SharedSend) {
        let log = {
            let streams = self.shared.streams.lock().unwrap();
            streams.get(&stream_id).map(|e| Arc::clone(&e.log))
        };
        if let Some(log) = log {
            log.lock().unwrap().detach_if(sub);
        }
    }

    /// Attach `sub` as the presence subscriber, replaying liveness events from
    /// `from_seq` (D4c) — the stream `subscribe` discipline on the conn table.
    // [impl->REQ-EP-4]
    pub(crate) fn subscribe_presence(&self, sub: SharedSend, from_seq: u64) {
        self.shared.presence.lock().unwrap().attach(sub, from_seq);
    }

    /// Detach `sub` from presence iff it is the current subscriber (brain
    /// disconnect cleanup; the log + its ring persist).
    pub(crate) fn detach_presence_sub(&self, sub: &SharedSend) {
        self.shared.presence.lock().unwrap().detach_if(sub);
    }

    /// Gracefully close the endpoint: every held connection flushes its QUIC
    /// CLOSE so peers' closed-watchers fire promptly (a bare host drop kills
    /// the runtime first and leaves peers to an idle timeout).
    pub fn close(&self) {
        let endpoint = Arc::clone(&self.endpoint);
        self.runtime.handle().block_on(async move {
            endpoint.close().await;
        });
    }
}


    /// Detach `sub` from a stream iff it is the current subscriber (brain
    /// disconnect cleanup; the stream + its ring persist).
    pub(crate) fn detach_stream_sub(&self, stream_id: u64, sub: &SharedSend) {
        let log = {
            let streams = self.shared.streams.lock().unwrap();
            streams.get(&stream_id).map(|e| Arc::clone(&e.log))
        };
        if let Some(log) = log {
            log.lock().unwrap().detach_if(sub);
        }
    }
}

    /// Detach `sub` from a stream iff it is the current subscriber (brain
    /// disconnect cleanup; the stream + its ring persist).
    pub(crate) fn detach_stream_sub(&self, stream_id: u64, sub: &SharedSend) {
        let log = {
            let streams = self.shared.streams.lock().unwrap();
            streams.get(&stream_id).map(|e| Arc::clone(&e.log))
        };
        if let Some(log) = log {
            log.lock().unwrap().detach_if(sub);
        }
    }

    /// Attach `sub` as the presence subscriber, replaying liveness events from
    /// `from_seq` (D4c) — the stream `subscribe` discipline on the conn table.
    // [impl->REQ-EP-4]
    pub(crate) fn subscribe_presence(&self, sub: SharedSend, from_seq: u64) {
        self.shared.presence.lock().unwrap().attach(sub, from_seq);
    }

    /// Detach `sub` from presence iff it is the current subscriber (brain
    /// disconnect cleanup; the log + its ring persist).
    pub(crate) fn detach_presence_sub(&self, sub: &SharedSend) {
        self.shared.presence.lock().unwrap().detach_if(sub);
    }

    /// Gracefully close the endpoint: every held connection flushes its QUIC
    /// CLOSE so peers' closed-watchers fire promptly (a bare host drop kills
    /// the runtime first and leaves peers to an idle timeout).
    pub fn close(&self) {
        let endpoint = Arc::clone(&self.endpoint);
        self.runtime.handle().block_on(async move {
            endpoint.close().await;
        });
    }
}

+    /// Attach `sub` as the presence subscriber, replaying liveness events from

+    /// `from_seq` (D4c) — the stream `subscribe` discipline on the conn table.

+    pub(crate) fn subscribe_presence(&self, sub: SharedSend, from_seq: u64) {

+        self.shared.presence.lock().unwrap().attach(sub, from_seq);

+    /// Detach `sub` from presence iff it is the current subscriber (brain

+    /// disconnect cleanup; the log + its ring persist).

+    pub(crate) fn detach_presence_sub(&self, sub: &SharedSend) {

+        self.shared.presence.lock().unwrap().detach_if(sub);

+    /// Gracefully close the endpoint: every held connection flushes its QUIC

+    /// CLOSE so peers' closed-watchers fire promptly (a bare host drop kills

+    /// the runtime first and leaves peers to an idle timeout).

+        let endpoint = Arc::clone(&self.endpoint);

+        self.runtime.handle().block_on(async move {


Updated task #1 status


Updated task #2 status


Now msg.rs — presence vocabulary:


/// Broker→brain: a stream's read side finished cleanly; `next_seq` tells the
/// brain the final cursor so it can verify it missed nothing.
pub const KIND_NET_STREAM_EOF: &str = "net-stream-eof";

/// Broker→brain: a stream's read side finished cleanly; `next_seq` tells the
/// brain the final cursor so it can verify it missed nothing.
pub const KIND_NET_STREAM_EOF: &str = "net-stream-eof";
/// Brain→broker: (re)subscribe to the broker's conn-liveness event log from a
/// cursor (D4c) — the PresenceChannel broker seam. Same gapless-resume
/// discipline as a stream subscribe.
// [impl->REQ-EP-4]
pub const KIND_NET_PRESENCE_SUBSCRIBE: &str = "net-presence-subscribe";
/// Broker→brain: one sequenced conn-liveness event (live or replayed).
pub const KIND_NET_PRESENCE_EVENT: &str = "net-presence-event";

/// [`NetPresenceEvent::kind`]: a connection joined the broker's conn table.
pub const PRESENCE_CONNECTED: &str = "connected";
/// [`NetPresenceEvent::kind`]: a connection ended and left the conn table.
pub const PRESENCE_DISCONNECTED: &str = "disconnected";


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

/// Broker→brain: a stream's read side finished cleanly; `next_seq` tells the
/// brain the final cursor so it can verify it missed nothing.
pub const KIND_NET_STREAM_EOF: &str = "net-stream-eof";
/// Brain→broker: (re)subscribe to the broker's conn-liveness event log from a
/// cursor (D4c) — the PresenceChannel broker seam. Same gapless-resume
/// discipline as a stream subscribe.
// [impl->REQ-EP-4]
pub const KIND_NET_PRESENCE_SUBSCRIBE: &str = "net-presence-subscribe";
/// Broker→brain: one sequenced conn-liveness event (live or replayed).
pub const KIND_NET_PRESENCE_EVENT: &str = "net-presence-event";

/// [`NetPresenceEvent::kind`]: a connection joined the broker's conn table.
pub const PRESENCE_CONNECTED: &str = "connected";
/// [`NetPresenceEvent::kind`]: a connection ended and left the conn table.
pub const PRESENCE_DISCONNECTED: &str = "disconnected";

+/// Brain→broker: (re)subscribe to the broker's conn-liveness event log from a

+/// cursor (D4c) — the PresenceChannel broker seam. Same gapless-resume

+pub const KIND_NET_PRESENCE_SUBSCRIBE: &str = "net-presence-subscribe";

+/// Broker→brain: one sequenced conn-liveness event (live or replayed).

+pub const KIND_NET_PRESENCE_EVENT: &str = "net-presence-event";

+/// [`NetPresenceEvent::kind`]: a connection joined the broker's conn table.

+pub const PRESENCE_CONNECTED: &str = "connected";

+/// [`NetPresenceEvent::kind`]: a connection ended and left the conn table.

+pub const PRESENCE_DISCONNECTED: &str = "disconnected";

/// `spawn` payload — the program + args + initial geometry. The broker spawns

/// it under a PTY (no shell interpretation; argv is passed through).

#[derive(Debug, Clone, Serialize, Deserialize)]


/// `net-stream-eof` event — a stream's read side finished cleanly.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetStreamEof {
    /// The finished stream.
    pub stream_id: u64,
    /// One past the final chunk's seq — the subscriber's completeness check.
    pub next_seq: u64,
}

/// `net-stream-eof` event — a stream's read side finished cleanly.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetStreamEof {
    /// The finished stream.
    pub stream_id: u64,
    /// One past the final chunk's seq — the subscriber's completeness check.
    pub next_seq: u64,
}

/// `net-presence-subscribe` payload — attach this connection as the presence
/// subscriber, replaying buffered liveness events with `seq >= from_seq` (D4c).
// [impl->REQ-EP-4]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetPresenceSubscribeReq {
    /// Lowest presence sequence the subscriber still needs (replay floor).
    pub from_seq: u64,
}

/// `net-presence-event` — one sequenced conn-liveness event (D4c).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetPresenceEvent {
    /// The broker-assigned monotonic presence sequence (the resume cursor).
    pub seq: u64,
    /// [`PRESENCE_CONNECTED`] or [`PRESENCE_DISCONNECTED`]. A string (not an
    /// enum) so an older brain tolerates kinds a newer broker may add (2.3).
    pub kind: String,
    /// The broker-minted id of the connection the event concerns.
    pub conn_id: u64,
    /// The remote node's stable name (hex pubkey).
    pub remote_id_hex: String,
}


/// `net-stream-eof` event — a stream's read side finished cleanly.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetStreamEof {
    /// The finished stream.
    pub stream_id: u64,
    /// One past the final chunk's seq — the subscriber's completeness check.
    pub next_seq: u64,
}

/// `net-presence-subscribe` payload — attach this connection as the presence
/// subscriber, replaying buffered liveness events with `seq >= from_seq` (D4c).
// [impl->REQ-EP-4]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetPresenceSubscribeReq {
    /// Lowest presence sequence the subscriber still needs (replay floor).
    pub from_seq: u64,
}

/// `net-presence-event` — one sequenced conn-liveness event (D4c).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetPresenceEvent {
    /// The broker-assigned monotonic presence sequence (the resume cursor).
    pub seq: u64,
    /// [`PRESENCE_CONNECTED`] or [`PRESENCE_DISCONNECTED`]. A string (not an
    /// enum) so an older brain tolerates kinds a newer broker may add (2.3).
    pub kind: String,
    /// The broker-minted id of the connection the event concerns.
    pub conn_id: u64,
    /// The remote node's stable name (hex pubkey).
    pub remote_id_hex: String,
}

+/// `net-presence-subscribe` payload — attach this connection as the presence

+/// subscriber, replaying buffered liveness events with `seq >= from_seq` (D4c).

+#[derive(Debug, Clone, Serialize, Deserialize)]

+    /// Lowest presence sequence the subscriber still needs (replay floor).

+/// `net-presence-event` — one sequenced conn-liveness event (D4c).

+    /// The broker-assigned monotonic presence sequence (the resume cursor).

+    /// [`PRESENCE_CONNECTED`] or [`PRESENCE_DISCONNECTED`]. A string (not an

+    /// enum) so an older brain tolerates kinds a newer broker may add (2.3).

+    /// The broker-minted id of the connection the event concerns.

+    /// The remote node's stable name (hex pubkey).

/// Encode raw bytes for an `input`/`output` payload.

pub fn encode_bytes(bytes: &[u8]) -> String {


/// Build a `net-stream-eof` event envelope (D4b).
pub fn net_stream_eof_envelope(stream_id: u64, next_seq: u64) -> Envelope {
    Envelope::new(
        KIND_NET_STREAM_EOF,
        serde_json::to_value(NetStreamEof { stream_id, next_seq })
            .expect("NetStreamEof serializes"),
    )
}

/// Build a `net-stream-eof` event envelope (D4b).
pub fn net_stream_eof_envelope(stream_id: u64, next_seq: u64) -> Envelope {
    Envelope::new(
        KIND_NET_STREAM_EOF,
        serde_json::to_value(NetStreamEof { stream_id, next_seq })
            .expect("NetStreamEof serializes"),
    )
}

/// Build a `net-presence-event` envelope (D4c) — the presence sibling of
/// [`net_stream_data_envelope`].
// [impl->REQ-EP-4]
pub fn net_presence_event_envelope(ev: &NetPresenceEvent) -> Envelope {
    Envelope::new(
        KIND_NET_PRESENCE_EVENT,
        serde_json::to_value(ev).expect("NetPresenceEvent serializes"),
    )
}


/// Build a `net-stream-eof` event envelope (D4b).
pub fn net_stream_eof_envelope(stream_id: u64, next_seq: u64) -> Envelope {
    Envelope::new(
        KIND_NET_STREAM_EOF,
        serde_json::to_value(NetStreamEof { stream_id, next_seq })
            .expect("NetStreamEof serializes"),
    )
}

/// Build a `net-presence-event` envelope (D4c) — the presence sibling of
/// [`net_stream_data_envelope`].
// [impl->REQ-EP-4]
pub fn net_presence_event_envelope(ev: &NetPresenceEvent) -> Envelope {
    Envelope::new(
        KIND_NET_PRESENCE_EVENT,
        serde_json::to_value(ev).expect("NetPresenceEvent serializes"),
    )
}

+/// Build a `net-presence-event` envelope (D4c) — the presence sibling of

+pub fn net_presence_event_envelope(ev: &NetPresenceEvent) -> Envelope {

+        serde_json::to_value(ev).expect("NetPresenceEvent serializes"),

/// Build a `net-dialed` ack envelope (D4a); `applied_now` distinguishes a fresh

/// dial from a deduped replay of the same durable op id.


// [unit->REQ-DAEMON-2] the output-envelope helper produces a decodable event.

    // [unit->REQ-EP-4] a presence event round-trips through its envelope helper,
    // and a future kind string decodes (no enum lock-in — 2.3 forward-compat).
    #[test]
    fn presence_event_round_trips_and_tolerates_future_kind() {
        let ev = NetPresenceEvent {
            seq: 4,
            kind: PRESENCE_DISCONNECTED.to_string(),
            conn_id: 2,
            remote_id_hex: "cafe".into(),
        };
        let env = net_presence_event_envelope(&ev);
        assert_eq!(env.kind, KIND_NET_PRESENCE_EVENT);
        let back: NetPresenceEvent = serde_json::from_value(env.payload).unwrap();
        assert_eq!((back.seq, back.conn_id), (4, 2));
        assert_eq!(back.kind, PRESENCE_DISCONNECTED);
        assert_eq!(back.remote_id_hex, "cafe");

        let wire = json!({
            "seq": 9, "kind": "throttled-at-d9", "conn_id": 1, "remote_id_hex": "ab",
            "field_added_later": true
        });
        let future: NetPresenceEvent = serde_json::from_value(wire).unwrap();
        assert_eq!(future.kind, "throttled-at-d9");
    }

    // [unit->REQ-DAEMON-2] the output-envelope helper produces a decodable event.


    // [unit->REQ-EP-4] a presence event round-trips through its envelope helper,
    // and a future kind string decodes (no enum lock-in — 2.3 forward-compat).
    #[test]
    fn presence_event_round_trips_and_tolerates_future_kind() {
        let ev = NetPresenceEvent {
            seq: 4,
            kind: PRESENCE_DISCONNECTED.to_string(),
            conn_id: 2,
            remote_id_hex: "cafe".into(),
        };
        let env = net_presence_event_envelope(&ev);
        assert_eq!(env.kind, KIND_NET_PRESENCE_EVENT);
        let back: NetPresenceEvent = serde_json::from_value(env.payload).unwrap();
        assert_eq!((back.seq, back.conn_id), (4, 2));
        assert_eq!(back.kind, PRESENCE_DISCONNECTED);
        assert_eq!(back.remote_id_hex, "cafe");

        let wire = json!({
            "seq": 9, "kind": "throttled-at-d9", "conn_id": 1, "remote_id_hex": "ab",
            "field_added_later": true
        });
        let future: NetPresenceEvent = serde_json::from_value(wire).unwrap();
        assert_eq!(future.kind, "throttled-at-d9");
    }

    // [unit->REQ-DAEMON-2] the output-envelope helper produces a decodable event.

+    // [unit->REQ-EP-4] a presence event round-trips through its envelope helper,

+    // and a future kind string decodes (no enum lock-in — 2.3 forward-compat).

+    fn presence_event_round_trips_and_tolerates_future_kind() {

+            kind: PRESENCE_DISCONNECTED.to_string(),

+            remote_id_hex: "cafe".into(),

+        let env = net_presence_event_envelope(&ev);

+        assert_eq!(env.kind, KIND_NET_PRESENCE_EVENT);

+        let back: NetPresenceEvent = serde_json::from_value(env.payload).unwrap();

+        assert_eq!((back.seq, back.conn_id), (4, 2));

+        assert_eq!(back.kind, PRESENCE_DISCONNECTED);

+        assert_eq!(back.remote_id_hex, "cafe");

+            "seq": 9, "kind": "throttled-at-d9", "conn_id": 1, "remote_id_hex": "ab",

+        let future: NetPresenceEvent = serde_json::from_value(wire).unwrap();

+        assert_eq!(future.kind, "throttled-at-d9");


use crate::msg::{
    applied_envelope, decode_bytes, net_dialed_envelope, output_envelope, ErrorEvent, ExitEvent,
    InputReq, KillReq, NetDialReq, NetSent, NetStatusReply, NetStreamOpenReq, NetStreamOpened,
    NetStreamSendReq, NetStreamSubscribeReq, NetStreamsReply, ResizeReq, SpawnReq, Spawned,
    SubscribeReq, KIND_ERROR, KIND_EXIT, KIND_INPUT, KIND_KILL, KIND_NET_DIAL, KIND_NET_SENT,
    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_RESIZE, KIND_SPAWN, KIND_SPAWNED, KIND_SUBSCRIBE,
};

use crate::msg::{
    applied_envelope, decode_bytes, net_dialed_envelope, output_envelope, ErrorEvent, ExitEvent,
    InputReq, KillReq, NetDialReq, NetPresenceSubscribeReq, NetSent, NetStatusReply,
    NetStreamOpenReq, NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq, NetStreamsReply,
    ResizeReq, SpawnReq, Spawned, SubscribeReq, KIND_ERROR, KIND_EXIT, KIND_INPUT, KIND_KILL,
    KIND_NET_DIAL, KIND_NET_PRESENCE_SUBSCRIBE, KIND_NET_SENT, 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_RESIZE,
    KIND_SPAWN, KIND_SPAWNED, KIND_SUBSCRIBE,
};


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)

use crate::msg::{
    applied_envelope, decode_bytes, net_dialed_envelope, output_envelope, ErrorEvent, ExitEvent,
    InputReq, KillReq, NetDialReq, NetSent, NetStatusReply, NetStreamOpenReq, NetStreamOpened,
    NetStreamSendReq, NetStreamSubscribeReq, NetStreamsReply, ResizeReq, SpawnReq, Spawned,
    SubscribeReq, KIND_ERROR, KIND_EXIT, KIND_INPUT, KIND_KILL, KIND_NET_DIAL, KIND_NET_SENT,
    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_RESIZE, KIND_SPAWN, KIND_SPAWNED, KIND_SUBSCRIBE,
};

use crate::msg::{
    applied_envelope, decode_bytes, net_dialed_envelope, output_envelope, ErrorEvent, ExitEvent,
    InputReq, KillReq, NetDialReq, NetPresenceSubscribeReq, NetSent, NetStatusReply,
    NetStreamOpenReq, NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq, NetStreamsReply,
    ResizeReq, SpawnReq, Spawned, SubscribeReq, KIND_ERROR, KIND_EXIT, KIND_INPUT, KIND_KILL,
    KIND_NET_DIAL, KIND_NET_PRESENCE_SUBSCRIBE, KIND_NET_SENT, 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_RESIZE,
    KIND_SPAWN, KIND_SPAWNED, KIND_SUBSCRIBE,
};

-    InputReq, KillReq, NetDialReq, NetSent, NetStatusReply, NetStreamOpenReq, NetStreamOpened,

-    NetStreamSendReq, NetStreamSubscribeReq, NetStreamsReply, ResizeReq, SpawnReq, Spawned,

-    SubscribeReq, KIND_ERROR, KIND_EXIT, KIND_INPUT, KIND_KILL, KIND_NET_DIAL, KIND_NET_SENT,

-    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_RESIZE, KIND_SPAWN, KIND_SPAWNED, KIND_SUBSCRIBE,

+    InputReq, KillReq, NetDialReq, NetPresenceSubscribeReq, NetSent, NetStatusReply,

+    NetStreamOpenReq, NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq, NetStreamsReply,

+    ResizeReq, SpawnReq, Spawned, SubscribeReq, KIND_ERROR, KIND_EXIT, KIND_INPUT, KIND_KILL,

+    KIND_NET_DIAL, KIND_NET_PRESENCE_SUBSCRIBE, KIND_NET_SENT, 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_RESIZE,

+    KIND_SPAWN, KIND_SPAWNED, KIND_SUBSCRIBE,

use crate::transport::{recv_hello, DaemonTransport, LocalSocketTransport};


        let mut my_subs: Vec<u64> = Vec::new();
        let mut my_stream_subs: Vec<u64> = Vec::new();

        let mut my_subs: Vec<u64> = Vec::new();
        let mut my_stream_subs: Vec<u64> = Vec::new();
        let mut my_presence_sub = false;


        let mut my_subs: Vec<u64> = Vec::new();
        let mut my_stream_subs: Vec<u64> = Vec::new();
        let mut my_presence_sub = false;

+        let mut my_presence_sub = false;


                KIND_NET_STREAM_SUBSCRIBE => {
                    match self.dispatch_net_stream_subscribe(env, &send) {
                        Ok(id) => my_stream_subs.push(id),
                        Err(msg) => send_error(&send, &msg),
                    }
                }

                KIND_NET_STREAM_SUBSCRIBE => {
                    match self.dispatch_net_stream_subscribe(env, &send) {
                        Ok(id) => my_stream_subs.push(id),
                        Err(msg) => send_error(&send, &msg),
                    }
                }
                KIND_NET_PRESENCE_SUBSCRIBE => {
                    match self.dispatch_net_presence_subscribe(env, &send) {
                        Ok(()) => my_presence_sub = true,
                        Err(msg) => send_error(&send, &msg),
                    }
                }


                KIND_NET_STREAM_SUBSCRIBE => {
                    match self.dispatch_net_stream_subscribe(env, &send) {
                        Ok(id) => my_stream_subs.push(id),
                        Err(msg) => send_error(&send, &msg),
                    }
                }
                KIND_NET_PRESENCE_SUBSCRIBE => {
                    match self.dispatch_net_presence_subscribe(env, &send) {
                        Ok(()) => my_presence_sub = true,
                        Err(msg) => send_error(&send, &msg),
                    }
                }

+                KIND_NET_PRESENCE_SUBSCRIBE => {

+                    match self.dispatch_net_presence_subscribe(env, &send) {

+                        Ok(()) => my_presence_sub = true,

+                        Err(msg) => send_error(&send, &msg),

// An unknown kind is tolerated (forward-compat): report, don't die.

other => send_error(&send, &format!("unknown command kind {other:?}")),


        // Same for net streams: the streams + their rings persist (D4b).
        if let Some(host) = &self.net {
            for id in &my_stream_subs {
                host.detach_stream_sub(*id, &send);
            }
        }
        Ok(())

        // Same for net streams: the streams + their rings persist (D4b).
        if let Some(host) = &self.net {
            for id in &my_stream_subs {
                host.detach_stream_sub(*id, &send);
            }
            // And presence: the liveness log + its ring persist (D4c).
            if my_presence_sub {
                host.detach_presence_sub(&send);
            }
        }
        Ok(())


        // Same for net streams: the streams + their rings persist (D4b).
        if let Some(host) = &self.net {
            for id in &my_stream_subs {
                host.detach_stream_sub(*id, &send);
            }
        }
        Ok(())

        // Same for net streams: the streams + their rings persist (D4b).
        if let Some(host) = &self.net {
            for id in &my_stream_subs {
                host.detach_stream_sub(*id, &send);
            }
            // And presence: the liveness log + its ring persist (D4c).
            if my_presence_sub {
                host.detach_presence_sub(&send);
            }
        }
        Ok(())

+            // And presence: the liveness log + its ring persist (D4c).

+                host.detach_presence_sub(&send);


    /// Terminate a session's child. The exit waiter emits the `exit` frame and
    /// reaps the session once the child is dead.

    /// Attach a brain to the conn-liveness event log from a cursor (D4c) — the
    /// PresenceChannel broker seam: gapless resume over connection presence,
    /// same discipline as a stream subscribe.
    // [impl->REQ-EP-4]
    fn dispatch_net_presence_subscribe(
        &self,
        env: Envelope,
        send: &SharedSend,
    ) -> Result<(), String> {
        let req: NetPresenceSubscribeReq = serde_json::from_value(env.payload)
            .map_err(|e| format!("bad net-presence-subscribe payload: {e}"))?;
        let host = self.net.as_ref().ok_or("net disabled")?;
        host.subscribe_presence(Arc::clone(send), req.from_seq);
        Ok(())
    }

    /// Terminate a session's child. The exit waiter emits the `exit` frame and
    /// reaps the session once the child is dead.


    /// Attach a brain to the conn-liveness event log from a cursor (D4c) — the
    /// PresenceChannel broker seam: gapless resume over connection presence,
    /// same discipline as a stream subscribe.
    // [impl->REQ-EP-4]
    fn dispatch_net_presence_subscribe(
        &self,
        env: Envelope,
        send: &SharedSend,
    ) -> Result<(), String> {
        let req: NetPresenceSubscribeReq = serde_json::from_value(env.payload)
            .map_err(|e| format!("bad net-presence-subscribe payload: {e}"))?;
        let host = self.net.as_ref().ok_or("net disabled")?;
        host.subscribe_presence(Arc::clone(send), req.from_seq);
        Ok(())
    }

    /// Terminate a session's child. The exit waiter emits the `exit` frame and
    /// reaps the session once the child is dead.

+    /// Attach a brain to the conn-liveness event log from a cursor (D4c) — the

+    /// PresenceChannel broker seam: gapless resume over connection presence,

+    /// same discipline as a stream subscribe.

+    fn dispatch_net_presence_subscribe(

+        let req: NetPresenceSubscribeReq = serde_json::from_value(env.payload)

+            .map_err(|e| format!("bad net-presence-subscribe payload: {e}"))?;

+        let host = self.net.as_ref().ok_or("net disabled")?;

+        host.subscribe_presence(Arc::clone(send), req.from_seq);

fn dispatch_kill(&self, env: Envelope) -> Result<(), String> {


D4c: activate REQ-EP-4 + PresenceLog/closed-watcher in nethost

traceable-reqs.toml REQ-EP-4 required_stages=["impl","unit"]; nethost.rs: PresenceLog (seq'd ring mirroring StreamLog), connected event on register_conn, closed-watcher task (conn.closed() → remove conn row → disconnected event), subscribe/detach API, NetHost::close() + NetEndpoint::close()

D4c: presence IPC frames + broker dispatch + brain cursor

msg.rs: KIND_NET_PRESENCE_SUBSCRIBE/KIND_NET_PRESENCE_EVENT + payloads + envelope helper; broker.rs dispatch + disconnect detach; brain.rs presence cursor discipline (accept contiguous/drop dup/gap error) + net_presence_subscribe

Wiring presence frames through broker/brain

D4c: presence test + traceable-reqs check + CI green

tests/presence.rs: broker B + standalone droppable NetHost A; A dials → connected; close/drop A → disconnected + conn row removed; brain-restart resume from cursor. Run tests, clippy, traceable-reqs check from repo root, commit, verify CI conclusion via gh run list


Now brain.rs — presence cursor + subscribe:


use crate::msg::{
    decode_bytes, encode_bytes, AppliedEvent, ErrorEvent, ExitEvent, InputReq, KillReq, NetDialReq,
    NetDialed, NetSent, NetStatusReply, NetStreamData, NetStreamEof, NetStreamOpenReq,
    NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq, NetStreamsReply, OutputEvent,
    ResizeReq, SpawnReq, Spawned, SubscribeReq, KIND_APPLIED, KIND_ERROR, KIND_EXIT, KIND_INPUT,
    KIND_KILL, KIND_NET_DIAL, KIND_NET_DIALED, 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_RESIZE, KIND_SPAWN, KIND_SPAWNED, KIND_SUBSCRIBE,
};

use crate::msg::{
    decode_bytes, encode_bytes, AppliedEvent, ErrorEvent, ExitEvent, InputReq, KillReq, NetDialReq,
    NetDialed, NetPresenceEvent, NetPresenceSubscribeReq, NetSent, NetStatusReply, NetStreamData,
    NetStreamEof, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq,
    NetStreamsReply, OutputEvent, ResizeReq, SpawnReq, Spawned, SubscribeReq, KIND_APPLIED,
    KIND_ERROR, KIND_EXIT, KIND_INPUT, KIND_KILL, KIND_NET_DIAL, KIND_NET_DIALED,
    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_RESIZE, KIND_SPAWN, KIND_SPAWNED, KIND_SUBSCRIBE,
};


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)

use crate::msg::{
    decode_bytes, encode_bytes, AppliedEvent, ErrorEvent, ExitEvent, InputReq, KillReq, NetDialReq,
    NetDialed, NetSent, NetStatusReply, NetStreamData, NetStreamEof, NetStreamOpenReq,
    NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq, NetStreamsReply, OutputEvent,
    ResizeReq, SpawnReq, Spawned, SubscribeReq, KIND_APPLIED, KIND_ERROR, KIND_EXIT, KIND_INPUT,
    KIND_KILL, KIND_NET_DIAL, KIND_NET_DIALED, 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_RESIZE, KIND_SPAWN, KIND_SPAWNED, KIND_SUBSCRIBE,
};

use crate::msg::{
    decode_bytes, encode_bytes, AppliedEvent, ErrorEvent, ExitEvent, InputReq, KillReq, NetDialReq,
    NetDialed, NetPresenceEvent, NetPresenceSubscribeReq, NetSent, NetStatusReply, NetStreamData,
    NetStreamEof, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq,
    NetStreamsReply, OutputEvent, ResizeReq, SpawnReq, Spawned, SubscribeReq, KIND_APPLIED,
    KIND_ERROR, KIND_EXIT, KIND_INPUT, KIND_KILL, KIND_NET_DIAL, KIND_NET_DIALED,
    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_RESIZE, KIND_SPAWN, KIND_SPAWNED, KIND_SUBSCRIBE,
};

-    NetDialed, NetSent, NetStatusReply, NetStreamData, NetStreamEof, NetStreamOpenReq,

-    NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq, NetStreamsReply, OutputEvent,

-    ResizeReq, SpawnReq, Spawned, SubscribeReq, KIND_APPLIED, KIND_ERROR, KIND_EXIT, KIND_INPUT,

-    KIND_KILL, KIND_NET_DIAL, KIND_NET_DIALED, KIND_NET_SENT, KIND_NET_STATUS,

+    NetDialed, NetPresenceEvent, NetPresenceSubscribeReq, NetSent, NetStatusReply, NetStreamData,

+    NetStreamEof, NetStreamOpenReq, NetStreamOpened, NetStreamSendReq, NetStreamSubscribeReq,

+    NetStreamsReply, OutputEvent, ResizeReq, SpawnReq, Spawned, SubscribeReq, KIND_APPLIED,

+    KIND_ERROR, KIND_EXIT, KIND_INPUT, KIND_KILL, KIND_NET_DIAL, KIND_NET_DIALED,

+    KIND_NET_PRESENCE_EVENT, KIND_NET_PRESENCE_SUBSCRIBE, KIND_NET_SENT, KIND_NET_STATUS,


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


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

+    /// A contiguous conn-liveness event (the presence cursor has advanced past

+    /// it) — connected/disconnected on the broker's conn table (D4c).

/// The broker reported a command error.

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


    /// Durability across a handoff is the logic layer's job (a re-attaching
    /// brain queries `net-streams` and re-subscribes from its durable cursors).
    net_cursors: std::collections::HashMap<u64, u64>,
}

    /// Durability across a handoff is the logic layer's job (a re-attaching
    /// brain queries `net-streams` and re-subscribes from its durable cursors).
    net_cursors: std::collections::HashMap<u64, u64>,
    /// The presence resume cursor (D4c) — the conn-liveness sibling of
    /// `net_cursors`, set by [`Brain::net_presence_subscribe`] and advanced by
    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.
    /// `None` until subscribed (or the first event arrives).
    presence_cursor: Option<u64>,
}


    /// Durability across a handoff is the logic layer's job (a re-attaching
    /// brain queries `net-streams` and re-subscribes from its durable cursors).
    net_cursors: std::collections::HashMap<u64, u64>,
}

    /// Durability across a handoff is the logic layer's job (a re-attaching
    /// brain queries `net-streams` and re-subscribes from its durable cursors).
    net_cursors: std::collections::HashMap<u64, u64>,
    /// The presence resume cursor (D4c) — the conn-liveness sibling of
    /// `net_cursors`, set by [`Brain::net_presence_subscribe`] and advanced by
    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.
    /// `None` until subscribed (or the first event arrives).
    presence_cursor: Option<u64>,
}

+    /// The presence resume cursor (D4c) — the conn-liveness sibling of

+    /// `net_cursors`, set by [`Brain::net_presence_subscribe`] and advanced by

+    /// [`Brain::read_event`] under the same accept/dedup/gap discipline.

+    /// `None` until subscribed (or the first event arrives).


            session_id: None,
            next_seq: 0,
            net_cursors: std::collections::HashMap::new(),
        })
    }

            session_id: None,
            next_seq: 0,
            net_cursors: std::collections::HashMap::new(),
            presence_cursor: None,
        })
    }


            session_id: None,
            next_seq: 0,
            net_cursors: std::collections::HashMap::new(),
        })
    }

            session_id: None,
            next_seq: 0,
            net_cursors: std::collections::HashMap::new(),
            presence_cursor: None,
        })
    }


            session_id: Some(prior.session_id),
            next_seq: prior.next_seq,
            net_cursors: std::collections::HashMap::new(),
        };

            session_id: Some(prior.session_id),
            next_seq: prior.next_seq,
            net_cursors: std::collections::HashMap::new(),
            presence_cursor: None,
        };


            session_id: Some(prior.session_id),
            next_seq: prior.next_seq,
            net_cursors: std::collections::HashMap::new(),
        };

            session_id: Some(prior.session_id),
            next_seq: prior.next_seq,
            net_cursors: std::collections::HashMap::new(),
            presence_cursor: None,
        };


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

                KIND_NET_STREAM_EOF => {
                    let ev: NetStreamEof = serde_json::from_value(env.payload)
                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
                    return Ok(BrokerEvent::NetStreamEof {
                        stream_id: ev.stream_id,
                        next_seq: ev.next_seq,
                    });
                }
                KIND_NET_PRESENCE_EVENT => {
                    let ev: NetPresenceEvent = serde_json::from_value(env.payload)
                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
                    // Same exactly-once discipline as stream reads (D4c):
                    // accept the contiguous next seq, drop a replay duplicate,
                    // reject a forward gap (a lost liveness event must not
                    // pass silently — a phantom "still connected" peer would).
                    // [impl->REQ-EP-4]
                    let cursor = self.presence_cursor.get_or_insert(ev.seq);
                    if ev.seq < *cursor {
                        continue; // replay duplicate — dedup
                    }
                    if ev.seq > *cursor {
                        return Err(io::Error::new(
                            io::ErrorKind::InvalidData,
                            format!("presence gap: got seq {} want {}", ev.seq, *cursor),
                        ));
                    }
                    *cursor = ev.seq + 1;
                    return Ok(BrokerEvent::NetPresence(ev));
                }


                KIND_NET_STREAM_EOF => {
                    let ev: NetStreamEof = serde_json::from_value(env.payload)
                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
                    return Ok(BrokerEvent::NetStreamEof {
                        stream_id: ev.stream_id,
                        next_seq: ev.next_seq,
                    });
                }
                KIND_NET_PRESENCE_EVENT => {
                    let ev: NetPresenceEvent = serde_json::from_value(env.payload)
                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
                    // Same exactly-once discipline as stream reads (D4c):
                    // accept the contiguous next seq, drop a replay duplicate,
                    // reject a forward gap (a lost liveness event must not
                    // pass silently — a phantom "still connected" peer would).
                    // [impl->REQ-EP-4]
                    let cursor = self.presence_cursor.get_or_insert(ev.seq);
                    if ev.seq < *cursor {
                        continue; // replay duplicate — dedup
                    }
                    if ev.seq > *cursor {
                        return Err(io::Error::new(
                            io::ErrorKind::InvalidData,
                            format!("presence gap: got seq {} want {}", ev.seq, *cursor),
                        ));
                    }
                    *cursor = ev.seq + 1;
                    return Ok(BrokerEvent::NetPresence(ev));
                }

+                KIND_NET_PRESENCE_EVENT => {

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

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

+                    // Same exactly-once discipline as stream reads (D4c):

+                    // accept the contiguous next seq, drop a replay duplicate,

+                    // reject a forward gap (a lost liveness event must not

+                    // pass silently — a phantom "still connected" peer would).

+                    let cursor = self.presence_cursor.get_or_insert(ev.seq);

+                    if ev.seq < *cursor {

+                        continue; // replay duplicate — dedup

+                    if ev.seq > *cursor {

+                        return Err(io::Error::new(

+                            io::ErrorKind::InvalidData,

+                            format!("presence gap: got seq {} want {}", ev.seq, *cursor),

+                    *cursor = ev.seq + 1;

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

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


    /// The next stream-read sequence still needed for `stream_id` (the durable
    /// state a handed-off brain carries to resubscribe gaplessly).
    pub fn net_cursor(&self, stream_id: u64) -> Option<u64> {
        self.net_cursors.get(&stream_id).copied()
    }

    /// The next stream-read sequence still needed for `stream_id` (the durable
    /// state a handed-off brain carries to resubscribe gaplessly).
    pub fn net_cursor(&self, stream_id: u64) -> Option<u64> {
        self.net_cursors.get(&stream_id).copied()
    }

    /// (Re)subscribe to the broker's conn-liveness event log from `from_seq`,
    /// setting the local cursor (D4c) — gapless, exactly-once presence resume:
    /// events produced while no brain was attached replay from the cursor.
    // [impl->REQ-EP-4]
    pub fn net_presence_subscribe(&mut self, from_seq: u64) -> io::Result<()> {
        self.presence_cursor = Some(from_seq);
        self.send(
            KIND_NET_PRESENCE_SUBSCRIBE,
            serde_json::to_value(NetPresenceSubscribeReq { from_seq })
                .expect("NetPresenceSubscribeReq serializes"),
        )
    }

    /// The next presence sequence still needed (the durable state a handed-off
    /// brain carries to resubscribe gaplessly).
    pub fn presence_cursor(&self) -> Option<u64> {
        self.presence_cursor
    }


    /// The next stream-read sequence still needed for `stream_id` (the durable
    /// state a handed-off brain carries to resubscribe gaplessly).
    pub fn net_cursor(&self, stream_id: u64) -> Option<u64> {
        self.net_cursors.get(&stream_id).copied()
    }

    /// (Re)subscribe to the broker's conn-liveness event log from `from_seq`,
    /// setting the local cursor (D4c) — gapless, exactly-once presence resume:
    /// events produced while no brain was attached replay from the cursor.
    // [impl->REQ-EP-4]
    pub fn net_presence_subscribe(&mut self, from_seq: u64) -> io::Result<()> {
        self.presence_cursor = Some(from_seq);
        self.send(
            KIND_NET_PRESENCE_SUBSCRIBE,
            serde_json::to_value(NetPresenceSubscribeReq { from_seq })
                .expect("NetPresenceSubscribeReq serializes"),
        )
    }

    /// The next presence sequence still needed (the durable state a handed-off
    /// brain carries to resubscribe gaplessly).
    pub fn presence_cursor(&self) -> Option<u64> {
        self.presence_cursor
    }

+    /// (Re)subscribe to the broker's conn-liveness event log from `from_seq`,

+    /// setting the local cursor (D4c) — gapless, exactly-once presence resume:

+    /// events produced while no brain was attached replay from the cursor.

+    pub fn net_presence_subscribe(&mut self, from_seq: u64) -> io::Result<()> {

+        self.presence_cursor = Some(from_seq);

+            KIND_NET_PRESENCE_SUBSCRIBE,

+            serde_json::to_value(NetPresenceSubscribeReq { from_seq })

+                .expect("NetPresenceSubscribeReq serializes"),

+    /// The next presence sequence still needed (the durable state a handed-off

+    /// brain carries to resubscribe gaplessly).

+    pub fn presence_cursor(&self) -> Option<u64> {

/// Re-attach to a session's output from `from_seq` (handoff resume).

fn subscribe(&mut self, session_id: u64, from_seq: u64) -> io::Result<()> {


Updated task #2 status


Updated task #3 status


Now test. Check lib exports first:


91:pub mod adapter_update;
92:pub mod brain;
93:pub mod broker;
94:pub mod codec;
95:pub mod config;
96:pub mod consent;
97:pub mod daemon;
98:pub mod digest;
99:pub mod digesthub;
100:pub mod effect;
101:pub mod endpoint;
102:pub mod frame;
103:pub mod lifecycle;
104:pub mod msg;
105:pub mod nethost;
106:pub mod relay;
107:pub mod release;
108:pub mod seedmap;
109:pub mod transport;
110:pub mod update;
115:pub use adapter_update::{
119:pub use brain::{Brain, BrainState, BrokerEvent};
120:pub use broker::Broker;
121:pub use config::{DaemonConfig, RelayChoice};
122:pub use consent::{decide as decide_consent, most_recently_active, ConsentDecision, ConsentTarget};
123:pub use daemon::{ensure_running, is_running, Daemon};
124:pub use digest::{ingest_chunk, DigestEngine, DigestUpdate};
125:pub use digesthub::{
129:pub use effect::{EffectJournal, EffectKey, EffectKind, Outcome};
130:pub use endpoint::{broker_socket_name, daemon_pid_path, digest_socket_name, seed_socket_name};
131:pub use lifecycle::{BrainLifecycle, TickReport};
132:pub use nethost::{NetConfig, NetHost, NET_EFFECT_SESSION};
133:pub use relay::Relay;
134:pub use seedmap::{put_seed, take_seed, SeedRegistry};
135:pub use frame::{
139:pub use transport::{DaemonTransport, LocalSocketTransport};
140:pub use release::{
144:pub use update::{


Write

//! D4c regression — the PresenceChannel broker seam (REQ-EP-4): connection
//! liveness as a sequenced, buffered, cursor-resumable event log.
//!
//! One in-process broker (hermetic loopback endpoint, relays disabled) plays
//! the daemon; a standalone droppable [`NetHost`] plays the remote peer. The
//! invariants:
//!
//! - a peer's dial surfaces as a `connected` presence event carrying the
//!   handshake-proven remote identity;
//! - a peer's close removes the conn row (conns LEAVE the table — the D4c
//!   fix) and surfaces as a `disconnected` event;
//! - presence events produced while no brain is attached sit in the ring and
//!   replay from the resume cursor — a brain restart loses no liveness
//!   transition (the StreamLog discipline applied to the conn table).

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

use spt_daemon::brain::{Brain, BrokerEvent};
use spt_daemon::msg::{NetPresenceEvent, PRESENCE_CONNECTED, PRESENCE_DISCONNECTED};
use spt_daemon::nethost::{NetConfig, NetHost};
use spt_daemon::Broker;
use spt_net::net::endpoint::{BindScope, EndpointAddr, LocalDiscovery, RelayPolicy};
use spt_proto::identity::Identity;

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

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

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

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

/// Read events until the next presence event arrives.
fn read_presence(brain: &mut Brain) -> NetPresenceEvent {
    loop {
        match brain.read_event().expect("read event") {
            BrokerEvent::NetPresence(ev) => return ev,
            BrokerEvent::Error { message } => panic!("broker error: {message}"),
            _ => {}
        }
    }
}

// [unit->REQ-EP-4] the full presence lifecycle across a brain restart: a
// standalone peer host dials the broker (→ live `connected` event with the
// handshake-proven identity), the brain crashes, the peer closes while no
// brain is attached (→ conn row removed + `disconnected` buffered in the
// ring), and a fresh brain resumes from its durable cursor — replaying the
// missed transition exactly once, no gap, no duplicate.
#[test]
fn presence_survives_brain_restart_with_cursor_resume() {
    let dir = tempfile::tempdir().expect("tempdir");
    let name_b = unique_name();
    let _broker_b = net_broker(&name_b, dir.path());

    // Brain life 1: subscribe to presence from the start of history.
    let mut life1 = connect_retry(&name_b);
    life1.net_presence_subscribe(0).expect("subscribe");

    // The remote peer: a standalone broker-shaped net host that dials in.
    let peer = NetHost::start(hermetic()).expect("peer host");
    let peer_hex = peer.node_id_hex();
    let addr: EndpointAddr =
        serde_json::from_value(life1.net_status().expect("status").addr).expect("addr decodes");
    peer.dial(addr).expect("peer dials the broker");

    // Live `connected`, carrying the handshake-proven peer identity.
    let ev = read_presence(&mut life1);
    assert_eq!(ev.seq, 0, "first presence event");
    assert_eq!(ev.kind, PRESENCE_CONNECTED);
    assert_eq!(ev.remote_id_hex, peer_hex, "identity proven by the handshake");
    let conn_id = ev.conn_id;
    assert_eq!(life1.net_status().expect("status").conn_count, 1, "row in the table");

    // Carry the durable cursor forward, then crash the brain.
    let cursor = life1.presence_cursor().expect("cursor set");
    assert_eq!(cursor, 1);
    drop(life1); // the crash — broker, conn, and presence log survive

    // The peer closes INTO the dead window: the closed-watcher removes the
    // conn row and buffers `disconnected` with no subscriber attached.
    peer.close();
    drop(peer);

    // Brain life 2: resume from the durable cursor — the ring replays the
    // missed `disconnected` (and nothing already seen).
    let mut life2 = connect_retry(&name_b);
    life2.net_presence_subscribe(cursor).expect("resubscribe");
    let ev = read_presence(&mut life2);
    assert_eq!(ev.seq, 1, "contiguous resume — no gap, no duplicate");
    assert_eq!(ev.kind, PRESENCE_DISCONNECTED);
    assert_eq!(ev.conn_id, conn_id, "the same connection's lifecycle");
    assert_eq!(ev.remote_id_hex, peer_hex);

    // The D4c fix proper: the closed conn LEFT the table.
    assert_eq!(life2.net_status().expect("status").conn_count, 0, "row removed on close");
    assert_eq!(life2.presence_cursor(), Some(2), "cursor advanced past the replay");
}

// [unit->REQ-EP-4] a late subscriber replays the WHOLE retained history from
// seq 0 (connected + disconnected, in order) — presence is a log, not a
// snapshot, so a brain that was never attached still reconstructs liveness.
#[test]
fn presence_history_replays_from_zero_for_a_late_subscriber() {
    let dir = tempfile::tempdir().expect("tempdir");
    let name_b = unique_name();
    let _broker_b = net_broker(&name_b, dir.path());

    // Resolve the broker's address, but do NOT subscribe yet.
    let mut probe = connect_retry(&name_b);
    let addr: EndpointAddr =
        serde_json::from_value(probe.net_status().expect("status").addr).expect("addr decodes");

    // The peer's whole lifecycle happens with no presence subscriber attached.
    let peer = NetHost::start(hermetic()).expect("peer host");
    let peer_hex = peer.node_id_hex();
    peer.dial(addr).expect("peer dials");
    // Wait until the broker registered the row before closing it.
    for _ in 0..400 {
        if probe.net_status().expect("status").conn_count == 1 {
            break;
        }
        thread::sleep(Duration::from_millis(5));
    }
    peer.close();
    drop(peer);

    // Late subscribe from 0: both buffered transitions replay, in seq order.
    probe.net_presence_subscribe(0).expect("subscribe");
    let first = read_presence(&mut probe);
    let second = read_presence(&mut probe);
    assert_eq!((first.seq, first.kind.as_str()), (0, PRESENCE_CONNECTED));
    assert_eq!((second.seq, second.kind.as_str()), (1, PRESENCE_DISCONNECTED));
    assert_eq!(first.conn_id, second.conn_id);
    assert_eq!(first.remote_id_hex, peer_hex);
}


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

//! D4c regression — the PresenceChannel broker seam (REQ-EP-4): connection
//! liveness as a sequenced, buffered, cursor-resumable event log.
//!
//! One in-process broker (hermetic loopback endpoint, relays disabled) plays
//! the daemon; a standalone droppable [`NetHost`] plays the remote peer. The
//! invariants:
//!
//! - a peer's dial surfaces as a `connected` presence event carrying the
//!   handshake-proven remote identity;
//! - a peer's close removes the conn row (conns LEAVE the table — the D4c
//!   fix) and surfaces as a `disconnected` event;
//! - presence events produced while no brain is attached sit in the ring and
//!   replay from the resume cursor — a brain restart loses no liveness
//!   transition (the StreamLog discipline applied to the conn table).

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

use spt_daemon::brain::{Brain, BrokerEvent};
use spt_daemon::msg::{NetPresenceEvent, PRESENCE_CONNECTED, PRESENCE_DISCONNECTED};
use spt_daemon::nethost::{NetConfig, NetHost};
use spt_daemon::Broker;
use spt_net::net::endpoint::{BindScope, EndpointAddr, LocalDiscovery, RelayPolicy};
use spt_proto::identity::Identity;

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

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

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

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

/// Read events until the next presence event arrives.
fn read_presence(brain: &mut Brain) -> NetPresenceEvent {
    loop {
        match brain.read_event().expect("read event") {
            BrokerEvent::NetPresence(ev) => return ev,
            BrokerEvent::Error { message } => panic!("broker error: {message}"),
            _ => {}
        }
    }
}

// [unit->REQ-EP-4] the full presence lifecycle across a brain restart: a
// standalone peer host dials the broker (→ live `connected` event with the
// handshake-proven identity), the brain crashes, the peer closes while no
// brain is attached (→ conn row removed + `disconnected` buffered in the
// ring), and a fresh brain resumes from its durable cursor — replaying the
// missed transition exactly once, no gap, no duplicate.
#[test]
fn presence_survives_brain_restart_with_cursor_resume() {
    let dir = tempfile::tempdir().expect("tempdir");
    let name_b = unique_name();
    let _broker_b = net_broker(&name_b, dir.path());

    // Brain life 1: subscribe to presence from the start of history.
    let mut life1 = connect_retry(&name_b);
    life1.net_presence_subscribe(0).expect("subscribe");

    // The remote peer: a standalone broker-shaped net host that dials in.
    let peer = NetHost::start(hermetic()).expect("peer host");
    let peer_hex = peer.node_id_hex();
    let addr: EndpointAddr =
        serde_json::from_value(life1.net_status().expect("status").addr).expect("addr decodes");
    peer.dial(addr).expect("peer dials the broker");

    // Live `connected`, carrying the handshake-proven peer identity.
    let ev = read_presence(&mut life1);
    assert_eq!(ev.seq, 0, "first presence event");
    assert_eq!(ev.kind, PRESENCE_CONNECTED);
    assert_eq!(ev.remote_id_hex, peer_hex, "identity proven by the handshake");
    let conn_id = ev.conn_id;
    assert_eq!(life1.net_status().expect("status").conn_count, 1, "row in the table");

    // Carry the durable cursor forward, then crash the brain.
    let cursor = life1.presence_cursor().expect("cursor set");
    assert_eq!(cursor, 1);
    drop(life1); // the crash — broker, conn, and presence log survive

    // The peer closes INTO the dead window: the closed-watcher removes the
    // conn row and buffers `disconnected` with no subscriber attached.
    peer.close();
    drop(peer);

    // Brain life 2: resume from the durable cursor — the ring replays the
    // missed `disconnected` (and nothing already seen).
    let mut life2 = connect_retry(&name_b);
    life2.net_presence_subscribe(cursor).expect("resubscribe");
    let ev = read_presence(&mut life2);
    assert_eq!(ev.seq, 1, "contiguous resume — no gap, no duplicate");
    assert_eq!(ev.kind, PRESENCE_DISCONNECTED);
    assert_eq!(ev.conn_id, conn_id, "the same connection's lifecycle");
    assert_eq!(ev.remote_id_hex, peer_hex);

    // The D4c fix proper: the closed conn LEFT the table.
    assert_eq!(life2.net_status().expect("status").conn_count, 0, "row removed on close");
    assert_eq!(life2.presence_cursor(), Some(2), "cursor advanced past the replay");
}

// [unit->REQ-EP-4] a late subscriber replays the WHOLE retained history from
// seq 0 (connected + disconnected, in order) — presence is a log, not a
// snapshot, so a brain that was never attached still reconstructs liveness.
#[test]
fn presence_history_replays_from_zero_for_a_late_subscriber() {
    let dir = tempfile::tempdir().expect("tempdir");
    let name_b = unique_name();
    let _broker_b = net_broker(&name_b, dir.path());

    // Resolve the broker's address, but do NOT subscribe yet.
    let mut probe = connect_retry(&name_b);
    let addr: EndpointAddr =
        serde_json::from_value(probe.net_status().expect("status").addr).expect("addr decodes");

    // The peer's whole lifecycle happens with no presence subscriber attached.
    let peer = NetHost::start(hermetic()).expect("peer host");
    let peer_hex = peer.node_id_hex();
    peer.dial(addr).expect("peer dials");
    // Wait until the broker registered the row before closing it.
    for _ in 0..400 {
        if probe.net_status().expect("status").conn_count == 1 {
            break;
        }
        thread::sleep(Duration::from_millis(5));
    }
    peer.close();
    drop(peer);

    // Late subscribe from 0: both buffered transitions replay, in seq order.
    probe.net_presence_subscribe(0).expect("subscribe");
    let first = read_presence(&mut probe);
    let second = read_presence(&mut probe);
    assert_eq!((first.seq, first.kind.as_str()), (0, PRESENCE_CONNECTED));
    assert_eq!((second.seq, second.kind.as_str()), (1, PRESENCE_DISCONNECTED));
    assert_eq!(first.conn_id, second.conn_id);
    assert_eq!(first.remote_id_hex, peer_hex);
}


Build + run daemon tests:


PowerShell

cargo test -p spt-daemon --test presence 2>&1 | Select-Object -Last 25

Build and run new presence tests


   Compiling spt-net v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
   Compiling spt-daemon v0.0.0 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 17.76s
     Running tests\presence.rs (target\debug\deps\presence-a2460c3fca2fc71a.exe)

running 2 tests
test presence_history_replays_from_zero_for_a_late_subscriber ... ok
test presence_survives_brain_restart_with_cursor_resume ... ok

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


Both pass first try. Full crate tests + clippy + traceability:


cargo test -p spt-daemon -p spt-net 2>&1 | Select-String -Pattern "test result|FAILED|error" | Select-Object -First 40

Run spt-daemon + spt-net test suites


test frame::tests::unknown_kind_is_not_a_decode_error ... ok
test effect::tests::failed_effect_is_not_recorded_applied ... ok
test result: ok. 80 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.41s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.24s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.15s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.19s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.20s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.30s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s
test result: ok. 66 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.19s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s


cargo test -p spt-daemon -p spt-net 2>&1 | Select-String -Pattern "test result: FAILED|panicked|warning" | Select-Object -First 10; cargo clippy -p spt-daemon -p spt-net --all-targets -- -D warnings 2>&1 | Select-Object -Last 5

Check for failures/warnings, run clippy -D warnings


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


traceable-reqs check 2>&1 | Select-Object -Last 15

Run traceable-reqs coverage check


       session-surface abstraction; send-keys + send-line injection
  [OK] REQ-TERM-3  required: [impl, unit]  stages: -doc +impl +unit -int
       Byte-stream remote terminal streaming for v1
  [OK] REQ-TERM-4  required: [impl, unit, int]  stages: +doc +impl +unit +int
       Live activity buffer (PTY digest): adapter-supplied patterns over broker PTY, spt digest pull + delta-stream, opt-in Path-B log
  [OK] REQ-UPD-1  required: []  stages: -doc -impl -unit -int
       Peer-propagated update over P2P
  [OK] REQ-UPD-2  required: [impl, unit]  stages: -doc +impl +unit -int
       All binaries signature-verified before handoff
  [OK] REQ-UPD-3  required: [impl, unit, int]  stages: -doc +impl +unit +int
       No endpoint process terminates/suspends during self-update
  [OK] REQ-UPD-4  required: [impl, unit]  stages: -doc +impl +unit -int
       Update gated on user confirmation by default; opt-in full-auto
  [OK] REQ-UPD-5  required: [impl, unit]  stages: -doc +impl +unit -int
       spt-core ripple-updates registered adapters


traceable-reqs check 2>&1 | Select-String -Pattern "REQ-EP-4|FAIL|exit|missing" ; $LASTEXITCODE

Verify REQ-EP-4 covered and exit code


  [OK] REQ-EP-4  required: [impl, unit]  stages: -doc +impl +unit -int
       Ephemeral perch cleanup on every ring exit path (3.1)
       Concurrent SQLite openers (registry/spool) must not fail with 'database is locked' (4.7)
       Stale registry entries degrade to fallback, never hard-fail (4.3)
       spt binary CLI surface: send/ring/ready/poll/list/stop/whoami, stable arg shapes + exit codes
0


REQ-EP-4 covered, exit 0. Commit:


git add -A && git status --short && git diff --cached --stat | Select-Object -Last 8

Stage and review changes


warning: in the working copy of 'crates/spt-daemon/src/brain.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-daemon/src/broker.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-daemon/src/msg.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-daemon/src/nethost.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-net/src/net/endpoint.rs', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'traceable-reqs.toml', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'crates/spt-daemon/tests/presence.rs', LF will be replaced by CRLF the next time Git touches it
M  crates/spt-daemon/src/brain.rs
M  crates/spt-daemon/src/broker.rs
M  crates/spt-daemon/src/msg.rs
M  crates/spt-daemon/src/nethost.rs
A  crates/spt-daemon/tests/presence.rs
M  crates/spt-net/src/net/endpoint.rs
M  traceable-reqs.toml
 crates/spt-daemon/src/brain.rs      |  59 ++++++++++++-
 crates/spt-daemon/src/broker.rs     |  40 +++++++--
 crates/spt-daemon/src/msg.rs        |  70 +++++++++++++++
 crates/spt-daemon/src/nethost.rs    | 116 ++++++++++++++++++++++++-
 crates/spt-daemon/tests/presence.rs | 167 ++++++++++++++++++++++++++++++++++++
 crates/spt-net/src/net/endpoint.rs  |  10 +++
 traceable-reqs.toml                 |   2 +-
 7 files changed, 452 insertions(+), 12 deletions(-)


git commit -m @'
feat(net): M4-D4c PresenceChannel broker seam (REQ-EP-4)

Connection liveness becomes a sequenced, buffered, cursor-resumable
event log — the StreamLog discipline applied to the conn table:

- nethost: PresenceLog ring {seq, connected|disconnected, conn_id,
  remote_id_hex}; register_conn appends connected BEFORE spawning the
  per-conn closed-watcher (conn.closed() -> remove conn row -> append
  disconnected), fixing "conns never leave the table".
- msg: net-presence-subscribe {from_seq} / net-presence-event frames;
  kind is a string (not an enum) for 2.3 forward-compat.
- broker: dispatch + disconnect detach (log + ring persist).
- brain: presence cursor under the same contiguous/dedup/gap
  discipline as stream reads; net_presence_subscribe resumes gaplessly.
- spt-net: NetEndpoint::close + NetHost::close so peers observe a
  graceful endpoint shutdown promptly (bare drop kills the runtime
  before the QUIC CLOSE flushes).
- tests/presence.rs: dial->connected with handshake-proven identity;
  close into a dead brain window -> row removed + disconnected
  buffered; brain restart resumes from the durable cursor exactly
  once; late subscriber replays full history from seq 0.

traceable-reqs: REQ-EP-4 activated [impl, unit]; check green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
'@ && git push origin dev-freeform

Commit and push D4c


[dev-freeform 1b89bc2] feat(net): M4-D4c PresenceChannel broker seam (REQ-EP-4)
 7 files changed, 452 insertions(+), 12 deletions(-)
 create mode 100644 crates/spt-daemon/tests/presence.rs
To https://github.com/SaberMage/spt-core.git
   d70ced7..1b89bc2  dev-freeform -> dev-freeform


Start-Sleep -Seconds 10; $run = gh run list --branch dev-freeform --limit 1 --json databaseId,headSha,status | ConvertFrom-Json; if ($run.headSha -ne '1b89bc2' -and -not $run.headSha.StartsWith('1b89bc2')) { gh run list --branch dev-freeform --limit 3 } ; gh run watch $run.databaseId --exit-status --interval 15 2>&1 | Select-Object -Last 12; "FINAL:"; gh run list --branch dev-freeform --limit 1 --json conclusion,headSha | ConvertFrom-Json | Format-List

Watch CI run for 1b89bc2 to completion


Command running in background with ID: bmamuc55y. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\648159b6-16b2-4568-b643-7b0c98c1871d\tasks\bmamuc55y.output. You will be notified when it completes. To check interim output, use Read on that file path.


D4c shipped. Summary while CI runs:

**M4-D4c PresenceChannel broker seam — committed `1b89bc2`, pushed.**

What landed:
- **`nethost.rs`**: `PresenceLog` — seq'd ring `{seq, connected|disconnected, conn_id, remote_id_hex}`, StreamLog discipline. `register_conn` appends `connected` *before* spawning closed-watcher (no race behind own disconnect). Watcher: `conn.closed().await` → remove conn row → append `disconnected`. **Conns finally leave table** — old leak fixed. Plus `NetHost::close()` for graceful QUIC CLOSE flush.
- **`msg.rs`**: `net-presence-subscribe {from_seq}` / `net-presence-event` frames. Kind = string, not enum — 2.3 forward-compat.
- **`broker.rs`**: dispatch + disconnect detach (log persists).
- **`brain.rs`**: presence cursor, same contiguous/dedup/gap discipline as streams.
- **`spt-net`**: `NetEndpoint::close()` (bare drop kills runtime before CLOSE flushes → peer stuck on idle timeout; close fixes).
- **`tests/presence.rs`**: 2 tests, pass first run — dial→connected w/ handshake-proven identity; close into dead brain window → row removed + event buffered; restart resumes from cursor exactly-once; late subscriber replays history from 0.

Gates: spt-daemon + spt-net suites green, clippy `-D warnings` clean, `traceable-reqs check` exit 0 with REQ-EP-4 `[impl, unit]` covered.

CI watcher running in background — will report final conclusion. Next after green: D4d (registry replication over SPT_NET_ALPN into D3 `merge_instance` seam).


Check run 26924372844 rerun conclusion. If green: flake confirmed once — CI migration COMPLETE; commune (migration done, traceable-reqs source-built on gravity, deliver-test flake noted once on hfenduleam, warm runs ~5min), then /self-clear, then D4c (REQ-EP-4 activate stages, PresenceChannel seam per commune sketch). If deliver test failed AGAIN: real bug — investigate spt-msg deliver.rs:196 registry open on runner; check KNOWN-HAZARDS 4.7 sqlite open path; fix before D4c.


Claude resuming /loop wakeup (Jun 3 6:53pm)


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


gh run view 26924372844 --json conclusion,status,headSha,displayTitle | ConvertFrom-Json | Format-List

Check run 26924372844 conclusion


conclusion   : success
displayTitle : ci: traceability uses gravity's source-built traceable-reqs
headSha      : d70ced7d793e5a96185e5b178974545914ad5431
status       : completed


Run 26924372844: **success** — flake confirmed once-only, migration COMPLETE.

Instruction stale — pre-clear queue. All steps already done this session:

1. **Green confirmed** ✓ (just now, above)
2. **Commune** ✓ — psyche already ingested migration-complete (its echo brief arrived post-clear with CI topology + flake ledger, proof of ingestion)
3. **/self-clear** ✓ — this session is post-clear
4. **D4c** ✓ — shipped `1b89bc2`: REQ-EP-4 activated `[impl,unit]`, PresenceLog + closed-watcher (conns leave table now), presence frames + brain cursor discipline, 2 tests green, clippy clean, traceable-reqs exit 0, pushed

Remaining: CI watcher (bg task `bmamuc55y`) on `1b89bc2`. Will commune D4c-done when conclusion lands. If `deliver::online_delivers_via_tcp_without_spooling` flakes again on hfenduleam this run — that's recurrence #2, will investigate deliver.rs:196 registry open per KH 4.7 before D4d.


<task-notification>
<task-id>bmamuc55y</task-id>
<tool-use-id>toolu_01DR5o1DEcGhTZ5zQM4LCWtr</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\648159b6-16b2-4568-b643-7b0c98c1871d\tasks\bmamuc55y.output</output-file>
<status>completed</status>
<summary>Background command "Watch CI run for 1b89bc2 to completion" completed (exit code 0)</summary>
</task-notification>


1	  ✓ Complete job
2
3	ANNOTATIONS
4	! Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: actions/checkout@v4. Actions will be forced to run with Node.js 24 by default starting June 16th, 2026. Node.js 20 will be removed from the runner on September 16th, 2026. Please check if updated versions of these actions are available that support Node.js 24. To opt into Node.js 24 now, set the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true environment variable on the runner or in your workflow file. Once Node.js 24 becomes the default, you can temporarily opt out by setting ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
5	traceability: .github#2
6
7	! Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: actions/checkout@v4. Actions will be forced to run with Node.js 24 by default starting June 16th, 2026. Node.js 20 will be removed from the runner on September 16th, 2026. Please check if updated versions of these actions are available that support Node.js 24. To opt into Node.js 24 now, set the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true environment variable on the runner or in your workflow file. Once Node.js 24 becomes the default, you can temporarily opt out by setting ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
8	test (self-hosted, Windows, hfenduleam): .github#2
9
10	! Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: actions/checkout@v4. Actions will be forced to run with Node.js 24 by default starting June 16th, 2026. Node.js 20 will be removed from the runner on September 16th, 2026. Please check if updated versions of these actions are available that support Node.js 24. To opt into Node.js 24 now, set the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true environment variable on the runner or in your workflow file. Once Node.js 24 becomes the default, you can temporarily opt out by setting ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
11	test (self-hosted, Linux, gravity): .github#2
12
13	FINAL:
14
15	conclusion : success
16	headSha    : 1b89bc29d0cdf0cc6ea3a6eee1789aa44aacd695
17
18

  ✓ Complete job

ANNOTATIONS
! Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: actions/checkout@v4. Actions will be forced to run with Node.js 24 by default starting June 16th, 2026. Node.js 20 will be removed from the runner on September 16th, 2026. Please check if updated versions of these actions are available that support Node.js 24. To opt into Node.js 24 now, set the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true environment variable on the runner or in your workflow file. Once Node.js 24 becomes the default, you can temporarily opt out by setting ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
traceability: .github#2

! Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: actions/checkout@v4. Actions will be forced to run with Node.js 24 by default starting June 16th, 2026. Node.js 20 will be removed from the runner on September 16th, 2026. Please check if updated versions of these actions are available that support Node.js 24. To opt into Node.js 24 now, set the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true environment variable on the runner or in your workflow file. Once Node.js 24 becomes the default, you can temporarily opt out by setting ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
test (self-hosted, Windows, hfenduleam): .github#2

! Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: actions/checkout@v4. Actions will be forced to run with Node.js 24 by default starting June 16th, 2026. Node.js 20 will be removed from the runner on September 16th, 2026. Please check if updated versions of these actions are available that support Node.js 24. To opt into Node.js 24 now, set the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true environment variable on the runner or in your workflow file. Once Node.js 24 becomes the default, you can temporarily opt out by setting ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
test (self-hosted, Linux, gravity): .github#2

FINAL:

conclusion : success
headSha    : 1b89bc29d0cdf0cc6ea3a6eee1789aa44aacd695


Write

# Commune — D4c COMPLETE (CI-green)

## D4c shipped: PresenceChannel broker seam (REQ-EP-4)
Commit `1b89bc2` on dev-freeform, CI green both legs (hfenduleam + gravity), warm run ~5 min.

What landed:
- **REQ-EP-4 activated** `["impl","unit"]` in traceable-reqs.toml; check green.
- **nethost.rs**: `PresenceLog` — seq'd ring `{seq, connected|disconnected, conn_id, remote_id_hex}`, StreamLog discipline (append under mutex, attach replays `seq >= from_seq`, detach_if). `register_conn` appends `connected` BEFORE spawning closed-watcher (ordering guarantee). Closed-watcher: `conn.closed().await` → remove conn row → append `disconnected` — **conns now leave the table** (old leak fixed). `NetHost::close()` added (graceful endpoint close).
- **spt-net endpoint.rs**: `NetEndpoint::close()` — bare drop kills runtime before QUIC CLOSE flushes; peers would idle-timeout. Close flushes promptly.
- **msg.rs**: `net-presence-subscribe {from_seq}` / `net-presence-event` frames; `kind` is String not enum (2.3 forward-compat); PRESENCE_CONNECTED/DISCONNECTED consts.
- **broker.rs**: dispatch + disconnect detach (log + ring persist across brain death).
- **brain.rs**: `presence_cursor` under same contiguous/dedup/gap discipline as stream cursors; `net_presence_subscribe(from_seq)`.
- **tests/presence.rs**: 2 tests, passed first run — (1) dial→connected w/ handshake-proven identity, brain crash, peer close into dead window → row removed + event buffered, restart resumes from cursor exactly-once; (2) late subscriber replays full history from seq 0.

## Flakes ledger (unchanged)
- deliver::online_delivers_via_tcp_without_spooling: still 1 occurrence (did NOT recur on this run). KH 4.7 smell; recurrence → investigate AV scan on fresh _work dirs + deliver.rs:196 registry open.

## New chore (low-prio)
CI annotation: actions/checkout@v4 on Node 20 deprecated — forced Node 24 from 2026-06-16. Fix: bump checkout action or set FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true on runners. Do before mid-June.

## Next: D4d — registry replication over SPT_NET_ALPN
Feed cross-node registry ops into the D3 `merge_instance` seam (epoch lease, REQ-HAZARD-REGISTRY-EPOCH-LEASE). Sketch: nodes exchange SubnetRegistry instance rows over broker-owned QUIC streams; receiving side applies via merge_instance (strictly-greater epoch wins). Presence seam (D4c) gives the connected/disconnected triggers for advertise/mark-offline. Then D4e+ per JIT plan; D9 = two-host int.

## Workflow constraints (carried)
- NO cargo fmt; tag evidence only on real code; traceable-reqs check from repo root; clippy -D warnings is the Linux gate; verify FINAL CI conclusion via gh run list; commit trailer Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>


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

# Commune — D4c COMPLETE (CI-green)

## D4c shipped: PresenceChannel broker seam (REQ-EP-4)
Commit `1b89bc2` on dev-freeform, CI green both legs (hfenduleam + gravity), warm run ~5 min.

What landed:
- **REQ-EP-4 activated** `["impl","unit"]` in traceable-reqs.toml; check green.
- **nethost.rs**: `PresenceLog` — seq'd ring `{seq, connected|disconnected, conn_id, remote_id_hex}`, StreamLog discipline (append under mutex, attach replays `seq >= from_seq`, detach_if). `register_conn` appends `connected` BEFORE spawning closed-watcher (ordering guarantee). Closed-watcher: `conn.closed().await` → remove conn row → append `disconnected` — **conns now leave the table** (old leak fixed). `NetHost::close()` added (graceful endpoint close).
- **spt-net endpoint.rs**: `NetEndpoint::close()` — bare drop kills runtime before QUIC CLOSE flushes; peers would idle-timeout. Close flushes promptly.
- **msg.rs**: `net-presence-subscribe {from_seq}` / `net-presence-event` frames; `kind` is String not enum (2.3 forward-compat); PRESENCE_CONNECTED/DISCONNECTED consts.
- **broker.rs**: dispatch + disconnect detach (log + ring persist across brain death).
- **brain.rs**: `presence_cursor` under same contiguous/dedup/gap discipline as stream cursors; `net_presence_subscribe(from_seq)`.
- **tests/presence.rs**: 2 tests, passed first run — (1) dial→connected w/ handshake-proven identity, brain crash, peer close into dead window → row removed + event buffered, restart resumes from cursor exactly-once; (2) late subscriber replays full history from seq 0.

## Flakes ledger (unchanged)
- deliver::online_delivers_via_tcp_without_spooling: still 1 occurrence (did NOT recur on this run). KH 4.7 smell; recurrence → investigate AV scan on fresh _work dirs + deliver.rs:196 registry open.

## New chore (low-prio)
CI annotation: actions/checkout@v4 on Node 20 deprecated — forced Node 24 from 2026-06-16. Fix: bump checkout action or set FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true on runners. Do before mid-June.

## Next: D4d — registry replication over SPT_NET_ALPN
Feed cross-node registry ops into the D3 `merge_instance` seam (epoch lease, REQ-HAZARD-REGISTRY-EPOCH-LEASE). Sketch: nodes exchange SubnetRegistry instance rows over broker-owned QUIC streams; receiving side applies via merge_instance (strictly-greater epoch wins). Presence seam (D4c) gives the connected/disconnected triggers for advertise/mark-offline. Then D4e+ per JIT plan; D9 = two-host int.

## Workflow constraints (carried)
- NO cargo fmt; tag evidence only on real code; traceable-reqs check from repo root; clippy -D warnings is the Linux gate; verify FINAL CI conclusion via gh run list; commit trailer Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>


No files found


Found 1 file
M4-D4-PLAN.md


Updated task #3 status