pub fn read_node_statuses(paths: &RunPaths) -> Result<Vec<(NodeId, Status)>>Expand description
The log-authoritative per-node status set for a run, replayed once from
events.jsonl — the source-of-truth alternative to a nodes/*.json
projection scan.
Returns every node a node.created event introduced, paired with the status
the log replays for it, deduped and sorted by numeric suffix (NodeId
order). Both the node set and each node’s status come from the log, so the
result includes a node whose node.created was fsynced while its projection
write was crash-interrupted (the node a nodes/*.json scan would silently
drop) and reports a node terminal whenever the log says so even if its
projection still reads live (the window crate::events documents the log
leading the projections through).
This is what lets a supervisor’s run-status roll-up stay log-authoritative:
terminalizing a run from the projection subset can miss a log-visible live
node and roll the run up while it is still running, which a later
rebuild_projections would then resurrect as live under a terminal run
(violating “a run must not terminalize while a log-visible node is live” —
issue rollup-status-log-authoritative). Feeding this into
aggregate_terminal_status closes that
window. It is the read half cancel_node’s in-lock self-roll-up already
uses via the cancel ledger; both now share the NodeStatusAcc state machine
so the supervisor tick and the cancel path can never diverge.
Reads through RunPaths::checked_events (a symlinked log is refused) and
shares the crate’s streaming torn-tail policy: a crash-truncated final line
is dropped, an interior unparseable line surfaces as
Error::CorruptEventLog, and a missing log yields an empty set.
Cost. One streaming pass over the whole log — O(total events), not
O(nodes). Memory stays bounded (each line is skimmed envelope + a few small
status fields, never the full node.report payload — a run with hundreds of
nodes and multi-KB reports is scanned without ever holding a report in
memory), but the work is linear in the event count, not the node count. A
caller that polls this every tick (the supervisor roll-up) re-reads from byte
0 each time; at the tool’s scale (tens of nodes, hundreds of events,
multi-second ticks) that is negligible, but an incremental fold that resumes
from the last consumed offset would be the optimization if a run’s log ever
grows large enough to matter.
§Errors
I/O errors reading the log, a rejected symlinked path, or an interior corrupt event line.