Skip to main content

tatara_process/
classification.rs

1//! The six classification dimensions — CRD-facing with `JsonSchema`,
2//! `From`/`Into` bridges to `tatara_core::domain::classification`.
3
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7use tatara_core::domain::classification as core;
8use tatara_core::domain::compliance_binding as core_compl;
9
10/// Lattice position of a Process — six orthogonal axes.
11#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
12#[serde(rename_all = "camelCase")]
13pub struct Classification {
14    pub point_type: ConvergencePointType,
15    pub substrate: SubstrateType,
16    #[serde(default)]
17    pub horizon: Horizon,
18    #[serde(default)]
19    pub calm: CalmClassification,
20    #[serde(default)]
21    pub data_classification: DataClassification,
22}
23
24impl Classification {
25    /// The workspace-baseline classification — a [`ConvergencePointType::Gate`]
26    /// point on the [`SubstrateType::Compute`] substrate with every other axis
27    /// at its [`Default`]. The `(Gate, Compute)` pair names an unremarkable
28    /// barrier point in the Compute plane: no domain-specific structural
29    /// claim (no fan-out / fan-in / broadcast / observation semantics beyond
30    /// the barrier gate) and no domain-specific substrate claim (no
31    /// `Financial` / `Network` / `Storage` / `Security` / `Identity` /
32    /// `Observability` / `Regulatory` plane bringing in its own compliance
33    /// baselines). The three defaulted axes ride at the intentional
34    /// workspace baseline the sibling closed-set primitives already own:
35    /// [`Horizon`] at [`HorizonKind::Bounded`] (terminates naturally, no
36    /// asymptotic metric axes required), [`CalmClassification::Monotone`]
37    /// (no coordination required per CALM), and
38    /// [`DataClassification::Internal`] (access-controlled but not
39    /// externally regulated).
40    ///
41    /// Pre-lift the six-line `Classification { point_type: Gate, substrate:
42    /// Compute, horizon: Default::default(), calm: Default::default(),
43    /// data_classification: Default::default() }` struct-literal recurred
44    /// at TEN sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold
45    /// — one production consumer plus nine test-fixture callsites spread
46    /// across four crates, each restating the SAME `(Gate, Compute)`
47    /// baseline verbatim:
48    /// * `crate::ephemeral::default_ephemeral_class` — the substitute
49    ///   [`EphemeralSpec::into::<crate::crd::ProcessSpec>`] fills into
50    ///   [`crate::crd::ProcessSpec::classification`] when the operator
51    ///   omits an explicit `:classification` slot on `(defephemeral …)`.
52    ///   The one PRODUCTION consumer of the shape — a regression that
53    ///   drifted its point-type or substrate axis silently retargets every
54    ///   unadorned ephemeral to a different plane.
55    /// * `crate::crd`'s + `crate::lib`'s + `crate::lifetime_clock`'s +
56    ///   `tatara_reconciler::{claim,render}`'s + `tatara_pool_reconciler::
57    ///   controller_pool`'s `empty_spec` / `empty_process_spec` /
58    ///   `ephemeral_process` / `permanent_process` test-fixture helpers +
59    ///   inline `ProcessSpec` literals — nine test-fixture callsites
60    ///   restating the SAME six-line struct-literal at the same shape.
61    ///
62    /// Post-lift every callsite reads `Classification::gate_compute()`;
63    /// a future workspace-wide baseline shift (a new [`Horizon`] default,
64    /// a promotion of `Compute` to a compound baseline that pre-fills a
65    /// canonical [`CalmClassification`], a per-baseline compliance overlay
66    /// stamping through the classification, or a rename of either axis
67    /// enum) lands at ONE substrate function here and every downstream
68    /// consumer inherits the upgrade mechanically. The current pin ties
69    /// the three defaulted axes to the sibling closed-set defaults
70    /// ([`HorizonKind::Bounded`], [`CalmClassification::Monotone`],
71    /// [`DataClassification::Internal`]) so a future change to any sibling
72    /// default surfaces at this primitive's tests rather than as silent
73    /// drift across ten independent callsites.
74    ///
75    /// Sibling to the `_or_default` / `_or_placeholder` primitive family on
76    /// [`crate::prelude::Process`] on the (return-form × axis) axis — those
77    /// primitives own the borrow-form projections off a live `Process`;
78    /// this one owns the construction shape for a fresh
79    /// [`crate::crd::ProcessSpec`] whose classification axis is
80    /// unremarkable. A future peer `Classification::observe_observability()`
81    /// or similar named variant lands as a sibling method here when a
82    /// second unremarkable-baseline shape opens.
83    ///
84    /// Theory anchor: THEORY.md §VI.1 (generation over composition — the
85    /// six-line struct-literal shape recurred at TEN hand-authored sites
86    /// past the ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger and is lifted
87    /// onto ONE workspace-wide owner here). THEORY.md §II.1 invariant 5
88    /// (composition preserves proofs — a regression that drifted the
89    /// baseline axis choice at only one consumer, or that broke the
90    /// sibling-default correspondence, surfaces at this primitive's tests
91    /// rather than as silent operator-visible skew between the ephemeral
92    /// sugar substitute and the ten downstream test-fixtures whose
93    /// assertions depend on the shape).
94    #[must_use]
95    pub fn gate_compute() -> Self {
96        Self {
97            point_type: ConvergencePointType::Gate,
98            substrate: SubstrateType::Compute,
99            horizon: Horizon::default(),
100            calm: CalmClassification::default(),
101            data_classification: DataClassification::default(),
102        }
103    }
104
105    /// Closed-set-driven presence probe — does this [`Classification`]
106    /// carry the given [`ConvergencePointType`] discriminator on its
107    /// [`Self::point_type`] slot? The ONE substrate primitive that owns
108    /// the `(Classification, ConvergencePointType) -> bool`
109    /// scalar-carrier walk shape.
110    ///
111    /// # Third scalar-carrier peer on the presence-probe axis
112    ///
113    /// Peer of [`crate::spec::SignalPolicy::has_sighup_strategy`] and
114    /// [`crate::encapsulates::EncapsulatesSpec::has_mode`] — all three
115    /// probe a scalar closed-set-discriminator field on an inner
116    /// [`crate::crd::ProcessSpec`] struct via a one-line
117    /// `self.<field> == kind` body. Together they compose the
118    /// SCALAR-CARRIER stratum of the workspace-wide closed-set-driven
119    /// presence-probe algebra (the workspace-wide algebra spans three
120    /// underlying representation kinds — Option-slot, slice, scalar —
121    /// see the [`crate::spec::SignalPolicy::has_sighup_strategy`]
122    /// docstring for the full-shape rundown; this method is the third
123    /// scalar-carrier instance).
124    ///
125    /// # Semantics — VARIANT match, not POPULATED slot
126    ///
127    /// `has_point_type(kind)` returns `true` iff `self.point_type ==
128    /// kind`. Distinct from BOTH prior scalar-carrier peers on the
129    /// (parent-shape × child-shape) axis:
130    ///
131    /// * [`crate::spec::SignalPolicy::has_sighup_strategy`] lives on a
132    ///   non-Option, DEFAULTED parent (`SignalPolicy: Default`) with a
133    ///   defaulted scalar child (`SighupStrategy: Default =
134    ///   Reconverge`) — a default carrier reads `true` for the default
135    ///   variant only.
136    /// * [`crate::encapsulates::EncapsulatesSpec::has_mode`] lives on
137    ///   an OPTION parent (`spec.encapsulates:
138    ///   Option<EncapsulatesSpec>`) with a defaulted scalar child
139    ///   (`EncapsulationMode: Default = Manage`) — a bare `None`
140    ///   parent reads `false` for every variant.
141    /// * `has_point_type` lives on a REQUIRED, non-Option, NON-DEFAULT
142    ///   parent ([`Classification`] has no `impl Default`) with a
143    ///   NON-DEFAULT scalar child ([`ConvergencePointType`] has no
144    ///   `impl Default`) — every well-formed [`crate::crd::ProcessSpec`]
145    ///   carries a `Classification` whose `point_type` slot is
146    ///   deliberately chosen by the operator, so the probe returns
147    ///   `true` on exactly ONE variant per spec and `false` on the
148    ///   other seven, with no default-arm short-circuit shortcut.
149    ///
150    /// This closes the (required-parent × required-scalar-child) corner
151    /// of the workspace-wide closed-set-driven presence-probe algebra
152    /// at its first substrate primitive.
153    ///
154    /// # Compounding
155    ///
156    /// A future closed-set-discriminator scalar field on
157    /// [`Classification`] (a peer `has_substrate`, `has_calm`,
158    /// `has_data_classification` — the four remaining
159    /// classification-axis closed sets) lands as ONE peer inherent
160    /// method with the same one-line `self.<field> == kind` body and
161    /// routes through the same `strip_and_classify_prefixed_kind::<K,
162    /// _>` shape in `tatara-check`. A future
163    /// [`ConvergencePointType`] variant (a hypothetical `Demux` /
164    /// `Mux` / `Pipeline` for finer topology carving) reaches every
165    /// downstream through ONE `ALL` entry on the closed set with the
166    /// probe body untouched.
167    ///
168    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition preserves
169    /// proofs; the scalar-carrier presence-probe body lives at ONE
170    /// substrate site so every downstream (`point-type-<kind>`
171    /// require-tag family in `tatara-check`, closed-set audit
172    /// dispatchers, future variant additions on
173    /// [`ConvergencePointType`]) binds through the SAME shape rather
174    /// than restating the `classification.point_type == kind` closure
175    /// body at each callsite. THEORY.md §VI.1 — generation over
176    /// composition; a future [`ConvergencePointType`] variant lands at
177    /// ONE `ALL` entry + ONE `as_str` arm on the closed set and the
178    /// probe picks it up mechanically without further per-consumer
179    /// edits.
180    #[must_use]
181    pub fn has_point_type(&self, kind: ConvergencePointType) -> bool {
182        self.point_type == kind
183    }
184
185    /// Closed-set-driven presence probe — does this [`Classification`]
186    /// carry the given [`SubstrateType`] discriminator on its
187    /// [`Self::substrate`] slot? The ONE substrate primitive that
188    /// owns the `(Classification, SubstrateType) -> bool`
189    /// scalar-carrier walk shape.
190    ///
191    /// # Fourth scalar-carrier peer on the presence-probe axis
192    ///
193    /// Peer of [`crate::spec::SignalPolicy::has_sighup_strategy`],
194    /// [`crate::encapsulates::EncapsulatesSpec::has_mode`], and
195    /// [`Self::has_point_type`] — all four probe a scalar closed-set-
196    /// discriminator field on an inner [`crate::crd::ProcessSpec`]
197    /// struct via a one-line `self.<field> == kind` body. Together
198    /// they compose the SCALAR-CARRIER stratum of the workspace-wide
199    /// closed-set-driven presence-probe algebra (the workspace-wide
200    /// algebra spans three underlying representation kinds — Option-
201    /// slot, slice, scalar — see the
202    /// [`crate::spec::SignalPolicy::has_sighup_strategy`] docstring
203    /// for the full-shape rundown; this method is the fourth scalar-
204    /// carrier instance).
205    ///
206    /// # Semantics — VARIANT match, not POPULATED slot
207    ///
208    /// `has_substrate(kind)` returns `true` iff `self.substrate ==
209    /// kind`. FIRST co-tenant on the (required-parent × required-
210    /// scalar-child) corner of the algebra with [`Self::has_point_type`]
211    /// — both probe REQUIRED, non-Option, NON-DEFAULT slots on the
212    /// same [`Classification`] parent whose two required axes carry
213    /// no [`Default`] impl, so exactly ONE of the eight [`SubstrateType`]
214    /// variants and exactly ONE of the eight [`ConvergencePointType`]
215    /// variants answer `true` per well-formed [`crate::crd::ProcessSpec`],
216    /// with no default-arm short-circuit shortcut. Distinct from the
217    /// two prior scalar-carrier peers on the (parent-shape × child-
218    /// shape) axis: `has_sighup_strategy` lives on a non-Option,
219    /// DEFAULTED parent ([`crate::spec::SignalPolicy`] carries
220    /// `#[derive(Default)]`) with a defaulted scalar child
221    /// ([`crate::signal::SighupStrategy`] defaults to
222    /// [`crate::signal::SighupStrategy::Reconverge`]); `has_mode`
223    /// lives on an OPTION parent (`spec.encapsulates:
224    /// Option<EncapsulatesSpec>`) with a defaulted scalar child
225    /// ([`crate::encapsulates::EncapsulationMode`] defaults to
226    /// [`crate::encapsulates::EncapsulationMode::Manage`]).
227    ///
228    /// This POPULATES the (required-parent × required-scalar-child)
229    /// corner of the workspace-wide closed-set-driven presence-probe
230    /// algebra at its SECOND substrate primitive after
231    /// [`Self::has_point_type`] opened the corner, pinning the corner
232    /// as a proven-repeatable primitive shape rather than a single-
233    /// example curiosity.
234    ///
235    /// # Compounding
236    ///
237    /// A future closed-set-discriminator scalar field on
238    /// [`Classification`] (a peer `has_calm` on [`CalmClassification`],
239    /// `has_data_classification` on [`DataClassification`] — the two
240    /// remaining defaulted-scalar-child classification-axis closed
241    /// sets) lands as ONE peer inherent method with the same one-line
242    /// `self.<field> == kind` body and routes through the same
243    /// `strip_and_classify_prefixed_kind::<K, _>` shape in
244    /// `tatara-check`. A future [`SubstrateType`] variant (a
245    /// hypothetical `Consensus` for governance substrates, `Physical`
246    /// for hardware substrates, `Cache` for ephemeral memoization
247    /// substrates) reaches every downstream through ONE `ALL` entry
248    /// on the closed set with the probe body untouched.
249    ///
250    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition preserves
251    /// proofs; the scalar-carrier presence-probe body lives at ONE
252    /// substrate site so every downstream (`substrate-<kind>`
253    /// require-tag family in `tatara-check`, closed-set audit
254    /// dispatchers, future variant additions on [`SubstrateType`])
255    /// binds through the SAME shape rather than restating the
256    /// `classification.substrate == kind` closure body at each
257    /// callsite. THEORY.md §VI.1 — generation over composition; a
258    /// future [`SubstrateType`] variant lands at ONE `ALL` entry +
259    /// ONE `as_str` arm on the closed set and the probe picks it up
260    /// mechanically without further per-consumer edits.
261    #[must_use]
262    pub fn has_substrate(&self, kind: SubstrateType) -> bool {
263        self.substrate == kind
264    }
265
266    /// Closed-set-driven presence probe — does this [`Classification`]
267    /// carry the given [`CalmClassification`] discriminator on its
268    /// [`Self::calm`] slot? The ONE substrate primitive that owns the
269    /// `(Classification, CalmClassification) -> bool` scalar-carrier
270    /// walk shape.
271    ///
272    /// # Fifth scalar-carrier peer on the presence-probe axis
273    ///
274    /// Peer of [`crate::spec::SignalPolicy::has_sighup_strategy`],
275    /// [`crate::encapsulates::EncapsulatesSpec::has_mode`],
276    /// [`Self::has_point_type`], and [`Self::has_substrate`] — all
277    /// five probe a scalar closed-set-discriminator field on an inner
278    /// [`crate::crd::ProcessSpec`] struct via a one-line
279    /// `self.<field> == kind` body. Together they compose the
280    /// SCALAR-CARRIER stratum of the workspace-wide closed-set-driven
281    /// presence-probe algebra (the workspace-wide algebra spans three
282    /// underlying representation kinds — Option-slot, slice, scalar
283    /// — see the [`crate::spec::SignalPolicy::has_sighup_strategy`]
284    /// docstring for the full-shape rundown; this method is the
285    /// fifth scalar-carrier instance).
286    ///
287    /// # Semantics — VARIANT match, not POPULATED slot
288    ///
289    /// `has_calm(kind)` returns `true` iff `self.calm == kind`. FIRST
290    /// occupant on a FRESH corner of the (parent-shape × child-shape)
291    /// axis: a REQUIRED, non-Option, NON-DEFAULT parent
292    /// ([`Classification`] has no `impl Default` because its two
293    /// required axes `point_type`/`substrate` carry no default)
294    /// combined with a DEFAULTED scalar child
295    /// ([`CalmClassification`] defaults to
296    /// [`CalmClassification::Monotone`] via `#[default]`). Distinct
297    /// from every prior scalar-carrier peer on the (parent-shape ×
298    /// child-shape) axis:
299    ///
300    /// * [`crate::spec::SignalPolicy::has_sighup_strategy`] lives on
301    ///   a non-Option, DEFAULTED parent
302    ///   ([`crate::spec::SignalPolicy`] carries `#[derive(Default)]`)
303    ///   with a defaulted scalar child
304    ///   ([`crate::signal::SighupStrategy`] defaults to
305    ///   [`crate::signal::SighupStrategy::Reconverge`]) — a bare
306    ///   `SignalPolicy` reads `true` on the default variant only.
307    /// * [`crate::encapsulates::EncapsulatesSpec::has_mode`] lives on
308    ///   an OPTION parent (`spec.encapsulates:
309    ///   Option<EncapsulatesSpec>`) with a defaulted scalar child
310    ///   ([`crate::encapsulates::EncapsulationMode`] defaults to
311    ///   [`crate::encapsulates::EncapsulationMode::Manage`]) — a
312    ///   bare `None` parent reads `false` for every variant.
313    /// * [`Self::has_point_type`] + [`Self::has_substrate`] both live
314    ///   on the REQUIRED, non-Option, NON-DEFAULT [`Classification`]
315    ///   parent with a NON-DEFAULT scalar child — every well-formed
316    ///   [`crate::crd::ProcessSpec`] carries a `Classification` whose
317    ///   corresponding slot was deliberately chosen by the operator,
318    ///   so exactly ONE of the eight variants answers `true` per
319    ///   spec.
320    /// * `has_calm` lives on the REQUIRED, non-Option, NON-DEFAULT
321    ///   [`Classification`] parent with a DEFAULTED scalar child
322    ///   ([`CalmClassification::Monotone`] is the [`Default`] via
323    ///   `#[default]`) — a bare `Classification` filled via
324    ///   `..Default::default()` on the defaulted axes reads `true`
325    ///   for the default variant ([`CalmClassification::Monotone`])
326    ///   and `false` for every other. Exactly ONE of the two variants
327    ///   answers `true` per spec, and the default-arm short-circuit
328    ///   is present (the operator can DECLINE to name the CALM axis
329    ///   and the spec still answers `true` on the default variant).
330    ///
331    /// This OPENS the (required-parent × defaulted-scalar-child)
332    /// corner of the workspace-wide closed-set-driven presence-probe
333    /// algebra at its first substrate primitive — a corner distinct
334    /// from all four prior scalar-carrier peers (which sit on the
335    /// three prior corners: defaulted-parent × defaulted-child,
336    /// Option-parent × defaulted-child, required-parent ×
337    /// required-child).
338    ///
339    /// # Compounding
340    ///
341    /// A future closed-set-discriminator scalar field on
342    /// [`Classification`] whose child carries `#[derive(Default)]`
343    /// (a peer `has_data_classification` on [`DataClassification`],
344    /// whose default is [`DataClassification::Internal`] via
345    /// `#[default]` — the remaining classification-axis closed set
346    /// on a defaulted-scalar-child slot) lands as ONE peer inherent
347    /// method with the same one-line `self.<field> == kind` body and
348    /// routes through the same `strip_and_classify_prefixed_kind::<K,
349    /// _>` shape in `tatara-check`. A future [`CalmClassification`]
350    /// variant (a hypothetical `ConditionallyMonotone` for ops that
351    /// are monotone under a witness, like CRDT joins under a fixed
352    /// schema) reaches every downstream through ONE `ALL` entry on
353    /// the closed set with the probe body untouched.
354    ///
355    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition
356    /// preserves proofs; the scalar-carrier presence-probe body
357    /// lives at ONE substrate site so every downstream
358    /// (`calm-<kind>` require-tag family in `tatara-check`,
359    /// closed-set audit dispatchers, future variant additions on
360    /// [`CalmClassification`]) binds through the SAME shape rather
361    /// than restating the `classification.calm == kind` closure body
362    /// at each callsite. THEORY.md §VI.1 — generation over
363    /// composition; a future [`CalmClassification`] variant lands at
364    /// ONE `ALL` entry + ONE `as_str` arm on the closed set and the
365    /// probe picks it up mechanically without further per-consumer
366    /// edits.
367    #[must_use]
368    pub fn has_calm(&self, kind: CalmClassification) -> bool {
369        self.calm == kind
370    }
371
372    /// Closed-set-driven presence probe — does this [`Classification`]
373    /// carry the given [`DataClassification`] discriminator on its
374    /// [`Self::data_classification`] slot? The ONE substrate primitive
375    /// that owns the `(Classification, DataClassification) -> bool`
376    /// scalar-carrier walk shape.
377    ///
378    /// # Sixth scalar-carrier peer on the presence-probe axis
379    ///
380    /// Peer of [`crate::spec::SignalPolicy::has_sighup_strategy`],
381    /// [`crate::encapsulates::EncapsulatesSpec::has_mode`],
382    /// [`Self::has_point_type`], [`Self::has_substrate`], and
383    /// [`Self::has_calm`] — all six probe a scalar closed-set-
384    /// discriminator field on an inner [`crate::crd::ProcessSpec`]
385    /// struct via a one-line `self.<field> == kind` body. Together
386    /// they compose the SCALAR-CARRIER stratum of the workspace-wide
387    /// closed-set-driven presence-probe algebra (the workspace-wide
388    /// algebra spans three underlying representation kinds — Option-
389    /// slot, slice, scalar — see the
390    /// [`crate::spec::SignalPolicy::has_sighup_strategy`] docstring
391    /// for the full-shape rundown; this method is the sixth scalar-
392    /// carrier instance).
393    ///
394    /// # Semantics — VARIANT match, not POPULATED slot
395    ///
396    /// `has_data_classification(kind)` returns `true` iff
397    /// `self.data_classification == kind`. SECOND co-tenant on the
398    /// (required-parent × defaulted-scalar-child) corner of the
399    /// algebra alongside [`Self::has_calm`] — both probe REQUIRED,
400    /// non-Option, NON-DEFAULT [`Classification`] parent slots with
401    /// a DEFAULTED scalar child ([`DataClassification`] defaults to
402    /// [`DataClassification::Internal`] via `#[default]`, sibling to
403    /// [`CalmClassification::Monotone`]'s `#[default]`), so exactly
404    /// ONE of the six [`DataClassification`] variants answers `true`
405    /// per spec AND the default-arm short-circuit is present (a
406    /// `Classification` filled via `..Default::default()` on the
407    /// `data_classification` axis reads `true` on the default
408    /// variant [`DataClassification::Internal`] and `false` on every
409    /// other).
410    ///
411    /// This POPULATES the (required-parent × defaulted-scalar-child)
412    /// corner of the workspace-wide closed-set-driven presence-probe
413    /// algebra at its SECOND substrate primitive after
414    /// [`Self::has_calm`] opened the corner, pinning the corner as a
415    /// proven-repeatable primitive shape rather than a single-example
416    /// curiosity. The corner-property contract ("bare
417    /// [`Classification`] reads `true` on the default variant")
418    /// now walks TWO independent defaulted-scalar-child slots on the
419    /// SAME [`Classification`] parent — a regression that promoted
420    /// a different [`DataClassification`] variant to `#[default]`
421    /// (or wired the arm to a fixed variant answer) fails HERE at
422    /// ONE narrow substrate site before drifting through every
423    /// unadorned Process's baseline data-classification answer.
424    ///
425    /// # Compounding
426    ///
427    /// This method exhausts the four scalar closed-set-discriminator
428    /// axes on [`Classification`] ([`Self::has_point_type`],
429    /// [`Self::has_substrate`], [`Self::has_calm`], and
430    /// [`Self::has_data_classification`]) — the six-axis classification
431    /// lattice now publishes ALL FOUR of its scalar-carrier presence
432    /// probes at ONE substrate site each. The remaining two axes
433    /// (`horizon` — a nested struct threading [`HorizonKind`] through
434    /// `horizon.kind`; the sixth axis is variant-dependent on the
435    /// [`HorizonKind::Asymptotic`] arm) live on nested-struct-scalar
436    /// slots rather than the direct-scalar corner the four current
437    /// peers span. A future [`DataClassification`] variant (a
438    /// hypothetical seventh variant beyond `Public / Internal /
439    /// Confidential / Pii / Phi / Pci` — say a `TradeSecret` bucket
440    /// for competitive-sensitive data, or an `Anonymized` bucket for
441    /// pseudonymized-PII whose regulatory posture differs) reaches
442    /// every downstream through ONE `ALL` entry on the closed set +
443    /// ONE `as_str` arm + ONE `sensitivity_rank` arm + one arm per
444    /// predicate with the probe body untouched.
445    ///
446    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition
447    /// preserves proofs; the scalar-carrier presence-probe body
448    /// lives at ONE substrate site so every downstream
449    /// (`data-classification-<kind>` require-tag family in
450    /// `tatara-check`, closed-set audit dispatchers, future variant
451    /// additions on [`DataClassification`]) binds through the SAME
452    /// shape rather than restating the
453    /// `classification.data_classification == kind` closure body at
454    /// each callsite. THEORY.md §VI.1 — generation over composition;
455    /// a future [`DataClassification`] variant lands at ONE `ALL`
456    /// entry + ONE `as_str` arm on the closed set and the probe
457    /// picks it up mechanically without further per-consumer edits.
458    #[must_use]
459    pub fn has_data_classification(&self, kind: DataClassification) -> bool {
460        self.data_classification == kind
461    }
462}
463
464/// Structural type — how data flows through the point.
465///
466/// Closed-set sibling on the classification axis algebra; the `ALL` /
467/// `as_str` / Display / `FromStr` triad mirrors
468/// [`DataClassification::ALL`], [`crate::pool::PoolPhase::ALL`],
469/// [`crate::pool::MemberState::ALL`], [`crate::pool::ReplacementPolicy::ALL`],
470/// [`crate::pool::ReturnPolicy::ALL`],
471/// [`crate::boundary::ConditionKind::ALL`],
472/// [`crate::lifetime::TeardownPolicy::ALL`],
473/// [`crate::lifetime::LifetimeKind::ALL`],
474/// [`crate::intent::IntentKind::ALL`],
475/// [`crate::phase::ProcessPhase::ALL`],
476/// [`crate::signal::ProcessSignal::ALL`]. The
477/// `(input_arity, output_arity)` projection (via [`Arity`]) closes the
478/// graph-topology contract: each variant lands in exactly one of the
479/// three structural buckets — endomorphic (1→1), diffusive (1→N), or
480/// convergent (N→1) — so future DAG composition / edge-cardinality
481/// validators dispatch on a typed projection rather than re-deriving
482/// from variant names.
483#[derive(
484    Clone,
485    Copy,
486    Debug,
487    PartialEq,
488    Eq,
489    Hash,
490    Serialize,
491    Deserialize,
492    JsonSchema,
493    tatara_closed_set::DeriveClosedSet,
494)]
495#[serde(rename_all = "PascalCase")]
496#[closed_set(via = "as_str", generate_unknown, display)]
497pub enum ConvergencePointType {
498    /// 1 input → 1 output (linear conversion).
499    Transform,
500    /// 1 input → N outputs (fan-out, spawns downstream DAGs).
501    Fork,
502    /// N inputs → 1 output (fan-in, merges upstream results).
503    Join,
504    /// N inputs → 1 output (barrier, waits for all inputs).
505    Gate,
506    /// N inputs → 1 output (choice, picks best by policy).
507    Select,
508    /// 1 input → N outputs same type (replicate signal).
509    Broadcast,
510    /// N inputs → 1 output (fold/aggregate).
511    Reduce,
512    /// 1 input → 1 output + side-channel (tap for observation).
513    Observe,
514}
515
516impl ConvergencePointType {
517    /// The closed set of point types — single source of truth that
518    /// drives the `as_str` / Display / `FromStr` triad AND the
519    /// `(input_arity, output_arity)` typed pair (via [`Arity`]) AND the
520    /// `is_endomorphic` / `is_diffusive` / `is_convergent` predicate
521    /// triple. Adding a ninth variant lands at one `ALL` entry + one
522    /// `as_str` arm + one `input_arity` arm + one `output_arity` arm +
523    /// one arm per predicate — exhaustively checked by the compiler
524    /// (the `[Self; 8]` array literal forces the arity) AND by the
525    /// per-variant truth-table contract test (a new variant must
526    /// declare its own `(input, output)` arity pair or any future
527    /// DAG composition validator that dispatches on
528    /// `(input_arity, output_arity)` will silently mis-wire it).
529    /// Closes the load-bearing classification-axis enum that
530    /// `tatara_core::domain::compliance_binding::PointSelector::ByType`
531    /// already dispatches against and that every `Process`'s
532    /// `Classification.point_type` reads as the topological identity
533    /// of the convergence point.
534    pub const ALL: [Self; 8] = [
535        Self::Transform,
536        Self::Fork,
537        Self::Join,
538        Self::Gate,
539        Self::Select,
540        Self::Broadcast,
541        Self::Reduce,
542        Self::Observe,
543    ];
544
545    /// Canonical PascalCase wire-format projection — matches the
546    /// serde `rename_all = "PascalCase"` output verbatim AND the CRD
547    /// `enum:` enumeration that the Process schema stamps on
548    /// `spec.classification.pointType`. Pinned by
549    /// `convergence_point_type_as_str_matches_serde` so a variant
550    /// rename can't drift between the typed surface, the CRD enum,
551    /// the YAML wire format AND any future operator-facing
552    /// diagnostic that composes `pointType={kind}` via Display
553    /// rather than a hard-coded literal that would silently rot.
554    /// Display + FromStr triad over `ALL` mirrors `DataClassification`
555    /// / `PoolPhase` / `MemberState` / `ReplacementPolicy` /
556    /// `ReturnPolicy` / `TeardownPolicy` / `ConditionKind` /
557    /// `ProcessPhase` / `ProcessSignal`.
558    pub const fn as_str(self) -> &'static str {
559        match self {
560            Self::Transform => "Transform",
561            Self::Fork => "Fork",
562            Self::Join => "Join",
563            Self::Gate => "Gate",
564            Self::Select => "Select",
565            Self::Broadcast => "Broadcast",
566            Self::Reduce => "Reduce",
567            Self::Observe => "Observe",
568        }
569    }
570
571    /// Cardinality of the input edge into this point — `One` for
572    /// `Transform | Fork | Broadcast | Observe` (single-source
573    /// projections), `Many` for `Join | Gate | Select | Reduce`
574    /// (multi-source convergent reductions). Closed-set match (not
575    /// `matches!`) so a future variant triggers the compiler's
576    /// exhaustiveness check at this site rather than silently
577    /// defaulting to `One`. Paired with [`Self::output_arity`] they
578    /// form the typed `(input, output)` projection that future
579    /// DAG composition validators (edge-cardinality checks: "you
580    /// can't connect a Fork's output to a Transform's input
581    /// without a Join in between") dispatch against — a single
582    /// projection per variant means a future `Demux` / `Mux` /
583    /// `Pipeline` point lands in exactly one cell of the
584    /// `Arity × Arity` topology table rather than rotting against
585    /// open-coded `== ConvergencePointType::Fork` checks.
586    pub const fn input_arity(self) -> Arity {
587        match self {
588            Self::Transform | Self::Fork | Self::Broadcast | Self::Observe => Arity::One,
589            Self::Join | Self::Gate | Self::Select | Self::Reduce => Arity::Many,
590        }
591    }
592
593    /// Cardinality of the output edge from this point — `Many` for
594    /// `Fork | Broadcast` (fan-out), `One` for everything else.
595    /// Closed-set match so a future variant triggers the compiler's
596    /// exhaustiveness check. See [`Self::input_arity`] for the
597    /// arity-pair contract + bucket definitions.
598    pub const fn output_arity(self) -> Arity {
599        match self {
600            Self::Fork | Self::Broadcast => Arity::Many,
601            Self::Transform
602            | Self::Join
603            | Self::Gate
604            | Self::Select
605            | Self::Reduce
606            | Self::Observe => Arity::One,
607        }
608    }
609
610    /// Does this point preserve the single-input single-output
611    /// shape? `(input, output) == (One, One)` — `Transform`
612    /// (identity-shaped reshape) and `Observe` (passthrough +
613    /// side-channel tap). Closed-set match so a future variant
614    /// triggers the compiler's exhaustiveness check. Paired with
615    /// `is_diffusive` and `is_convergent` they form the three-way
616    /// disjoint bucket carving sealed by
617    /// `convergence_point_type_buckets_cover_every_variant` AND
618    /// `convergence_point_type_arity_pair_agrees_with_bucket` —
619    /// the bridge that lets the bucket predicates and the arity
620    /// pair name the same topology partition from two angles.
621    pub const fn is_endomorphic(self) -> bool {
622        match self {
623            Self::Transform | Self::Observe => true,
624            Self::Fork
625            | Self::Join
626            | Self::Gate
627            | Self::Select
628            | Self::Broadcast
629            | Self::Reduce => false,
630        }
631    }
632
633    /// Does this point fan out — single input replicated/split
634    /// across many outputs? `(input, output) == (One, Many)` —
635    /// `Fork` and `Broadcast`. Closed-set match so a future variant
636    /// triggers the compiler's exhaustiveness check. See
637    /// `is_endomorphic` for the bucket-carving contract.
638    pub const fn is_diffusive(self) -> bool {
639        match self {
640            Self::Fork | Self::Broadcast => true,
641            Self::Transform
642            | Self::Join
643            | Self::Gate
644            | Self::Select
645            | Self::Reduce
646            | Self::Observe => false,
647        }
648    }
649
650    /// Does this point reduce — many inputs collapsed to one
651    /// output? `(input, output) == (Many, One)` — `Join`, `Gate`,
652    /// `Select`, `Reduce`. Closed-set match so a future variant
653    /// triggers the compiler's exhaustiveness check. See
654    /// `is_endomorphic` for the bucket-carving contract. The
655    /// impossible `(Many, Many)` topology bucket is pinned empty
656    /// by `convergence_point_type_arity_pair_agrees_with_bucket`
657    /// — a `(Many, Many)` point would mean "many independent
658    /// inputs replicated across many independent outputs", which
659    /// has no convergence semantics: every DAG-composition
660    /// validator would have to special-case it. A future variant
661    /// that wants `(Many, Many)` must first extend the bucket
662    /// carving deliberately.
663    pub const fn is_convergent(self) -> bool {
664        match self {
665            Self::Join | Self::Gate | Self::Select | Self::Reduce => true,
666            Self::Transform | Self::Fork | Self::Broadcast | Self::Observe => false,
667        }
668    }
669}
670
671// `impl FromStr for ConvergencePointType` +
672// `impl tatara_lisp::ClosedSet for ConvergencePointType` +
673// `impl std::fmt::Display for ConvergencePointType` +
674// `pub struct UnknownConvergencePointType(pub String)` are all generated
675// by `#[derive(tatara_closed_set::DeriveClosedSet)]` +
676// `#[closed_set(via = "as_str", generate_unknown, display)]` on the
677// enum declaration above. `label` delegates to the inherent
678// `ConvergencePointType::as_str` — the inherent name (PascalCase
679// `as_str`) stays the load-bearing wire-vocabulary projection that
680// matches the serde `rename_all = "PascalCase"` output AND the CRD
681// `enum:` enumeration the Process schema stamps on
682// `spec.classification.pointType` verbatim, while generic
683// `T: ClosedSet` consumers reach the STABLE workspace-wide name
684// (`label`). The `display` flag emits the
685// `f.write_str(self.as_str())` delegation block at the same
686// proc-macro site rather than a hand-rolled `fmt::Display` block per
687// implementor. The auto-derived carrier label "convergence point
688// type" matches the prior hand-rolled `#[error("unknown convergence
689// point type: {0}")]` annotation byte-for-byte. Symmetric to the
690// other five classification-axis closed-sets in this file
691// (`SubstrateType` / `HorizonKind` / `OptimizationDirection` /
692// `CalmClassification` / `DataClassification`) AND every other
693// `#[derive(DeriveClosedSet)]` implementor across the workspace
694// (`crate::pool::{ReplacementPolicy,MemberState,PoolPhase,ReturnPolicy}`,
695// `crate::export::{ArtifactKind,ReportFormat,ChannelKind,ExportTrigger}`,
696// `crate::allocation::{RequestorKind,AllocationPhase}`).
697
698/// Edge cardinality of a [`ConvergencePointType`]'s input or output.
699///
700/// Typed projection used by [`ConvergencePointType::input_arity`] and
701/// [`ConvergencePointType::output_arity`] so DAG composition validators
702/// reach for a closed-set enum rather than re-deriving the in/out
703/// cardinality from variant names. `Many` is the "≥1, could be N"
704/// cardinality — it carries no upper bound because the convergence
705/// point's variant tag is already the structural identity; the
706/// number itself is a runtime property of the DAG, not the typescape.
707#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, tatara_closed_set::DeriveClosedSet)]
708#[closed_set(via = "as_str", display, generate_unknown)]
709pub enum Arity {
710    /// Single edge — exactly one input or one output.
711    One,
712    /// Multiple edges — any number ≥ 1.
713    Many,
714}
715
716impl Arity {
717    /// The closed set of arities — single source of truth that
718    /// drives `as_str` / Display AND the `is_one` predicate. Adding
719    /// a third variant (e.g. `Arity::Zero` for sinks) lands at one
720    /// `ALL` entry + one `as_str` arm + one predicate arm —
721    /// exhaustively checked by the compiler.
722    pub const ALL: [Self; 2] = [Self::One, Self::Many];
723
724    /// Canonical projection — `"One" | "Many"`. Pinned by
725    /// `arity_display_matches_as_str` so a future Display impl
726    /// can't drift from the canonical string.
727    pub const fn as_str(self) -> &'static str {
728        match self {
729            Self::One => "One",
730            Self::Many => "Many",
731        }
732    }
733
734    /// Is this the single-edge cardinality? Closed-set match (not
735    /// `matches!`) so a future variant triggers the compiler's
736    /// exhaustiveness check.
737    pub const fn is_one(self) -> bool {
738        match self {
739            Self::One => true,
740            Self::Many => false,
741        }
742    }
743}
744
745// `impl fmt::Display for Arity` + `impl std::str::FromStr for Arity` +
746// `impl tatara_lisp::ClosedSet for Arity` + `pub struct UnknownArity(pub
747// String)` are all generated by
748// `#[derive(tatara_closed_set::DeriveClosedSet)]` +
749// `#[closed_set(via = "as_str", display, generate_unknown)]` on the enum
750// declaration above. The inherent `as_str` projection stays load-bearing
751// — the canonical `"One" | "Many"` string every DAG composition
752// validator reads; `via = "as_str"` binds `ClosedSet::label` to the same
753// projection so the substrate-wide `assert_display_matches_label` /
754// `assert_closed_set_well_formed` primitives dispatch through the same
755// byte-identical shape every other closed-set implementor across the
756// crate publishes. Aligns `Arity` with the substrate-wide
757// `#[derive(DeriveClosedSet)]` idiom that every other closed-set enum on
758// this classification axis (`ConvergencePointType`, `SubstrateType`,
759// `HorizonKind`, `OptimizationDirection`, `CalmClassification`,
760// `DataClassification`) already carries — the last hand-rolled
761// `impl fmt::Display` on the axis is closed at ONE substrate site.
762
763/// Operational substrate.
764///
765/// Closed-set sibling on the classification axis algebra; the `ALL` /
766/// `as_str` / Display / `FromStr` triad mirrors
767/// [`ConvergencePointType::ALL`], [`DataClassification::ALL`],
768/// [`crate::pool::PoolPhase::ALL`], [`crate::pool::MemberState::ALL`],
769/// [`crate::pool::ReplacementPolicy::ALL`],
770/// [`crate::pool::ReturnPolicy::ALL`],
771/// [`crate::boundary::ConditionKind::ALL`],
772/// [`crate::lifetime::TeardownPolicy::ALL`],
773/// [`crate::lifetime::LifetimeKind::ALL`],
774/// [`crate::intent::IntentKind::ALL`],
775/// [`crate::phase::ProcessPhase::ALL`],
776/// [`crate::signal::ProcessSignal::ALL`]. The
777/// `is_resource` / `is_policy` / `is_telemetry` predicate triple
778/// carves the eight variants into three structurally-disjoint
779/// substrate planes — resource (you allocate from it), policy (it
780/// gates access for other workloads), telemetry (it observes other
781/// workloads) — so future compliance-baseline selectors that
782/// dispatch on a substrate's plane (resource budgets only apply to
783/// resource substrates; policy substrates inherit baselines from
784/// what they govern; telemetry substrates inherit baselines from
785/// what they observe) read a typed projection rather than
786/// re-deriving from variant names.
787#[derive(
788    Clone,
789    Copy,
790    Debug,
791    PartialEq,
792    Eq,
793    Hash,
794    PartialOrd,
795    Ord,
796    Serialize,
797    Deserialize,
798    JsonSchema,
799    tatara_closed_set::DeriveClosedSet,
800)]
801#[serde(rename_all = "PascalCase")]
802#[closed_set(via = "as_str", generate_unknown, display)]
803pub enum SubstrateType {
804    Financial,
805    Compute,
806    Network,
807    Storage,
808    Security,
809    Identity,
810    Observability,
811    Regulatory,
812}
813
814impl SubstrateType {
815    /// The closed set of substrates — single source of truth that
816    /// drives the `as_str` / Display / `FromStr` triad AND the
817    /// `is_resource` / `is_policy` / `is_telemetry` predicate triple.
818    /// Adding a ninth variant lands at one `ALL` entry + one
819    /// `as_str` arm + one arm per predicate — exhaustively checked
820    /// by the compiler (the `[Self; 8]` array literal forces the
821    /// arity) AND by the per-variant plane-bucket contract test (a
822    /// new variant must declare its own plane or any future
823    /// compliance-baseline selector that dispatches on
824    /// `(is_resource, is_policy, is_telemetry)` will silently
825    /// mis-classify it). Closes the load-bearing classification-axis
826    /// enum that
827    /// `tatara_core::domain::compliance_binding::PointSelector::BySubstrate`
828    /// already dispatches against and that every `Process`'s
829    /// `Classification.substrate` reads as the operational
830    /// substrate the convergence point lives on.
831    pub const ALL: [Self; 8] = [
832        Self::Financial,
833        Self::Compute,
834        Self::Network,
835        Self::Storage,
836        Self::Security,
837        Self::Identity,
838        Self::Observability,
839        Self::Regulatory,
840    ];
841
842    /// Canonical PascalCase wire-format projection — matches the
843    /// serde `rename_all = "PascalCase"` output verbatim AND the CRD
844    /// `enum:` enumeration that the Process schema stamps on
845    /// `spec.classification.substrate`. Pinned by
846    /// `substrate_type_as_str_matches_serde` so a variant rename
847    /// can't drift between the typed surface, the CRD enum, the YAML
848    /// wire format AND any future operator-facing diagnostic that
849    /// composes `substrate={kind}` via Display rather than a
850    /// hard-coded literal that would silently rot. Display + FromStr
851    /// triad over `ALL` mirrors `ConvergencePointType` /
852    /// `DataClassification` / `PoolPhase` / `MemberState` /
853    /// `ReplacementPolicy` / `ReturnPolicy` / `TeardownPolicy` /
854    /// `ConditionKind` / `ProcessPhase` / `ProcessSignal`.
855    pub const fn as_str(self) -> &'static str {
856        match self {
857            Self::Financial => "Financial",
858            Self::Compute => "Compute",
859            Self::Network => "Network",
860            Self::Storage => "Storage",
861            Self::Security => "Security",
862            Self::Identity => "Identity",
863            Self::Observability => "Observability",
864            Self::Regulatory => "Regulatory",
865        }
866    }
867
868    /// Is this a resource substrate — one you allocate budgets from
869    /// to run workloads? `Financial | Compute | Network | Storage`.
870    /// Closed-set match (not `matches!`) so a future variant
871    /// triggers the compiler's exhaustiveness check at this site
872    /// rather than silently defaulting to `false`. Paired with
873    /// `is_policy` and `is_telemetry` they form the three-way
874    /// disjoint plane carving sealed by
875    /// `substrate_type_buckets_cover_every_variant` — the bridge
876    /// that lets future compliance-baseline selectors dispatch on
877    /// plane without re-deriving from variant names.
878    pub const fn is_resource(self) -> bool {
879        match self {
880            Self::Financial | Self::Compute | Self::Network | Self::Storage => true,
881            Self::Security | Self::Identity | Self::Observability | Self::Regulatory => false,
882        }
883    }
884
885    /// Is this a policy substrate — one that gates access or
886    /// compliance for other workloads rather than carrying their
887    /// payload? `Security | Identity | Regulatory`. Closed-set match
888    /// so a future variant triggers the compiler's exhaustiveness
889    /// check. See `is_resource` for the bucket-carving contract.
890    pub const fn is_policy(self) -> bool {
891        match self {
892            Self::Security | Self::Identity | Self::Regulatory => true,
893            Self::Financial
894            | Self::Compute
895            | Self::Network
896            | Self::Storage
897            | Self::Observability => false,
898        }
899    }
900
901    /// Is this a telemetry substrate — one that passively observes
902    /// other workloads (metrics, logs, traces) without carrying
903    /// their payload or gating their access? `Observability` only.
904    /// Closed-set match so a future variant triggers the compiler's
905    /// exhaustiveness check. See `is_resource` for the
906    /// bucket-carving contract. A telemetry substrate's compliance
907    /// baseline is inherited from what it observes — the singleton
908    /// bucket is intentional, not a placeholder.
909    pub const fn is_telemetry(self) -> bool {
910        match self {
911            Self::Observability => true,
912            Self::Financial
913            | Self::Compute
914            | Self::Network
915            | Self::Storage
916            | Self::Security
917            | Self::Identity
918            | Self::Regulatory => false,
919        }
920    }
921}
922
923// `impl FromStr for SubstrateType` +
924// `impl tatara_lisp::ClosedSet for SubstrateType` +
925// `impl std::fmt::Display for SubstrateType` +
926// `pub struct UnknownSubstrateType(pub String)` are all generated by
927// `#[derive(tatara_closed_set::DeriveClosedSet)]` +
928// `#[closed_set(via = "as_str", generate_unknown, display)]` on the
929// enum declaration above. The auto-derived carrier label "substrate
930// type" matches the prior hand-rolled `#[error("unknown substrate
931// type: {0}")]` annotation byte-for-byte. See the retrofit comment
932// block on [`ConvergencePointType`] for the canonical narrative.
933
934/// How long the point runs. Flattened struct-of-optionals so the OpenAPI
935/// schema carries a single `kind` discriminator without per-variant merge.
936#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
937#[serde(rename_all = "camelCase")]
938pub struct Horizon {
939    #[serde(default)]
940    pub kind: HorizonKind,
941    /// Metric being optimized (Asymptotic only).
942    #[serde(default, skip_serializing_if = "Option::is_none")]
943    pub metric: Option<String>,
944    /// Whether to minimize or maximize the metric (Asymptotic only).
945    #[serde(default, skip_serializing_if = "Option::is_none")]
946    pub direction: Option<OptimizationDirection>,
947    /// Rate threshold considered healthy (Asymptotic only).
948    #[serde(default, skip_serializing_if = "Option::is_none")]
949    pub healthy_rate_threshold: Option<f64>,
950}
951
952/// The shape of a convergence horizon's lifetime — does the point
953/// run toward a fixed point and terminate, or run in perpetuity with
954/// a rate signal?
955///
956/// Closed-set sibling on the classification axis algebra; the `ALL` /
957/// `as_str` / Display / `FromStr` triad mirrors
958/// [`ConvergencePointType::ALL`], [`SubstrateType::ALL`],
959/// [`DataClassification::ALL`], [`CalmClassification::ALL`],
960/// [`OptimizationDirection::ALL`], [`crate::pool::PoolPhase::ALL`],
961/// [`crate::pool::MemberState::ALL`],
962/// [`crate::pool::ReplacementPolicy::ALL`],
963/// [`crate::pool::ReturnPolicy::ALL`],
964/// [`crate::boundary::ConditionKind::ALL`],
965/// [`crate::lifetime::TeardownPolicy::ALL`],
966/// [`crate::lifetime::LifetimeKind::ALL`],
967/// [`crate::intent::IntentKind::ALL`],
968/// [`crate::phase::ProcessPhase::ALL`],
969/// [`crate::signal::ProcessSignal::ALL`]. The [`Self::terminates`]
970/// predicate is the load-bearing horizon-shape primitive — schedulers
971/// asking "will this Process ever reach `Reaped` via natural
972/// termination?" read it as the typed image of the lattice ordering
973/// (`Bounded ≤ Asymptotic` because the bounded horizon strictly
974/// refines the asymptotic one by also terminating) rather than
975/// re-deriving from the variant name. The
976/// [`Self::requires_metric_axes`] predicate is the typed validity
977/// witness for the [`Horizon`] struct's three `Option<…>` fields
978/// (`metric`, `direction`, `healthy_rate_threshold`) — they're
979/// `Some(_)` iff the kind requires them, so the implicit invariant
980/// the optionality encodes becomes a checkable per-kind predicate
981/// instead of operator folklore.
982#[derive(
983    Clone,
984    Copy,
985    Debug,
986    PartialEq,
987    Eq,
988    Hash,
989    Serialize,
990    Deserialize,
991    JsonSchema,
992    Default,
993    tatara_closed_set::DeriveClosedSet,
994)]
995#[serde(rename_all = "PascalCase")]
996#[closed_set(via = "as_str", generate_unknown, display)]
997pub enum HorizonKind {
998    /// Has a fixed point — distance reaches 0 and terminates.
999    #[default]
1000    Bounded,
1001    /// Runs in perpetuity — rate is the health signal, not distance.
1002    Asymptotic,
1003}
1004
1005impl HorizonKind {
1006    /// The closed set of horizon kinds — single source of truth that
1007    /// drives the `as_str` / Display / `FromStr` triad AND the
1008    /// `terminates` predicate AND the `requires_metric_axes` shape-
1009    /// validity witness. Adding a third variant (e.g. a `Periodic`
1010    /// sentinel for "terminates on each window boundary then
1011    /// re-arms", which neither perpetually-running nor singularly-
1012    /// terminating names) lands at one `ALL` entry + one `as_str`
1013    /// arm + one `terminates` arm + one `requires_metric_axes` arm —
1014    /// exhaustively checked by the compiler (the `[Self; 2]` array
1015    /// literal forces the arity) AND by the per-variant truth-table
1016    /// tests (a new variant must declare its own termination AND
1017    /// metric-axes requirement, or every scheduler / horizon-shape
1018    /// validator will silently bucket it). Closes the load-bearing
1019    /// classification sub-axis that the `Horizon.kind` field threads
1020    /// through every `Classification.horizon` field on every
1021    /// Process — the last open sibling on the classification axis
1022    /// algebra after `OptimizationDirection` (980a318),
1023    /// `CalmClassification` (da3430c), `SubstrateType` (b9d7b3b),
1024    /// `ConvergencePointType` (7941527), `Arity`, and
1025    /// `DataClassification` (81bffa0).
1026    pub const ALL: [Self; 2] = [Self::Bounded, Self::Asymptotic];
1027
1028    /// Canonical PascalCase wire-format projection — matches the
1029    /// serde `rename_all = "PascalCase"` output verbatim AND the CRD
1030    /// `enum:` enumeration the Process schema stamps on
1031    /// `spec.classification.horizon.kind`. Pinned by
1032    /// `horizon_kind_as_str_matches_serde` so a variant rename
1033    /// can't drift between the typed surface, the CRD enum, the
1034    /// YAML wire format AND any future operator-facing diagnostic
1035    /// composing `horizon.kind={kind}` via Display rather than a
1036    /// hard-coded literal. Display + FromStr triad over `ALL`
1037    /// mirrors every sibling closed-set enum in this crate.
1038    pub const fn as_str(self) -> &'static str {
1039        match self {
1040            Self::Bounded => "Bounded",
1041            Self::Asymptotic => "Asymptotic",
1042        }
1043    }
1044
1045    /// LOAD-BEARING HORIZON-SHAPE PRIMITIVE: does this kind terminate
1046    /// naturally — i.e. does it have a fixed point that
1047    /// `ConvergenceDistance` can reach? Closed-set match (not
1048    /// `matches!`) so a future variant triggers the compiler's
1049    /// exhaustiveness check rather than silently defaulting to
1050    /// `false` (which would silently mis-route a terminating
1051    /// variant through the asymptotic rate-window evaluator) or
1052    /// `true` (which would silently invent a fixed point for a
1053    /// perpetual variant). `Bounded ⇒ true`, `Asymptotic ⇒ false`
1054    /// is the typed image of the documented lattice ordering
1055    /// `Bounded ≤ Asymptotic` — the bounded horizon strictly refines
1056    /// the asymptotic one BY ALSO TERMINATING. Future schedulers
1057    /// asking "will this Process reach `Reaped` via natural
1058    /// termination?" read this predicate, and the tatara-lattice
1059    /// `Lattice for Horizon` impl (which currently dispatches on
1060    /// `self.kind == HorizonKind::Bounded` at three sites) can be
1061    /// recast in a future run to read `self.kind.terminates()` so
1062    /// the lattice basis is the typed primitive rather than a
1063    /// variant-name comparison.
1064    pub const fn terminates(self) -> bool {
1065        match self {
1066            Self::Bounded => true,
1067            Self::Asymptotic => false,
1068        }
1069    }
1070
1071    /// LOAD-BEARING SHAPE-VALIDITY WITNESS: does this kind require
1072    /// the three asymptotic-only [`Horizon`] axes (`metric`,
1073    /// `direction`, `healthy_rate_threshold`) to be `Some(_)`?
1074    /// Closed-set match (not `matches!`) so a future variant
1075    /// triggers the compiler's exhaustiveness check rather than
1076    /// silently defaulting to `false` (which would silently let an
1077    /// asymptotic-shaped variant ship with missing metric axes and
1078    /// trip the rate-window evaluator at runtime). `Bounded ⇒
1079    /// false`, `Asymptotic ⇒ true` is the typed image of the
1080    /// optionality the [`Horizon`] struct encodes via three
1081    /// `Option<…>` fields — the implicit invariant ("Asymptotic
1082    /// only" in the field docs) is now a checkable per-kind
1083    /// predicate. Future horizon-shape validators (CRD admission,
1084    /// `tatara-check` form linter, Lisp authoring-time predicate)
1085    /// read this rather than re-deriving from variant names.
1086    /// Pinned as the antisymmetric partner of [`Self::terminates`]
1087    /// — exactly one of `(terminates, requires_metric_axes)` is
1088    /// true per variant — by
1089    /// `horizon_kind_terminate_xor_requires_metric_axes`.
1090    pub const fn requires_metric_axes(self) -> bool {
1091        match self {
1092            Self::Bounded => false,
1093            Self::Asymptotic => true,
1094        }
1095    }
1096}
1097
1098// `impl FromStr for HorizonKind` +
1099// `impl tatara_lisp::ClosedSet for HorizonKind` +
1100// `impl std::fmt::Display for HorizonKind` +
1101// `pub struct UnknownHorizonKind(pub String)` are all generated by
1102// `#[derive(tatara_closed_set::DeriveClosedSet)]` +
1103// `#[closed_set(via = "as_str", generate_unknown, display)]` on the
1104// enum declaration above. The auto-derived carrier label "horizon
1105// kind" matches the prior hand-rolled `#[error("unknown horizon
1106// kind: {0}")]` annotation byte-for-byte. See the retrofit comment
1107// block on [`ConvergencePointType`] for the canonical narrative.
1108
1109impl Horizon {
1110    pub fn bounded() -> Self {
1111        Self::default()
1112    }
1113
1114    pub fn asymptotic(
1115        metric: impl Into<String>,
1116        direction: OptimizationDirection,
1117        threshold: f64,
1118    ) -> Self {
1119        Self {
1120            kind: HorizonKind::Asymptotic,
1121            metric: Some(metric.into()),
1122            direction: Some(direction),
1123            healthy_rate_threshold: Some(threshold),
1124        }
1125    }
1126}
1127
1128/// Direction of asymptotic optimization — does the metric trend
1129/// downward (cost / latency / error rate) or upward
1130/// (throughput / coverage / revenue)?
1131///
1132/// Closed-set sibling on the classification axis algebra; the `ALL` /
1133/// `as_str` / Display / `FromStr` triad mirrors
1134/// [`ConvergencePointType::ALL`], [`SubstrateType::ALL`],
1135/// [`DataClassification::ALL`], [`CalmClassification::ALL`],
1136/// [`crate::pool::PoolPhase::ALL`], [`crate::pool::MemberState::ALL`],
1137/// [`crate::pool::ReplacementPolicy::ALL`],
1138/// [`crate::pool::ReturnPolicy::ALL`],
1139/// [`crate::boundary::ConditionKind::ALL`],
1140/// [`crate::lifetime::TeardownPolicy::ALL`],
1141/// [`crate::lifetime::LifetimeKind::ALL`],
1142/// [`crate::intent::IntentKind::ALL`],
1143/// [`crate::phase::ProcessPhase::ALL`],
1144/// [`crate::signal::ProcessSignal::ALL`]. The
1145/// [`Self::is_improvement`] predicate is the load-bearing
1146/// optimization primitive — `Asymptotic` horizons read it as the
1147/// typed image of "did this metric sample improve over the last
1148/// one?" rather than re-deriving `<` vs `>` from the variant name
1149/// at every consumer site (rate-window evaluators, breathe-band
1150/// regression detectors, asymptotic-health probes).
1151#[derive(
1152    Clone,
1153    Copy,
1154    Debug,
1155    PartialEq,
1156    Eq,
1157    Hash,
1158    Serialize,
1159    Deserialize,
1160    JsonSchema,
1161    Default,
1162    tatara_closed_set::DeriveClosedSet,
1163)]
1164#[serde(rename_all = "PascalCase")]
1165#[closed_set(via = "as_str", generate_unknown, display)]
1166pub enum OptimizationDirection {
1167    /// Cost / latency / error rate — lower is better. The default for
1168    /// an under-specified `Asymptotic` horizon so an unannotated
1169    /// metric can't silently flip the rate-window evaluator's polarity
1170    /// (a future `Maximize`-default-via-rename would silently invert
1171    /// every existing alert that treats decreasing rate as healthy).
1172    #[default]
1173    Minimize,
1174    /// Throughput / coverage / revenue — higher is better.
1175    Maximize,
1176}
1177
1178impl OptimizationDirection {
1179    /// The closed set of optimization directions — single source of
1180    /// truth that drives the `as_str` / Display / `FromStr` triad AND
1181    /// the `prefers_lower` partition AND the `is_improvement`
1182    /// load-bearing primitive AND both `From` bridge arms. Adding a
1183    /// third variant (e.g. a `Stabilize` sentinel for "drive toward
1184    /// a target value", which neither minimization nor maximization
1185    /// names) lands at one `ALL` entry + one `as_str` arm + one
1186    /// `prefers_lower` arm + one `is_improvement` arm + two bridge
1187    /// arms — exhaustively checked by the compiler (the `[Self; 2]`
1188    /// array literal forces the arity) AND by the per-variant
1189    /// truth-table tests (a new variant must declare its own
1190    /// improvement semantics, or every asymptotic-health probe will
1191    /// silently bucket it). Closes the load-bearing classification
1192    /// sub-axis that the `Horizon.direction` field threads through
1193    /// every `Asymptotic` Process.
1194    pub const ALL: [Self; 2] = [Self::Minimize, Self::Maximize];
1195
1196    /// Canonical PascalCase wire-format projection — matches the serde
1197    /// `rename_all = "PascalCase"` output verbatim AND the CRD `enum:`
1198    /// enumeration the Process schema stamps on
1199    /// `spec.classification.horizon.direction`. Pinned by
1200    /// `optimization_direction_as_str_matches_serde` so a variant
1201    /// rename can't drift between the typed surface, the CRD enum, the
1202    /// YAML wire format AND any future operator-facing diagnostic
1203    /// composed as `direction={kind}` via Display rather than a
1204    /// hard-coded literal. Display + `FromStr` triad over `ALL`
1205    /// mirrors every sibling closed-set enum in this crate.
1206    pub const fn as_str(self) -> &'static str {
1207        match self {
1208            Self::Minimize => "Minimize",
1209            Self::Maximize => "Maximize",
1210        }
1211    }
1212
1213    /// Does this direction prefer numerically lower values?
1214    /// Closed-set match (not `matches!`) so a future variant triggers
1215    /// the compiler's exhaustiveness check at this site rather than
1216    /// silently defaulting to `false` (which would mis-bucket a
1217    /// `Stabilize`-style variant onto the maximization path). The
1218    /// boolean partition is the algebraic shape of an optimization
1219    /// direction: `Minimize ⇒ true`, `Maximize ⇒ false`. Mirrors
1220    /// [`CalmClassification::requires_coordination`] — a two-variant
1221    /// truth-table that any future dispatch on a per-direction policy
1222    /// (rate-window evaluator polarity, breathe-band regression
1223    /// detector sign, asymptotic-health threshold direction) reads
1224    /// once rather than re-deriving from the variant name.
1225    pub const fn prefers_lower(self) -> bool {
1226        match self {
1227            Self::Minimize => true,
1228            Self::Maximize => false,
1229        }
1230    }
1231
1232    /// LOAD-BEARING OPTIMIZATION PRIMITIVE: under this direction, is
1233    /// `after` strictly better than `before`? Closed-set match so a
1234    /// future variant triggers the compiler's exhaustiveness check
1235    /// rather than silently defaulting to `false` (which would
1236    /// silently mark every sample as a regression). For `Minimize`,
1237    /// improvement means `after < before`; for `Maximize`, `after >
1238    /// before`. Strict inequality so a no-op sample (equal values) is
1239    /// NOT counted as improvement — pinned by
1240    /// `optimization_direction_no_op_is_not_improvement`, which
1241    /// guarantees a flatlined rate-window evaluator doesn't silently
1242    /// keep claiming "still improving" forever and skipping the
1243    /// healthy-rate-threshold gate. NaN on either operand short-
1244    /// circuits to `false` (no improvement claim from indeterminate
1245    /// data) via the standard `PartialOrd` behavior — pinned by
1246    /// `optimization_direction_nan_is_not_improvement`. The
1247    /// asymmetry contract (`is_improvement(a, b)` xor
1248    /// `is_improvement(b, a)` for distinct finite samples) is pinned
1249    /// by `optimization_direction_is_improvement_is_antisymmetric`,
1250    /// the algebraic shape that every asymptotic-health rate-window
1251    /// evaluator depends on to avoid double-counting an improvement
1252    /// as a regression on the reverse traversal.
1253    pub fn is_improvement(self, before: f64, after: f64) -> bool {
1254        match self {
1255            Self::Minimize => after < before,
1256            Self::Maximize => after > before,
1257        }
1258    }
1259}
1260
1261// `impl FromStr for OptimizationDirection` +
1262// `impl tatara_lisp::ClosedSet for OptimizationDirection` +
1263// `impl std::fmt::Display for OptimizationDirection` +
1264// `pub struct UnknownOptimizationDirection(pub String)` are all
1265// generated by `#[derive(tatara_closed_set::DeriveClosedSet)]` +
1266// `#[closed_set(via = "as_str", generate_unknown, display)]` on the
1267// enum declaration above. The auto-derived carrier label
1268// "optimization direction" matches the prior hand-rolled
1269// `#[error("unknown optimization direction: {0}")]` annotation
1270// byte-for-byte. See the retrofit comment block on
1271// [`ConvergencePointType`] for the canonical narrative.
1272
1273/// CALM theorem classification — determines whether coordination is required.
1274///
1275/// Closed-set sibling on the classification axis algebra; the `ALL` /
1276/// `as_str` / Display / `FromStr` triad mirrors
1277/// [`ConvergencePointType::ALL`], [`SubstrateType::ALL`],
1278/// [`DataClassification::ALL`], [`crate::pool::PoolPhase::ALL`],
1279/// [`crate::pool::MemberState::ALL`], [`crate::pool::ReplacementPolicy::ALL`],
1280/// [`crate::pool::ReturnPolicy::ALL`],
1281/// [`crate::boundary::ConditionKind::ALL`],
1282/// [`crate::lifetime::TeardownPolicy::ALL`],
1283/// [`crate::lifetime::LifetimeKind::ALL`],
1284/// [`crate::intent::IntentKind::ALL`],
1285/// [`crate::phase::ProcessPhase::ALL`],
1286/// [`crate::signal::ProcessSignal::ALL`]. The
1287/// [`Self::requires_coordination`] predicate is the CALM theorem
1288/// keystone — Hellerstein's "Consistency As Logical Monotonicity"
1289/// states that a program can be distributed without coordination iff
1290/// it computes a monotone function, so `Monotone ⇒ no coordination`
1291/// and `NonMonotone ⇒ requires coordination` is a typed image of the
1292/// theorem itself rather than a runtime convention. Future reconciler
1293/// dispatch on `calm.requires_coordination()` (Raft for non-monotone
1294/// writes; gossip for monotone ones) reads this projection rather
1295/// than re-deriving from variant names.
1296#[derive(
1297    Clone,
1298    Copy,
1299    Debug,
1300    PartialEq,
1301    Eq,
1302    Hash,
1303    Serialize,
1304    Deserialize,
1305    JsonSchema,
1306    Default,
1307    tatara_closed_set::DeriveClosedSet,
1308)]
1309#[serde(rename_all = "PascalCase")]
1310#[closed_set(via = "as_str", generate_unknown, display)]
1311pub enum CalmClassification {
1312    /// Can be distributed without coordination (CALM ⇒ the program
1313    /// computes a monotone function).
1314    #[default]
1315    Monotone,
1316    /// Requires coordination (CALM ⇒ the program is not monotone).
1317    NonMonotone,
1318}
1319
1320impl CalmClassification {
1321    /// The closed set of CALM classifications — single source of truth
1322    /// that drives the `as_str` / Display / `FromStr` triad AND the
1323    /// `requires_coordination` predicate. Adding a third variant
1324    /// (e.g. a `ConditionallyMonotone` sentinel for ops that are
1325    /// monotone under a witness, like CRDT joins under a fixed
1326    /// schema) lands at one `ALL` entry + one `as_str` arm + one
1327    /// predicate arm + one bridge-pair arm — exhaustively checked by
1328    /// the compiler (the `[Self; 2]` array literal forces the arity)
1329    /// AND by the per-variant predicate truth-table test (a new
1330    /// variant must declare its own coordination requirement or any
1331    /// future reconciler-side dispatch will silently bucket it).
1332    /// Closes the load-bearing classification-axis enum that the
1333    /// `Classification.calm` field exposes to every Process and that
1334    /// [`tatara_lattice`]'s boolean-lattice `Lattice for
1335    /// CalmClassification` impl reads via [`Self::requires_coordination`]
1336    /// as the lattice's `top()` predicate.
1337    pub const ALL: [Self; 2] = [Self::Monotone, Self::NonMonotone];
1338
1339    /// Canonical PascalCase wire-format projection — matches the
1340    /// serde `rename_all = "PascalCase"` output verbatim AND the CRD
1341    /// `enum:` enumeration that the Process schema stamps on
1342    /// `spec.classification.calm`. Pinned by
1343    /// `calm_classification_as_str_matches_serde` so a variant rename
1344    /// can't drift between the typed surface, the CRD enum, the YAML
1345    /// wire format AND any future operator-facing diagnostic that
1346    /// composes `calm={kind}` via Display rather than a hard-coded
1347    /// literal that would silently rot. Display + FromStr triad over
1348    /// `ALL` mirrors every sibling closed-set enum in this crate.
1349    pub const fn as_str(self) -> &'static str {
1350        match self {
1351            Self::Monotone => "Monotone",
1352            Self::NonMonotone => "NonMonotone",
1353        }
1354    }
1355
1356    /// CALM-THEOREM KEYSTONE: does this classification require
1357    /// distributed coordination? Closed-set match (not `matches!`) so
1358    /// a future variant triggers the compiler's exhaustiveness check
1359    /// at this site rather than silently defaulting to `false` and
1360    /// shipping a non-monotone operation onto the no-coordination
1361    /// path. The theorem (Hellerstein 2010) states that a program can
1362    /// be distributed without coordination iff it computes a monotone
1363    /// function — `Monotone ⇒ false` and `NonMonotone ⇒ true` is the
1364    /// typed image of that biconditional. Consumers (future reconciler
1365    /// dispatch between Raft writes and gossip propagation; current
1366    /// `tatara_lattice` boolean-lattice ordering where `Monotone ≤
1367    /// NonMonotone`) read this predicate rather than re-deriving from
1368    /// variant names.
1369    pub const fn requires_coordination(self) -> bool {
1370        match self {
1371            Self::Monotone => false,
1372            Self::NonMonotone => true,
1373        }
1374    }
1375}
1376
1377// `impl FromStr for CalmClassification` +
1378// `impl tatara_lisp::ClosedSet for CalmClassification` +
1379// `impl std::fmt::Display for CalmClassification` +
1380// `pub struct UnknownCalmClassification(pub String)` are all generated
1381// by `#[derive(tatara_closed_set::DeriveClosedSet)]` +
1382// `#[closed_set(via = "as_str", generate_unknown, display)]` on the
1383// enum declaration above. The auto-derived carrier label
1384// "calm classification" matches the prior hand-rolled
1385// `#[error("unknown calm classification: {0}")]` annotation
1386// byte-for-byte. See the retrofit comment block on
1387// [`ConvergencePointType`] for the canonical narrative.
1388
1389/// Data sensitivity, drives compliance baseline selection.
1390///
1391/// Sibling closed-set on the classification axis algebra; the `ALL` /
1392/// `as_str` / Display / `FromStr` triad mirrors
1393/// [`crate::pool::PoolPhase::ALL`], [`crate::pool::MemberState::ALL`],
1394/// [`crate::pool::ReplacementPolicy::ALL`],
1395/// [`crate::pool::ReturnPolicy::ALL`],
1396/// [`crate::boundary::ConditionKind::ALL`],
1397/// [`crate::lifetime::TeardownPolicy::ALL`],
1398/// [`crate::lifetime::LifetimeKind::ALL`],
1399/// [`crate::intent::IntentKind::ALL`],
1400/// [`crate::phase::ProcessPhase::ALL`],
1401/// [`crate::signal::ProcessSignal::ALL`].
1402#[derive(
1403    Clone,
1404    Copy,
1405    Debug,
1406    PartialEq,
1407    Eq,
1408    PartialOrd,
1409    Ord,
1410    Hash,
1411    Serialize,
1412    Deserialize,
1413    JsonSchema,
1414    Default,
1415    tatara_closed_set::DeriveClosedSet,
1416)]
1417#[serde(rename_all = "PascalCase")]
1418#[closed_set(via = "as_str", generate_unknown, display)]
1419pub enum DataClassification {
1420    Public,
1421    #[default]
1422    Internal,
1423    Confidential,
1424    Pii,
1425    Phi,
1426    Pci,
1427}
1428
1429impl DataClassification {
1430    /// The closed set of data classifications — single source of truth
1431    /// that drives the `as_str` / Display / `FromStr` triad AND the
1432    /// `sensitivity_rank` total-order projection AND the
1433    /// `is_restricted` / `is_regulated` predicate pair. Adding a
1434    /// seventh variant lands at one `ALL` entry + one `as_str` arm +
1435    /// one `sensitivity_rank` arm + one arm per predicate —
1436    /// exhaustively checked by the compiler (the `[Self; 6]` array
1437    /// literal forces the arity) AND by the per-variant truth-table
1438    /// contract test (a new variant must declare its own
1439    /// `(is_restricted, is_regulated)` bucket or any future
1440    /// compliance-baseline auto-selector that dispatches on the pair
1441    /// will silently bucket it into the wrong sensitivity column).
1442    /// This closes the sixth classification-axis enum and the closure
1443    /// is consumed by [`tatara_lattice`]'s total-order `Lattice` impl
1444    /// via [`Self::sensitivity_rank`] so the lattice ordering no
1445    /// longer rides silently on declaration order.
1446    pub const ALL: [Self; 6] = [
1447        Self::Public,
1448        Self::Internal,
1449        Self::Confidential,
1450        Self::Pii,
1451        Self::Phi,
1452        Self::Pci,
1453    ];
1454
1455    /// Canonical PascalCase wire-format projection — matches the
1456    /// serde `rename_all = "PascalCase"` output verbatim AND the CRD
1457    /// `enum:` enumeration that the Process schema stamps on
1458    /// `spec.classification.dataClassification`. Pinned by
1459    /// `data_classification_as_str_matches_serde` so a variant rename
1460    /// can't drift between the typed surface, the CRD enum, the YAML
1461    /// wire format AND any future operator-facing diagnostic that
1462    /// composes `dataClassification={class}` via Display rather than
1463    /// a hard-coded literal that would silently rot. Display +
1464    /// FromStr triad over `ALL` mirrors `PoolPhase` / `MemberState` /
1465    /// `ReplacementPolicy` / `ReturnPolicy` / `TeardownPolicy` /
1466    /// `ConditionKind` / `ProcessPhase` / `ProcessSignal`.
1467    pub const fn as_str(self) -> &'static str {
1468        match self {
1469            Self::Public => "Public",
1470            Self::Internal => "Internal",
1471            Self::Confidential => "Confidential",
1472            Self::Pii => "Pii",
1473            Self::Phi => "Phi",
1474            Self::Pci => "Pci",
1475        }
1476    }
1477
1478    /// Explicit total-order rank, sealed at one site so the lattice
1479    /// ordering stops riding silently on declaration order. Pre-lift
1480    /// the tatara-lattice `Lattice for DataClassification` impl
1481    /// compared variants via `(*self as u8) <= (*other as u8)`, so a
1482    /// future variant inserted in the middle of the enum (say a
1483    /// `Restricted` between `Internal` and `Confidential`) would
1484    /// silently shift every subsequent variant's `as u8` value AND
1485    /// the lattice's `leq` relation — no compile error, no test
1486    /// failure, but every compliance-baseline comparison
1487    /// downstream would have moved by one slot. Post-lift the rank
1488    /// is declared explicitly per variant; an insertion forces the
1489    /// author to pick a rank deliberately (and
1490    /// `data_classification_rank_is_strictly_monotone_over_all`
1491    /// pins the existing six variants at 0..6 so the lattice's
1492    /// total order remains the documented
1493    /// `Public < Internal < Confidential < Pii < Phi < Pci`).
1494    pub const fn sensitivity_rank(self) -> u8 {
1495        match self {
1496            Self::Public => 0,
1497            Self::Internal => 1,
1498            Self::Confidential => 2,
1499            Self::Pii => 3,
1500            Self::Phi => 4,
1501            Self::Pci => 5,
1502        }
1503    }
1504
1505    /// Is this classification subject to external regulatory regime
1506    /// (HIPAA / PCI-DSS / GDPR-style data-subject controls)?
1507    /// Closed-set match (not `matches!`) so a future variant triggers
1508    /// the compiler's exhaustiveness check at this site rather than
1509    /// silently defaulting to `false`. Paired with `is_restricted`
1510    /// they form the two-axis projection that future
1511    /// compliance-baseline auto-selectors dispatch against —
1512    /// `(false, false)` ⇒ freely distributable (`Public`);
1513    /// `(false, true)` ⇒ access-controlled but not regulated
1514    /// (`Internal | Confidential`); `(true, true)` ⇒ regulated data
1515    /// that implies access control (`Pii | Phi | Pci`). The
1516    /// impossible bucket `(true, false)` — regulated data without
1517    /// access control — is pinned empty by
1518    /// `data_classification_regulated_implies_restricted`.
1519    pub const fn is_regulated(self) -> bool {
1520        match self {
1521            Self::Pii | Self::Phi | Self::Pci => true,
1522            Self::Public | Self::Internal | Self::Confidential => false,
1523        }
1524    }
1525
1526    /// Does this classification require access controls beyond
1527    /// freely-distributable? Closed-set match so a future variant
1528    /// triggers the compiler's exhaustiveness check. See
1529    /// `is_regulated` for the predicate-pair contract + bucket
1530    /// definitions.
1531    pub const fn is_restricted(self) -> bool {
1532        match self {
1533            Self::Public => false,
1534            Self::Internal | Self::Confidential | Self::Pii | Self::Phi | Self::Pci => true,
1535        }
1536    }
1537}
1538
1539// `impl FromStr for DataClassification` +
1540// `impl tatara_lisp::ClosedSet for DataClassification` +
1541// `impl std::fmt::Display for DataClassification` +
1542// `pub struct UnknownDataClassification(pub String)` are all generated
1543// by `#[derive(tatara_closed_set::DeriveClosedSet)]` +
1544// `#[closed_set(via = "as_str", generate_unknown, display)]` on the
1545// enum declaration above. The auto-derived carrier label
1546// "data classification" matches the prior hand-rolled
1547// `#[error("unknown data classification: {0}")]` annotation
1548// byte-for-byte. See the retrofit comment block on
1549// [`ConvergencePointType`] for the canonical narrative.
1550
1551// ───────────────────────────── bridges to tatara-core ─────────────────
1552
1553impl From<ConvergencePointType> for core::ConvergencePointType {
1554    fn from(v: ConvergencePointType) -> Self {
1555        use ConvergencePointType::*;
1556        match v {
1557            Transform => Self::Transform,
1558            Fork => Self::Fork,
1559            Join => Self::Join,
1560            Gate => Self::Gate,
1561            Select => Self::Select,
1562            Broadcast => Self::Broadcast,
1563            Reduce => Self::Reduce,
1564            Observe => Self::Observe,
1565        }
1566    }
1567}
1568
1569impl From<core::ConvergencePointType> for ConvergencePointType {
1570    fn from(v: core::ConvergencePointType) -> Self {
1571        use core::ConvergencePointType as C;
1572        match v {
1573            C::Transform => Self::Transform,
1574            C::Fork => Self::Fork,
1575            C::Join => Self::Join,
1576            C::Gate => Self::Gate,
1577            C::Select => Self::Select,
1578            C::Broadcast => Self::Broadcast,
1579            C::Reduce => Self::Reduce,
1580            C::Observe => Self::Observe,
1581        }
1582    }
1583}
1584
1585impl From<SubstrateType> for core::SubstrateType {
1586    fn from(v: SubstrateType) -> Self {
1587        use SubstrateType::*;
1588        match v {
1589            Financial => Self::Financial,
1590            Compute => Self::Compute,
1591            Network => Self::Network,
1592            Storage => Self::Storage,
1593            Security => Self::Security,
1594            Identity => Self::Identity,
1595            Observability => Self::Observability,
1596            Regulatory => Self::Regulatory,
1597        }
1598    }
1599}
1600
1601impl From<core::SubstrateType> for SubstrateType {
1602    fn from(v: core::SubstrateType) -> Self {
1603        use core::SubstrateType as C;
1604        match v {
1605            C::Financial => Self::Financial,
1606            C::Compute => Self::Compute,
1607            C::Network => Self::Network,
1608            C::Storage => Self::Storage,
1609            C::Security => Self::Security,
1610            C::Identity => Self::Identity,
1611            C::Observability => Self::Observability,
1612            C::Regulatory => Self::Regulatory,
1613        }
1614    }
1615}
1616
1617impl From<OptimizationDirection> for core::OptimizationDirection {
1618    fn from(v: OptimizationDirection) -> Self {
1619        match v {
1620            OptimizationDirection::Minimize => Self::Minimize,
1621            OptimizationDirection::Maximize => Self::Maximize,
1622        }
1623    }
1624}
1625
1626impl From<core::OptimizationDirection> for OptimizationDirection {
1627    fn from(v: core::OptimizationDirection) -> Self {
1628        use core::OptimizationDirection as C;
1629        match v {
1630            C::Minimize => Self::Minimize,
1631            C::Maximize => Self::Maximize,
1632        }
1633    }
1634}
1635
1636impl From<Horizon> for core::ConvergenceHorizon {
1637    fn from(v: Horizon) -> Self {
1638        match v.kind {
1639            HorizonKind::Bounded => Self::Bounded,
1640            HorizonKind::Asymptotic => Self::Asymptotic {
1641                metric: v.metric.unwrap_or_default(),
1642                direction: v.direction.unwrap_or_default().into(),
1643                healthy_rate_threshold: v.healthy_rate_threshold.unwrap_or_default(),
1644            },
1645        }
1646    }
1647}
1648
1649impl From<CalmClassification> for core::CalmClassification {
1650    fn from(v: CalmClassification) -> Self {
1651        match v {
1652            CalmClassification::Monotone => Self::Monotone,
1653            CalmClassification::NonMonotone => Self::NonMonotone,
1654        }
1655    }
1656}
1657
1658impl From<core::CalmClassification> for CalmClassification {
1659    fn from(v: core::CalmClassification) -> Self {
1660        use core::CalmClassification as C;
1661        match v {
1662            C::Monotone => Self::Monotone,
1663            C::NonMonotone => Self::NonMonotone,
1664        }
1665    }
1666}
1667
1668impl From<DataClassification> for core_compl::DataClassification {
1669    fn from(v: DataClassification) -> Self {
1670        use DataClassification::*;
1671        match v {
1672            Public => Self::Public,
1673            Internal => Self::Internal,
1674            Confidential => Self::Confidential,
1675            Pii => Self::Pii,
1676            Phi => Self::Phi,
1677            Pci => Self::Pci,
1678        }
1679    }
1680}
1681
1682impl From<core_compl::DataClassification> for DataClassification {
1683    fn from(v: core_compl::DataClassification) -> Self {
1684        use core_compl::DataClassification as C;
1685        match v {
1686            C::Public => Self::Public,
1687            C::Internal => Self::Internal,
1688            C::Confidential => Self::Confidential,
1689            C::Pii => Self::Pii,
1690            C::Phi => Self::Phi,
1691            C::Pci => Self::Pci,
1692        }
1693    }
1694}
1695
1696#[cfg(test)]
1697mod tests {
1698    use super::*;
1699    // The closed-set tests below call `T::from_str(bad)` via the
1700    // derive-generated `FromStr` impls — bring the trait into scope at
1701    // the test module so the lib body doesn't carry an otherwise-unused
1702    // `use std::str::FromStr;` at the file head.
1703    use std::str::FromStr;
1704
1705    #[test]
1706    fn bridges_roundtrip() {
1707        let pt: core::ConvergencePointType = ConvergencePointType::Gate.into();
1708        let back: ConvergencePointType = pt.into();
1709        assert_eq!(back, ConvergencePointType::Gate);
1710
1711        let sub: core::SubstrateType = SubstrateType::Observability.into();
1712        let back: SubstrateType = sub.into();
1713        assert_eq!(back, SubstrateType::Observability);
1714    }
1715
1716    #[test]
1717    fn data_classification_ordering() {
1718        assert!(DataClassification::Public < DataClassification::Pii);
1719        assert!(DataClassification::Internal < DataClassification::Confidential);
1720    }
1721
1722    #[test]
1723    fn horizon_default_is_bounded() {
1724        assert_eq!(Horizon::default().kind, HorizonKind::Bounded);
1725    }
1726
1727    // ── Classification::gate_compute substrate pins ─────────────────────
1728    //
1729    // The six-line `Classification { point_type: Gate, substrate: Compute,
1730    // horizon: Default::default(), calm: Default::default(),
1731    // data_classification: Default::default() }` struct-literal was
1732    // open-coded verbatim at ten hand-authored callsites before the
1733    // primitive closed it. These pins bind the composed shape at
1734    // fail-before-pass-after granularity so a regression that flipped a
1735    // baseline axis, drifted a sibling default, or leaked a non-baseline
1736    // slot into the substrate composer surfaces HERE rather than as
1737    // silent operator-visible drift at every unadorned ephemeral env
1738    // (the one production consumer, `default_ephemeral_class`) AND every
1739    // downstream test fixture that keys assertions on the shape.
1740
1741    #[test]
1742    fn gate_compute_composes_the_five_baseline_axes() {
1743        // Primary shape: every axis parked at the workspace baseline.
1744        // A regression that flipped `point_type` off `Gate` or
1745        // `substrate` off `Compute` — the two axes with no `Default` —
1746        // surfaces here.
1747        let c = Classification::gate_compute();
1748        assert_eq!(c.point_type, ConvergencePointType::Gate);
1749        assert_eq!(c.substrate, SubstrateType::Compute);
1750        assert_eq!(c.horizon, Horizon::default());
1751        assert_eq!(c.calm, CalmClassification::default());
1752        assert_eq!(c.data_classification, DataClassification::default());
1753    }
1754
1755    #[test]
1756    fn gate_compute_defaulted_axes_ride_sibling_closed_set_defaults() {
1757        // Pins the sibling-default correspondence the doc comment
1758        // names — a regression that flipped a sibling default (a new
1759        // `HorizonKind` variant promoted to `#[default]`, a rename of
1760        // `CalmClassification::Monotone`, a promotion of `Pii` above
1761        // `Internal` in the `DataClassification` ordering) would move
1762        // the baseline HERE rather than at every downstream consumer.
1763        let c = Classification::gate_compute();
1764        assert_eq!(c.horizon.kind, HorizonKind::Bounded);
1765        assert_eq!(c.calm, CalmClassification::Monotone);
1766        assert_eq!(c.data_classification, DataClassification::Internal);
1767    }
1768
1769    #[test]
1770    fn gate_compute_matches_hand_authored_pre_lift_bytewise() {
1771        // Byte-identical parity with the pre-lift six-line struct-literal
1772        // that recurred at ten hand-authored sites. A regression that
1773        // reshaped the primitive would diverge from the pre-lift block
1774        // HERE rather than at every downstream fixture that keys on the
1775        // shape.
1776        let composed = Classification::gate_compute();
1777        let hand_authored = Classification {
1778            point_type: ConvergencePointType::Gate,
1779            substrate: SubstrateType::Compute,
1780            horizon: Horizon::default(),
1781            calm: CalmClassification::default(),
1782            data_classification: DataClassification::default(),
1783        };
1784        assert_eq!(composed, hand_authored);
1785    }
1786
1787    #[test]
1788    fn gate_compute_is_call_time_construction_not_a_shared_singleton() {
1789        // Two independent calls produce structurally-equal but distinct
1790        // values — pins that the primitive is a plain constructor
1791        // rather than a `lazy_static` clone (which would leak a shared
1792        // singleton whose in-place mutation at one consumer would
1793        // silently mutate the shape at every other consumer). The `!=`
1794        // check on `&mut _`-obtained pointer addresses is intentional:
1795        // a shared singleton would collide, and the pin catches the
1796        // regression at the primitive rather than at the operator-facing
1797        // shape-drift downstream.
1798        let a = Classification::gate_compute();
1799        let b = Classification::gate_compute();
1800        assert_eq!(a, b);
1801        assert!(!std::ptr::eq(&a, &b));
1802    }
1803
1804    // ── closed-set algebra contracts for DataClassification
1805    //    (ALL × as_str × FromStr × rank × predicate pair) ────────────
1806
1807    /// Structural well-formedness of [`DataClassification`] as a
1808    /// [`tatara_lisp::ClosedSet`] implementor — the workspace-wide
1809    /// testkit lift that pins all three structural invariants (`ALL`
1810    /// is non-empty, every variant round-trips through
1811    /// `label ↔ parse_label`, labels are pairwise distinct, `""` is
1812    /// outside the closed set) at ONE call site. Replaces the hand-
1813    /// derived `data_classification_all_is_unique_and_complete` +
1814    /// `data_classification_roundtrip_via_as_str` + the empty-input arm
1815    /// of `unknown_data_classification_errors`. `FromStr` delegates to
1816    /// `<Self as tatara_closed_set::ClosedSet>::parse_label`, so this helper
1817    /// exercises the same code path the reconciler hits when parsing a
1818    /// CRD `enum:`-validated `dataClassification` value back to the
1819    /// typed classification.
1820    #[test]
1821    fn data_classification_is_well_formed_closed_set() {
1822        tatara_closed_set::assert_closed_set_well_formed::<DataClassification>();
1823    }
1824
1825    /// CANONICAL-KEY CONTRACT: `as_str` matches serde's PascalCase
1826    /// output verbatim for every variant. A future variant rename (or
1827    /// an `as_str` arm typo) lands here at one site, instead of
1828    /// drifting between the typed surface, the CRD enum, and the YAML
1829    /// wire format the reconciler stamps on
1830    /// `spec.classification.dataClassification`.
1831    #[test]
1832    fn data_classification_as_str_matches_serde() {
1833        crate::tagged_union::assert_label_matches_serde_serialization::<DataClassification>();
1834    }
1835
1836    /// The Display impl IS `as_str` — pinning this lets future callers
1837    /// reach for either projection without drift. Any operator-facing
1838    /// "dataClassification={class}" diagnostic that composes through
1839    /// Display inherits the canonical wire-format string automatically.
1840    #[test]
1841    fn data_classification_display_matches_as_str() {
1842        crate::tagged_union::assert_display_matches_label::<DataClassification>();
1843    }
1844
1845    /// `FromStr` rejects strings that aren't in the canonical
1846    /// projection — lowercased / typo / cross-axis-leaked — and the
1847    /// error echoes the input verbatim so the operator-facing
1848    /// diagnostic carries the offending value, not a normalized form.
1849    /// The empty-input arm is pinned by
1850    /// [`data_classification_is_well_formed_closed_set`] via the
1851    /// `tatara_lisp::ClosedSet` testkit; the cases here pin the
1852    /// verbatim-echo contract on the [`UnknownDataClassification`]
1853    /// newtype, which the trait's `make_unknown` can't see.
1854    #[test]
1855    fn unknown_data_classification_errors() {
1856        for bad in [
1857            "pii",          // lowercased
1858            "PII",          // uppercased
1859            "PersonalData", // typo
1860            "internal_data",
1861            "Steady",   // PoolPhase-axis leak
1862            "Replace",  // ReturnPolicy-axis leak
1863            "Attested", // ProcessPhase-axis leak
1864            "Compute",  // SubstrateType-axis leak
1865            "Gate",     // ConvergencePointType-axis leak
1866            "Monotone", // CalmClassification-axis leak
1867        ] {
1868            let err = DataClassification::from_str(bad).unwrap_err();
1869            assert_eq!(err.0, bad, "error payload should echo input verbatim");
1870        }
1871    }
1872
1873    // `unknown_data_classification_message_matches_substrate_convention`
1874    // removed — clause (5) of
1875    // `tatara_closed_set::assert_closed_set_well_formed::<DataClassification>()`
1876    // verifies the substrate-wide `"unknown {SET_LABEL}: {input}"`
1877    // shape generically (called from
1878    // `data_classification_is_well_formed_closed_set` above); the
1879    // `SET_LABEL` projection is pinned by
1880    // `tatara_lisp_derive::pascal_to_spaced_lowercase_tests`.
1881
1882    /// TRUTH-TABLE CONTRACT: the predicate pair agrees with the
1883    /// documented per-variant compliance role. Pinning this table at
1884    /// one site means any future compliance-baseline auto-selector
1885    /// reads the same projection that the reconciler writes.
1886    #[test]
1887    fn data_classification_predicate_truth_tables() {
1888        assert!(!DataClassification::Public.is_restricted());
1889        assert!(!DataClassification::Public.is_regulated());
1890
1891        assert!(DataClassification::Internal.is_restricted());
1892        assert!(!DataClassification::Internal.is_regulated());
1893
1894        assert!(DataClassification::Confidential.is_restricted());
1895        assert!(!DataClassification::Confidential.is_regulated());
1896
1897        assert!(DataClassification::Pii.is_restricted());
1898        assert!(DataClassification::Pii.is_regulated());
1899
1900        assert!(DataClassification::Phi.is_restricted());
1901        assert!(DataClassification::Phi.is_regulated());
1902
1903        assert!(DataClassification::Pci.is_restricted());
1904        assert!(DataClassification::Pci.is_regulated());
1905    }
1906
1907    /// IMPLICATION CONTRACT: every regulated classification is also
1908    /// restricted. The impossible bucket (regulated AND
1909    /// freely-distributable) is pinned empty so a future variant that
1910    /// returned `(true, false)` from the predicate pair would FAIL
1911    /// here, forcing the author to either flip `is_restricted` or
1912    /// extend the consumer dispatch sites (compliance-baseline
1913    /// auto-selector, audit-log mandatory-fields validator)
1914    /// deliberately rather than silently producing a regulated class
1915    /// the API server would accept as freely-distributable. Encoded as
1916    /// material implication `is_regulated → is_restricted` so the
1917    /// boolean reads as the documented contract, not its NAND form.
1918    #[test]
1919    fn data_classification_regulated_implies_restricted() {
1920        for class in DataClassification::ALL {
1921            assert!(
1922                !class.is_regulated() || class.is_restricted(),
1923                "{class:?} is regulated but not restricted — \
1924                 regulated data is by definition not freely distributable",
1925            );
1926        }
1927    }
1928
1929    /// COVERAGE CONTRACT: every variant lands in exactly one of three
1930    /// compliance buckets — freely distributable (`Public`),
1931    /// restricted-only (`Internal | Confidential`), or regulated
1932    /// (`Pii | Phi | Pci`). Pins the three buckets at their declared
1933    /// cardinalities (1, 2, 3 — sum to `ALL.len()`) so a future
1934    /// variant lands somewhere deliberately.
1935    #[test]
1936    fn data_classification_buckets_cover_every_variant() {
1937        let mut free = 0u32;
1938        let mut restricted_only = 0u32;
1939        let mut regulated = 0u32;
1940        for class in DataClassification::ALL {
1941            match (class.is_restricted(), class.is_regulated()) {
1942                (false, false) => free += 1,
1943                (true, false) => restricted_only += 1,
1944                (true, true) => regulated += 1,
1945                (false, true) => {
1946                    panic!("regulated_implies_restricted already pins this empty for {class:?}")
1947                }
1948            }
1949        }
1950        assert_eq!(free, 1, "free bucket: Public");
1951        assert_eq!(
1952            restricted_only, 2,
1953            "restricted-only bucket: Internal + Confidential"
1954        );
1955        assert_eq!(regulated, 3, "regulated bucket: Pii + Phi + Pci");
1956        assert_eq!(
1957            free + restricted_only + regulated,
1958            DataClassification::ALL.len() as u32
1959        );
1960    }
1961
1962    /// MONOTONE-RANK CONTRACT: `sensitivity_rank` is strictly
1963    /// monotone over `ALL`'s declared order, so the lattice ordering
1964    /// `Public < Internal < Confidential < Pii < Phi < Pci` is sealed
1965    /// at one site (this enum's projection) instead of riding on the
1966    /// silent `as u8` cast in [`tatara_lattice`]. A future variant
1967    /// inserted in the middle would either preserve strict monotonicity
1968    /// here (and the lattice keeps working) or FAIL here at compile or
1969    /// test time (and the author has to renumber deliberately). Also
1970    /// pins the rank codomain at `0..ALL.len()` so no variant can
1971    /// silently outrank the documented top.
1972    #[test]
1973    fn data_classification_rank_is_strictly_monotone_over_all() {
1974        let ranks: Vec<u8> = DataClassification::ALL
1975            .into_iter()
1976            .map(DataClassification::sensitivity_rank)
1977            .collect();
1978        for win in ranks.windows(2) {
1979            assert!(win[0] < win[1], "ranks not strictly monotone: {ranks:?}");
1980        }
1981        assert_eq!(*ranks.first().unwrap(), 0, "bottom rank must be 0");
1982        assert_eq!(
1983            *ranks.last().unwrap(),
1984            (DataClassification::ALL.len() as u8) - 1,
1985            "top rank must be ALL.len() - 1"
1986        );
1987    }
1988
1989    /// RANK-AGREES-WITH-ORD CONTRACT: the typed `sensitivity_rank`
1990    /// projection agrees with the derived `PartialOrd` / `Ord` for
1991    /// every pair in `ALL × ALL`. This is the bridge that lets
1992    /// [`tatara_lattice`]'s total-order `Lattice for DataClassification`
1993    /// impl call `sensitivity_rank` instead of `as u8` without changing
1994    /// any observable lattice behavior — and it lets a future
1995    /// reordering of the enum's variant declarations land at this test
1996    /// site (forcing the rank arms to be renumbered) rather than
1997    /// silently shifting the lattice's `leq` relation.
1998    #[test]
1999    fn data_classification_rank_agrees_with_partial_ord() {
2000        for a in DataClassification::ALL {
2001            for b in DataClassification::ALL {
2002                assert_eq!(
2003                    a.sensitivity_rank() <= b.sensitivity_rank(),
2004                    a <= b,
2005                    "rank vs. PartialOrd drift on ({a:?}, {b:?})"
2006                );
2007            }
2008        }
2009    }
2010
2011    /// DEFAULT-AGREEMENT CONTRACT: `DataClassification::default()`
2012    /// returns `Internal` (the variant tagged `#[default]`), AND that
2013    /// variant lands in the restricted-only bucket — neither freely
2014    /// distributable nor externally regulated. A future `#[default]`
2015    /// rename without flipping the predicates fails here.
2016    #[test]
2017    fn data_classification_default_is_internal_in_restricted_only_bucket() {
2018        let d = DataClassification::default();
2019        assert_eq!(d, DataClassification::Internal);
2020        assert!(d.is_restricted());
2021        assert!(!d.is_regulated());
2022        assert_eq!(d.sensitivity_rank(), 1);
2023    }
2024
2025    /// BRIDGE ROUND-TRIP CONTRACT: every variant survives the
2026    /// CRD-facing (`PascalCase`) ↔ tatara-core (`snake_case`)
2027    /// `From` hop. Today the bridge is two hand-written 6-arm matches
2028    /// in this file; pinning the round-trip over `ALL` means a future
2029    /// variant added without extending the bridge fails here at one
2030    /// site instead of drifting between the CRD wire format and the
2031    /// `core_compl::DataClassification` selector axis.
2032    #[test]
2033    fn data_classification_bridge_roundtrip_over_all() {
2034        for class in DataClassification::ALL {
2035            let core: core_compl::DataClassification = class.into();
2036            let back: DataClassification = core.into();
2037            assert_eq!(back, class, "bridge round-trip failed for {class:?}");
2038        }
2039    }
2040
2041    // ── closed-set algebra contracts for ConvergencePointType
2042    //    (ALL × as_str × FromStr × arity-pair × predicate triple) ────
2043
2044    /// Structural well-formedness of [`ConvergencePointType`] as a
2045    /// [`tatara_lisp::ClosedSet`] implementor — the workspace-wide
2046    /// testkit lift that pins all three structural invariants (`ALL`
2047    /// is non-empty, every variant round-trips through `label ↔
2048    /// parse_label`, labels are pairwise distinct, `""` is outside
2049    /// the closed set) at ONE call site. Replaces the hand-derived
2050    /// `convergence_point_type_all_is_unique_and_complete` +
2051    /// `convergence_point_type_roundtrip_via_as_str` + the empty-
2052    /// input arm of `unknown_convergence_point_type_errors`.
2053    /// `FromStr` delegates to `<Self as tatara_closed_set::ClosedSet>::parse_label`,
2054    /// so this helper exercises the same code path the reconciler
2055    /// hits when parsing a CRD `enum:`-validated value back to the
2056    /// typed point-type. The forced `[Self; 8]` array literal on
2057    /// `ConvergencePointType::ALL` still pins the cardinality at the
2058    /// declaration site.
2059    #[test]
2060    fn convergence_point_type_is_well_formed_closed_set() {
2061        tatara_closed_set::assert_closed_set_well_formed::<ConvergencePointType>();
2062    }
2063
2064    /// CANONICAL-KEY CONTRACT: `as_str` matches serde's PascalCase
2065    /// output verbatim for every variant. A future variant rename (or
2066    /// an `as_str` arm typo) lands here at one site, instead of
2067    /// drifting between the typed surface, the CRD enum, and the YAML
2068    /// wire format the reconciler reads from
2069    /// `spec.classification.pointType`.
2070    #[test]
2071    fn convergence_point_type_as_str_matches_serde() {
2072        crate::tagged_union::assert_label_matches_serde_serialization::<ConvergencePointType>();
2073    }
2074
2075    /// The Display impl IS `as_str` — pinning this lets future callers
2076    /// reach for either projection without drift.
2077    #[test]
2078    fn convergence_point_type_display_matches_as_str() {
2079        crate::tagged_union::assert_display_matches_label::<ConvergencePointType>();
2080    }
2081
2082    /// `FromStr` rejects strings outside the canonical projection —
2083    /// lowercased / typo / cross-axis-leaked — and the error echoes
2084    /// the input verbatim so the operator-facing diagnostic surfaces
2085    /// the bad value, not a normalized form. The empty-input arm is
2086    /// pinned by [`convergence_point_type_is_well_formed_closed_set`]
2087    /// via the `tatara_lisp::ClosedSet` testkit; the cases here pin
2088    /// the verbatim-echo contract on the
2089    /// [`UnknownConvergencePointType`] newtype, which the trait's
2090    /// `make_unknown` can't see.
2091    #[test]
2092    fn unknown_convergence_point_type_errors() {
2093        for bad in [
2094            "gate",       // lowercased
2095            "GATE",       // uppercased
2096            "Transformr", // typo
2097            "Filter",
2098            "Steady",   // PoolPhase-axis leak
2099            "Pii",      // DataClassification-axis leak
2100            "Attested", // ProcessPhase-axis leak
2101            "Compute",  // SubstrateType-axis leak
2102            "Monotone", // CalmClassification-axis leak
2103            "PromQL",   // ConditionKind-axis leak
2104        ] {
2105            let err = ConvergencePointType::from_str(bad).unwrap_err();
2106            assert_eq!(err.0, bad, "error payload should echo input verbatim");
2107        }
2108    }
2109
2110    // `unknown_convergence_point_type_message_matches_substrate_convention`
2111    // removed — clause (5) of
2112    // `tatara_closed_set::assert_closed_set_well_formed::<ConvergencePointType>()`
2113    // verifies the substrate-wide `"unknown {SET_LABEL}: {input}"`
2114    // shape generically (called from
2115    // `convergence_point_type_is_well_formed_closed_set` above); the
2116    // `SET_LABEL` projection is pinned by
2117    // `tatara_lisp_derive::pascal_to_spaced_lowercase_tests`.
2118
2119    /// TRUTH-TABLE CONTRACT: the predicate triple agrees with the
2120    /// documented per-variant topology role. Pinning this table at
2121    /// one site means any future DAG validator reads the same
2122    /// projection that compliance bindings dispatch against.
2123    #[test]
2124    fn convergence_point_type_predicate_truth_tables() {
2125        // Endomorphic: 1→1
2126        assert!(ConvergencePointType::Transform.is_endomorphic());
2127        assert!(!ConvergencePointType::Transform.is_diffusive());
2128        assert!(!ConvergencePointType::Transform.is_convergent());
2129
2130        assert!(ConvergencePointType::Observe.is_endomorphic());
2131        assert!(!ConvergencePointType::Observe.is_diffusive());
2132        assert!(!ConvergencePointType::Observe.is_convergent());
2133
2134        // Diffusive: 1→N
2135        assert!(!ConvergencePointType::Fork.is_endomorphic());
2136        assert!(ConvergencePointType::Fork.is_diffusive());
2137        assert!(!ConvergencePointType::Fork.is_convergent());
2138
2139        assert!(!ConvergencePointType::Broadcast.is_endomorphic());
2140        assert!(ConvergencePointType::Broadcast.is_diffusive());
2141        assert!(!ConvergencePointType::Broadcast.is_convergent());
2142
2143        // Convergent: N→1
2144        for t in [
2145            ConvergencePointType::Join,
2146            ConvergencePointType::Gate,
2147            ConvergencePointType::Select,
2148            ConvergencePointType::Reduce,
2149        ] {
2150            assert!(!t.is_endomorphic(), "{t:?} should not be endomorphic");
2151            assert!(!t.is_diffusive(), "{t:?} should not be diffusive");
2152            assert!(t.is_convergent(), "{t:?} should be convergent");
2153        }
2154    }
2155
2156    /// COVERAGE CONTRACT: every variant lands in *exactly one* of the
2157    /// three topology buckets — endomorphic, diffusive, or convergent.
2158    /// Pins the three buckets at their declared cardinalities (2, 2, 4
2159    /// — sum to `ALL.len()`) so a future variant lands somewhere
2160    /// deliberately. No variant returns true from more than one
2161    /// predicate; no variant returns false from all three.
2162    #[test]
2163    fn convergence_point_type_buckets_cover_every_variant() {
2164        let mut endomorphic = 0u32;
2165        let mut diffusive = 0u32;
2166        let mut convergent = 0u32;
2167        for t in ConvergencePointType::ALL {
2168            let buckets = [t.is_endomorphic(), t.is_diffusive(), t.is_convergent()];
2169            let hits: u32 = buckets.iter().map(|b| u32::from(*b)).sum();
2170            assert_eq!(
2171                hits, 1,
2172                "{t:?} landed in {hits} buckets: {buckets:?} (must be exactly one)"
2173            );
2174            if t.is_endomorphic() {
2175                endomorphic += 1;
2176            }
2177            if t.is_diffusive() {
2178                diffusive += 1;
2179            }
2180            if t.is_convergent() {
2181                convergent += 1;
2182            }
2183        }
2184        assert_eq!(endomorphic, 2, "endomorphic bucket: Transform + Observe");
2185        assert_eq!(diffusive, 2, "diffusive bucket: Fork + Broadcast");
2186        assert_eq!(
2187            convergent, 4,
2188            "convergent bucket: Join + Gate + Select + Reduce"
2189        );
2190        assert_eq!(
2191            endomorphic + diffusive + convergent,
2192            ConvergencePointType::ALL.len() as u32
2193        );
2194    }
2195
2196    /// ARITY-PAIR ⇔ BUCKET CONTRACT: the `(input_arity, output_arity)`
2197    /// projection names the same topology partition as the
2198    /// `is_endomorphic` / `is_diffusive` / `is_convergent` predicate
2199    /// triple. `(One, One) ⇒ endomorphic`; `(One, Many) ⇒ diffusive`;
2200    /// `(Many, One) ⇒ convergent`. The impossible `(Many, Many)`
2201    /// bucket is pinned empty here — a `(Many, Many)` point would
2202    /// have no convergence semantics (many independent inputs
2203    /// replicated across many independent outputs) and every future
2204    /// DAG-composition validator would have to special-case it. This
2205    /// seal is the bridge that lets a future graph validator dispatch
2206    /// on either projection (arity pair OR bucket predicates) without
2207    /// drift — and a future variant that wants `(Many, Many)` must
2208    /// extend the bucket carving deliberately rather than silently
2209    /// shipping a fourth topology class.
2210    #[test]
2211    fn convergence_point_type_arity_pair_agrees_with_bucket() {
2212        for t in ConvergencePointType::ALL {
2213            match (t.input_arity(), t.output_arity()) {
2214                (Arity::One, Arity::One) => assert!(
2215                    t.is_endomorphic(),
2216                    "{t:?} has (One, One) arity but is not endomorphic"
2217                ),
2218                (Arity::One, Arity::Many) => assert!(
2219                    t.is_diffusive(),
2220                    "{t:?} has (One, Many) arity but is not diffusive"
2221                ),
2222                (Arity::Many, Arity::One) => assert!(
2223                    t.is_convergent(),
2224                    "{t:?} has (Many, One) arity but is not convergent"
2225                ),
2226                (Arity::Many, Arity::Many) => panic!(
2227                    "{t:?} has (Many, Many) arity — pinned empty; \
2228                     extend the topology carving before adding a variant here"
2229                ),
2230            }
2231        }
2232    }
2233
2234    /// BRIDGE ROUND-TRIP CONTRACT: every variant survives the
2235    /// CRD-facing (`PascalCase`) ↔ tatara-core (`snake_case`)
2236    /// `From` hop. Today the bridge is two hand-written 8-arm
2237    /// matches in this file; pinning the round-trip over `ALL`
2238    /// means a future variant added without extending the bridge
2239    /// fails here at one site instead of drifting between the CRD
2240    /// wire format and the
2241    /// `core::ConvergencePointType` selector axis that
2242    /// `compliance_binding::PointSelector::ByType` already
2243    /// dispatches against.
2244    #[test]
2245    fn convergence_point_type_bridge_roundtrip_over_all() {
2246        for t in ConvergencePointType::ALL {
2247            let core_t: core::ConvergencePointType = t.into();
2248            let back: ConvergencePointType = core_t.into();
2249            assert_eq!(back, t, "bridge round-trip failed for {t:?}");
2250        }
2251    }
2252
2253    // ── closed-set algebra contracts for Arity ───────────────────
2254
2255    /// `ALL` is the source of truth — pin its closure so a variant
2256    /// added without an `ALL` entry fails here. The arity is asserted
2257    /// by the `[Self; 2]` array type itself.
2258    #[test]
2259    fn arity_all_is_unique_and_complete() {
2260        let mut seen = std::collections::HashSet::new();
2261        for a in Arity::ALL {
2262            assert!(seen.insert(a), "duplicate variant in ALL: {a:?}");
2263        }
2264        assert_eq!(seen.len(), Arity::ALL.len());
2265    }
2266
2267    /// The Display impl IS `as_str` — pinning this lets future
2268    /// callers reach for either projection without drift. No serde
2269    /// matching here because `Arity` is a typed projection, not a
2270    /// CRD-facing enum — it never crosses the wire. Routed through
2271    /// the substrate-wide [`crate::tagged_union::assert_display_matches_label`]
2272    /// primitive so the sweep body lives at ONE substrate site rather
2273    /// than restated per-implementor. Also exercised through the
2274    /// substrate-wide `every_production_display_impl_binds_through_the_testkit_primitive`
2275    /// sweep so a per-crate test-site drop cannot silently disable the
2276    /// check.
2277    #[test]
2278    fn arity_display_matches_as_str() {
2279        crate::tagged_union::assert_display_matches_label::<Arity>();
2280    }
2281
2282    /// PREDICATE CONTRACT: `is_one` is true exactly for `Arity::One`.
2283    /// The disjointness against `Many` is structural (only two
2284    /// variants) but pinning the codomain here means a future
2285    /// `Arity::Zero` variant must declare its own `is_one` arm
2286    /// deliberately rather than silently defaulting through a
2287    /// non-closed-set match.
2288    #[test]
2289    fn arity_is_one_predicate_truth_table() {
2290        assert!(Arity::One.is_one());
2291        assert!(!Arity::Many.is_one());
2292    }
2293
2294    // ── closed-set algebra contracts for SubstrateType
2295    //    (ALL × as_str × FromStr × predicate triple × bridge) ─────────
2296
2297    /// Structural well-formedness of [`SubstrateType`] as a
2298    /// [`tatara_lisp::ClosedSet`] implementor — see
2299    /// [`convergence_point_type_is_well_formed_closed_set`] for the
2300    /// canonical lift narrative. Replaces
2301    /// `substrate_type_all_is_unique_and_complete` +
2302    /// `substrate_type_roundtrip_via_as_str` + the empty-input arm
2303    /// of `unknown_substrate_type_errors`.
2304    #[test]
2305    fn substrate_type_is_well_formed_closed_set() {
2306        tatara_closed_set::assert_closed_set_well_formed::<SubstrateType>();
2307    }
2308
2309    /// CANONICAL-KEY CONTRACT: `as_str` matches serde's PascalCase
2310    /// output verbatim for every variant. A future variant rename
2311    /// (or an `as_str` arm typo) lands here at one site, instead of
2312    /// drifting between the typed surface, the CRD enum, and the
2313    /// YAML wire format the reconciler reads from
2314    /// `spec.classification.substrate`.
2315    #[test]
2316    fn substrate_type_as_str_matches_serde() {
2317        crate::tagged_union::assert_label_matches_serde_serialization::<SubstrateType>();
2318    }
2319
2320    /// The Display impl IS `as_str` — pinning this lets future
2321    /// callers reach for either projection without drift. Any
2322    /// operator-facing `substrate={kind}` diagnostic that composes
2323    /// through Display inherits the canonical wire-format string
2324    /// automatically.
2325    #[test]
2326    fn substrate_type_display_matches_as_str() {
2327        crate::tagged_union::assert_display_matches_label::<SubstrateType>();
2328    }
2329
2330    /// `FromStr` rejects strings outside the canonical projection —
2331    /// lowercased / typo / cross-axis-leaked — and the error echoes
2332    /// the input verbatim so the operator-facing diagnostic surfaces
2333    /// the bad value, not a normalized form. The empty-input arm is
2334    /// pinned by [`substrate_type_is_well_formed_closed_set`] via
2335    /// the `tatara_lisp::ClosedSet` testkit; the cases here pin the
2336    /// verbatim-echo contract on the [`UnknownSubstrateType`]
2337    /// newtype, which the trait's `make_unknown` can't see.
2338    #[test]
2339    fn unknown_substrate_type_errors() {
2340        for bad in [
2341            "compute",  // lowercased
2342            "COMPUTE",  // uppercased
2343            "Computte", // typo
2344            "Database", "Steady",   // PoolPhase-axis leak
2345            "Pii",      // DataClassification-axis leak
2346            "Attested", // ProcessPhase-axis leak
2347            "Gate",     // ConvergencePointType-axis leak
2348            "Monotone", // CalmClassification-axis leak
2349            "PromQL",   // ConditionKind-axis leak
2350        ] {
2351            let err = SubstrateType::from_str(bad).unwrap_err();
2352            assert_eq!(err.0, bad, "error payload should echo input verbatim");
2353        }
2354    }
2355
2356    // `unknown_substrate_type_message_matches_substrate_convention`
2357    // removed — clause (5) of
2358    // `tatara_closed_set::assert_closed_set_well_formed::<SubstrateType>()`
2359    // verifies the substrate-wide `"unknown {SET_LABEL}: {input}"`
2360    // shape generically (called from
2361    // `substrate_type_is_well_formed_closed_set` above); the
2362    // `SET_LABEL` projection is pinned by
2363    // `tatara_lisp_derive::pascal_to_spaced_lowercase_tests`.
2364
2365    /// TRUTH-TABLE CONTRACT: the predicate triple agrees with the
2366    /// documented per-variant plane role. Pinning this table at one
2367    /// site means any future compliance-baseline selector reads the
2368    /// same projection that the reconciler stamps on the CRD.
2369    #[test]
2370    fn substrate_type_predicate_truth_tables() {
2371        // Resource plane: you allocate budgets from it.
2372        for t in [
2373            SubstrateType::Financial,
2374            SubstrateType::Compute,
2375            SubstrateType::Network,
2376            SubstrateType::Storage,
2377        ] {
2378            assert!(t.is_resource(), "{t:?} should be a resource substrate");
2379            assert!(!t.is_policy(), "{t:?} should not be a policy substrate");
2380            assert!(
2381                !t.is_telemetry(),
2382                "{t:?} should not be a telemetry substrate"
2383            );
2384        }
2385
2386        // Policy plane: it gates access for other workloads.
2387        for t in [
2388            SubstrateType::Security,
2389            SubstrateType::Identity,
2390            SubstrateType::Regulatory,
2391        ] {
2392            assert!(!t.is_resource(), "{t:?} should not be a resource substrate");
2393            assert!(t.is_policy(), "{t:?} should be a policy substrate");
2394            assert!(
2395                !t.is_telemetry(),
2396                "{t:?} should not be a telemetry substrate"
2397            );
2398        }
2399
2400        // Telemetry plane: it observes other workloads.
2401        assert!(!SubstrateType::Observability.is_resource());
2402        assert!(!SubstrateType::Observability.is_policy());
2403        assert!(SubstrateType::Observability.is_telemetry());
2404    }
2405
2406    /// COVERAGE CONTRACT: every variant lands in *exactly one* of
2407    /// the three plane buckets — resource, policy, or telemetry.
2408    /// Pins the three buckets at their declared cardinalities (4,
2409    /// 3, 1 — sum to `ALL.len()`) so a future variant lands
2410    /// somewhere deliberately. No variant returns true from more
2411    /// than one predicate; no variant returns false from all three.
2412    #[test]
2413    fn substrate_type_buckets_cover_every_variant() {
2414        let mut resource = 0u32;
2415        let mut policy = 0u32;
2416        let mut telemetry = 0u32;
2417        for t in SubstrateType::ALL {
2418            let buckets = [t.is_resource(), t.is_policy(), t.is_telemetry()];
2419            let hits: u32 = buckets.iter().map(|b| u32::from(*b)).sum();
2420            assert_eq!(
2421                hits, 1,
2422                "{t:?} landed in {hits} buckets: {buckets:?} (must be exactly one)"
2423            );
2424            if t.is_resource() {
2425                resource += 1;
2426            }
2427            if t.is_policy() {
2428                policy += 1;
2429            }
2430            if t.is_telemetry() {
2431                telemetry += 1;
2432            }
2433        }
2434        assert_eq!(
2435            resource, 4,
2436            "resource bucket: Financial + Compute + Network + Storage"
2437        );
2438        assert_eq!(policy, 3, "policy bucket: Security + Identity + Regulatory");
2439        assert_eq!(telemetry, 1, "telemetry bucket: Observability");
2440        assert_eq!(
2441            resource + policy + telemetry,
2442            SubstrateType::ALL.len() as u32
2443        );
2444    }
2445
2446    /// BRIDGE ROUND-TRIP CONTRACT: every variant survives the
2447    /// CRD-facing (`PascalCase`) ↔ tatara-core (`snake_case`)
2448    /// `From` hop. Today the bridge is two hand-written 8-arm
2449    /// matches in this file; pinning the round-trip over `ALL`
2450    /// means a future variant added without extending the bridge
2451    /// fails here at one site instead of drifting between the CRD
2452    /// wire format and the `core::SubstrateType` selector axis
2453    /// that `compliance_binding::PointSelector::BySubstrate`
2454    /// already dispatches against.
2455    #[test]
2456    fn substrate_type_bridge_roundtrip_over_all() {
2457        for t in SubstrateType::ALL {
2458            let core_t: core::SubstrateType = t.into();
2459            let back: SubstrateType = core_t.into();
2460            assert_eq!(back, t, "bridge round-trip failed for {t:?}");
2461        }
2462    }
2463
2464    // ── closed-set algebra contracts for CalmClassification
2465    //    (ALL × as_str × FromStr × requires_coordination × bridge) ─────
2466
2467    /// Structural well-formedness of [`CalmClassification`] as a
2468    /// [`tatara_lisp::ClosedSet`] implementor — see
2469    /// [`convergence_point_type_is_well_formed_closed_set`] for the
2470    /// canonical lift narrative. Replaces
2471    /// `calm_classification_all_is_unique_and_complete` +
2472    /// `calm_classification_roundtrip_via_as_str` + the empty-input
2473    /// arm of `unknown_calm_classification_errors`.
2474    #[test]
2475    fn calm_classification_is_well_formed_closed_set() {
2476        tatara_closed_set::assert_closed_set_well_formed::<CalmClassification>();
2477    }
2478
2479    /// CANONICAL-KEY CONTRACT: `as_str` matches serde's PascalCase
2480    /// output verbatim for every variant. A future variant rename
2481    /// (or an `as_str` arm typo) lands here at one site, instead of
2482    /// drifting between the typed surface, the CRD enum, and the
2483    /// YAML wire format the reconciler reads from
2484    /// `spec.classification.calm`.
2485    #[test]
2486    fn calm_classification_as_str_matches_serde() {
2487        crate::tagged_union::assert_label_matches_serde_serialization::<CalmClassification>();
2488    }
2489
2490    /// The Display impl IS `as_str` — pinning this lets future
2491    /// callers reach for either projection without drift. Any
2492    /// operator-facing `calm={kind}` diagnostic that composes
2493    /// through Display inherits the canonical wire-format string
2494    /// automatically.
2495    #[test]
2496    fn calm_classification_display_matches_as_str() {
2497        crate::tagged_union::assert_display_matches_label::<CalmClassification>();
2498    }
2499
2500    /// `FromStr` rejects strings outside the canonical projection —
2501    /// lowercased / typo / cross-axis-leaked — and the error echoes
2502    /// the input verbatim so the operator-facing diagnostic surfaces
2503    /// the bad value, not a normalized form. The empty-input arm is
2504    /// pinned by [`calm_classification_is_well_formed_closed_set`]
2505    /// via the `tatara_lisp::ClosedSet` testkit; the cases here pin
2506    /// the verbatim-echo contract on the
2507    /// [`UnknownCalmClassification`] newtype, which the trait's
2508    /// `make_unknown` can't see.
2509    #[test]
2510    fn unknown_calm_classification_errors() {
2511        for bad in [
2512            "monotone",     // lowercased
2513            "MONOTONE",     // uppercased
2514            "Mono",         // typo
2515            "non_monotone", // core's snake_case form (must not cross axes)
2516            "non-monotone", // dashed
2517            "Monotonic",    // close-typo
2518            "Steady",       // PoolPhase-axis leak
2519            "Pii",          // DataClassification-axis leak
2520            "Attested",     // ProcessPhase-axis leak
2521            "Compute",      // SubstrateType-axis leak
2522            "Gate",         // ConvergencePointType-axis leak
2523            "PromQL",       // ConditionKind-axis leak
2524        ] {
2525            let err = CalmClassification::from_str(bad).unwrap_err();
2526            assert_eq!(err.0, bad, "error payload should echo input verbatim");
2527        }
2528    }
2529
2530    // `unknown_calm_classification_message_matches_substrate_convention`
2531    // removed — clause (5) of
2532    // `tatara_closed_set::assert_closed_set_well_formed::<CalmClassification>()`
2533    // verifies the substrate-wide `"unknown {SET_LABEL}: {input}"`
2534    // shape generically (called from
2535    // `calm_classification_is_well_formed_closed_set` above); the
2536    // `SET_LABEL` projection is pinned by
2537    // `tatara_lisp_derive::pascal_to_spaced_lowercase_tests`.
2538
2539    /// CALM-THEOREM TRUTH-TABLE CONTRACT: `requires_coordination`
2540    /// implements the biconditional half of Hellerstein's CALM
2541    /// theorem — `Monotone ⇒ false` and `NonMonotone ⇒ true`.
2542    /// Pinning this table at one site means any future reconciler
2543    /// dispatch that picks between Raft writes and gossip
2544    /// propagation reads the same projection the lattice ordering
2545    /// (`Monotone ≤ NonMonotone`) does. A future variant that
2546    /// flipped this mapping would have to renumber every consumer
2547    /// deliberately rather than silently shipping a non-monotone
2548    /// operation onto the no-coordination path.
2549    #[test]
2550    fn calm_classification_requires_coordination_truth_table() {
2551        assert!(!CalmClassification::Monotone.requires_coordination());
2552        assert!(CalmClassification::NonMonotone.requires_coordination());
2553    }
2554
2555    /// COVERAGE CONTRACT: every variant lands in exactly one of two
2556    /// coordination buckets — no-coordination (`Monotone`) or
2557    /// requires-coordination (`NonMonotone`). Pins the two buckets
2558    /// at their declared cardinalities (1, 1 — sum to `ALL.len()`)
2559    /// so a future variant lands somewhere deliberately. The
2560    /// biconditional structure of the CALM theorem makes this
2561    /// partition exhaustive by construction.
2562    #[test]
2563    fn calm_classification_buckets_cover_every_variant() {
2564        let mut no_coord = 0u32;
2565        let mut coord = 0u32;
2566        for c in CalmClassification::ALL {
2567            if c.requires_coordination() {
2568                coord += 1;
2569            } else {
2570                no_coord += 1;
2571            }
2572        }
2573        assert_eq!(no_coord, 1, "no-coordination bucket: Monotone");
2574        assert_eq!(coord, 1, "requires-coordination bucket: NonMonotone");
2575        assert_eq!(no_coord + coord, CalmClassification::ALL.len() as u32);
2576    }
2577
2578    /// DEFAULT-AGREEMENT CONTRACT: `CalmClassification::default()`
2579    /// returns `Monotone` (the variant tagged `#[default]`) AND that
2580    /// variant lands in the no-coordination bucket. A future
2581    /// `#[default]` rename without flipping the predicate fails
2582    /// here — the default for an under-specified Process must
2583    /// remain the no-coordination side so that an unannotated
2584    /// Process can't silently demand Raft writes the reconciler
2585    /// isn't configured to provide.
2586    #[test]
2587    fn calm_classification_default_is_monotone_no_coordination() {
2588        let c = CalmClassification::default();
2589        assert_eq!(c, CalmClassification::Monotone);
2590        assert!(!c.requires_coordination());
2591    }
2592
2593    /// BRIDGE ROUND-TRIP CONTRACT: every variant survives the
2594    /// CRD-facing (`PascalCase`) ↔ tatara-core (`snake_case`)
2595    /// `From` hop. Today the bridge is two hand-written 2-arm
2596    /// matches in this file; pinning the round-trip over `ALL`
2597    /// means a future variant added without extending the bridge
2598    /// fails here at one site instead of drifting between the CRD
2599    /// wire format and the `core::CalmClassification` selector
2600    /// axis. Closes the asymmetry that pre-lift had a
2601    /// `From<CalmClassification> for core::CalmClassification`
2602    /// forward bridge but no reverse — symmetric to every other
2603    /// classification-axis bridge in this file.
2604    #[test]
2605    fn calm_classification_bridge_roundtrip_over_all() {
2606        for c in CalmClassification::ALL {
2607            let core_c: core::CalmClassification = c.into();
2608            let back: CalmClassification = core_c.into();
2609            assert_eq!(back, c, "bridge round-trip failed for {c:?}");
2610        }
2611    }
2612
2613    // ── closed-set algebra contracts for OptimizationDirection
2614    //    (ALL × as_str × FromStr × prefers_lower × is_improvement) ───
2615
2616    /// Structural well-formedness of [`OptimizationDirection`] as a
2617    /// [`tatara_lisp::ClosedSet`] implementor — see
2618    /// [`convergence_point_type_is_well_formed_closed_set`] for the
2619    /// canonical lift narrative. Replaces
2620    /// `optimization_direction_all_is_unique_and_complete` +
2621    /// `optimization_direction_roundtrip_via_as_str` + the empty-
2622    /// input arm of `unknown_optimization_direction_errors`.
2623    #[test]
2624    fn optimization_direction_is_well_formed_closed_set() {
2625        tatara_closed_set::assert_closed_set_well_formed::<OptimizationDirection>();
2626    }
2627
2628    /// CANONICAL-KEY CONTRACT: `as_str` matches serde's PascalCase
2629    /// output verbatim for every variant. A future variant rename
2630    /// (or an `as_str` arm typo) lands here at one site, instead of
2631    /// drifting between the typed surface, the CRD enum, and the
2632    /// YAML wire format the reconciler reads from
2633    /// `spec.classification.horizon.direction`.
2634    #[test]
2635    fn optimization_direction_as_str_matches_serde() {
2636        crate::tagged_union::assert_label_matches_serde_serialization::<OptimizationDirection>();
2637    }
2638
2639    /// The Display impl IS `as_str` — pinning this lets future
2640    /// callers reach for either projection without drift. Any
2641    /// operator-facing `direction={kind}` diagnostic that composes
2642    /// through Display inherits the canonical wire-format string
2643    /// automatically.
2644    #[test]
2645    fn optimization_direction_display_matches_as_str() {
2646        crate::tagged_union::assert_display_matches_label::<OptimizationDirection>();
2647    }
2648
2649    /// `FromStr` rejects strings outside the canonical projection —
2650    /// lowercased / typo / cross-axis-leaked — and the error echoes
2651    /// the input verbatim so the operator-facing diagnostic surfaces
2652    /// the bad value, not a normalized form. The empty-input arm is
2653    /// pinned by [`optimization_direction_is_well_formed_closed_set`]
2654    /// via the `tatara_lisp::ClosedSet` testkit; the cases here pin
2655    /// the verbatim-echo contract on the
2656    /// [`UnknownOptimizationDirection`] newtype, which the trait's
2657    /// `make_unknown` can't see.
2658    #[test]
2659    fn unknown_optimization_direction_errors() {
2660        for bad in [
2661            "minimize", // lowercased
2662            "MINIMIZE", // uppercased
2663            "Minimze",  // typo
2664            "Lower",    // synonym, not canonical
2665            "Higher",   // synonym, not canonical
2666            "Asc",      // wire-leak from sort-order axis
2667            "Desc",     // wire-leak from sort-order axis
2668            "Bounded",  // HorizonKind-axis leak
2669            "Monotone", // CalmClassification-axis leak
2670            "Steady",   // PoolPhase-axis leak
2671            "Pii",      // DataClassification-axis leak
2672            "Attested", // ProcessPhase-axis leak
2673            "Compute",  // SubstrateType-axis leak
2674            "Gate",     // ConvergencePointType-axis leak
2675            "PromQL",   // ConditionKind-axis leak
2676        ] {
2677            let err = OptimizationDirection::from_str(bad).unwrap_err();
2678            assert_eq!(err.0, bad, "error payload should echo input verbatim");
2679        }
2680    }
2681
2682    // `unknown_optimization_direction_message_matches_substrate_convention`
2683    // removed — clause (5) of
2684    // `tatara_closed_set::assert_closed_set_well_formed::<OptimizationDirection>()`
2685    // verifies the substrate-wide `"unknown {SET_LABEL}: {input}"`
2686    // shape generically (called from
2687    // `optimization_direction_is_well_formed_closed_set` above); the
2688    // `SET_LABEL` projection is pinned by
2689    // `tatara_lisp_derive::pascal_to_spaced_lowercase_tests`.
2690
2691    /// TRUTH-TABLE CONTRACT: `prefers_lower` is the boolean
2692    /// partition `Minimize ⇒ true`, `Maximize ⇒ false`. Pinning this
2693    /// table at one site means any future dispatch on per-direction
2694    /// polarity (rate-window evaluator, breathe-band regression
2695    /// detector) reads the same projection rather than re-deriving
2696    /// from the variant name. Mirrors
2697    /// [`CalmClassification::requires_coordination`]'s truth-table
2698    /// shape.
2699    #[test]
2700    fn optimization_direction_prefers_lower_truth_table() {
2701        assert!(OptimizationDirection::Minimize.prefers_lower());
2702        assert!(!OptimizationDirection::Maximize.prefers_lower());
2703    }
2704
2705    /// COVERAGE CONTRACT: every variant lands in exactly one of two
2706    /// polarity buckets — prefers-lower (`Minimize`) or
2707    /// prefers-higher (`Maximize`). Pins the two buckets at their
2708    /// declared cardinalities (1, 1 — sum to `ALL.len()`) so a
2709    /// future variant lands somewhere deliberately.
2710    #[test]
2711    fn optimization_direction_buckets_cover_every_variant() {
2712        let mut lower = 0u32;
2713        let mut higher = 0u32;
2714        for d in OptimizationDirection::ALL {
2715            if d.prefers_lower() {
2716                lower += 1;
2717            } else {
2718                higher += 1;
2719            }
2720        }
2721        assert_eq!(lower, 1, "prefers-lower bucket: Minimize");
2722        assert_eq!(higher, 1, "prefers-higher bucket: Maximize");
2723        assert_eq!(lower + higher, OptimizationDirection::ALL.len() as u32);
2724    }
2725
2726    /// LOAD-BEARING TRUTH-TABLE: `is_improvement` answers "is `after`
2727    /// strictly better than `before` under this direction?" for the
2728    /// canonical samples. Pins the strict-improvement semantic at
2729    /// one site so a future rate-window evaluator or breathe-band
2730    /// regression detector reads the same projection that the
2731    /// asymptotic-health probe writes.
2732    #[test]
2733    fn optimization_direction_is_improvement_truth_table() {
2734        // Minimize: lower-is-better
2735        assert!(OptimizationDirection::Minimize.is_improvement(10.0, 5.0));
2736        assert!(!OptimizationDirection::Minimize.is_improvement(5.0, 10.0));
2737
2738        // Maximize: higher-is-better
2739        assert!(OptimizationDirection::Maximize.is_improvement(5.0, 10.0));
2740        assert!(!OptimizationDirection::Maximize.is_improvement(10.0, 5.0));
2741    }
2742
2743    /// NO-OP CONTRACT: a sample equal to the previous one is NOT an
2744    /// improvement under either direction. Pinning this guarantees
2745    /// a flatlined rate-window evaluator doesn't silently keep
2746    /// claiming "still improving" forever and skipping the
2747    /// healthy-rate-threshold gate.
2748    #[test]
2749    fn optimization_direction_no_op_is_not_improvement() {
2750        for d in OptimizationDirection::ALL {
2751            assert!(
2752                !d.is_improvement(7.0, 7.0),
2753                "{d:?}: equal samples must not count as improvement",
2754            );
2755            assert!(
2756                !d.is_improvement(0.0, 0.0),
2757                "{d:?}: zero/zero must not count as improvement",
2758            );
2759        }
2760    }
2761
2762    /// NaN CONTRACT: NaN on either operand short-circuits to `false`
2763    /// (no improvement claim from indeterminate data) via the
2764    /// standard `PartialOrd` behavior. Without this, a rate-window
2765    /// evaluator that sampled a NaN partway through (a transient
2766    /// metric-scrape failure) would either panic on an `Ord`
2767    /// comparison or — worse — silently claim improvement on the
2768    /// next valid sample by treating NaN as the worst case.
2769    #[test]
2770    fn optimization_direction_nan_is_not_improvement() {
2771        let nan = f64::NAN;
2772        for d in OptimizationDirection::ALL {
2773            assert!(
2774                !d.is_improvement(nan, 1.0),
2775                "{d:?}: NaN before must not count as improvement",
2776            );
2777            assert!(
2778                !d.is_improvement(1.0, nan),
2779                "{d:?}: NaN after must not count as improvement",
2780            );
2781            assert!(
2782                !d.is_improvement(nan, nan),
2783                "{d:?}: NaN/NaN must not count as improvement",
2784            );
2785        }
2786    }
2787
2788    /// ANTISYMMETRY CONTRACT: for distinct finite samples,
2789    /// `is_improvement(a, b)` xor `is_improvement(b, a)` —
2790    /// exactly one direction of the pair counts as improvement.
2791    /// This is the algebraic shape every asymptotic-health
2792    /// rate-window evaluator depends on to avoid double-counting
2793    /// an improvement as a regression on the reverse traversal.
2794    /// A future variant that returned `true` for both directions
2795    /// (or `false` for both, the equal-sample case) would FAIL
2796    /// here, forcing the author to extend the consumer dispatch
2797    /// deliberately.
2798    #[test]
2799    fn optimization_direction_is_improvement_is_antisymmetric() {
2800        let pairs = [(1.0_f64, 2.0_f64), (0.0, 100.0), (-3.5, 3.5), (1e9, 1e-9)];
2801        for d in OptimizationDirection::ALL {
2802            for (a, b) in pairs {
2803                assert!(a != b, "test fixture requires distinct samples");
2804                assert!(
2805                    d.is_improvement(a, b) ^ d.is_improvement(b, a),
2806                    "{d:?}: antisymmetry violated on ({a}, {b})",
2807                );
2808            }
2809        }
2810    }
2811
2812    /// DEFAULT-AGREEMENT CONTRACT:
2813    /// `OptimizationDirection::default()` returns `Minimize` (the
2814    /// variant tagged `#[default]`), AND that variant lands in the
2815    /// prefers-lower bucket. A future `#[default]` rename without
2816    /// flipping the predicate fails here — `Minimize` is the
2817    /// canonical default for distributed-systems asymptotic
2818    /// optimization (cost / latency / error rate), so an
2819    /// unannotated metric must not silently flip the rate-window
2820    /// evaluator's polarity. This is also the same value the
2821    /// `Horizon → ConvergenceHorizon` bridge falls back to when
2822    /// `direction` is unset, so pinning the default here pins the
2823    /// bridge's behavior at one site.
2824    #[test]
2825    fn optimization_direction_default_is_minimize_prefers_lower() {
2826        let d = OptimizationDirection::default();
2827        assert_eq!(d, OptimizationDirection::Minimize);
2828        assert!(d.prefers_lower());
2829    }
2830
2831    /// BRIDGE ROUND-TRIP CONTRACT: every variant survives the
2832    /// CRD-facing (`PascalCase`) ↔ tatara-core (`snake_case`)
2833    /// `From` hop. Pre-lift the bridge was a one-way
2834    /// `From<OptimizationDirection> for core::OptimizationDirection`
2835    /// with no reverse — asymmetric to every other classification-
2836    /// axis bridge in this file. Pinning the round-trip over `ALL`
2837    /// means a future variant added without extending the bridge
2838    /// fails here at one site instead of drifting between the CRD
2839    /// wire format and `core::OptimizationDirection`.
2840    #[test]
2841    fn optimization_direction_bridge_roundtrip_over_all() {
2842        for d in OptimizationDirection::ALL {
2843            let core_d: core::OptimizationDirection = d.into();
2844            let back: OptimizationDirection = core_d.into();
2845            assert_eq!(back, d, "bridge round-trip failed for {d:?}");
2846        }
2847    }
2848
2849    // ── closed-set algebra contracts for HorizonKind
2850    //    (ALL × as_str × FromStr × terminates × requires_metric_axes) ──
2851
2852    /// Structural well-formedness of [`HorizonKind`] as a
2853    /// [`tatara_lisp::ClosedSet`] implementor — see
2854    /// [`convergence_point_type_is_well_formed_closed_set`] for the
2855    /// canonical lift narrative. Replaces
2856    /// `horizon_kind_all_is_unique_and_complete` +
2857    /// `horizon_kind_roundtrip_via_as_str` + the empty-input arm of
2858    /// `unknown_horizon_kind_errors`.
2859    #[test]
2860    fn horizon_kind_is_well_formed_closed_set() {
2861        tatara_closed_set::assert_closed_set_well_formed::<HorizonKind>();
2862    }
2863
2864    /// CANONICAL-KEY CONTRACT: `as_str` matches serde's PascalCase
2865    /// output verbatim for every variant. A future variant rename
2866    /// (or an `as_str` arm typo) lands here at one site, instead of
2867    /// drifting between the typed surface, the CRD enum, and the
2868    /// YAML wire format the reconciler stamps on
2869    /// `spec.classification.horizon.kind`.
2870    #[test]
2871    fn horizon_kind_as_str_matches_serde() {
2872        crate::tagged_union::assert_label_matches_serde_serialization::<HorizonKind>();
2873    }
2874
2875    /// The Display impl IS `as_str` — pinning this lets future
2876    /// callers reach for either projection without drift. Any
2877    /// operator-facing `horizon.kind={kind}` diagnostic that
2878    /// composes through Display inherits the canonical wire-format
2879    /// string automatically.
2880    #[test]
2881    fn horizon_kind_display_matches_as_str() {
2882        crate::tagged_union::assert_display_matches_label::<HorizonKind>();
2883    }
2884
2885    /// `FromStr` rejects strings outside the canonical projection —
2886    /// lowercased / typo / cross-axis-leaked — and the error echoes
2887    /// the input verbatim so the operator-facing diagnostic surfaces
2888    /// the bad value, not a normalized form. The empty-input arm is
2889    /// pinned by [`horizon_kind_is_well_formed_closed_set`] via the
2890    /// `tatara_lisp::ClosedSet` testkit; the cases here pin the
2891    /// verbatim-echo contract on the [`UnknownHorizonKind`] newtype,
2892    /// which the trait's `make_unknown` can't see.
2893    #[test]
2894    fn unknown_horizon_kind_errors() {
2895        for bad in [
2896            "bounded",   // lowercased
2897            "BOUNDED",   // uppercased
2898            "Boundd",    // typo
2899            "Finite",    // synonym, not canonical
2900            "Perpetual", // synonym, not canonical
2901            "Infinite",  // synonym, not canonical
2902            "Minimize",  // OptimizationDirection-axis leak
2903            "Monotone",  // CalmClassification-axis leak
2904            "Pii",       // DataClassification-axis leak
2905            "Steady",    // PoolPhase-axis leak
2906            "Attested",  // ProcessPhase-axis leak
2907            "Compute",   // SubstrateType-axis leak
2908            "Gate",      // ConvergencePointType-axis leak
2909            "PromQL",    // ConditionKind-axis leak
2910        ] {
2911            let err = HorizonKind::from_str(bad).unwrap_err();
2912            assert_eq!(err.0, bad, "error payload should echo input verbatim");
2913        }
2914    }
2915
2916    // `unknown_horizon_kind_message_matches_substrate_convention`
2917    // removed — clause (5) of
2918    // `tatara_closed_set::assert_closed_set_well_formed::<HorizonKind>()`
2919    // verifies the substrate-wide `"unknown {SET_LABEL}: {input}"`
2920    // shape generically (called from
2921    // `horizon_kind_is_well_formed_closed_set` above); the
2922    // `SET_LABEL` projection is pinned by
2923    // `tatara_lisp_derive::pascal_to_spaced_lowercase_tests`.
2924
2925    /// LOAD-BEARING TRUTH-TABLE: `terminates` is the boolean
2926    /// partition `Bounded ⇒ true`, `Asymptotic ⇒ false`. Pinning
2927    /// this table at one site means any future scheduler asking
2928    /// "will this Process reach `Reaped` via natural termination?"
2929    /// reads the same projection that the lattice ordering encodes
2930    /// (Bounded ≤ Asymptotic BECAUSE the bounded horizon strictly
2931    /// refines the asymptotic one by also terminating).
2932    #[test]
2933    fn horizon_kind_terminates_truth_table() {
2934        assert!(HorizonKind::Bounded.terminates());
2935        assert!(!HorizonKind::Asymptotic.terminates());
2936    }
2937
2938    /// LOAD-BEARING TRUTH-TABLE: `requires_metric_axes` is the
2939    /// boolean partition `Bounded ⇒ false`, `Asymptotic ⇒ true` —
2940    /// the typed image of the optionality the [`Horizon`] struct
2941    /// encodes via its three `Option<…>` fields (`metric`,
2942    /// `direction`, `healthy_rate_threshold`). The implicit
2943    /// "Asymptotic only" invariant in the field docs is now a
2944    /// checkable per-kind predicate. Pinning this table at one site
2945    /// means any future horizon-shape validator (CRD admission,
2946    /// `tatara-check` form linter, Lisp authoring-time predicate)
2947    /// reads the same projection.
2948    #[test]
2949    fn horizon_kind_requires_metric_axes_truth_table() {
2950        assert!(!HorizonKind::Bounded.requires_metric_axes());
2951        assert!(HorizonKind::Asymptotic.requires_metric_axes());
2952    }
2953
2954    /// COVERAGE CONTRACT: every variant lands in exactly one of two
2955    /// termination buckets — terminating (`Bounded`) or perpetual
2956    /// (`Asymptotic`). Pins the two buckets at their declared
2957    /// cardinalities (1, 1 — sum to `ALL.len()`) so a future variant
2958    /// lands somewhere deliberately.
2959    #[test]
2960    fn horizon_kind_buckets_cover_every_variant() {
2961        let mut terminating = 0u32;
2962        let mut perpetual = 0u32;
2963        for k in HorizonKind::ALL {
2964            if k.terminates() {
2965                terminating += 1;
2966            } else {
2967                perpetual += 1;
2968            }
2969        }
2970        assert_eq!(terminating, 1, "terminating bucket: Bounded");
2971        assert_eq!(perpetual, 1, "perpetual bucket: Asymptotic");
2972        assert_eq!(terminating + perpetual, HorizonKind::ALL.len() as u32);
2973    }
2974
2975    /// ANTISYMMETRY CONTRACT: for every variant, exactly one of
2976    /// `(terminates, requires_metric_axes)` is true — the two
2977    /// predicates carve the variants into complementary buckets
2978    /// (terminating ↔ no metric axes; perpetual ↔ requires metric
2979    /// axes). A future variant that returned `true` for both (a
2980    /// terminating horizon that nonetheless tracks an asymptotic
2981    /// metric) or `false` for both (an inert horizon with no
2982    /// termination AND no metric signal — there'd be nothing to
2983    /// observe) would fail here, forcing the author to extend
2984    /// either the predicates or the [`Horizon`] struct's
2985    /// optionality contract deliberately.
2986    #[test]
2987    fn horizon_kind_terminate_xor_requires_metric_axes() {
2988        for k in HorizonKind::ALL {
2989            assert!(
2990                k.terminates() ^ k.requires_metric_axes(),
2991                "{k:?}: terminates() XOR requires_metric_axes() must hold",
2992            );
2993        }
2994    }
2995
2996    /// DEFAULT-AGREEMENT CONTRACT: `HorizonKind::default()` returns
2997    /// `Bounded` (the variant tagged `#[default]`), AND that
2998    /// variant lands in the terminating bucket. A future
2999    /// `#[default]` rename without flipping the predicate fails
3000    /// here — `Bounded` is the canonical default for a convergence
3001    /// horizon (a point with no asymptotic axes declared should
3002    /// terminate naturally, not silently flip into a perpetual
3003    /// rate-window evaluator with zero threshold). This is also
3004    /// the same value `Horizon::default()` carries, so pinning the
3005    /// default here pins the struct-default behavior at one site.
3006    #[test]
3007    fn horizon_kind_default_is_bounded_terminates() {
3008        let k = HorizonKind::default();
3009        assert_eq!(k, HorizonKind::Bounded);
3010        assert!(k.terminates());
3011        assert!(!k.requires_metric_axes());
3012    }
3013
3014    /// HORIZON ↔ KIND AGREEMENT: every variant in `HorizonKind::ALL`
3015    /// composes with the existing [`Horizon::bounded`] /
3016    /// [`Horizon::asymptotic`] constructors to produce a `Horizon`
3017    /// whose `kind` matches AND whose `Option<…>` fields agree
3018    /// with `requires_metric_axes`. Pins the implicit contract
3019    /// between the kind discriminator and the optionality at one
3020    /// site — a future kind added without extending either the
3021    /// constructors or `requires_metric_axes` fails here before
3022    /// drifting between the typed surface and the documented
3023    /// "Asymptotic only" field invariant.
3024    #[test]
3025    fn horizon_kind_agrees_with_struct_optionality() {
3026        let bounded = Horizon::bounded();
3027        assert_eq!(bounded.kind, HorizonKind::Bounded);
3028        assert!(!bounded.kind.requires_metric_axes());
3029        assert!(bounded.metric.is_none());
3030        assert!(bounded.direction.is_none());
3031        assert!(bounded.healthy_rate_threshold.is_none());
3032
3033        let asymp = Horizon::asymptotic("p99_latency", OptimizationDirection::Minimize, 0.1);
3034        assert_eq!(asymp.kind, HorizonKind::Asymptotic);
3035        assert!(asymp.kind.requires_metric_axes());
3036        assert!(asymp.metric.is_some());
3037        assert!(asymp.direction.is_some());
3038        assert!(asymp.healthy_rate_threshold.is_some());
3039    }
3040
3041    // ── scalar-carrier presence probe on Classification × ConvergencePointType ──
3042    //
3043    // Fail-before-pass-after granularity: [`Classification::has_point_type`]
3044    // did not exist before this commit — every consumer of the
3045    // `(Classification, ConvergencePointType) -> bool` scalar-carrier
3046    // probe shape restated the `classification.point_type == kind`
3047    // equality body at its own callsite. Post-lift the shape lives at
3048    // ONE substrate owner and every downstream (the `point-type-<kind>`
3049    // require-tag family in `tatara-check`, future audit dispatchers
3050    // walking [`ConvergencePointType::ALL`], any future CRD-facing
3051    // closed-set discriminator on a required scalar `ProcessSpec` field
3052    // such as `has_substrate`/`has_calm`/`has_data_classification`)
3053    // binds through the SAME `has(kind)` shape the Option-slot
3054    // (`Intent::has`, `Lifetime::has`), slice-level
3055    // (`ConditionSliceExt::has_kind`, `DependsOnSliceExt::has_must_reach`,
3056    // `ComplianceBindingSliceExt::has_verification_phase`,
3057    // `ExportSpecSliceExt::has_{when,channel_kind,report_format,artifact_kind}`),
3058    // and prior scalar-carrier
3059    // (`SignalPolicy::has_sighup_strategy`,
3060    // `EncapsulatesSpec::has_mode`) peers publish.
3061
3062    /// DIAGONAL — for every [`ConvergencePointType`] variant, a
3063    /// [`Classification`] whose `point_type` field is set to that
3064    /// variant returns `true` from `has_point_type` on that same
3065    /// variant AND `false` on every other variant. Sweep the
3066    /// [`ConvergencePointType::ALL`] × ALL cross so a regression that
3067    /// hard-coded the arm to a single variant (silently returning
3068    /// `true` on every populated classification regardless of query
3069    /// kind) or wired the equality to a fixed unrelated field fails
3070    /// HERE at the substrate primitive before landing at the
3071    /// operator-facing checks.lisp surface.
3072    #[test]
3073    fn classification_has_point_type_returns_true_iff_variant_matches() {
3074        for populated in ConvergencePointType::ALL {
3075            let c = Classification {
3076                point_type: populated,
3077                substrate: SubstrateType::Compute,
3078                horizon: Horizon::default(),
3079                calm: CalmClassification::default(),
3080                data_classification: DataClassification::default(),
3081            };
3082            for query in ConvergencePointType::ALL {
3083                assert_eq!(
3084                    c.has_point_type(query),
3085                    query == populated,
3086                    "point_type={populated:?}: query {query:?} classification drifted",
3087                );
3088            }
3089        }
3090    }
3091
3092    /// GATE-COMPUTE BASELINE — the workspace-baseline
3093    /// [`Classification::gate_compute`] shape carries
3094    /// `point_type: Gate`, so `has_point_type` returns `true` on
3095    /// [`ConvergencePointType::Gate`] and `false` on every other of
3096    /// the eight variants. Pins the composition of the substrate's
3097    /// baseline-constructor primitive with the scalar-carrier
3098    /// presence probe — a regression that flipped
3099    /// `gate_compute().point_type` off `Gate` (or wired
3100    /// `has_point_type` to a fixed variant answer) fails here at ONE
3101    /// narrow site before drifting across every unadorned ephemeral
3102    /// env (`default_ephemeral_class`) and every downstream test
3103    /// fixture that keys assertions on the shape.
3104    #[test]
3105    fn classification_gate_compute_has_point_type_gate_only() {
3106        let c = Classification::gate_compute();
3107        for kind in ConvergencePointType::ALL {
3108            let expected = kind == ConvergencePointType::Gate;
3109            assert_eq!(
3110                c.has_point_type(kind),
3111                expected,
3112                "gate_compute (point_type=Gate) must return {expected} for {kind:?}",
3113            );
3114        }
3115    }
3116
3117    // ── scalar-carrier presence probe on Classification × SubstrateType ──
3118    //
3119    // Fail-before-pass-after granularity: [`Classification::has_substrate`]
3120    // did not exist before this commit — every consumer of the
3121    // `(Classification, SubstrateType) -> bool` scalar-carrier probe
3122    // shape restated the `classification.substrate == kind` equality
3123    // body at its own callsite. Post-lift the shape lives at ONE
3124    // substrate owner and every downstream (the `substrate-<kind>`
3125    // require-tag family in `tatara-check`, future audit dispatchers
3126    // walking [`SubstrateType::ALL`], any future CRD-facing closed-set
3127    // discriminator on a required scalar `ProcessSpec` field such as
3128    // `has_calm`/`has_data_classification`) binds through the SAME
3129    // `has(kind)` shape the Option-slot (`Intent::has`, `Lifetime::has`),
3130    // slice-level (`ConditionSliceExt::has_kind`,
3131    // `DependsOnSliceExt::has_must_reach`,
3132    // `ComplianceBindingSliceExt::has_verification_phase`,
3133    // `ExportSpecSliceExt::has_{when,channel_kind,report_format,artifact_kind}`),
3134    // and prior scalar-carrier
3135    // (`SignalPolicy::has_sighup_strategy`,
3136    // `EncapsulatesSpec::has_mode`, `Classification::has_point_type`)
3137    // peers publish.
3138
3139    /// DIAGONAL — for every [`SubstrateType`] variant, a
3140    /// [`Classification`] whose `substrate` field is set to that
3141    /// variant returns `true` from `has_substrate` on that same
3142    /// variant AND `false` on every other variant. Sweep the
3143    /// [`SubstrateType::ALL`] × ALL cross so a regression that
3144    /// hard-coded the arm to a single variant (silently returning
3145    /// `true` on every populated classification regardless of query
3146    /// kind) or wired the equality to a fixed unrelated field (a
3147    /// stray probe on `classification.point_type`) fails HERE at the
3148    /// substrate primitive before landing at the operator-facing
3149    /// checks.lisp surface.
3150    #[test]
3151    fn classification_has_substrate_returns_true_iff_variant_matches() {
3152        for populated in SubstrateType::ALL {
3153            let c = Classification {
3154                point_type: ConvergencePointType::Gate,
3155                substrate: populated,
3156                horizon: Horizon::default(),
3157                calm: CalmClassification::default(),
3158                data_classification: DataClassification::default(),
3159            };
3160            for query in SubstrateType::ALL {
3161                assert_eq!(
3162                    c.has_substrate(query),
3163                    query == populated,
3164                    "substrate={populated:?}: query {query:?} classification drifted",
3165                );
3166            }
3167        }
3168    }
3169
3170    /// GATE-COMPUTE BASELINE — the workspace-baseline
3171    /// [`Classification::gate_compute`] shape carries
3172    /// `substrate: Compute`, so `has_substrate` returns `true` on
3173    /// [`SubstrateType::Compute`] and `false` on every other of the
3174    /// eight variants. Pins the composition of the substrate's
3175    /// baseline-constructor primitive with the fourth scalar-carrier
3176    /// presence probe — a regression that flipped
3177    /// `gate_compute().substrate` off `Compute` (or wired
3178    /// `has_substrate` to a fixed variant answer, or crossed the
3179    /// wires to `point_type`) fails here at ONE narrow site before
3180    /// drifting across every unadorned ephemeral env
3181    /// (`default_ephemeral_class`) and every downstream test fixture
3182    /// that keys assertions on the shape. Byte-symmetric with the
3183    /// peer `classification_gate_compute_has_point_type_gate_only`
3184    /// pin on the third scalar-carrier — the two co-tenants on the
3185    /// (required-parent × required-scalar-child) corner walk their
3186    /// own required axis independently.
3187    #[test]
3188    fn classification_gate_compute_has_substrate_compute_only() {
3189        let c = Classification::gate_compute();
3190        for kind in SubstrateType::ALL {
3191            let expected = kind == SubstrateType::Compute;
3192            assert_eq!(
3193                c.has_substrate(kind),
3194                expected,
3195                "gate_compute (substrate=Compute) must return {expected} for {kind:?}",
3196            );
3197        }
3198    }
3199
3200    /// TWO-AXIS INDEPENDENCE — the two co-tenants on the (required-
3201    /// parent × required-scalar-child) corner of the presence-probe
3202    /// algebra ([`Classification::has_point_type`] and
3203    /// [`Classification::has_substrate`]) probe distinct required
3204    /// scalar slots on the SAME [`Classification`] parent, so a
3205    /// carrier with `point_type: Fork` AND `substrate: Storage`
3206    /// answers `true` on both fine tags simultaneously and `false`
3207    /// on every off-diagonal probe of either axis. Pins the two
3208    /// probes' independence at ONE narrow site — a regression that
3209    /// collapsed either onto the other's field (a stray probe of
3210    /// `has_substrate` reading `self.point_type`, or of
3211    /// `has_point_type` reading `self.substrate`) would fail HERE
3212    /// before landing at any consumer. The audit `every Fork-topology
3213    /// Storage-plane point handles SIGHUP by Restart` composes this
3214    /// exact two-axis conjunction on the required scalars of the
3215    /// six-axis classification lattice.
3216    #[test]
3217    fn classification_has_point_type_and_has_substrate_are_independent() {
3218        let c = Classification {
3219            point_type: ConvergencePointType::Fork,
3220            substrate: SubstrateType::Storage,
3221            horizon: Horizon::default(),
3222            calm: CalmClassification::default(),
3223            data_classification: DataClassification::default(),
3224        };
3225        assert!(c.has_point_type(ConvergencePointType::Fork));
3226        assert!(c.has_substrate(SubstrateType::Storage));
3227        assert!(!c.has_point_type(ConvergencePointType::Gate));
3228        assert!(!c.has_substrate(SubstrateType::Compute));
3229        // Cross-wiring probe: `has_point_type(Storage-as-if-Point)` and
3230        // `has_substrate(Fork-as-if-Substrate)` cannot even typecheck
3231        // — the closed-set enums are disjoint types — but a stray
3232        // implementation reading the WRONG required field would flip
3233        // both diagonal answers off. The four asserts above pin the
3234        // independence at ONE narrow site.
3235    }
3236
3237    // ── scalar-carrier presence probe on Classification × CalmClassification ──
3238    //
3239    // Fail-before-pass-after granularity: [`Classification::has_calm`]
3240    // did not exist before this commit — every consumer of the
3241    // `(Classification, CalmClassification) -> bool` scalar-carrier
3242    // probe shape restated the `classification.calm == kind` equality
3243    // body at its own callsite. Post-lift the shape lives at ONE
3244    // substrate owner and every downstream (the `calm-<kind>`
3245    // require-tag family in `tatara-check`, future audit dispatchers
3246    // walking [`CalmClassification::ALL`], any future CRD-facing
3247    // closed-set discriminator on a defaulted scalar `ProcessSpec`
3248    // field such as `has_data_classification`) binds through the SAME
3249    // `has(kind)` shape the Option-slot (`Intent::has`, `Lifetime::has`),
3250    // slice-level (`ConditionSliceExt::has_kind`,
3251    // `DependsOnSliceExt::has_must_reach`,
3252    // `ComplianceBindingSliceExt::has_verification_phase`,
3253    // `ExportSpecSliceExt::has_{when,channel_kind,report_format,artifact_kind}`),
3254    // and prior scalar-carrier
3255    // (`SignalPolicy::has_sighup_strategy`,
3256    // `EncapsulatesSpec::has_mode`, `Classification::has_point_type`,
3257    // `Classification::has_substrate`) peers publish. FIRST occupant
3258    // on the (required-parent × defaulted-scalar-child) corner of the
3259    // presence-probe algebra — a fresh corner distinct from all four
3260    // prior scalar-carrier peers.
3261
3262    /// DIAGONAL — for every [`CalmClassification`] variant, a
3263    /// [`Classification`] whose `calm` field is set to that variant
3264    /// returns `true` from `has_calm` on that same variant AND
3265    /// `false` on every other variant. Sweep the
3266    /// [`CalmClassification::ALL`] × ALL cross so a regression that
3267    /// hard-coded the arm to a single variant (silently returning
3268    /// `true` on every populated classification regardless of query
3269    /// kind) or wired the equality to a fixed unrelated field (a
3270    /// stray probe on `classification.point_type` or
3271    /// `classification.substrate`) fails HERE at the substrate
3272    /// primitive before landing at the operator-facing checks.lisp
3273    /// surface.
3274    #[test]
3275    fn classification_has_calm_returns_true_iff_variant_matches() {
3276        for populated in CalmClassification::ALL {
3277            let c = Classification {
3278                point_type: ConvergencePointType::Gate,
3279                substrate: SubstrateType::Compute,
3280                horizon: Horizon::default(),
3281                calm: populated,
3282                data_classification: DataClassification::default(),
3283            };
3284            for query in CalmClassification::ALL {
3285                assert_eq!(
3286                    c.has_calm(query),
3287                    query == populated,
3288                    "calm={populated:?}: query {query:?} classification drifted",
3289                );
3290            }
3291        }
3292    }
3293
3294    /// GATE-COMPUTE BASELINE — the workspace-baseline
3295    /// [`Classification::gate_compute`] shape carries
3296    /// `calm: CalmClassification::default()` which is
3297    /// [`CalmClassification::Monotone`] via `#[default]`, so
3298    /// `has_calm` returns `true` on [`CalmClassification::Monotone`]
3299    /// and `false` on [`CalmClassification::NonMonotone`]. Pins the
3300    /// composition of the substrate's baseline-constructor primitive
3301    /// with the FIFTH scalar-carrier presence probe AND the sibling-
3302    /// default correspondence documented on [`Classification::gate_compute`]
3303    /// (which pins the three defaulted axes to the sibling closed-set
3304    /// defaults `HorizonKind::Bounded` / `CalmClassification::Monotone`
3305    /// / `DataClassification::Internal`) — a regression that flipped
3306    /// `gate_compute().calm` off `Monotone` (or promoted a different
3307    /// variant to `#[default]` on the closed set, or wired `has_calm`
3308    /// to a fixed variant answer, or crossed the wires to
3309    /// `point_type` / `substrate`) fails here at ONE narrow site
3310    /// before drifting across every unadorned ephemeral env
3311    /// (`default_ephemeral_class`) and every downstream test fixture
3312    /// that keys assertions on the shape. FIRST occupant on the
3313    /// (required-parent × defaulted-scalar-child) corner — locks the
3314    /// corner's characteristic "default-arm short-circuit" property
3315    /// at ONE narrow classifier site: a bare classification answers
3316    /// `true` on the default variant (distinct from the
3317    /// required-child corner peers, where a bare classification must
3318    /// name a variant deliberately to answer `true`).
3319    #[test]
3320    fn classification_gate_compute_has_calm_monotone_only() {
3321        let c = Classification::gate_compute();
3322        for kind in CalmClassification::ALL {
3323            let expected = kind == CalmClassification::Monotone;
3324            assert_eq!(
3325                c.has_calm(kind),
3326                expected,
3327                "gate_compute (calm=Monotone) must return {expected} for {kind:?}",
3328            );
3329        }
3330    }
3331
3332    /// THREE-AXIS INDEPENDENCE — the three co-tenants on the
3333    /// [`Classification`] parent
3334    /// ([`Classification::has_point_type`] +
3335    /// [`Classification::has_substrate`] on the (required-parent ×
3336    /// required-scalar-child) corner AND [`Classification::has_calm`]
3337    /// on the fresh (required-parent × defaulted-scalar-child)
3338    /// corner) probe distinct scalar slots on the SAME parent, so a
3339    /// carrier with `point_type: Fork` AND `substrate: Storage` AND
3340    /// `calm: NonMonotone` answers `true` on all three fine tags
3341    /// simultaneously and `false` on every off-diagonal probe of any
3342    /// axis. Pins the three probes' independence at ONE narrow site
3343    /// — a regression that collapsed any of the three onto another's
3344    /// field (a stray probe of `has_calm` reading `self.point_type`
3345    /// or `self.substrate`, or of either required-axis probe reading
3346    /// `self.calm`) would fail HERE before landing at any consumer.
3347    /// The audit `every Fork-topology Storage-plane NonMonotone-CALM
3348    /// point declares a Raft-guarded write path` composes this exact
3349    /// three-axis conjunction on the required + defaulted scalars of
3350    /// the six-axis classification lattice.
3351    #[test]
3352    fn classification_has_point_type_and_has_substrate_and_has_calm_are_independent() {
3353        let c = Classification {
3354            point_type: ConvergencePointType::Fork,
3355            substrate: SubstrateType::Storage,
3356            horizon: Horizon::default(),
3357            calm: CalmClassification::NonMonotone,
3358            data_classification: DataClassification::default(),
3359        };
3360        assert!(c.has_point_type(ConvergencePointType::Fork));
3361        assert!(c.has_substrate(SubstrateType::Storage));
3362        assert!(c.has_calm(CalmClassification::NonMonotone));
3363        assert!(!c.has_point_type(ConvergencePointType::Gate));
3364        assert!(!c.has_substrate(SubstrateType::Compute));
3365        assert!(!c.has_calm(CalmClassification::Monotone));
3366    }
3367
3368    // ── scalar-carrier presence probe on Classification × DataClassification ──
3369    //
3370    // Fail-before-pass-after granularity:
3371    // [`Classification::has_data_classification`] did not exist before
3372    // this commit — every consumer of the
3373    // `(Classification, DataClassification) -> bool` scalar-carrier
3374    // probe shape would have to restate the
3375    // `classification.data_classification == kind` equality body at
3376    // its own callsite. Post-lift the shape lives at ONE substrate
3377    // owner and every downstream (the `data-classification-<kind>`
3378    // require-tag family in `tatara-check`, future audit dispatchers
3379    // walking [`DataClassification::ALL`], any future CRD-facing
3380    // closed-set discriminator on a defaulted scalar `ProcessSpec`
3381    // field) binds through the SAME `has(kind)` shape the four prior
3382    // scalar-carrier peers on [`Classification`]
3383    // ([`Classification::has_point_type`],
3384    // [`Classification::has_substrate`],
3385    // [`Classification::has_calm`]) plus
3386    // [`crate::spec::SignalPolicy::has_sighup_strategy`] and
3387    // [`crate::encapsulates::EncapsulatesSpec::has_mode`] publish.
3388    // SECOND occupant on the (required-parent × defaulted-scalar-
3389    // child) corner of the presence-probe algebra after
3390    // [`Classification::has_calm`] opened it — pins the corner as a
3391    // proven-repeatable primitive shape rather than a single-example
3392    // curiosity and closes the four-scalar-carrier corner-coverage
3393    // contract on the six-axis classification lattice.
3394
3395    /// DIAGONAL — for every [`DataClassification`] variant, a
3396    /// [`Classification`] whose `data_classification` field is set to
3397    /// that variant returns `true` from `has_data_classification` on
3398    /// that same variant AND `false` on every other variant. Sweep
3399    /// the [`DataClassification::ALL`] × ALL cross so a regression
3400    /// that hard-coded the arm to a single variant (silently returning
3401    /// `true` on every populated classification regardless of query
3402    /// kind) or wired the equality to a fixed unrelated field (a
3403    /// stray probe on `classification.point_type` /
3404    /// `classification.substrate` / `classification.calm`) fails HERE
3405    /// at the substrate primitive before landing at the operator-
3406    /// facing checks.lisp surface.
3407    #[test]
3408    fn classification_has_data_classification_returns_true_iff_variant_matches() {
3409        for populated in DataClassification::ALL {
3410            let c = Classification {
3411                point_type: ConvergencePointType::Gate,
3412                substrate: SubstrateType::Compute,
3413                horizon: Horizon::default(),
3414                calm: CalmClassification::default(),
3415                data_classification: populated,
3416            };
3417            for query in DataClassification::ALL {
3418                assert_eq!(
3419                    c.has_data_classification(query),
3420                    query == populated,
3421                    "data_classification={populated:?}: query {query:?} classification drifted",
3422                );
3423            }
3424        }
3425    }
3426
3427    /// GATE-COMPUTE BASELINE — the workspace-baseline
3428    /// [`Classification::gate_compute`] shape carries
3429    /// `data_classification: DataClassification::default()` which is
3430    /// [`DataClassification::Internal`] via `#[default]`, so
3431    /// `has_data_classification` returns `true` on
3432    /// [`DataClassification::Internal`] and `false` on every other
3433    /// variant ([`DataClassification::Public`],
3434    /// [`DataClassification::Confidential`],
3435    /// [`DataClassification::Pii`], [`DataClassification::Phi`],
3436    /// [`DataClassification::Pci`]). Pins the composition of the
3437    /// substrate's baseline-constructor primitive with the SIXTH
3438    /// scalar-carrier presence probe AND the sibling-default
3439    /// correspondence documented on [`Classification::gate_compute`]
3440    /// (which pins the three defaulted axes to the sibling closed-set
3441    /// defaults `HorizonKind::Bounded` / `CalmClassification::Monotone`
3442    /// / `DataClassification::Internal`) — a regression that flipped
3443    /// `gate_compute().data_classification` off `Internal` (or
3444    /// promoted a different variant to `#[default]` on the closed
3445    /// set, or wired `has_data_classification` to a fixed variant
3446    /// answer, or crossed the wires to `point_type` / `substrate` /
3447    /// `calm`) fails here at ONE narrow site before drifting across
3448    /// every unadorned ephemeral env (`default_ephemeral_class`) and
3449    /// every downstream test fixture that keys assertions on the
3450    /// shape. SECOND occupant on the (required-parent × defaulted-
3451    /// scalar-child) corner — pins the corner's characteristic
3452    /// "default-arm short-circuit" property on its second occupant
3453    /// (peer to `classification_gate_compute_has_calm_monotone_only`
3454    /// which pins the same shape on the corner's first occupant).
3455    #[test]
3456    fn classification_gate_compute_has_data_classification_internal_only() {
3457        let c = Classification::gate_compute();
3458        for kind in DataClassification::ALL {
3459            let expected = kind == DataClassification::Internal;
3460            assert_eq!(
3461                c.has_data_classification(kind),
3462                expected,
3463                "gate_compute (data_classification=Internal) must return {expected} for {kind:?}",
3464            );
3465        }
3466    }
3467
3468    /// FOUR-AXIS INDEPENDENCE — the four scalar-carrier co-tenants
3469    /// on the [`Classification`] parent
3470    /// ([`Classification::has_point_type`] plus
3471    /// [`Classification::has_substrate`] on the (required-parent ×
3472    /// required-scalar-child) corner AND [`Classification::has_calm`]
3473    /// plus [`Classification::has_data_classification`] on the
3474    /// (required-parent × defaulted-scalar-child) corner) probe
3475    /// distinct scalar slots on the SAME parent, so a carrier with
3476    /// `point_type: Fork` AND `substrate: Storage` AND
3477    /// `calm: NonMonotone` AND `data_classification: Pii` answers
3478    /// `true` on all four fine tags simultaneously and `false` on
3479    /// every off-diagonal probe of any axis. Pins the four probes'
3480    /// independence at ONE narrow site — a regression that collapsed
3481    /// any of the four onto another's field (a stray probe of
3482    /// `has_data_classification` reading `self.point_type` /
3483    /// `self.substrate` / `self.calm`, or of any prior probe reading
3484    /// `self.data_classification`) would fail HERE before landing at
3485    /// any consumer. The audit `every Fork-topology Storage-plane
3486    /// NonMonotone-CALM Pii-classification point declares a
3487    /// Raft-guarded write path AND a downstream PII-scrub sink`
3488    /// composes this exact four-axis conjunction on the required +
3489    /// defaulted scalars of the six-axis classification lattice.
3490    /// Closes the four-scalar-carrier corner-coverage contract on
3491    /// [`Classification`] — its two required-scalar-child slots
3492    /// (`point_type`, `substrate`) AND its two defaulted-scalar-
3493    /// child slots (`calm`, `data_classification`) all publish
3494    /// independent presence probes through the same shape.
3495    #[test]
3496    fn classification_four_scalar_carrier_probes_are_independent() {
3497        let c = Classification {
3498            point_type: ConvergencePointType::Fork,
3499            substrate: SubstrateType::Storage,
3500            horizon: Horizon::default(),
3501            calm: CalmClassification::NonMonotone,
3502            data_classification: DataClassification::Pii,
3503        };
3504        assert!(c.has_point_type(ConvergencePointType::Fork));
3505        assert!(c.has_substrate(SubstrateType::Storage));
3506        assert!(c.has_calm(CalmClassification::NonMonotone));
3507        assert!(c.has_data_classification(DataClassification::Pii));
3508        assert!(!c.has_point_type(ConvergencePointType::Gate));
3509        assert!(!c.has_substrate(SubstrateType::Compute));
3510        assert!(!c.has_calm(CalmClassification::Monotone));
3511        assert!(!c.has_data_classification(DataClassification::Internal));
3512        assert!(!c.has_data_classification(DataClassification::Public));
3513        assert!(!c.has_data_classification(DataClassification::Phi));
3514    }
3515}