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}