diff --git a/src/control/queries.rs b/src/control/queries.rs index 3efd27ec..58c148b8 100644 --- a/src/control/queries.rs +++ b/src/control/queries.rs @@ -2680,8 +2680,8 @@ mod tests { assert_snapshot("show_stats_history_all_peers", &render_response(resp)); } - /// The five Category-D queries cut over to off-loop serving in R3. Served - /// via `snapshot_dispatch`; coverage asserted in + /// The five derived/routing/cache queries served off-loop via + /// `snapshot_dispatch`; coverage asserted in /// `snapshot_dispatch_serves_category_d_queries` below. const OFF_LOOP_CATEGORY_D: &[&str] = &[ "show_tree", @@ -2691,7 +2691,7 @@ mod tests { "show_identity_cache", ]; - /// The six Category-E queries cut over to off-loop serving in R4. Served via + /// The six per-entity table queries served off-loop via /// `snapshot_dispatch`; coverage asserted in /// `snapshot_dispatch_serves_category_e_queries`. const OFF_LOOP_CATEGORY_E: &[&str] = &[ @@ -2703,7 +2703,7 @@ mod tests { "show_mmp", ]; - /// Milestone-completion contract: every pure-read `show_*` query is served + /// Contract: every pure-read `show_*` query is served /// off-loop via `snapshot_dispatch`, and the rx_loop control path carries no /// `show_*` arm at all — only the mutating COMMAND handlers (`connect` / /// `disconnect`) reach it. This test enumerates the full read surface and @@ -2777,8 +2777,8 @@ mod tests { /// the rx_loop source carries no `queries::dispatch` call and no /// `starts_with("show_")` routing branch. Reads the committed source of /// `src/node/handlers/rx_loop.rs` and asserts both markers are absent. This - /// is the milestone's "remove `show_*` from the data-plane dispatch path" - /// invariant, guarded against regression. + /// guards the "no `show_*` on the data-plane dispatch path" invariant + /// against regression. #[test] fn rx_loop_has_no_show_dispatch() { let src = include_str!("../node/handlers/rx_loop.rs"); @@ -2841,10 +2841,10 @@ mod tests { } } - /// The R1/R2 scalar-and-series queries are served off-loop via + /// The scalar-and-series queries are served off-loop via /// `snapshot_dispatch`; mutations return `None` and take the rx_loop COMMAND /// path. (`show_stats_peers` / `show_stats_history_all_peers`, formerly - /// asserted on-loop here, were cut over in R5 — see + /// asserted on-loop here, are now served off-loop too — see /// `snapshot_dispatch_serves_every_read_query` for the full read surface.) #[test] fn snapshot_dispatch_serves_scalar_and_series_queries() { @@ -2879,7 +2879,7 @@ mod tests { "show_stats_all_history", Some(json!({ "window": "10s", "granularity": "1s" })), ), - // R3 Category-D cutover. + // Derived/routing/cache queries, served off-loop. ("show_tree", None), ("show_bloom", None), ("show_cache", None), @@ -2901,7 +2901,7 @@ mod tests { } } - /// R5 cutover + byte-identity: after a `record_stats_history()` tick the + /// Byte-identity: after a `record_stats_history()` tick the /// off-loop `show_acl` / `show_stats_peers` / `show_stats_history_all_peers` /// renders each equal their on-loop oracle byte-for-byte, and all three are /// served off-loop via `snapshot_dispatch`. @@ -2987,7 +2987,7 @@ mod tests { assert_eq!(snap.connection_count, node.connection_count()); assert_eq!(snap.estimated_mesh_size, node.estimated_mesh_size()); assert_eq!(snap.effective_ipv6_mtu, node.effective_ipv6_mtu()); - // R5: the ACL status projection matches the node's live ACL status. + // The ACL status projection matches the node's live ACL status. assert_eq!(snap.acl_status, node.peer_acl_status()); // Off-loop render must equal the on-loop render byte-for-byte. @@ -2999,9 +2999,9 @@ mod tests { ); } - /// The five Category-D queries are served off-loop via `snapshot_dispatch` - /// (return `Some` with status ok); everything not cut over stays on the - /// rx_loop path (`None`). + /// The five derived/routing/cache queries are served off-loop via + /// `snapshot_dispatch` (return `Some` with status ok); everything not cut + /// over stays on the rx_loop path (`None`). #[test] fn snapshot_dispatch_serves_category_d_queries() { use super::super::protocol::Request; @@ -3024,7 +3024,7 @@ mod tests { } // Mutations take the rx_loop COMMAND path. (Every read query, including - // the per-peer stats-series queries, is served off-loop as of R5.) + // the per-peer stats-series queries, is served off-loop.) for cmd in ["connect", "disconnect"] { assert!( snapshot_dispatch(&req(cmd), &handle).is_none(), @@ -3034,7 +3034,7 @@ mod tests { } /// The tick-published `RoutingSnapshot` reflects node state, and each - /// off-loop Category-D render equals its on-loop render byte-for-byte + /// off-loop routing render equals its on-loop render byte-for-byte /// (modulo the volatile-key redaction the wire-schema tests already apply). #[test] fn routing_snapshot_matches_on_loop_after_tick() { @@ -3093,10 +3093,11 @@ mod tests { ); } - // ---- R4 Category-E coverage ------------------------------------------ + // ---- per-entity table coverage --------------------------------------- - /// The six Category-E queries are served off-loop via `snapshot_dispatch` - /// (return `Some` with status ok); mutations take the rx_loop COMMAND path. + /// The six per-entity table queries are served off-loop via + /// `snapshot_dispatch` (return `Some` with status ok); mutations take the + /// rx_loop COMMAND path. #[test] fn snapshot_dispatch_serves_category_e_queries() { use super::super::protocol::Request; @@ -3119,7 +3120,7 @@ mod tests { } // Mutations take the rx_loop COMMAND path. (Every read query is served - // off-loop as of R5.) + // off-loop.) for cmd in ["connect", "disconnect"] { assert!( snapshot_dispatch(&req(cmd), &handle).is_none(), @@ -3129,7 +3130,7 @@ mod tests { } /// Freshness + fidelity: after a `record_stats_history()` tick (the entity - /// publisher site) each off-loop Category-E render equals its on-loop render + /// publisher site) each off-loop per-entity render equals its on-loop render /// byte-for-byte, and the seeded snapshot is empty before the first tick. #[test] fn entity_snapshot_matches_on_loop_after_tick() { @@ -3179,7 +3180,7 @@ mod tests { ); } - /// Structural sharing (the R4 umbrella mandate): a republish in which only + /// Structural sharing: a republish in which only /// one row changed re-allocates only that one `Arc` — every unchanged /// row is reused by pointer (`Arc::ptr_eq`). Exercises /// [`reconcile_rows`](super::super::snapshot::reconcile_rows), the diff --git a/src/control/read_handle.rs b/src/control/read_handle.rs index 030dc83e..acca38eb 100644 --- a/src/control/read_handle.rs +++ b/src/control/read_handle.rs @@ -53,14 +53,14 @@ pub(crate) struct ControlReadHandle { /// Metrics registry (counters / gauges) for `show_stats_*`. metrics: Arc, /// stats_history dual-ring read copy + the scalar gauges/counts - /// `show_status` needs, published from the tick (R2, Q1-b). + /// `show_status` needs, published from the tick. stats: Arc>, - /// Category-D derived/routing/cache read view (tree / bloom / coord / - /// identity + F-queue scalars), published from the tick (R3). + /// Derived/routing/cache read view (tree / bloom / coord / + /// identity + F-queue scalars), published from the tick. routing: Arc>, - /// Category-E per-entity table read view (peers / sessions / links / + /// Per-entity table read view (peers / sessions / links / /// connections / transports + mmp), published from the tick with - /// `Vec>` structural sharing (R4). + /// `Vec>` structural sharing. entities: Arc>, } @@ -96,19 +96,19 @@ impl ControlReadHandle { } /// Load the latest published stats snapshot (the freshest available by - /// construction; no IO_TIMEOUT staleness gate, per Q1-e). + /// construction; no IO_TIMEOUT staleness gate). pub(crate) fn stats(&self) -> arc_swap::Guard> { self.stats.load() } - /// Load the latest published Category-D routing snapshot (freshest - /// available by construction; no staleness gate, per Q1-e). + /// Load the latest published routing snapshot (freshest + /// available by construction; no staleness gate). pub(crate) fn routing(&self) -> arc_swap::Guard> { self.routing.load() } - /// Load the latest published Category-E entity snapshot (freshest available - /// by construction; no staleness gate, per Q1-e). + /// Load the latest published entity snapshot (freshest available + /// by construction; no staleness gate). pub(crate) fn entities(&self) -> arc_swap::Guard> { self.entities.load() } @@ -133,18 +133,18 @@ pub(crate) fn snapshot_dispatch(request: &Request, handle: &ControlReadHandle) - )), "show_stats_list" => Some(Response::ok(queries::show_stats_list())), "show_metrics" => Some(Response::ok(queries::show_metrics_from_handle(handle))), - // R5: peer-ACL status, served from the tick-published `StatsSnapshot`. + // Peer-ACL status, served from the tick-published `StatsSnapshot`. // The ACL is an `arc_swap::ArcSwap` reloaded only on the tick; // its status projection is captured at the same tick. "show_acl" => Some(Response::ok(queries::show_acl_from_handle(handle))), - // R2: served from the tick-published `StatsSnapshot` (rings + scalar + // Served from the tick-published `StatsSnapshot` (rings + scalar // gauges/counts). `show_status` and the two node-level/per-peer series // queries carry enough data in the snapshot to render faithfully // off-loop, including the parameterized series selectors (the snapshot // holds the full rings, so any metric / window / granularity is // satisfiable). // - // R5 closes out the per-peer stats queries: `show_stats_peers` and + // The per-peer stats queries: `show_stats_peers` and // `show_stats_history_all_peers` now read the snapshot's per-peer // `peer_meta` (live `is_active`, resolved npub / display name, captured // at publish time) joined against the `history` rings, so they no longer @@ -163,7 +163,7 @@ pub(crate) fn snapshot_dispatch(request: &Request, handle: &ControlReadHandle) - handle, request.params.as_ref(), )), - // R3: served from the tick-published `RoutingSnapshot` (tree / bloom / + // Served from the tick-published `RoutingSnapshot` (tree / bloom / // coord cache / identity cache + F-queue scalars). Display names are // resolved at publish time, so these render entirely off-loop. The // counter-family `stats` blocks come from the `MetricsRegistry` (also @@ -175,7 +175,7 @@ pub(crate) fn snapshot_dispatch(request: &Request, handle: &ControlReadHandle) - "show_identity_cache" => Some(Response::ok(queries::show_identity_cache_from_handle( handle, ))), - // R4: served from the tick-published `EntitySnapshot` (per-entity + // Served from the tick-published `EntitySnapshot` (per-entity // `Vec>` tables with structural sharing). Display names, // tree-relationship flags, and Nostr-traversal state are resolved at // publish time, so these render entirely off-loop. All six are diff --git a/src/control/snapshot.rs b/src/control/snapshot.rs index c7132202..042f79b3 100644 --- a/src/control/snapshot.rs +++ b/src/control/snapshot.rs @@ -12,10 +12,10 @@ //! peer / session / link / connection / transport counts), plus //! `peer_aliases` (effectively immutable after construction). //! -//! The snapshot holds *data*, not rendered `Response` envelopes (Q1-d): +//! The snapshot holds *data*, not rendered `Response` envelopes: //! rendering happens in the control task off the rx_loop. Staleness is bounded //! by the tick interval and is never staler than the underlying data, which -//! also advances only on the tick (Q1-b). +//! also advances only on the tick. use std::collections::HashMap; use std::sync::Arc; @@ -27,7 +27,7 @@ use crate::node::stats_history::StatsHistory; use crate::upper::tun::TunState; /// Read-only snapshot of the stats-history rings plus the scalar gauges and -/// counts `show_status` reports. Published from the tick (Q1-b). +/// counts `show_status` reports. Published from the tick. #[derive(Clone)] pub(crate) struct StatsSnapshot { /// Cloned read copy of the history rings (the dual-ring read side). @@ -65,14 +65,14 @@ pub(crate) struct StatsSnapshot { pub peer_aliases: Arc>, /// Loaded peer-ACL status (`show_acl`). The ACL itself is an /// `arc_swap::ArcSwap` mutated only by the tick's `reload_peer_acl`; - /// the human-readable status is a cheap projection of it (R5). + /// the human-readable status is a cheap projection of it. pub acl_status: PeerAclStatus, /// Per-stats-history-peer metadata resolved against the live peer/session /// tables and host map at publish time (`show_stats_peers` / /// `show_stats_history_all_peers`), keyed by `NodeAddr`. The lifecycle /// timestamps and per-peer metric rings stay in `history`; this map carries /// only the cross-subsystem fields a renderer can't derive from the rings - /// alone (`is_active`, resolved `npub`, resolved `display_name`) (R5). + /// alone (`is_active`, resolved `npub`, resolved `display_name`). pub peer_meta: Arc>, } @@ -137,10 +137,10 @@ fn empty_acl_status() -> PeerAclStatus { } // ===================================================================== -// RoutingSnapshot (R3 — Category-D derived/routing/cache read view) +// RoutingSnapshot (derived/routing/cache read view) // ===================================================================== -/// Read-only snapshot of the Category-D derived/routing/cache subsystems that +/// Read-only snapshot of the derived/routing/cache subsystems that /// the pure-snapshot `show_tree` / `show_bloom` / `show_cache` / `show_routing` /// / `show_identity_cache` queries render. Published via `ArcSwap`. /// @@ -149,22 +149,22 @@ fn empty_acl_status() -> PeerAclStatus { /// cohesive routing view holding the four subsystems (tree / bloom / coord /// cache / identity cache) plus the F-queue summary scalars. /// -/// **Publisher placement (Q1).** The four subsystems mutate at many scattered +/// **Publisher placement.** The four subsystems mutate at many scattered /// handler sites (28 `coord_cache_mut` call sites, 16 `tree_state_mut`, ~32 /// identity-cache touches), and every projected row needs a *display name* -/// resolved against the live peer/session tables and host map — Category-E +/// resolved against the live peer/session tables and host map — per-entity /// state reachable only with `&Node`. Wiring an on-change `publish_*` at each /// mutation site would be large, error-prone surgery, and each call would still /// need `&Node` to resolve names across subsystem boundaries. So this snapshot -/// is published from the **tick** (Q1-b acceptable-at-mutator / the documented -/// interim the spec permits, mirroring R2's stats publish): the tick is the one -/// site with coherent `&Node` access to resolve all display names together. A +/// is published from the **tick**, the same placement the stats snapshot +/// above uses: the tick is the one site with coherent `&Node` access to +/// resolve all display names together. A /// single combined cell is the natural shape because there is exactly one /// publisher — the multi-mutator "rebuild the whole snapshot N times" hazard -/// that Q1-c warns against does not arise. +/// does not arise. /// /// The snapshot holds *data* (typed rows + scalars), not rendered `Response` -/// envelopes (Q1-d); rendering happens off the rx_loop in the control task. The +/// envelopes; rendering happens off the rx_loop in the control task. The /// counter-family `stats` blocks the queries also emit come from the /// `MetricsRegistry` (already `Arc`-shared in the handle) at render time, not /// from this snapshot. @@ -173,9 +173,9 @@ fn empty_acl_status() -> PeerAclStatus { /// the captured absolute timestamps, so the rendered age stays fresh relative /// to the read, exactly as the on-loop queries computed it. /// -/// Forward-compat: when step 5 structurally extracts the Category-D subsystems -/// into typed types, these projections become thin views over them without -/// changing the read-handle interface or this publisher placement. +/// Forward-compat: if the derived/routing/cache subsystems are later +/// extracted into typed types, these projections become thin views over them +/// without changing the read-handle interface or this publisher placement. #[derive(Clone)] pub(crate) struct RoutingSnapshot { /// Spanning-tree read view (`show_tree`). @@ -396,10 +396,10 @@ pub(crate) struct IdentityRow { } // ===================================================================== -// EntitySnapshot (R4 — Category-E per-entity table read views) +// EntitySnapshot (per-entity table read views) // ===================================================================== -/// Read-only snapshot of the Category-E per-entity tables that the +/// Read-only snapshot of the per-entity tables that the /// pure-snapshot `show_peers` / `show_sessions` / `show_links` / /// `show_connections` / `show_transports` / `show_mmp` queries render. /// Published via `ArcSwap`. @@ -407,7 +407,7 @@ pub(crate) struct IdentityRow { /// This is the `entities` cell: peers / sessions / links / connections / /// transports, published per-entity with `Vec>` structural sharing. /// -/// **Structural sharing (the umbrella mandate).** Every entity table is a +/// **Structural sharing.** Every entity table is a /// `Vec>`, so a republish in which only one row changed re-allocates /// only that one `Arc` — the unchanged rows are reused by pointer from the /// previous snapshot (`Arc::ptr_eq`-stable). The publisher diffs each freshly @@ -415,10 +415,11 @@ pub(crate) struct IdentityRow { /// keeps the old `Arc` when they are equal. A clone of the snapshot for each /// accepted control connection is then a vector of cheap pointer clones, not a /// deep table copy. This is what keeps the per-tick publish cost off the hot -/// path at scale, as the umbrella requires for R4. +/// path at scale. /// -/// **Publisher placement (Q1).** Like R3, this is published from the **tick**, -/// not per-mutator. Two reasons, both stronger than for R3: +/// **Publisher placement.** Like the routing snapshot, this is published from +/// the **tick**, not per-mutator. Two reasons, both stronger here than for the +/// routing snapshot: /// /// 1. Every projected row needs a *display name* resolved against the live /// peer/session tables and host map (`&Node`), and `show_peers` additionally @@ -429,23 +430,22 @@ pub(crate) struct IdentityRow { /// metrics, `last_seen`, noise counters, replay/decrypt counters) are /// mutated continuously on the **data plane / rx_loop**, not at the discrete /// peer/session/link lifecycle mutators. Per-lifecycle-mutator publication -/// (Q1-a) would therefore not even capture freshness for those fields; the +/// would therefore not even capture freshness for those fields; the /// tick is the natural cadence at which this read view advances. /// -/// The diff-and-reuse therefore satisfies the structural-sharing goal the -/// umbrella mandates (only changed rows re-allocate) while keeping a single -/// coherent `&Node` publisher — the "no monolithic per-tick *re-allocation* of -/// every row" warning is honored because unchanged rows are reused, not rebuilt. -/// This is the documented acceptable interim (the spec's tick-publish-with- -/// Arc-reuse fallback), consistent with R3. +/// The diff-and-reuse therefore satisfies the structural-sharing goal (only +/// changed rows re-allocate) while keeping a single coherent `&Node` +/// publisher: there is no monolithic per-tick *re-allocation* of every row, +/// because unchanged rows are reused rather than rebuilt. This is the same +/// tick publish placement the routing snapshot uses, for the same reason. /// -/// The snapshot holds typed rows (Q1-d data, not rendered `Response` +/// The snapshot holds typed rows (data, not rendered `Response` /// envelopes). Time-relative fields (`idle_ms`) are derived at render time from /// captured absolute timestamps, so the rendered age stays fresh relative to /// the read, exactly as the on-loop queries computed it. /// -/// Forward-compat: step 10 later extracts the session table into a typed -/// `(transport_id, our_index)`-indexed type; these projections then become thin +/// Forward-compat: if the session table is later extracted into a typed +/// `(transport_id, our_index)`-indexed type, these projections become thin /// views over it without changing the read-handle interface or this publisher /// placement. #[derive(Clone)] @@ -733,7 +733,7 @@ pub(crate) struct MmpSessionRow { /// one, preserving structural sharing: an `Arc` from `prev` is reused /// (kept by pointer) whenever a new row matches an old row by identity `key` /// **and** compares equal by value, so only changed/new rows allocate a fresh -/// `Arc`. This is the `Vec>` discipline the R4 umbrella mandates — a +/// `Arc`. This is the `Vec>` structural-sharing discipline — a /// single-row change re-allocates one row, not the whole table, keeping the /// per-tick publish cost off the hot path at scale. /// diff --git a/src/node/mod.rs b/src/node/mod.rs index 8e9f8b78..e5efad54 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -440,19 +440,19 @@ pub struct Node { /// live mutable `stats_history` above stays on the tick. stats_snapshot: std::sync::Arc>, - /// Read-side snapshot of the Category-D derived/routing/cache subsystems + /// Read-side snapshot of the derived/routing/cache subsystems /// (tree / bloom / coord cache / identity cache + F-queue scalars) that the /// `show_tree` / `show_bloom` / `show_cache` / `show_routing` / /// `show_identity_cache` queries render off the rx_loop. Published from the - /// tick (see [`Self::publish_routing_snapshot`] for the Q1 rationale). + /// tick (see [`Self::publish_routing_snapshot`] for the rationale). routing_snapshot: std::sync::Arc>, - /// Read-side snapshot of the Category-E per-entity tables (peers / sessions + /// Read-side snapshot of the per-entity tables (peers / sessions /// / links / connections / transports + mmp) that the `show_peers` / /// `show_sessions` / `show_links` / `show_connections` / `show_transports` /// / `show_mmp` queries render off the rx_loop. Published from the tick with /// `Vec>` structural sharing (unchanged rows reused by pointer); - /// see [`Self::publish_entities_snapshot`] for the Q1 rationale. + /// see [`Self::publish_entities_snapshot`] for the rationale. entities_snapshot: std::sync::Arc>, // === TUN Interface === @@ -1596,14 +1596,14 @@ impl Node { self.stats_history.tick(now, &snap, &peer_snaps); - // Publish the read-side snapshot (R2 dual-ring, Q1-b). The tick is the + // Publish the read copy of the dual-ring stats snapshot. The tick is the // natural and sole mutator of `stats_history`, so publishing here can // never produce false staleness: the snapshot and the underlying data - // advance together. This is data, not a rendered response (Q1-d), and - // it is published only here, not in a monolithic per-tick rebuild of - // every query (Q1-c). It also is not gated behind any slow I/O on the - // tick the way the abandoned 2edc8a1 republish was. - // Per-stats-history-peer metadata (R5). `show_stats_peers` / + // advance together. What is published is data, not a rendered response, + // and it is published only here, rather than as a monolithic per-tick + // rebuild of every query's result. It also is not gated behind any slow + // I/O on the tick the way the abandoned 2edc8a1 republish was. + // Per-stats-history-peer metadata. `show_stats_peers` / // `show_stats_history_all_peers` need each tracked peer's live // membership (`is_active`), resolved npub, and display name — all // cross-subsystem reads against the live peer table and host map, @@ -1671,11 +1671,11 @@ impl Node { }; self.stats_snapshot.store(std::sync::Arc::new(snapshot)); - // Publish the Category-D routing read view alongside the stats + // Publish the routing read view alongside the stats // snapshot, from the same tick. self.publish_routing_snapshot(); - // Publish the Category-E per-entity read view from the same tick, with + // Publish the per-entity read view from the same tick, with // `Vec>` structural sharing against the previous snapshot. self.publish_entities_snapshot(); } @@ -1702,26 +1702,26 @@ impl Node { None } - /// Project the Category-D derived/routing/cache state into a + /// Project the derived/routing/cache state into a /// [`RoutingSnapshot`](crate::control::snapshot::RoutingSnapshot) and /// publish it via `ArcSwap`, so `show_tree` / `show_bloom` / `show_cache` /// / `show_routing` / `show_identity_cache` render off the rx_loop. /// - /// **Q1 publisher placement.** The four projected subsystems (tree / bloom + /// **Publisher placement.** The four projected subsystems (tree / bloom /// / coord cache / identity cache) mutate at dozens of scattered handler /// sites, and every projected row carries a *display name* resolved against /// the live peer/session tables and host map — state reachable only with - /// `&Node`. Per-mutator on-change publication (Q1-a) would therefore be - /// large, error-prone surgery, and each call would still need `&Node` to - /// resolve names across subsystem boundaries. So this projection is - /// published from the tick — the documented acceptable interim (the spec's - /// "publish from the tick" allowance, mirroring R2's stats publish). The - /// tick is the one site with coherent `&Node` access to resolve every - /// display name together. A single combined cell is the natural shape - /// because there is exactly one publisher, so the multi-mutator - /// whole-snapshot-rebuild hazard Q1-c warns against does not arise. + /// `&Node`. Publishing on change from each individual mutator would + /// therefore be large, error-prone surgery, and each call would still need + /// `&Node` to resolve names across subsystem boundaries. So this projection + /// is published from the tick instead, the same placement the stats + /// snapshot above uses. The tick is the one site with coherent `&Node` + /// access to resolve every display name together. A single combined cell is + /// the natural shape because there is exactly one publisher, so the + /// whole-snapshot-rebuild hazard that afflicts multi-mutator designs does + /// not arise. /// - /// The snapshot holds typed rows + scalars (Q1-d data, not rendered + /// The snapshot holds typed rows + scalars (data, not rendered /// responses); the counter-family `stats` blocks the queries also emit are /// served from the `MetricsRegistry` (already `Arc`-shared) at render time. fn publish_routing_snapshot(&self) { @@ -1903,33 +1903,34 @@ impl Node { self.routing_snapshot.store(std::sync::Arc::new(snapshot)); } - /// Project the Category-E per-entity tables (peers / sessions / links / + /// Project the per-entity tables (peers / sessions / links / /// connections / transports + mmp) into an /// [`EntitySnapshot`](crate::control::snapshot::EntitySnapshot) and publish /// it via `ArcSwap`, so `show_peers` / `show_sessions` / `show_links` / /// `show_connections` / `show_transports` / `show_mmp` render off the /// rx_loop. /// - /// **Q1 publisher placement (tick, like R3).** Every projected row needs a - /// display name resolved against the live peer/session tables and host map + /// **Publisher placement (from the tick, as with the routing snapshot + /// above).** Every projected row needs a display name resolved against the + /// live peer/session tables and host map /// (`&Node`); `show_peers` additionally needs the live tree state to derive /// `is_parent` / `is_child` plus the Nostr-discovery failure-state map — /// cross-subsystem reads available only with `&Node`. And most fields /// (link/session traffic counters, MMP metrics, `last_seen`, noise counters) /// mutate continuously on the data plane, not at the discrete entity - /// lifecycle mutators, so per-lifecycle-mutator publication (Q1-a) would not - /// capture their freshness anyway. The tick is the natural cadence with - /// coherent `&Node` access. + /// lifecycle mutators, so publishing on change from each lifecycle mutator + /// would not capture their freshness anyway. The tick is the natural + /// cadence with coherent `&Node` access. /// - /// **Structural sharing (the R4 umbrella mandate).** Each table is a - /// `Vec>`. The freshly-projected rows are reconciled against the + /// **Structural sharing.** Each table is a `Vec>`. + /// The freshly-projected rows are reconciled against the /// previously published snapshot via /// [`reconcile_rows`](crate::control::snapshot::reconcile_rows): a row's /// `Arc` is reused (kept by pointer) whenever it matches the prior row by /// identity and compares equal by value, so a tick in which only one /// peer/session changed re-allocates only that one row, not the whole table. - /// This is what keeps the publish cost off the hot path at scale (the exact - /// thing the umbrella warns a naive per-tick rebuild would violate). + /// This is what keeps the publish cost off the hot path at scale, which a + /// naive whole-table rebuild on every tick would not. fn publish_entities_snapshot(&self) { use crate::control::snapshot as snap;