Skip to main content

zenkey_fleet/report/
generate.rs

1//! The generator plane (#162): what a mock producer was asked to publish,
2//! and what it actually did.
3//!
4//! Every plan entry and every report here describes traffic that **did not
5//! happen on a real fleet**. The synthetic marker rides the samples
6//! themselves; these shapes are the paper trail that says a run was a
7//! rehearsal.
8
9use serde::Serialize;
10use zenkey::schema::TypeSchema;
11
12/// A single deliberate deviation from a known-valid synthesized sample (#163).
13///
14/// Fault injection is a *mode of the generator*, not a sibling tool: it reuses
15/// the whole registry walk, synthesis, scheduling, and guard machinery, then
16/// perturbs one dimension of the output **after synthesis** — so the delta
17/// from valid is always known, printable (`Fault::delta`), and stamped into
18/// the marker (`"fault": "<kind>"`, RFC 09 §5.3). The point is
19/// consumer-robustness testing: a consumer that crashes on a truncated payload
20/// fails RFC 09 §5.1 O1's spirit — a non-conforming sample is a fact to
21/// report, not an error to die on.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
23#[serde(rename_all = "kebab-case")]
24pub enum Fault {
25    /// Cut the encoded payload to half its bytes — a partial frame the
26    /// decoder meets mid-value.
27    Truncate,
28    /// Replace the body with a JSON value of the wrong shape for the declared
29    /// type (a bare string where a structured type is declared).
30    WrongType,
31    /// Add an undeclared field to the (JSON) body — the extra key a strict
32    /// schema must reject or a lenient one must ignore, never choke on.
33    ExtraField,
34    /// Publish on a key that matches no registered subject (a trailing chunk
35    /// the registry never declared).
36    UnregisteredKey,
37    /// Publish under a QoS profile other than the subject's declared one
38    /// (RFC 04 §3) — the observed-vs-declared mismatch a doctor listen flags.
39    WrongQos,
40    /// Publish with no wire `Encoding` set, though the subject declares one —
41    /// a consumer keyed on the encoding meets a blank.
42    MissingEncoding,
43    /// Publish a state sample carrying no HLC timestamp: LWW cannot order it
44    /// (RFC 04 §4), and freshness is unjudgeable.
45    Unstamped,
46}
47
48/// One subject the run will publish, fully resolved — the plan is printed
49/// before anything touches the bus (the replay dry-run precedent).
50#[derive(Debug, Clone, Serialize)]
51pub struct GenPlanEntry {
52    pub key: String,
53    pub class: String,
54    pub producer: String,
55    pub type_name: String,
56    pub qos: String,
57    /// `declared` or `default` — where the profile came from (#158's rule:
58    /// a generator that picks QoS silently is the write-side O4 mistake).
59    pub qos_source: &'static str,
60    pub rate_hz: f64,
61    /// `describe` / `schema-set` / `placeholder` — where the body shape
62    /// came from.
63    pub body_source: &'static str,
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub encoding: Option<String>,
66    /// For `events`: the hard cap on sends within the run (the declared
67    /// budget, RFC 04 §1.3) — a generator must not out-shout the registry
68    /// it claims to follow.
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub events_cap: Option<u64>,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub note: Option<String>,
73    /// The fault this entry injects (#163), `None` for a conforming entry —
74    /// stamped into every one of its samples' markers.
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub fault: Option<Fault>,
77    /// The printable delta from valid this fault introduces (#163) — stated
78    /// in the plan before a byte moves.
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub fault_delta: Option<String>,
81    #[serde(skip)]
82    pub schema: Option<TypeSchema>,
83    /// Absolute chunk index (into the full wire key) of the per-send unique
84    /// id — set for events, whose keys MUST be write-once (RFC 04 §1.3).
85    #[serde(skip)]
86    pub unique_chunk: Option<usize>,
87}
88
89/// What one run did.
90#[derive(Debug, Clone, Serialize)]
91pub struct GenReport {
92    pub duration_s: f64,
93    pub entries: usize,
94    pub sent: u64,
95    /// Bodies the schema refused to encode — counted, never silently
96    /// skipped (these are a bug in the synthesizer or the schema, and
97    /// either way the operator hears about it).
98    pub refused: u64,
99    #[serde(skip_serializing_if = "Vec::is_empty")]
100    pub first_errors: Vec<String>,
101}