Skip to main content

zenkey_fleet/report/
retired.rs

1//! The retirement plane (RFC 04 §1.2, RFC 09 §6): what a retired key family
2//! left behind, and how much of that was even asked about.
3
4use super::asked::Asked;
5use super::cutover::CutoverVerdict;
6use serde::Serialize;
7
8/// One `[[deprecated]]` ledger entry, judged (issue #226): the four facts of
9/// the burn-down, each honest about whether it was even obtainable, and a
10/// per-entry verdict in [`CutoverVerdict`]'s vocabulary — `retired` is
11/// `cutover` run per ledger line, and a fourth vocabulary would only give the
12/// same three states new names.
13#[derive(Debug, Clone, Serialize)]
14pub struct RetiredEntry {
15    /// The producer (or service) whose local slice carries the ledger entry.
16    pub producer: String,
17    /// The retired subject pattern, as the ledger spells it.
18    pub path: String,
19    /// Registry version the retirement was recorded in, when the ledger says.
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub since: Option<String>,
22    /// The declared replacement subject, if any (RFC 08 §3).
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub replaced_by: Option<String>,
25    /// The base-relative wire family this entry maps to. The ledger records
26    /// no class, so the class position is `*` — which by D2/D4 still cannot
27    /// reach a verbatim plane.
28    pub selector: String,
29    /// Fact 1: samples heard on the retired family over the listen window.
30    /// `NotAsked` = no window ran — not listened is not absent (RFC 09 §5.1
31    /// O4).
32    #[serde(skip_serializing_if = "Asked::is_not_asked", default)]
33    pub wire_samples: Asked<u64>,
34    /// Fact 2: a live producer's served introspect slice still declares the
35    /// retired path as an **active** subject — the RFC 08 §6.1 lie, a finding
36    /// in its own right. `None` = no served slice for this producer answered
37    /// (which, for a fully retired producer, is the desired end state).
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub still_declared: Option<bool>,
40    /// Fact 3: sessions declaring a subscriber intersecting the family
41    /// (admin space). `None` = no admin space answered — unknown, not zero.
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub subscribers: Option<usize>,
44    /// Fact 4: samples heard on `replaced_by` over the window — the per-entry
45    /// `cutover` pair. `NotAsked` = not listened, or no replacement declared
46    /// (a question that does not exist for this entry was not put — the
47    /// [`Asked`] reading covers both).
48    #[serde(skip_serializing_if = "Asked::is_not_asked", default)]
49    pub replacement_samples: Asked<u64>,
50    pub verdict: CutoverVerdict,
51}
52
53/// The `zenctl check retired` report (issue #226): the deprecation
54/// burn-down. The append-only ledger records dozens of individual
55/// retirements; this says which ones are actually *finished* — a migration
56/// without a burn-down list is a belief.
57#[derive(Debug, Clone, Serialize)]
58pub struct RetiredReport {
59    /// The registry directories the ledger was read from. Stated because the
60    /// coverage claim is exactly these files: a `--registry` dir may be one
61    /// team's slice of the fleet's ledger, and the report must not read as
62    /// fleet totality.
63    pub registries: Vec<String>,
64    pub entries: Vec<RetiredEntry>,
65    /// The listen window, when one ran. `NotAsked` = wire facts were not
66    /// asked.
67    #[serde(skip_serializing_if = "Asked::is_not_asked", default)]
68    pub window_s: Asked<f64>,
69    /// Samples heard under `<base>/v1/` over the window — the fleet's proof
70    /// of life, which is what lets a silent no-replacement entry pass rather
71    /// than a dead fleet passing every silence check for free (RFC 05 §3.1).
72    /// Gated with `window_s`.
73    #[serde(skip_serializing_if = "Asked::is_not_asked", default)]
74    pub plane_samples: Asked<u64>,
75    /// Samples the bounded observer missed during the window (O6): non-zero
76    /// weakens every silence claim and the report says so. `NotAsked` = no
77    /// listen window ran, so there was no observer to miss anything —
78    /// matching its sibling wire facts (`window_s`/`plane_samples`); an
79    /// unconditional `0` used to claim a clean observation nobody made
80    /// (RFC 09 §5.1 O4, review finding R6).
81    #[serde(skip_serializing_if = "Asked::is_not_asked", default)]
82    pub dropped: Asked<u64>,
83    /// Served introspect slices that answered (RFC 08 §6).
84    pub introspect_answered: usize,
85    /// Declared entities the admin sweep returned; `None` = no admin space
86    /// answered (`adminspace.enabled` defaults to false) — "not available",
87    /// never "nothing declared" (RFC 09 §5.1 O4).
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub admin_entities: Option<usize>,
90    /// Worst entry verdict: any failure fails, else any unproven, else pass.
91    pub verdict: CutoverVerdict,
92}