Skip to main content

zenkey_fleet/report/
node.rs

1//! The node plane (RFC 04 §5): who is up, what they run, and which
2//! deployment bases were seen at all.
3
4use serde::Serialize;
5
6/// One producer on one origin — row-shaped so a `--watch` loop can diff it
7/// and a GUI can select it (`origin/producer` is the stable row identity).
8#[derive(Debug, Clone, Serialize)]
9pub struct NodeRow {
10    pub origin: String,
11    /// Live producer name (from the liveliness token; zero payload).
12    pub producer: String,
13    /// The producing app, when a registry slice joined (`--verbose`).
14    /// `None` = not asked / no slice — never a default (O4).
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub app: Option<String>,
17    /// Registry version from the joined slice, same provenance rule.
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub registry_version: Option<String>,
20}
21
22#[derive(Debug, Clone, Serialize)]
23pub struct NodeList {
24    pub nodes: Vec<NodeRow>,
25    /// Whether a slice join was even attempted (`--verbose`) — keeps "asked,
26    /// no slice served" distinguishable from "not asked" in rows whose
27    /// `app`/`registry_version` are `None` (O4).
28    pub slices_joined: bool,
29}
30
31#[derive(Debug, Clone, Serialize)]
32pub struct BaseList {
33    /// Discovered bases; `base` is a plain string, `""` for the empty base.
34    pub bases: Vec<crate::DiscoveredBase>,
35}
36
37/// One producer's story on one node — the enrichment §6.3 promised
38/// (issue #40). Every field is honest about its provenance: absent
39/// introspection is `None`, never a default (RFC 09 §5.1 O4).
40#[derive(Debug, Clone, serde::Serialize)]
41pub struct ProducerInfo {
42    pub name: String,
43    /// A liveliness token stands (RFC 04 §5 — the only presence signal).
44    pub alive: bool,
45    /// From this origin's served introspect slice, when it answered.
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub app: Option<String>,
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub registry_version: Option<String>,
50    pub subjects: usize,
51    pub procedures: usize,
52    #[serde(skip_serializing_if = "Vec::is_empty", default)]
53    pub blob_tiers: Vec<String>,
54    /// Declared `@media` streams (RFC 08 §2/§6, v1.16 — the slice finally
55    /// carries what §6 claimed through v1.7): discoverable off the bus, so
56    /// a viewer can enumerate streams without a compiled-in registry.
57    #[serde(skip_serializing_if = "Vec::is_empty", default)]
58    pub media: Vec<MediaStreamInfo>,
59    /// Deprecated subjects this build still serves — RFC 08 §6's headline
60    /// buy ("which hosts still serve a deprecated subject").
61    pub deprecated_served: usize,
62}
63
64/// One declared media stream, as the slice states it (RFC 08 §2).
65#[derive(Debug, Clone, serde::Serialize)]
66pub struct MediaStreamInfo {
67    /// The stream pattern after `@media/<producer>/`.
68    pub path: String,
69    /// The declared wire encoding (`image/jpeg`, `video/*`).
70    pub encoding: String,
71}
72
73/// Freshness of one declared state subject on this node (RFC 04 §1.2).
74#[derive(Debug, Clone, serde::Serialize)]
75pub struct Freshness {
76    pub producer: String,
77    pub path: String,
78    pub ttl_s: i64,
79    /// Seconds since the newest matching sample's HLC stamp; `None` when no
80    /// sample answered — which is "not seen", not "fresh" (O4).
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub age_s: Option<i64>,
83    /// `age > ttl`, or no sample at all for a declared live subject.
84    pub stale: bool,
85}
86
87/// One node, joined: liveliness × introspect × state freshness.
88#[derive(Debug, Clone, serde::Serialize)]
89pub struct NodeInfo {
90    pub origin: String,
91    pub producers: Vec<ProducerInfo>,
92    #[serde(skip_serializing_if = "Vec::is_empty", default)]
93    pub freshness: Vec<Freshness>,
94}