From 119b85d28e6dcbda100d49e1a723bb62a82a757f Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Fri, 17 Jul 2026 01:33:06 +0000 Subject: [PATCH] node: correct stale liveness claims in machine, executor, and timer docs Several module and field docs still described the per-peer machine as unwired shadow scaffolding. It has been live for some time: machines are inserted at dial and inbound msg1, stepped by the handshake handlers and the rekey-cadence and liveness-reap routers, and the executor's SwapSendState/CompleteDrain/InvalidateSendState arms are the authoritative paths (the inline bodies survive only as debug-assert release fallbacks). Rewrite those docs to the current truth while keeping the still-true dormancy facts: PeerEvent::Timeout and PeerEvent::Tick are never dispatched in production, retransmit fires on the machine-armed deadline while the timeout reaper keys on timer presence with the config threshold, and the remaining inert executor stubs are SendRekey, SendLinkMessage, and the connected-UDP arms. Drop the stale allow(dead_code) on the peer_machines field. --- src/node/dataplane/peer_actions.rs | 37 ++++++++++++++++++------------ src/node/handlers/handshake.rs | 7 +++--- src/node/mod.rs | 23 +++++++++++-------- src/peer/machine.rs | 28 +++++++++++++--------- 4 files changed, 57 insertions(+), 38 deletions(-) diff --git a/src/node/dataplane/peer_actions.rs b/src/node/dataplane/peer_actions.rs index bd171f6..de46ca6 100644 --- a/src/node/dataplane/peer_actions.rs +++ b/src/node/dataplane/peer_actions.rs @@ -12,14 +12,17 @@ //! (`handle_msg1` → `step(InboundMsg1)`), the outbound msg2 promote //! (`handle_msg2` looks up the dial-persisted machine), the connectionless //! outbound msg1 send (`SendHandshake` with `their_index == None` → -//! `send_stored_msg1`, driven from `initiate_connection`), and the +//! `send_stored_msg1`, driven from `initiate_connection`), the //! connection-oriented dial (`OpenTransport` performs the non-blocking //! `transport.connect`; `TransportConnected` drives the connect-resolution msg1 -//! send from `poll_pending_connects`). +//! send from `poll_pending_connects`), the rekey cadence (`check_rekey` → +//! `route_rekey_cadence` → `RekeyConsume`, driving the `SwapSendState` and +//! `CompleteDrain` arms), and the liveness reap (`route_link_dead` → +//! `LinkDeadSuspected`, driving `InvalidateSendState` → `remove_active_peer`). //! -//! Not yet driven, so their arms stay inert stubs: rekey/crypto installs, -//! link-control frames, and the connected-UDP plane. `RegisterDecryptSession` is -//! a deliberate no-op — see its arm for the note. +//! The genuine inert stubs remaining are `SendRekey`, `SendLinkMessage`, and +//! the connected-UDP arms. `RegisterDecryptSession` is a deliberate no-op — +//! see its arm for the note. //! //! The timer arms (`SetTimer`/`CancelTimer`) populate/clear the per-peer timer //! store (`peer_timers`). The `HandshakeRetransmit` and `HandshakeTimeout` @@ -386,13 +389,15 @@ impl Node { } } PeerAction::SwapSendState { .. } => { - // Initiator cutover. Reproduces the `ConnAction::Cutover` - // body in `handlers/rekey.rs:53-88` EXACTLY. `addr` is resolved + // Initiator cutover: the live authoritative rekey-cadence + // path, routed here from `check_rekey` via + // `route_rekey_cadence` → `PeerEvent::RekeyConsume`; the + // inline body survives only as `cutover_peer_inline`, a + // debug-assert release fallback. `addr` is resolved // from the ambient verified identity (as `InvalidateSendState` // does). The decrypt re-register folds HERE, gated on // `did_cutover` — the generic `RegisterDecryptSession` arm stays a - // no-op so a promote never double-registers. Shadow-only until the - // cadence fold routes here. + // no-op so a promote never double-registers. let node_addr = *ambient.verified_identity.node_addr(); let did_cutover = if let Some(peer) = self.peers.get_mut(&node_addr) { if let Some(_old_our_index) = peer.cutover_to_new_session() { @@ -436,12 +441,14 @@ impl Node { let _ = did_cutover; } PeerAction::CompleteDrain { peer: node_addr } => { - // Initiator drain completion. Reproduces the - // `ConnAction::Drain` body in `handlers/rekey.rs:90-111` EXACTLY. - // Extract the real previous index + transport_id under the peer - // borrow, drop the borrow, then run the cache_key cleanup (which - // takes &mut self for unregister_decrypt_worker_session). - // Shadow-only until the cadence fold routes here. + // Initiator drain completion: the live authoritative + // rekey-cadence path, routed here from `check_rekey` via + // `route_rekey_cadence` → `PeerEvent::RekeyConsume`; the + // inline body survives only as `drain_peer_inline`, a + // debug-assert release fallback. Extract the real previous + // index + transport_id under the peer borrow, drop the + // borrow, then run the cache_key cleanup (which takes + // &mut self for unregister_decrypt_worker_session). let drained = self.peers.get_mut(&node_addr).and_then(|peer| { peer.complete_drain().map(|idx| (idx, peer.transport_id())) }); diff --git a/src/node/handlers/handshake.rs b/src/node/handlers/handshake.rs index e479612..f87c2d9 100644 --- a/src/node/handlers/handshake.rs +++ b/src/node/handlers/handshake.rs @@ -1271,9 +1271,10 @@ impl Node { }; // The outbound Msg2 Promote step cancels the two dial-armed handshake // timers (the machine survives promotion, so they would otherwise linger - // in the driver's shadow store) and then promotes. The cancels are - // behavior-neutral at this rung — `peer_timers` is written but not yet - // driven — and `PromoteToActive` is still what performs the promotion. + // in `peer_timers` until `drive_peer_timers` lazily discards them — the + // promoted leg's `connections` entry is gone and the machine has left + // `SentMsg1`, so they can no longer fire) and then promotes. + // `PromoteToActive` is still what performs the promotion. debug_assert_eq!( promote_actions, vec![ diff --git a/src/node/mod.rs b/src/node/mod.rs index fb939b8..2770b4b 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -355,22 +355,27 @@ pub struct Node { // === Per-Peer Control Machines === /// Per-peer lifecycle control FSMs, keyed by the stable `LinkId` that spans - /// the handshake→active lifetime. A NEW parallel structure introduced by the + /// the handshake→active lifetime. A parallel structure introduced by the /// node-runtime decomposition: `connections`/`peers` stay byte-unchanged (hot - /// path pristine) and are cut over to this machine home path-by-path. Unwired - /// initially — the executor (`dataplane/peer_actions.rs`) and advance helper - /// exist but the live `handle_msg1`/`handle_msg2` path does not drive them yet; - /// the inbound cutover is wired later. - #[allow(dead_code)] + /// path pristine) and are cut over to this machine home path-by-path. + /// Machines are inserted at dial and inbound msg1, and stepped in production + /// by the handshake handlers, the rekey-cadence and liveness-reap routers, + /// and the lifecycle paths, with the executor (`dataplane/peer_actions.rs`) + /// performing the returned actions. Timer FIRING decisions remain + /// shell-side: `PeerEvent::Timeout` is never dispatched in production. peer_machines: HashMap, /// Per-peer timer store, keyed by `LinkId` then `TimerKind`, holding each /// armed timer's absolute deadline (ms). The sans-IO time-as-input backing /// for `PeerEvent::Timeout`: populated/cleared by the machine's /// `SetTimer`/`CancelTimer` actions (`dataplane/peer_actions.rs`) and dropped - /// alongside the machine through the `remove_peer_machine` choke-point. At - /// this rung it is a SHADOW of the legacy tick timers — written but not yet - /// read by any driver (the handshake-kind fold wires the reader). + /// alongside the machine through the `remove_peer_machine` choke-point. The + /// `HandshakeRetransmit`/`HandshakeTimeout` kinds are driven by + /// `drive_peer_timers` (the retransmit fires on the stored deadline; the + /// timeout reap keys on the timer's presence, with the threshold read from + /// config); the rekey/liveness kinds are still SHADOWS of their + /// own shell drivers, and the machine's `on_timeout` handlers stay dormant + /// (`PeerEvent::Timeout` is never dispatched in production). peer_timers: HashMap>, // === Peers (Active Phase) === diff --git a/src/peer/machine.rs b/src/peer/machine.rs index 9d1b8f1..2b546b8 100644 --- a/src/peer/machine.rs +++ b/src/peer/machine.rs @@ -3,9 +3,12 @@ //! The unified per-peer lifecycle state machine that folds the scattered //! `connections`/`peers`/rekey state carriers into one place. It provides the //! FSM types, the machine struct (control-tier state only), and the pure `step` -//! reducer, plus its unit tests. It is **unwired** — nothing in the codebase -//! calls it yet; the driver wiring and the send-state boundary land in later -//! commits. +//! reducer, plus its unit tests. `step` is driven in production by the +//! handshake handlers, the rekey-cadence and liveness-reap routers, and the +//! dial/lifecycle paths, with the executor in +//! `crate::node::dataplane::peer_actions` performing the returned actions. +//! Still dormant: `PeerEvent::Timeout` is never dispatched — timer FIRING +//! decisions stay with the shell drivers. //! //! ## Shape //! @@ -66,11 +69,14 @@ use crate::{NodeAddr, PeerIdentity}; // ============================================================================ // Timing placeholders // -// This module is unwired; the real intervals come from `NodeConfig` when the -// driver is wired. The `poll_*` cores already take the interval/backoff as -// arguments, so these are only used to compute `SetTimer{at_ms}` deadlines and -// the `Closed{backoff_deadline_ms}` park time. The unit tests assert on timer -// *kinds*, not exact deadlines. +// The `poll_*` cores already take the interval/backoff as arguments, so these +// are only used to compute `SetTimer{at_ms}` deadlines and the +// `Closed{backoff_deadline_ms}` park time. The handshake timers are armed +// live at dial time from these constants: the retransmit driver fires on the +// machine-armed deadline, while the timeout reaper keys on the timer's +// presence with its threshold read from `NodeConfig`, which also governs the +// reschedule cadence shell-side. The unit tests assert on timer *kinds*, not +// exact deadlines. // ============================================================================ const HANDSHAKE_RETRANSMIT_INTERVAL_MS: u64 = 1_000; @@ -1267,9 +1273,9 @@ impl PeerMachine { } fn on_tick(&mut self, now: u64) -> Vec { - // The driver evaluates due machine timers on the quantized tick and - // re-enters the Timeout{kind} handlers. This module is unwired; the - // deadline bookkeeping is threaded from the driver, so Tick is a no-op here. + // Dormant no-op: `PeerEvent::Tick` is not dispatched in production. + // The shell drivers evaluate due timer deadlines themselves, so there + // is no machine-side bookkeeping to advance here. let _ = now; Vec::new() }