Skip to main content

zenkey_fleet/
report.rs

1//! Typed reports — the shared contract between the engine and every frontend.
2//!
3//! Moved from zenctl (issue #34; the redesign doc called this move "one
4//! refactor unlocking scripting, tests, GUI parity"). A report struct is the
5//! stable output shape: zenctl renders it as a table or serde JSON/NDJSON,
6//! zengui renders it as widgets, and both stay in agreement because neither
7//! owns it.
8//!
9//! ## The placement rule
10//!
11//! > **Every serde-pinned wire shape in this crate lives under `report/`,
12//! > split by domain, and its pinned-shape test lives next to it. A type the
13//! > wire never sees stays in the module that computes it.**
14//!
15//! Serde is the whole test. A `#[derive(Serialize)]` (or `Deserialize`) on a
16//! public type means somebody outside this process reads it: a
17//! `--format json` consumer, a script, a `.zrec` file, a pane. That is a
18//! contract, it changes only deliberately, and it belongs where the contracts
19//! are. A type without it — `StatsTable`, `RetentionStats`, `SchemaStore`,
20//! `FetchedValue` — is an implementation detail of whichever module computes
21//! it, and moving it here would only put distance between it and its
22//! callers.
23//!
24//! The rule exists because there was none. Forty shapes lived in one
25//! 1,800-line `report.rs` while `WhyReport` lived in `why.rs`,
26//! `Transition` and `WatchdogSummary` in `condition.rs`, `ReplayReport` and
27//! `GenReport` in theirs, `LatencyReport` in `stats.rs`, `SliceDisagreement`
28//! in `registry.rs`, `ProducerInfo` in `roster.rs` — and nothing said which
29//! was right, so a new shape landed wherever it was first needed and the
30//! split widened by one every time. The rule has no exceptions: not for a
31//! shape that "belongs with its producer", not for a one-variant enum. An
32//! exception is how the last rule died.
33//!
34//! **Domains, not layers.** The files below are named for what a shape is
35//! *about* — `topic`, `doctor`, `blob`, `tape` — because that is the axis a
36//! reader looking for a shape thinks along. Which layer produced it
37//! ([`crate::bus`], [`crate::model`], [`crate::judge`], [`crate::tape`]) is
38//! deliberately not the axis: `topic` gathers the shapes of one plane
39//! whether they were observed, projected or judged, and splitting them by
40//! producer would scatter one contract across four files.
41//!
42//! **One public namespace.** The domain files are private modules,
43//! re-exported flat: every shape is spelled `zenkey_fleet::report::Thing`,
44//! never `report::topic::Thing`. There is one path to each item, so the
45//! split is free to be re-cut — a domain that grows can be halved, two that
46//! never differed can be merged — without a single call site moving. The
47//! files are organisation; the module is the interface.
48
49mod admin;
50mod asked;
51mod bench;
52mod blob;
53mod call;
54mod condition;
55mod cutover;
56mod discover;
57mod doctor;
58mod expect;
59mod field;
60mod generate;
61mod interface;
62mod judgement;
63mod node;
64mod rate;
65mod registry;
66mod retired;
67mod schema;
68mod scout;
69mod seed;
70mod service;
71mod tape;
72mod topic;
73mod why;
74
75pub use admin::*;
76pub use asked::*;
77pub use bench::*;
78pub use blob::*;
79pub use call::*;
80pub use condition::*;
81pub use cutover::*;
82pub use discover::*;
83pub use doctor::*;
84pub use expect::*;
85pub use field::*;
86pub use generate::*;
87pub use interface::*;
88pub use judgement::*;
89pub use node::*;
90pub use rate::*;
91pub use registry::*;
92pub use retired::*;
93pub use schema::*;
94pub use scout::*;
95pub use seed::*;
96pub use service::*;
97pub use tape::*;
98pub use topic::*;
99pub use why::*;