Drop refactor-stage labels from the control-plane comments

Eighty-one comment lines named stages of the refactor that moved the
control-plane read path off the event loop: R0 through R5, category letters,
cut-over steps. Those names existed only in my planning notes, so a reader of
the published source could not resolve any of them. Each comment keeps what it
says about the code and loses the stage it happened during.

Five blocks in the node module are copied from the branch above rather than
rewritten here. That branch had already been partly de-jargoned upstream, and
its rewrite re-wrapped lines that carry no label at all, so deriving the text
independently would have produced something plausible and different, and the
merge would have conflicted. The copied text is byte-identical, checked by
hash on both sides.

Three bare commit hashes are deliberately left alone. None is reachable from
any branch, but one sits inside the text above, so removing it would break
that identity.
This commit is contained in:
Johnathan Corgan
2026-08-16 13:08:41 +00:00
parent 09215db909
commit 2970bd07eb
4 changed files with 107 additions and 105 deletions
+23 -22
View File
@@ -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<Row>` — every unchanged
/// row is reused by pointer (`Arc::ptr_eq`). Exercises
/// [`reconcile_rows`](super::super::snapshot::reconcile_rows), the
+15 -15
View File
@@ -53,14 +53,14 @@ pub(crate) struct ControlReadHandle {
/// Metrics registry (counters / gauges) for `show_stats_*`.
metrics: Arc<MetricsRegistry>,
/// 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<ArcSwap<StatsSnapshot>>,
/// 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<ArcSwap<RoutingSnapshot>>,
/// 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<Arc<Row>>` structural sharing (R4).
/// `Vec<Arc<Row>>` structural sharing.
entities: Arc<ArcSwap<EntitySnapshot>>,
}
@@ -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<Arc<StatsSnapshot>> {
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<Arc<RoutingSnapshot>> {
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<Arc<EntitySnapshot>> {
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<PeerAcl>` 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<Arc<Row>>` 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
+34 -34
View File
@@ -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<HashMap<NodeAddr, String>>,
/// Loaded peer-ACL status (`show_acl`). The ACL itself is an
/// `arc_swap::ArcSwap<PeerAcl>` 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<HashMap<NodeAddr, StatsPeerMeta>>,
}
@@ -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<Arc<Row>>` structural sharing.
///
/// **Structural sharing (the umbrella mandate).** Every entity table is a
/// **Structural sharing.** Every entity table is a
/// `Vec<Arc<Row>>`, so a republish in which only one row changed re-allocates
/// only that one `Arc<Row>` — 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<Row>` 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<Arc<Row>>` discipline the R4 umbrella mandates — a
/// `Arc`. This is the `Vec<Arc<Row>>` 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.
///
+35 -34
View File
@@ -440,19 +440,19 @@ pub struct Node {
/// live mutable `stats_history` above stays on the tick.
stats_snapshot: std::sync::Arc<arc_swap::ArcSwap<crate::control::snapshot::StatsSnapshot>>,
/// 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<arc_swap::ArcSwap<crate::control::snapshot::RoutingSnapshot>>,
/// 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<Arc<Row>>` 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<arc_swap::ArcSwap<crate::control::snapshot::EntitySnapshot>>,
// === 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<Arc<Row>>` 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<Arc<Row>>`. The freshly-projected rows are reconciled against the
/// **Structural sharing.** Each table is a `Vec<Arc<Row>>`.
/// 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;