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 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 — the [`Self::has_horizon_kind`] peer opens that
438 /// fresh (required-parent × nested-struct-scalar-child) corner
439 /// with the same `has(kind)` shape composed through one struct
440 /// hop. A future [`DataClassification`] variant (a
441 /// hypothetical seventh variant beyond `Public / Internal /
442 /// Confidential / Pii / Phi / Pci` — say a `TradeSecret` bucket
443 /// for competitive-sensitive data, or an `Anonymized` bucket for
444 /// pseudonymized-PII whose regulatory posture differs) reaches
445 /// every downstream through ONE `ALL` entry on the closed set +
446 /// ONE `as_str` arm + ONE `sensitivity_rank` arm + one arm per
447 /// predicate with the probe body untouched.
448 ///
449 /// Theory anchor: THEORY.md §II.1 invariant 5 — composition
450 /// preserves proofs; the scalar-carrier presence-probe body
451 /// lives at ONE substrate site so every downstream
452 /// (`data-classification-<kind>` require-tag family in
453 /// `tatara-check`, closed-set audit dispatchers, future variant
454 /// additions on [`DataClassification`]) binds through the SAME
455 /// shape rather than restating the
456 /// `classification.data_classification == kind` closure body at
457 /// each callsite. THEORY.md §VI.1 — generation over composition;
458 /// a future [`DataClassification`] variant lands at ONE `ALL`
459 /// entry + ONE `as_str` arm on the closed set and the probe
460 /// picks it up mechanically without further per-consumer edits.
461 #[must_use]
462 pub fn has_data_classification(&self, kind: DataClassification) -> bool {
463 self.data_classification == kind
464 }
465
466 /// Closed-set-driven presence probe — does this [`Classification`]
467 /// carry the given [`HorizonKind`] discriminator on its
468 /// [`Self::horizon`]`.kind` slot? The ONE substrate primitive that
469 /// owns the `(Classification, HorizonKind) -> bool` nested-struct-
470 /// scalar-carrier walk shape.
471 ///
472 /// # Seventh peer on the presence-probe axis — first on a fresh corner
473 ///
474 /// Peer of [`crate::spec::SignalPolicy::has_sighup_strategy`],
475 /// [`crate::encapsulates::EncapsulatesSpec::has_mode`],
476 /// [`Self::has_point_type`], [`Self::has_substrate`],
477 /// [`Self::has_calm`], and [`Self::has_data_classification`] on the
478 /// workspace-wide closed-set-driven presence-probe algebra — the
479 /// four scalar-carrier peers on [`Classification`] all read a
480 /// closed-set discriminator DIRECTLY off a scalar `Classification`
481 /// slot (`point_type`, `substrate`, `calm`, `data_classification`).
482 /// `has_horizon_kind` instead threads through a NESTED-STRUCT
483 /// intermediary ([`Horizon`], the defaulted nested struct owning
484 /// the `horizon` axis on the six-axis classification lattice) to
485 /// reach a scalar [`HorizonKind`] discriminator on
486 /// `horizon.kind`. This OPENS the (required-parent × nested-
487 /// struct-scalar-child) corner of the algebra at its FIRST
488 /// substrate primitive — a fresh corner distinct from all four
489 /// corner-property-exhaustive scalar-carrier peers on
490 /// [`Classification`].
491 ///
492 /// # Semantics — VARIANT match on the nested scalar, not POPULATED nested struct
493 ///
494 /// `has_horizon_kind(kind)` returns `true` iff
495 /// `self.horizon.kind == kind`. The nested [`Horizon`] struct
496 /// carries [`Default`] via `#[derive(Default)]` and its `kind`
497 /// field defaults to [`HorizonKind::Bounded`] via `#[default]`, so
498 /// a [`Classification`] filled via `..Default::default()` on the
499 /// `horizon` axis reads `true` on the default kind
500 /// [`HorizonKind::Bounded`] and `false` on
501 /// [`HorizonKind::Asymptotic`]. The default-arm short-circuit is
502 /// therefore present at this corner too — but through the extra
503 /// struct hop the peer scalar-carrier peers on the defaulted-
504 /// child corner (`has_calm`, `has_data_classification`) walk
505 /// directly. A regression that dropped `#[default]` on
506 /// [`HorizonKind`], or that replaced `Horizon::default()` in
507 /// [`Classification::gate_compute`] with an explicit non-`Bounded`
508 /// kind, surfaces at this primitive's tests before drifting
509 /// through every unadorned Process's baseline horizon answer.
510 ///
511 /// # Compounding
512 ///
513 /// This method OPENS the (required-parent × nested-struct-scalar-
514 /// child) corner of the workspace-wide closed-set-driven
515 /// presence-probe algebra, distinct from the four corner-property-
516 /// exhaustive scalar-carrier peers on [`Classification`]
517 /// ([`Self::has_point_type`], [`Self::has_substrate`],
518 /// [`Self::has_calm`], [`Self::has_data_classification`]) whose
519 /// bodies read a closed-set discriminator directly off a scalar
520 /// slot. A future co-tenant on this fresh corner (a peer probe on
521 /// another nested-struct's scalar discriminator, e.g. a
522 /// hypothetical `has_optimization_direction` reaching
523 /// `spec.classification.horizon.direction.unwrap_or_default()`, or
524 /// a nested-struct-scalar discriminator on a different `ProcessSpec`
525 /// field's inner struct) lands as ONE peer inherent method with
526 /// the same two-hop `self.<outer>.<inner> == kind` body and routes
527 /// through the same `strip_and_classify_prefixed_kind::<K, _>`
528 /// shape in `tatara-check`. A future [`HorizonKind`] variant (a
529 /// hypothetical `Periodic` sentinel for "terminates on each
530 /// window boundary then re-arms", pre-flagged on the closed set's
531 /// `ALL` docstring) reaches every downstream through ONE `ALL`
532 /// entry + one `as_str` arm + one `terminates` arm + one
533 /// `requires_metric_axes` arm on the closed set with the probe
534 /// body untouched.
535 ///
536 /// Theory anchor: THEORY.md §II.1 invariant 5 — composition
537 /// preserves proofs; the nested-struct-scalar-carrier presence-
538 /// probe body lives at ONE substrate site so every downstream
539 /// (`horizon-<kind>` require-tag family in `tatara-check`, future
540 /// audit dispatchers walking [`HorizonKind::ALL`], future variant
541 /// additions on [`HorizonKind`]) binds through the SAME
542 /// `has(kind)` shape rather than restating the
543 /// `classification.horizon.kind == kind` closure body at each
544 /// callsite. THEORY.md §VI.1 — generation over composition; a
545 /// future [`HorizonKind`] variant lands at ONE `ALL` entry + ONE
546 /// `as_str` arm on the closed set and the probe picks it up
547 /// mechanically without further per-consumer edits.
548 #[must_use]
549 pub fn has_horizon_kind(&self, kind: HorizonKind) -> bool {
550 self.horizon.kind == kind
551 }
552
553 /// Closed-set-driven presence probe — does this [`Classification`]
554 /// carry the given [`OptimizationDirection`] discriminator on its
555 /// [`Self::horizon`]`.direction` slot (with the substrate
556 /// `Option::unwrap_or_default` treating `None` as the closed set's
557 /// `#[default] Minimize`)? The ONE substrate primitive that owns
558 /// the `(Classification, OptimizationDirection) -> bool` nested-
559 /// struct-Option-scalar-carrier walk shape.
560 ///
561 /// # Second occupant on the nested-struct-scalar-child corner —
562 /// the Option-hop co-tenant
563 ///
564 /// Peer of [`Self::has_horizon_kind`] on the (required-parent ×
565 /// nested-struct-scalar-child) corner opened by that method — same
566 /// two-hop composition through the nested defaulted [`Horizon`]
567 /// intermediary, but the inner scalar slot is `direction:
568 /// Option<OptimizationDirection>` (an `Option`-hop past the same
569 /// nested [`Horizon`]) rather than a bare scalar. The corner
570 /// therefore admits BOTH direct nested-scalar shapes ([`Horizon`]
571 /// carries `kind: HorizonKind` directly, [`Self::has_horizon_kind`]
572 /// walks it) AND Option-nested-scalar shapes ([`Horizon`] carries
573 /// `direction: Option<OptimizationDirection>`, this method walks
574 /// it through `Option::unwrap_or_default`), pinning the corner as
575 /// a proven-repeatable primitive shape rather than a single-
576 /// example curiosity. Every prior scalar-carrier peer on
577 /// [`Classification`] (`has_point_type`, `has_substrate`,
578 /// `has_calm`, `has_data_classification`) reads a closed-set
579 /// discriminator DIRECTLY off a scalar `Classification` slot; this
580 /// method (like [`Self::has_horizon_kind`]) threads through the
581 /// nested [`Horizon`] intermediary, and additionally traverses the
582 /// `Option`-slot with `unwrap_or_default` so the operator's
583 /// `:requires (optimization-direction-Minimize)` on an unadorned
584 /// baseline still answers `true` on the closed set's default arm.
585 ///
586 /// # Semantics — VARIANT match on the Option-defaulted nested
587 /// scalar, not POPULATED Option
588 ///
589 /// `has_optimization_direction(kind)` returns `true` iff
590 /// `self.horizon.direction.unwrap_or_default() == kind`.
591 /// [`OptimizationDirection`] carries `#[default] Minimize` via
592 /// the derived [`Default`] impl, so a Process filled through
593 /// [`Horizon::bounded`] (which leaves `direction: None`) or
594 /// through `Horizon::default()` (same shape, `direction: None`)
595 /// answers `true` on [`OptimizationDirection::Minimize`] and
596 /// `false` on [`OptimizationDirection::Maximize`]. This mirrors
597 /// the default-arm short-circuit contract every other closed-set-
598 /// defaulted-child probe on [`Classification`] publishes
599 /// (`has_calm`, `has_data_classification`, `has_horizon_kind`) —
600 /// the `Option`-hop is soft-mapped to the closed set's default
601 /// arm rather than surfaced as a distinct presence axis. A
602 /// regression that flipped [`OptimizationDirection`]'s
603 /// `#[default]` off `Minimize` (which would silently invert every
604 /// unadorned `Asymptotic` Process's rate-window evaluator
605 /// polarity — see the [`OptimizationDirection::Minimize`] variant
606 /// docstring) fails at this probe's default-arm tests before
607 /// drifting through every downstream consumer.
608 ///
609 /// # Semantics rationale — Option-hop as default vs presence
610 ///
611 /// The `direction: Option<OptimizationDirection>` slot on
612 /// [`Horizon`] is documented as "Asymptotic only" — a `Bounded`
613 /// horizon has no meaningful direction so the operator leaves
614 /// it `None`. Yet the closed set carries `#[default] Minimize`,
615 /// so a bare `Bounded` Process's optimization direction reads
616 /// as `Minimize` at every consumer downstream via
617 /// [`Option::unwrap_or_default`]. That default IS the substrate's
618 /// operator-facing answer for "what direction would this Process
619 /// optimize toward if it became Asymptotic without further
620 /// annotation?", and a `:requires (optimization-direction-
621 /// Minimize)` audit at the checks.lisp surface correctly matches
622 /// every unadorned Process — matching the corner-property contract
623 /// every other defaulted-child probe publishes. An operator who
624 /// wants a strict presence axis (`is direction *actually* set?`)
625 /// gets that answer through a distinct future primitive
626 /// (`has_optimization_direction_set`) that would read the
627 /// `is_some` bit alone — orthogonal to this variant-equality
628 /// probe. This method commits to the variant-equality
629 /// interpretation so the corner-property contract stays uniform
630 /// with the four scalar-carrier peers.
631 ///
632 /// # Compounding
633 ///
634 /// This method POPULATES the (required-parent × nested-struct-
635 /// scalar-child) corner at its SECOND substrate primitive after
636 /// [`Self::has_horizon_kind`] opened it — pinning the corner as
637 /// a proven-repeatable primitive shape rather than a single-
638 /// example curiosity, and DEMONSTRATING that the corner admits
639 /// both direct-scalar and Option-scalar traversals through the
640 /// same nested-struct intermediary via the closed set's default.
641 /// A future co-tenant on this corner (a peer probe on another
642 /// nested-struct's scalar or Option-scalar discriminator, e.g.
643 /// a hypothetical `has_backend_port_family` reaching
644 /// `spec.routing.as_ref().and_then(|r| r.backend.tls_issuer.as_ref()).is_some()`
645 /// or a nested-struct-scalar discriminator on
646 /// `spec.encapsulates.<some-inner>.kind`) lands as ONE peer
647 /// inherent method with the same two-hop `self.<outer>.<inner>`
648 /// walk (with or without an Option-hop threading through the
649 /// closed set's `Default`) and routes through the same
650 /// `strip_and_classify_prefixed_kind::<K, _>` shape in
651 /// `tatara-check`. A future [`OptimizationDirection`] variant
652 /// (a hypothetical `Stabilize` sentinel for "drive toward a
653 /// target value", pre-flagged on the closed set's `ALL`
654 /// docstring) reaches every downstream through ONE `ALL` entry
655 /// + one `as_str` arm + one `prefers_lower` arm + one
656 /// `is_improvement` arm on the closed set with the probe body
657 /// untouched.
658 ///
659 /// Theory anchor: THEORY.md §II.1 invariant 5 — composition
660 /// preserves proofs; the nested-struct-Option-scalar-carrier
661 /// presence-probe body lives at ONE substrate site so every
662 /// downstream (`optimization-direction-<kind>` require-tag family
663 /// in `tatara-check`, future audit dispatchers walking
664 /// [`OptimizationDirection::ALL`], future variant additions on
665 /// [`OptimizationDirection`]) binds through the SAME
666 /// `has(kind)` shape rather than restating the
667 /// `classification.horizon.direction.unwrap_or_default() == kind`
668 /// closure body at each callsite. THEORY.md §VI.1 — generation
669 /// over composition; a future [`OptimizationDirection`] variant
670 /// lands at ONE `ALL` entry + ONE `as_str` arm on the closed set
671 /// and the probe picks it up mechanically without further
672 /// per-consumer edits.
673 #[must_use]
674 pub fn has_optimization_direction(&self, kind: OptimizationDirection) -> bool {
675 self.horizon.direction.unwrap_or_default() == kind
676 }
677}
678
679/// Structural type — how data flows through the point.
680///
681/// Closed-set sibling on the classification axis algebra; the `ALL` /
682/// `as_str` / Display / `FromStr` triad mirrors
683/// [`DataClassification::ALL`], [`crate::pool::PoolPhase::ALL`],
684/// [`crate::pool::MemberState::ALL`], [`crate::pool::ReplacementPolicy::ALL`],
685/// [`crate::pool::ReturnPolicy::ALL`],
686/// [`crate::boundary::ConditionKind::ALL`],
687/// [`crate::lifetime::TeardownPolicy::ALL`],
688/// [`crate::lifetime::LifetimeKind::ALL`],
689/// [`crate::intent::IntentKind::ALL`],
690/// [`crate::phase::ProcessPhase::ALL`],
691/// [`crate::signal::ProcessSignal::ALL`]. The
692/// `(input_arity, output_arity)` projection (via [`Arity`]) closes the
693/// graph-topology contract: each variant lands in exactly one of the
694/// three structural buckets — endomorphic (1→1), diffusive (1→N), or
695/// convergent (N→1) — so future DAG composition / edge-cardinality
696/// validators dispatch on a typed projection rather than re-deriving
697/// from variant names.
698#[derive(
699 Clone,
700 Copy,
701 Debug,
702 PartialEq,
703 Eq,
704 Hash,
705 Serialize,
706 Deserialize,
707 JsonSchema,
708 tatara_closed_set::DeriveClosedSet,
709)]
710#[serde(rename_all = "PascalCase")]
711#[closed_set(via = "as_str", generate_unknown, display)]
712pub enum ConvergencePointType {
713 /// 1 input → 1 output (linear conversion).
714 Transform,
715 /// 1 input → N outputs (fan-out, spawns downstream DAGs).
716 Fork,
717 /// N inputs → 1 output (fan-in, merges upstream results).
718 Join,
719 /// N inputs → 1 output (barrier, waits for all inputs).
720 Gate,
721 /// N inputs → 1 output (choice, picks best by policy).
722 Select,
723 /// 1 input → N outputs same type (replicate signal).
724 Broadcast,
725 /// N inputs → 1 output (fold/aggregate).
726 Reduce,
727 /// 1 input → 1 output + side-channel (tap for observation).
728 Observe,
729}
730
731impl ConvergencePointType {
732 /// The closed set of point types — single source of truth that
733 /// drives the `as_str` / Display / `FromStr` triad AND the
734 /// `(input_arity, output_arity)` typed pair (via [`Arity`]) AND the
735 /// `is_endomorphic` / `is_diffusive` / `is_convergent` predicate
736 /// triple. Adding a ninth variant lands at one `ALL` entry + one
737 /// `as_str` arm + one `input_arity` arm + one `output_arity` arm +
738 /// one arm per predicate — exhaustively checked by the compiler
739 /// (the `[Self; 8]` array literal forces the arity) AND by the
740 /// per-variant truth-table contract test (a new variant must
741 /// declare its own `(input, output)` arity pair or any future
742 /// DAG composition validator that dispatches on
743 /// `(input_arity, output_arity)` will silently mis-wire it).
744 /// Closes the load-bearing classification-axis enum that
745 /// `tatara_core::domain::compliance_binding::PointSelector::ByType`
746 /// already dispatches against and that every `Process`'s
747 /// `Classification.point_type` reads as the topological identity
748 /// of the convergence point.
749 pub const ALL: [Self; 8] = [
750 Self::Transform,
751 Self::Fork,
752 Self::Join,
753 Self::Gate,
754 Self::Select,
755 Self::Broadcast,
756 Self::Reduce,
757 Self::Observe,
758 ];
759
760 /// Canonical PascalCase wire-format projection — matches the
761 /// serde `rename_all = "PascalCase"` output verbatim AND the CRD
762 /// `enum:` enumeration that the Process schema stamps on
763 /// `spec.classification.pointType`. Pinned by
764 /// `convergence_point_type_as_str_matches_serde` so a variant
765 /// rename can't drift between the typed surface, the CRD enum,
766 /// the YAML wire format AND any future operator-facing
767 /// diagnostic that composes `pointType={kind}` via Display
768 /// rather than a hard-coded literal that would silently rot.
769 /// Display + FromStr triad over `ALL` mirrors `DataClassification`
770 /// / `PoolPhase` / `MemberState` / `ReplacementPolicy` /
771 /// `ReturnPolicy` / `TeardownPolicy` / `ConditionKind` /
772 /// `ProcessPhase` / `ProcessSignal`.
773 pub const fn as_str(self) -> &'static str {
774 match self {
775 Self::Transform => "Transform",
776 Self::Fork => "Fork",
777 Self::Join => "Join",
778 Self::Gate => "Gate",
779 Self::Select => "Select",
780 Self::Broadcast => "Broadcast",
781 Self::Reduce => "Reduce",
782 Self::Observe => "Observe",
783 }
784 }
785
786 /// Cardinality of the input edge into this point — `One` for
787 /// `Transform | Fork | Broadcast | Observe` (single-source
788 /// projections), `Many` for `Join | Gate | Select | Reduce`
789 /// (multi-source convergent reductions). Closed-set match (not
790 /// `matches!`) so a future variant triggers the compiler's
791 /// exhaustiveness check at this site rather than silently
792 /// defaulting to `One`. Paired with [`Self::output_arity`] they
793 /// form the typed `(input, output)` projection that future
794 /// DAG composition validators (edge-cardinality checks: "you
795 /// can't connect a Fork's output to a Transform's input
796 /// without a Join in between") dispatch against — a single
797 /// projection per variant means a future `Demux` / `Mux` /
798 /// `Pipeline` point lands in exactly one cell of the
799 /// `Arity × Arity` topology table rather than rotting against
800 /// open-coded `== ConvergencePointType::Fork` checks.
801 pub const fn input_arity(self) -> Arity {
802 match self {
803 Self::Transform | Self::Fork | Self::Broadcast | Self::Observe => Arity::One,
804 Self::Join | Self::Gate | Self::Select | Self::Reduce => Arity::Many,
805 }
806 }
807
808 /// Cardinality of the output edge from this point — `Many` for
809 /// `Fork | Broadcast` (fan-out), `One` for everything else.
810 /// Closed-set match so a future variant triggers the compiler's
811 /// exhaustiveness check. See [`Self::input_arity`] for the
812 /// arity-pair contract + bucket definitions.
813 pub const fn output_arity(self) -> Arity {
814 match self {
815 Self::Fork | Self::Broadcast => Arity::Many,
816 Self::Transform
817 | Self::Join
818 | Self::Gate
819 | Self::Select
820 | Self::Reduce
821 | Self::Observe => Arity::One,
822 }
823 }
824
825 /// Does this point preserve the single-input single-output
826 /// shape? `(input, output) == (One, One)` — `Transform`
827 /// (identity-shaped reshape) and `Observe` (passthrough +
828 /// side-channel tap). Closed-set match so a future variant
829 /// triggers the compiler's exhaustiveness check. Paired with
830 /// `is_diffusive` and `is_convergent` they form the three-way
831 /// disjoint bucket carving sealed by
832 /// `convergence_point_type_buckets_cover_every_variant` AND
833 /// `convergence_point_type_arity_pair_agrees_with_bucket` —
834 /// the bridge that lets the bucket predicates and the arity
835 /// pair name the same topology partition from two angles.
836 pub const fn is_endomorphic(self) -> bool {
837 match self {
838 Self::Transform | Self::Observe => true,
839 Self::Fork
840 | Self::Join
841 | Self::Gate
842 | Self::Select
843 | Self::Broadcast
844 | Self::Reduce => false,
845 }
846 }
847
848 /// Does this point fan out — single input replicated/split
849 /// across many outputs? `(input, output) == (One, Many)` —
850 /// `Fork` and `Broadcast`. Closed-set match so a future variant
851 /// triggers the compiler's exhaustiveness check. See
852 /// `is_endomorphic` for the bucket-carving contract.
853 pub const fn is_diffusive(self) -> bool {
854 match self {
855 Self::Fork | Self::Broadcast => true,
856 Self::Transform
857 | Self::Join
858 | Self::Gate
859 | Self::Select
860 | Self::Reduce
861 | Self::Observe => false,
862 }
863 }
864
865 /// Does this point reduce — many inputs collapsed to one
866 /// output? `(input, output) == (Many, One)` — `Join`, `Gate`,
867 /// `Select`, `Reduce`. Closed-set match so a future variant
868 /// triggers the compiler's exhaustiveness check. See
869 /// `is_endomorphic` for the bucket-carving contract. The
870 /// impossible `(Many, Many)` topology bucket is pinned empty
871 /// by `convergence_point_type_arity_pair_agrees_with_bucket`
872 /// — a `(Many, Many)` point would mean "many independent
873 /// inputs replicated across many independent outputs", which
874 /// has no convergence semantics: every DAG-composition
875 /// validator would have to special-case it. A future variant
876 /// that wants `(Many, Many)` must first extend the bucket
877 /// carving deliberately.
878 pub const fn is_convergent(self) -> bool {
879 match self {
880 Self::Join | Self::Gate | Self::Select | Self::Reduce => true,
881 Self::Transform | Self::Fork | Self::Broadcast | Self::Observe => false,
882 }
883 }
884}
885
886// `impl FromStr for ConvergencePointType` +
887// `impl tatara_lisp::ClosedSet for ConvergencePointType` +
888// `impl std::fmt::Display for ConvergencePointType` +
889// `pub struct UnknownConvergencePointType(pub String)` are all generated
890// by `#[derive(tatara_closed_set::DeriveClosedSet)]` +
891// `#[closed_set(via = "as_str", generate_unknown, display)]` on the
892// enum declaration above. `label` delegates to the inherent
893// `ConvergencePointType::as_str` — the inherent name (PascalCase
894// `as_str`) stays the load-bearing wire-vocabulary projection that
895// matches the serde `rename_all = "PascalCase"` output AND the CRD
896// `enum:` enumeration the Process schema stamps on
897// `spec.classification.pointType` verbatim, while generic
898// `T: ClosedSet` consumers reach the STABLE workspace-wide name
899// (`label`). The `display` flag emits the
900// `f.write_str(self.as_str())` delegation block at the same
901// proc-macro site rather than a hand-rolled `fmt::Display` block per
902// implementor. The auto-derived carrier label "convergence point
903// type" matches the prior hand-rolled `#[error("unknown convergence
904// point type: {0}")]` annotation byte-for-byte. Symmetric to the
905// other five classification-axis closed-sets in this file
906// (`SubstrateType` / `HorizonKind` / `OptimizationDirection` /
907// `CalmClassification` / `DataClassification`) AND every other
908// `#[derive(DeriveClosedSet)]` implementor across the workspace
909// (`crate::pool::{ReplacementPolicy,MemberState,PoolPhase,ReturnPolicy}`,
910// `crate::export::{ArtifactKind,ReportFormat,ChannelKind,ExportTrigger}`,
911// `crate::allocation::{RequestorKind,AllocationPhase}`).
912
913/// Edge cardinality of a [`ConvergencePointType`]'s input or output.
914///
915/// Typed projection used by [`ConvergencePointType::input_arity`] and
916/// [`ConvergencePointType::output_arity`] so DAG composition validators
917/// reach for a closed-set enum rather than re-deriving the in/out
918/// cardinality from variant names. `Many` is the "≥1, could be N"
919/// cardinality — it carries no upper bound because the convergence
920/// point's variant tag is already the structural identity; the
921/// number itself is a runtime property of the DAG, not the typescape.
922#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, tatara_closed_set::DeriveClosedSet)]
923#[closed_set(via = "as_str", display, generate_unknown)]
924pub enum Arity {
925 /// Single edge — exactly one input or one output.
926 One,
927 /// Multiple edges — any number ≥ 1.
928 Many,
929}
930
931impl Arity {
932 /// The closed set of arities — single source of truth that
933 /// drives `as_str` / Display AND the `is_one` predicate. Adding
934 /// a third variant (e.g. `Arity::Zero` for sinks) lands at one
935 /// `ALL` entry + one `as_str` arm + one predicate arm —
936 /// exhaustively checked by the compiler.
937 pub const ALL: [Self; 2] = [Self::One, Self::Many];
938
939 /// Canonical projection — `"One" | "Many"`. Pinned by
940 /// `arity_display_matches_as_str` so a future Display impl
941 /// can't drift from the canonical string.
942 pub const fn as_str(self) -> &'static str {
943 match self {
944 Self::One => "One",
945 Self::Many => "Many",
946 }
947 }
948
949 /// Is this the single-edge cardinality? Closed-set match (not
950 /// `matches!`) so a future variant triggers the compiler's
951 /// exhaustiveness check.
952 pub const fn is_one(self) -> bool {
953 match self {
954 Self::One => true,
955 Self::Many => false,
956 }
957 }
958}
959
960// `impl fmt::Display for Arity` + `impl std::str::FromStr for Arity` +
961// `impl tatara_lisp::ClosedSet for Arity` + `pub struct UnknownArity(pub
962// String)` are all generated by
963// `#[derive(tatara_closed_set::DeriveClosedSet)]` +
964// `#[closed_set(via = "as_str", display, generate_unknown)]` on the enum
965// declaration above. The inherent `as_str` projection stays load-bearing
966// — the canonical `"One" | "Many"` string every DAG composition
967// validator reads; `via = "as_str"` binds `ClosedSet::label` to the same
968// projection so the substrate-wide `assert_display_matches_label` /
969// `assert_closed_set_well_formed` primitives dispatch through the same
970// byte-identical shape every other closed-set implementor across the
971// crate publishes. Aligns `Arity` with the substrate-wide
972// `#[derive(DeriveClosedSet)]` idiom that every other closed-set enum on
973// this classification axis (`ConvergencePointType`, `SubstrateType`,
974// `HorizonKind`, `OptimizationDirection`, `CalmClassification`,
975// `DataClassification`) already carries — the last hand-rolled
976// `impl fmt::Display` on the axis is closed at ONE substrate site.
977
978/// Operational substrate.
979///
980/// Closed-set sibling on the classification axis algebra; the `ALL` /
981/// `as_str` / Display / `FromStr` triad mirrors
982/// [`ConvergencePointType::ALL`], [`DataClassification::ALL`],
983/// [`crate::pool::PoolPhase::ALL`], [`crate::pool::MemberState::ALL`],
984/// [`crate::pool::ReplacementPolicy::ALL`],
985/// [`crate::pool::ReturnPolicy::ALL`],
986/// [`crate::boundary::ConditionKind::ALL`],
987/// [`crate::lifetime::TeardownPolicy::ALL`],
988/// [`crate::lifetime::LifetimeKind::ALL`],
989/// [`crate::intent::IntentKind::ALL`],
990/// [`crate::phase::ProcessPhase::ALL`],
991/// [`crate::signal::ProcessSignal::ALL`]. The
992/// `is_resource` / `is_policy` / `is_telemetry` predicate triple
993/// carves the eight variants into three structurally-disjoint
994/// substrate planes — resource (you allocate from it), policy (it
995/// gates access for other workloads), telemetry (it observes other
996/// workloads) — so future compliance-baseline selectors that
997/// dispatch on a substrate's plane (resource budgets only apply to
998/// resource substrates; policy substrates inherit baselines from
999/// what they govern; telemetry substrates inherit baselines from
1000/// what they observe) read a typed projection rather than
1001/// re-deriving from variant names.
1002#[derive(
1003 Clone,
1004 Copy,
1005 Debug,
1006 PartialEq,
1007 Eq,
1008 Hash,
1009 PartialOrd,
1010 Ord,
1011 Serialize,
1012 Deserialize,
1013 JsonSchema,
1014 tatara_closed_set::DeriveClosedSet,
1015)]
1016#[serde(rename_all = "PascalCase")]
1017#[closed_set(via = "as_str", generate_unknown, display)]
1018pub enum SubstrateType {
1019 Financial,
1020 Compute,
1021 Network,
1022 Storage,
1023 Security,
1024 Identity,
1025 Observability,
1026 Regulatory,
1027}
1028
1029impl SubstrateType {
1030 /// The closed set of substrates — single source of truth that
1031 /// drives the `as_str` / Display / `FromStr` triad AND the
1032 /// `is_resource` / `is_policy` / `is_telemetry` predicate triple.
1033 /// Adding a ninth variant lands at one `ALL` entry + one
1034 /// `as_str` arm + one arm per predicate — exhaustively checked
1035 /// by the compiler (the `[Self; 8]` array literal forces the
1036 /// arity) AND by the per-variant plane-bucket contract test (a
1037 /// new variant must declare its own plane or any future
1038 /// compliance-baseline selector that dispatches on
1039 /// `(is_resource, is_policy, is_telemetry)` will silently
1040 /// mis-classify it). Closes the load-bearing classification-axis
1041 /// enum that
1042 /// `tatara_core::domain::compliance_binding::PointSelector::BySubstrate`
1043 /// already dispatches against and that every `Process`'s
1044 /// `Classification.substrate` reads as the operational
1045 /// substrate the convergence point lives on.
1046 pub const ALL: [Self; 8] = [
1047 Self::Financial,
1048 Self::Compute,
1049 Self::Network,
1050 Self::Storage,
1051 Self::Security,
1052 Self::Identity,
1053 Self::Observability,
1054 Self::Regulatory,
1055 ];
1056
1057 /// Canonical PascalCase wire-format projection — matches the
1058 /// serde `rename_all = "PascalCase"` output verbatim AND the CRD
1059 /// `enum:` enumeration that the Process schema stamps on
1060 /// `spec.classification.substrate`. Pinned by
1061 /// `substrate_type_as_str_matches_serde` so a variant rename
1062 /// can't drift between the typed surface, the CRD enum, the YAML
1063 /// wire format AND any future operator-facing diagnostic that
1064 /// composes `substrate={kind}` via Display rather than a
1065 /// hard-coded literal that would silently rot. Display + FromStr
1066 /// triad over `ALL` mirrors `ConvergencePointType` /
1067 /// `DataClassification` / `PoolPhase` / `MemberState` /
1068 /// `ReplacementPolicy` / `ReturnPolicy` / `TeardownPolicy` /
1069 /// `ConditionKind` / `ProcessPhase` / `ProcessSignal`.
1070 pub const fn as_str(self) -> &'static str {
1071 match self {
1072 Self::Financial => "Financial",
1073 Self::Compute => "Compute",
1074 Self::Network => "Network",
1075 Self::Storage => "Storage",
1076 Self::Security => "Security",
1077 Self::Identity => "Identity",
1078 Self::Observability => "Observability",
1079 Self::Regulatory => "Regulatory",
1080 }
1081 }
1082
1083 /// Is this a resource substrate — one you allocate budgets from
1084 /// to run workloads? `Financial | Compute | Network | Storage`.
1085 /// Closed-set match (not `matches!`) so a future variant
1086 /// triggers the compiler's exhaustiveness check at this site
1087 /// rather than silently defaulting to `false`. Paired with
1088 /// `is_policy` and `is_telemetry` they form the three-way
1089 /// disjoint plane carving sealed by
1090 /// `substrate_type_buckets_cover_every_variant` — the bridge
1091 /// that lets future compliance-baseline selectors dispatch on
1092 /// plane without re-deriving from variant names.
1093 pub const fn is_resource(self) -> bool {
1094 match self {
1095 Self::Financial | Self::Compute | Self::Network | Self::Storage => true,
1096 Self::Security | Self::Identity | Self::Observability | Self::Regulatory => false,
1097 }
1098 }
1099
1100 /// Is this a policy substrate — one that gates access or
1101 /// compliance for other workloads rather than carrying their
1102 /// payload? `Security | Identity | Regulatory`. Closed-set match
1103 /// so a future variant triggers the compiler's exhaustiveness
1104 /// check. See `is_resource` for the bucket-carving contract.
1105 pub const fn is_policy(self) -> bool {
1106 match self {
1107 Self::Security | Self::Identity | Self::Regulatory => true,
1108 Self::Financial
1109 | Self::Compute
1110 | Self::Network
1111 | Self::Storage
1112 | Self::Observability => false,
1113 }
1114 }
1115
1116 /// Is this a telemetry substrate — one that passively observes
1117 /// other workloads (metrics, logs, traces) without carrying
1118 /// their payload or gating their access? `Observability` only.
1119 /// Closed-set match so a future variant triggers the compiler's
1120 /// exhaustiveness check. See `is_resource` for the
1121 /// bucket-carving contract. A telemetry substrate's compliance
1122 /// baseline is inherited from what it observes — the singleton
1123 /// bucket is intentional, not a placeholder.
1124 pub const fn is_telemetry(self) -> bool {
1125 match self {
1126 Self::Observability => true,
1127 Self::Financial
1128 | Self::Compute
1129 | Self::Network
1130 | Self::Storage
1131 | Self::Security
1132 | Self::Identity
1133 | Self::Regulatory => false,
1134 }
1135 }
1136}
1137
1138// `impl FromStr for SubstrateType` +
1139// `impl tatara_lisp::ClosedSet for SubstrateType` +
1140// `impl std::fmt::Display for SubstrateType` +
1141// `pub struct UnknownSubstrateType(pub String)` are all generated by
1142// `#[derive(tatara_closed_set::DeriveClosedSet)]` +
1143// `#[closed_set(via = "as_str", generate_unknown, display)]` on the
1144// enum declaration above. The auto-derived carrier label "substrate
1145// type" matches the prior hand-rolled `#[error("unknown substrate
1146// type: {0}")]` annotation byte-for-byte. See the retrofit comment
1147// block on [`ConvergencePointType`] for the canonical narrative.
1148
1149/// How long the point runs. Flattened struct-of-optionals so the OpenAPI
1150/// schema carries a single `kind` discriminator without per-variant merge.
1151#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
1152#[serde(rename_all = "camelCase")]
1153pub struct Horizon {
1154 #[serde(default)]
1155 pub kind: HorizonKind,
1156 /// Metric being optimized (Asymptotic only).
1157 #[serde(default, skip_serializing_if = "Option::is_none")]
1158 pub metric: Option<String>,
1159 /// Whether to minimize or maximize the metric (Asymptotic only).
1160 #[serde(default, skip_serializing_if = "Option::is_none")]
1161 pub direction: Option<OptimizationDirection>,
1162 /// Rate threshold considered healthy (Asymptotic only).
1163 #[serde(default, skip_serializing_if = "Option::is_none")]
1164 pub healthy_rate_threshold: Option<f64>,
1165}
1166
1167/// The shape of a convergence horizon's lifetime — does the point
1168/// run toward a fixed point and terminate, or run in perpetuity with
1169/// a rate signal?
1170///
1171/// Closed-set sibling on the classification axis algebra; the `ALL` /
1172/// `as_str` / Display / `FromStr` triad mirrors
1173/// [`ConvergencePointType::ALL`], [`SubstrateType::ALL`],
1174/// [`DataClassification::ALL`], [`CalmClassification::ALL`],
1175/// [`OptimizationDirection::ALL`], [`crate::pool::PoolPhase::ALL`],
1176/// [`crate::pool::MemberState::ALL`],
1177/// [`crate::pool::ReplacementPolicy::ALL`],
1178/// [`crate::pool::ReturnPolicy::ALL`],
1179/// [`crate::boundary::ConditionKind::ALL`],
1180/// [`crate::lifetime::TeardownPolicy::ALL`],
1181/// [`crate::lifetime::LifetimeKind::ALL`],
1182/// [`crate::intent::IntentKind::ALL`],
1183/// [`crate::phase::ProcessPhase::ALL`],
1184/// [`crate::signal::ProcessSignal::ALL`]. The [`Self::terminates`]
1185/// predicate is the load-bearing horizon-shape primitive — schedulers
1186/// asking "will this Process ever reach `Reaped` via natural
1187/// termination?" read it as the typed image of the lattice ordering
1188/// (`Bounded ≤ Asymptotic` because the bounded horizon strictly
1189/// refines the asymptotic one by also terminating) rather than
1190/// re-deriving from the variant name. The
1191/// [`Self::requires_metric_axes`] predicate is the typed validity
1192/// witness for the [`Horizon`] struct's three `Option<…>` fields
1193/// (`metric`, `direction`, `healthy_rate_threshold`) — they're
1194/// `Some(_)` iff the kind requires them, so the implicit invariant
1195/// the optionality encodes becomes a checkable per-kind predicate
1196/// instead of operator folklore.
1197#[derive(
1198 Clone,
1199 Copy,
1200 Debug,
1201 PartialEq,
1202 Eq,
1203 Hash,
1204 Serialize,
1205 Deserialize,
1206 JsonSchema,
1207 Default,
1208 tatara_closed_set::DeriveClosedSet,
1209)]
1210#[serde(rename_all = "PascalCase")]
1211#[closed_set(via = "as_str", generate_unknown, display)]
1212pub enum HorizonKind {
1213 /// Has a fixed point — distance reaches 0 and terminates.
1214 #[default]
1215 Bounded,
1216 /// Runs in perpetuity — rate is the health signal, not distance.
1217 Asymptotic,
1218}
1219
1220impl HorizonKind {
1221 /// The closed set of horizon kinds — single source of truth that
1222 /// drives the `as_str` / Display / `FromStr` triad AND the
1223 /// `terminates` predicate AND the `requires_metric_axes` shape-
1224 /// validity witness. Adding a third variant (e.g. a `Periodic`
1225 /// sentinel for "terminates on each window boundary then
1226 /// re-arms", which neither perpetually-running nor singularly-
1227 /// terminating names) lands at one `ALL` entry + one `as_str`
1228 /// arm + one `terminates` arm + one `requires_metric_axes` arm —
1229 /// exhaustively checked by the compiler (the `[Self; 2]` array
1230 /// literal forces the arity) AND by the per-variant truth-table
1231 /// tests (a new variant must declare its own termination AND
1232 /// metric-axes requirement, or every scheduler / horizon-shape
1233 /// validator will silently bucket it). Closes the load-bearing
1234 /// classification sub-axis that the `Horizon.kind` field threads
1235 /// through every `Classification.horizon` field on every
1236 /// Process — the last open sibling on the classification axis
1237 /// algebra after `OptimizationDirection` (980a318),
1238 /// `CalmClassification` (da3430c), `SubstrateType` (b9d7b3b),
1239 /// `ConvergencePointType` (7941527), `Arity`, and
1240 /// `DataClassification` (81bffa0).
1241 pub const ALL: [Self; 2] = [Self::Bounded, Self::Asymptotic];
1242
1243 /// Canonical PascalCase wire-format projection — matches the
1244 /// serde `rename_all = "PascalCase"` output verbatim AND the CRD
1245 /// `enum:` enumeration the Process schema stamps on
1246 /// `spec.classification.horizon.kind`. Pinned by
1247 /// `horizon_kind_as_str_matches_serde` so a variant rename
1248 /// can't drift between the typed surface, the CRD enum, the
1249 /// YAML wire format AND any future operator-facing diagnostic
1250 /// composing `horizon.kind={kind}` via Display rather than a
1251 /// hard-coded literal. Display + FromStr triad over `ALL`
1252 /// mirrors every sibling closed-set enum in this crate.
1253 pub const fn as_str(self) -> &'static str {
1254 match self {
1255 Self::Bounded => "Bounded",
1256 Self::Asymptotic => "Asymptotic",
1257 }
1258 }
1259
1260 /// LOAD-BEARING HORIZON-SHAPE PRIMITIVE: does this kind terminate
1261 /// naturally — i.e. does it have a fixed point that
1262 /// `ConvergenceDistance` can reach? Closed-set match (not
1263 /// `matches!`) so a future variant triggers the compiler's
1264 /// exhaustiveness check rather than silently defaulting to
1265 /// `false` (which would silently mis-route a terminating
1266 /// variant through the asymptotic rate-window evaluator) or
1267 /// `true` (which would silently invent a fixed point for a
1268 /// perpetual variant). `Bounded ⇒ true`, `Asymptotic ⇒ false`
1269 /// is the typed image of the documented lattice ordering
1270 /// `Bounded ≤ Asymptotic` — the bounded horizon strictly refines
1271 /// the asymptotic one BY ALSO TERMINATING. Future schedulers
1272 /// asking "will this Process reach `Reaped` via natural
1273 /// termination?" read this predicate, and the tatara-lattice
1274 /// `Lattice for Horizon` impl (which currently dispatches on
1275 /// `self.kind == HorizonKind::Bounded` at three sites) can be
1276 /// recast in a future run to read `self.kind.terminates()` so
1277 /// the lattice basis is the typed primitive rather than a
1278 /// variant-name comparison.
1279 pub const fn terminates(self) -> bool {
1280 match self {
1281 Self::Bounded => true,
1282 Self::Asymptotic => false,
1283 }
1284 }
1285
1286 /// LOAD-BEARING SHAPE-VALIDITY WITNESS: does this kind require
1287 /// the three asymptotic-only [`Horizon`] axes (`metric`,
1288 /// `direction`, `healthy_rate_threshold`) to be `Some(_)`?
1289 /// Closed-set match (not `matches!`) so a future variant
1290 /// triggers the compiler's exhaustiveness check rather than
1291 /// silently defaulting to `false` (which would silently let an
1292 /// asymptotic-shaped variant ship with missing metric axes and
1293 /// trip the rate-window evaluator at runtime). `Bounded ⇒
1294 /// false`, `Asymptotic ⇒ true` is the typed image of the
1295 /// optionality the [`Horizon`] struct encodes via three
1296 /// `Option<…>` fields — the implicit invariant ("Asymptotic
1297 /// only" in the field docs) is now a checkable per-kind
1298 /// predicate. Future horizon-shape validators (CRD admission,
1299 /// `tatara-check` form linter, Lisp authoring-time predicate)
1300 /// read this rather than re-deriving from variant names.
1301 /// Pinned as the antisymmetric partner of [`Self::terminates`]
1302 /// — exactly one of `(terminates, requires_metric_axes)` is
1303 /// true per variant — by
1304 /// `horizon_kind_terminate_xor_requires_metric_axes`.
1305 pub const fn requires_metric_axes(self) -> bool {
1306 match self {
1307 Self::Bounded => false,
1308 Self::Asymptotic => true,
1309 }
1310 }
1311}
1312
1313// `impl FromStr for HorizonKind` +
1314// `impl tatara_lisp::ClosedSet for HorizonKind` +
1315// `impl std::fmt::Display for HorizonKind` +
1316// `pub struct UnknownHorizonKind(pub String)` are all generated by
1317// `#[derive(tatara_closed_set::DeriveClosedSet)]` +
1318// `#[closed_set(via = "as_str", generate_unknown, display)]` on the
1319// enum declaration above. The auto-derived carrier label "horizon
1320// kind" matches the prior hand-rolled `#[error("unknown horizon
1321// kind: {0}")]` annotation byte-for-byte. See the retrofit comment
1322// block on [`ConvergencePointType`] for the canonical narrative.
1323
1324impl Horizon {
1325 pub fn bounded() -> Self {
1326 Self::default()
1327 }
1328
1329 pub fn asymptotic(
1330 metric: impl Into<String>,
1331 direction: OptimizationDirection,
1332 threshold: f64,
1333 ) -> Self {
1334 Self {
1335 kind: HorizonKind::Asymptotic,
1336 metric: Some(metric.into()),
1337 direction: Some(direction),
1338 healthy_rate_threshold: Some(threshold),
1339 }
1340 }
1341}
1342
1343/// Direction of asymptotic optimization — does the metric trend
1344/// downward (cost / latency / error rate) or upward
1345/// (throughput / coverage / revenue)?
1346///
1347/// Closed-set sibling on the classification axis algebra; the `ALL` /
1348/// `as_str` / Display / `FromStr` triad mirrors
1349/// [`ConvergencePointType::ALL`], [`SubstrateType::ALL`],
1350/// [`DataClassification::ALL`], [`CalmClassification::ALL`],
1351/// [`crate::pool::PoolPhase::ALL`], [`crate::pool::MemberState::ALL`],
1352/// [`crate::pool::ReplacementPolicy::ALL`],
1353/// [`crate::pool::ReturnPolicy::ALL`],
1354/// [`crate::boundary::ConditionKind::ALL`],
1355/// [`crate::lifetime::TeardownPolicy::ALL`],
1356/// [`crate::lifetime::LifetimeKind::ALL`],
1357/// [`crate::intent::IntentKind::ALL`],
1358/// [`crate::phase::ProcessPhase::ALL`],
1359/// [`crate::signal::ProcessSignal::ALL`]. The
1360/// [`Self::is_improvement`] predicate is the load-bearing
1361/// optimization primitive — `Asymptotic` horizons read it as the
1362/// typed image of "did this metric sample improve over the last
1363/// one?" rather than re-deriving `<` vs `>` from the variant name
1364/// at every consumer site (rate-window evaluators, breathe-band
1365/// regression detectors, asymptotic-health probes).
1366#[derive(
1367 Clone,
1368 Copy,
1369 Debug,
1370 PartialEq,
1371 Eq,
1372 Hash,
1373 Serialize,
1374 Deserialize,
1375 JsonSchema,
1376 Default,
1377 tatara_closed_set::DeriveClosedSet,
1378)]
1379#[serde(rename_all = "PascalCase")]
1380#[closed_set(via = "as_str", generate_unknown, display)]
1381pub enum OptimizationDirection {
1382 /// Cost / latency / error rate — lower is better. The default for
1383 /// an under-specified `Asymptotic` horizon so an unannotated
1384 /// metric can't silently flip the rate-window evaluator's polarity
1385 /// (a future `Maximize`-default-via-rename would silently invert
1386 /// every existing alert that treats decreasing rate as healthy).
1387 #[default]
1388 Minimize,
1389 /// Throughput / coverage / revenue — higher is better.
1390 Maximize,
1391}
1392
1393impl OptimizationDirection {
1394 /// The closed set of optimization directions — single source of
1395 /// truth that drives the `as_str` / Display / `FromStr` triad AND
1396 /// the `prefers_lower` partition AND the `is_improvement`
1397 /// load-bearing primitive AND both `From` bridge arms. Adding a
1398 /// third variant (e.g. a `Stabilize` sentinel for "drive toward
1399 /// a target value", which neither minimization nor maximization
1400 /// names) lands at one `ALL` entry + one `as_str` arm + one
1401 /// `prefers_lower` arm + one `is_improvement` arm + two bridge
1402 /// arms — exhaustively checked by the compiler (the `[Self; 2]`
1403 /// array literal forces the arity) AND by the per-variant
1404 /// truth-table tests (a new variant must declare its own
1405 /// improvement semantics, or every asymptotic-health probe will
1406 /// silently bucket it). Closes the load-bearing classification
1407 /// sub-axis that the `Horizon.direction` field threads through
1408 /// every `Asymptotic` Process.
1409 pub const ALL: [Self; 2] = [Self::Minimize, Self::Maximize];
1410
1411 /// Canonical PascalCase wire-format projection — matches the serde
1412 /// `rename_all = "PascalCase"` output verbatim AND the CRD `enum:`
1413 /// enumeration the Process schema stamps on
1414 /// `spec.classification.horizon.direction`. Pinned by
1415 /// `optimization_direction_as_str_matches_serde` so a variant
1416 /// rename can't drift between the typed surface, the CRD enum, the
1417 /// YAML wire format AND any future operator-facing diagnostic
1418 /// composed as `direction={kind}` via Display rather than a
1419 /// hard-coded literal. Display + `FromStr` triad over `ALL`
1420 /// mirrors every sibling closed-set enum in this crate.
1421 pub const fn as_str(self) -> &'static str {
1422 match self {
1423 Self::Minimize => "Minimize",
1424 Self::Maximize => "Maximize",
1425 }
1426 }
1427
1428 /// Does this direction prefer numerically lower values?
1429 /// Closed-set match (not `matches!`) so a future variant triggers
1430 /// the compiler's exhaustiveness check at this site rather than
1431 /// silently defaulting to `false` (which would mis-bucket a
1432 /// `Stabilize`-style variant onto the maximization path). The
1433 /// boolean partition is the algebraic shape of an optimization
1434 /// direction: `Minimize ⇒ true`, `Maximize ⇒ false`. Mirrors
1435 /// [`CalmClassification::requires_coordination`] — a two-variant
1436 /// truth-table that any future dispatch on a per-direction policy
1437 /// (rate-window evaluator polarity, breathe-band regression
1438 /// detector sign, asymptotic-health threshold direction) reads
1439 /// once rather than re-deriving from the variant name.
1440 pub const fn prefers_lower(self) -> bool {
1441 match self {
1442 Self::Minimize => true,
1443 Self::Maximize => false,
1444 }
1445 }
1446
1447 /// LOAD-BEARING OPTIMIZATION PRIMITIVE: under this direction, is
1448 /// `after` strictly better than `before`? Closed-set match so a
1449 /// future variant triggers the compiler's exhaustiveness check
1450 /// rather than silently defaulting to `false` (which would
1451 /// silently mark every sample as a regression). For `Minimize`,
1452 /// improvement means `after < before`; for `Maximize`, `after >
1453 /// before`. Strict inequality so a no-op sample (equal values) is
1454 /// NOT counted as improvement — pinned by
1455 /// `optimization_direction_no_op_is_not_improvement`, which
1456 /// guarantees a flatlined rate-window evaluator doesn't silently
1457 /// keep claiming "still improving" forever and skipping the
1458 /// healthy-rate-threshold gate. NaN on either operand short-
1459 /// circuits to `false` (no improvement claim from indeterminate
1460 /// data) via the standard `PartialOrd` behavior — pinned by
1461 /// `optimization_direction_nan_is_not_improvement`. The
1462 /// asymmetry contract (`is_improvement(a, b)` xor
1463 /// `is_improvement(b, a)` for distinct finite samples) is pinned
1464 /// by `optimization_direction_is_improvement_is_antisymmetric`,
1465 /// the algebraic shape that every asymptotic-health rate-window
1466 /// evaluator depends on to avoid double-counting an improvement
1467 /// as a regression on the reverse traversal.
1468 pub fn is_improvement(self, before: f64, after: f64) -> bool {
1469 match self {
1470 Self::Minimize => after < before,
1471 Self::Maximize => after > before,
1472 }
1473 }
1474}
1475
1476// `impl FromStr for OptimizationDirection` +
1477// `impl tatara_lisp::ClosedSet for OptimizationDirection` +
1478// `impl std::fmt::Display for OptimizationDirection` +
1479// `pub struct UnknownOptimizationDirection(pub String)` are all
1480// generated by `#[derive(tatara_closed_set::DeriveClosedSet)]` +
1481// `#[closed_set(via = "as_str", generate_unknown, display)]` on the
1482// enum declaration above. The auto-derived carrier label
1483// "optimization direction" matches the prior hand-rolled
1484// `#[error("unknown optimization direction: {0}")]` annotation
1485// byte-for-byte. See the retrofit comment block on
1486// [`ConvergencePointType`] for the canonical narrative.
1487
1488/// CALM theorem classification — determines whether coordination is required.
1489///
1490/// Closed-set sibling on the classification axis algebra; the `ALL` /
1491/// `as_str` / Display / `FromStr` triad mirrors
1492/// [`ConvergencePointType::ALL`], [`SubstrateType::ALL`],
1493/// [`DataClassification::ALL`], [`crate::pool::PoolPhase::ALL`],
1494/// [`crate::pool::MemberState::ALL`], [`crate::pool::ReplacementPolicy::ALL`],
1495/// [`crate::pool::ReturnPolicy::ALL`],
1496/// [`crate::boundary::ConditionKind::ALL`],
1497/// [`crate::lifetime::TeardownPolicy::ALL`],
1498/// [`crate::lifetime::LifetimeKind::ALL`],
1499/// [`crate::intent::IntentKind::ALL`],
1500/// [`crate::phase::ProcessPhase::ALL`],
1501/// [`crate::signal::ProcessSignal::ALL`]. The
1502/// [`Self::requires_coordination`] predicate is the CALM theorem
1503/// keystone — Hellerstein's "Consistency As Logical Monotonicity"
1504/// states that a program can be distributed without coordination iff
1505/// it computes a monotone function, so `Monotone ⇒ no coordination`
1506/// and `NonMonotone ⇒ requires coordination` is a typed image of the
1507/// theorem itself rather than a runtime convention. Future reconciler
1508/// dispatch on `calm.requires_coordination()` (Raft for non-monotone
1509/// writes; gossip for monotone ones) reads this projection rather
1510/// than re-deriving from variant names.
1511#[derive(
1512 Clone,
1513 Copy,
1514 Debug,
1515 PartialEq,
1516 Eq,
1517 Hash,
1518 Serialize,
1519 Deserialize,
1520 JsonSchema,
1521 Default,
1522 tatara_closed_set::DeriveClosedSet,
1523)]
1524#[serde(rename_all = "PascalCase")]
1525#[closed_set(via = "as_str", generate_unknown, display)]
1526pub enum CalmClassification {
1527 /// Can be distributed without coordination (CALM ⇒ the program
1528 /// computes a monotone function).
1529 #[default]
1530 Monotone,
1531 /// Requires coordination (CALM ⇒ the program is not monotone).
1532 NonMonotone,
1533}
1534
1535impl CalmClassification {
1536 /// The closed set of CALM classifications — single source of truth
1537 /// that drives the `as_str` / Display / `FromStr` triad AND the
1538 /// `requires_coordination` predicate. Adding a third variant
1539 /// (e.g. a `ConditionallyMonotone` sentinel for ops that are
1540 /// monotone under a witness, like CRDT joins under a fixed
1541 /// schema) lands at one `ALL` entry + one `as_str` arm + one
1542 /// predicate arm + one bridge-pair arm — exhaustively checked by
1543 /// the compiler (the `[Self; 2]` array literal forces the arity)
1544 /// AND by the per-variant predicate truth-table test (a new
1545 /// variant must declare its own coordination requirement or any
1546 /// future reconciler-side dispatch will silently bucket it).
1547 /// Closes the load-bearing classification-axis enum that the
1548 /// `Classification.calm` field exposes to every Process and that
1549 /// [`tatara_lattice`]'s boolean-lattice `Lattice for
1550 /// CalmClassification` impl reads via [`Self::requires_coordination`]
1551 /// as the lattice's `top()` predicate.
1552 pub const ALL: [Self; 2] = [Self::Monotone, Self::NonMonotone];
1553
1554 /// Canonical PascalCase wire-format projection — matches the
1555 /// serde `rename_all = "PascalCase"` output verbatim AND the CRD
1556 /// `enum:` enumeration that the Process schema stamps on
1557 /// `spec.classification.calm`. Pinned by
1558 /// `calm_classification_as_str_matches_serde` so a variant rename
1559 /// can't drift between the typed surface, the CRD enum, the YAML
1560 /// wire format AND any future operator-facing diagnostic that
1561 /// composes `calm={kind}` via Display rather than a hard-coded
1562 /// literal that would silently rot. Display + FromStr triad over
1563 /// `ALL` mirrors every sibling closed-set enum in this crate.
1564 pub const fn as_str(self) -> &'static str {
1565 match self {
1566 Self::Monotone => "Monotone",
1567 Self::NonMonotone => "NonMonotone",
1568 }
1569 }
1570
1571 /// CALM-THEOREM KEYSTONE: does this classification require
1572 /// distributed coordination? Closed-set match (not `matches!`) so
1573 /// a future variant triggers the compiler's exhaustiveness check
1574 /// at this site rather than silently defaulting to `false` and
1575 /// shipping a non-monotone operation onto the no-coordination
1576 /// path. The theorem (Hellerstein 2010) states that a program can
1577 /// be distributed without coordination iff it computes a monotone
1578 /// function — `Monotone ⇒ false` and `NonMonotone ⇒ true` is the
1579 /// typed image of that biconditional. Consumers (future reconciler
1580 /// dispatch between Raft writes and gossip propagation; current
1581 /// `tatara_lattice` boolean-lattice ordering where `Monotone ≤
1582 /// NonMonotone`) read this predicate rather than re-deriving from
1583 /// variant names.
1584 pub const fn requires_coordination(self) -> bool {
1585 match self {
1586 Self::Monotone => false,
1587 Self::NonMonotone => true,
1588 }
1589 }
1590}
1591
1592// `impl FromStr for CalmClassification` +
1593// `impl tatara_lisp::ClosedSet for CalmClassification` +
1594// `impl std::fmt::Display for CalmClassification` +
1595// `pub struct UnknownCalmClassification(pub String)` are all generated
1596// by `#[derive(tatara_closed_set::DeriveClosedSet)]` +
1597// `#[closed_set(via = "as_str", generate_unknown, display)]` on the
1598// enum declaration above. The auto-derived carrier label
1599// "calm classification" matches the prior hand-rolled
1600// `#[error("unknown calm classification: {0}")]` annotation
1601// byte-for-byte. See the retrofit comment block on
1602// [`ConvergencePointType`] for the canonical narrative.
1603
1604/// Data sensitivity, drives compliance baseline selection.
1605///
1606/// Sibling closed-set on the classification axis algebra; the `ALL` /
1607/// `as_str` / Display / `FromStr` triad mirrors
1608/// [`crate::pool::PoolPhase::ALL`], [`crate::pool::MemberState::ALL`],
1609/// [`crate::pool::ReplacementPolicy::ALL`],
1610/// [`crate::pool::ReturnPolicy::ALL`],
1611/// [`crate::boundary::ConditionKind::ALL`],
1612/// [`crate::lifetime::TeardownPolicy::ALL`],
1613/// [`crate::lifetime::LifetimeKind::ALL`],
1614/// [`crate::intent::IntentKind::ALL`],
1615/// [`crate::phase::ProcessPhase::ALL`],
1616/// [`crate::signal::ProcessSignal::ALL`].
1617#[derive(
1618 Clone,
1619 Copy,
1620 Debug,
1621 PartialEq,
1622 Eq,
1623 PartialOrd,
1624 Ord,
1625 Hash,
1626 Serialize,
1627 Deserialize,
1628 JsonSchema,
1629 Default,
1630 tatara_closed_set::DeriveClosedSet,
1631)]
1632#[serde(rename_all = "PascalCase")]
1633#[closed_set(via = "as_str", generate_unknown, display)]
1634pub enum DataClassification {
1635 Public,
1636 #[default]
1637 Internal,
1638 Confidential,
1639 Pii,
1640 Phi,
1641 Pci,
1642}
1643
1644impl DataClassification {
1645 /// The closed set of data classifications — single source of truth
1646 /// that drives the `as_str` / Display / `FromStr` triad AND the
1647 /// `sensitivity_rank` total-order projection AND the
1648 /// `is_restricted` / `is_regulated` predicate pair. Adding a
1649 /// seventh variant lands at one `ALL` entry + one `as_str` arm +
1650 /// one `sensitivity_rank` arm + one arm per predicate —
1651 /// exhaustively checked by the compiler (the `[Self; 6]` array
1652 /// literal forces the arity) AND by the per-variant truth-table
1653 /// contract test (a new variant must declare its own
1654 /// `(is_restricted, is_regulated)` bucket or any future
1655 /// compliance-baseline auto-selector that dispatches on the pair
1656 /// will silently bucket it into the wrong sensitivity column).
1657 /// This closes the sixth classification-axis enum and the closure
1658 /// is consumed by [`tatara_lattice`]'s total-order `Lattice` impl
1659 /// via [`Self::sensitivity_rank`] so the lattice ordering no
1660 /// longer rides silently on declaration order.
1661 pub const ALL: [Self; 6] = [
1662 Self::Public,
1663 Self::Internal,
1664 Self::Confidential,
1665 Self::Pii,
1666 Self::Phi,
1667 Self::Pci,
1668 ];
1669
1670 /// Canonical PascalCase wire-format projection — matches the
1671 /// serde `rename_all = "PascalCase"` output verbatim AND the CRD
1672 /// `enum:` enumeration that the Process schema stamps on
1673 /// `spec.classification.dataClassification`. Pinned by
1674 /// `data_classification_as_str_matches_serde` so a variant rename
1675 /// can't drift between the typed surface, the CRD enum, the YAML
1676 /// wire format AND any future operator-facing diagnostic that
1677 /// composes `dataClassification={class}` via Display rather than
1678 /// a hard-coded literal that would silently rot. Display +
1679 /// FromStr triad over `ALL` mirrors `PoolPhase` / `MemberState` /
1680 /// `ReplacementPolicy` / `ReturnPolicy` / `TeardownPolicy` /
1681 /// `ConditionKind` / `ProcessPhase` / `ProcessSignal`.
1682 pub const fn as_str(self) -> &'static str {
1683 match self {
1684 Self::Public => "Public",
1685 Self::Internal => "Internal",
1686 Self::Confidential => "Confidential",
1687 Self::Pii => "Pii",
1688 Self::Phi => "Phi",
1689 Self::Pci => "Pci",
1690 }
1691 }
1692
1693 /// Explicit total-order rank, sealed at one site so the lattice
1694 /// ordering stops riding silently on declaration order. Pre-lift
1695 /// the tatara-lattice `Lattice for DataClassification` impl
1696 /// compared variants via `(*self as u8) <= (*other as u8)`, so a
1697 /// future variant inserted in the middle of the enum (say a
1698 /// `Restricted` between `Internal` and `Confidential`) would
1699 /// silently shift every subsequent variant's `as u8` value AND
1700 /// the lattice's `leq` relation — no compile error, no test
1701 /// failure, but every compliance-baseline comparison
1702 /// downstream would have moved by one slot. Post-lift the rank
1703 /// is declared explicitly per variant; an insertion forces the
1704 /// author to pick a rank deliberately (and
1705 /// `data_classification_rank_is_strictly_monotone_over_all`
1706 /// pins the existing six variants at 0..6 so the lattice's
1707 /// total order remains the documented
1708 /// `Public < Internal < Confidential < Pii < Phi < Pci`).
1709 pub const fn sensitivity_rank(self) -> u8 {
1710 match self {
1711 Self::Public => 0,
1712 Self::Internal => 1,
1713 Self::Confidential => 2,
1714 Self::Pii => 3,
1715 Self::Phi => 4,
1716 Self::Pci => 5,
1717 }
1718 }
1719
1720 /// Is this classification subject to external regulatory regime
1721 /// (HIPAA / PCI-DSS / GDPR-style data-subject controls)?
1722 /// Closed-set match (not `matches!`) so a future variant triggers
1723 /// the compiler's exhaustiveness check at this site rather than
1724 /// silently defaulting to `false`. Paired with `is_restricted`
1725 /// they form the two-axis projection that future
1726 /// compliance-baseline auto-selectors dispatch against —
1727 /// `(false, false)` ⇒ freely distributable (`Public`);
1728 /// `(false, true)` ⇒ access-controlled but not regulated
1729 /// (`Internal | Confidential`); `(true, true)` ⇒ regulated data
1730 /// that implies access control (`Pii | Phi | Pci`). The
1731 /// impossible bucket `(true, false)` — regulated data without
1732 /// access control — is pinned empty by
1733 /// `data_classification_regulated_implies_restricted`.
1734 pub const fn is_regulated(self) -> bool {
1735 match self {
1736 Self::Pii | Self::Phi | Self::Pci => true,
1737 Self::Public | Self::Internal | Self::Confidential => false,
1738 }
1739 }
1740
1741 /// Does this classification require access controls beyond
1742 /// freely-distributable? Closed-set match so a future variant
1743 /// triggers the compiler's exhaustiveness check. See
1744 /// `is_regulated` for the predicate-pair contract + bucket
1745 /// definitions.
1746 pub const fn is_restricted(self) -> bool {
1747 match self {
1748 Self::Public => false,
1749 Self::Internal | Self::Confidential | Self::Pii | Self::Phi | Self::Pci => true,
1750 }
1751 }
1752}
1753
1754// `impl FromStr for DataClassification` +
1755// `impl tatara_lisp::ClosedSet for DataClassification` +
1756// `impl std::fmt::Display for DataClassification` +
1757// `pub struct UnknownDataClassification(pub String)` are all generated
1758// by `#[derive(tatara_closed_set::DeriveClosedSet)]` +
1759// `#[closed_set(via = "as_str", generate_unknown, display)]` on the
1760// enum declaration above. The auto-derived carrier label
1761// "data classification" matches the prior hand-rolled
1762// `#[error("unknown data classification: {0}")]` annotation
1763// byte-for-byte. See the retrofit comment block on
1764// [`ConvergencePointType`] for the canonical narrative.
1765
1766// ───────────────────────────── bridges to tatara-core ─────────────────
1767
1768impl From<ConvergencePointType> for core::ConvergencePointType {
1769 fn from(v: ConvergencePointType) -> Self {
1770 use ConvergencePointType::*;
1771 match v {
1772 Transform => Self::Transform,
1773 Fork => Self::Fork,
1774 Join => Self::Join,
1775 Gate => Self::Gate,
1776 Select => Self::Select,
1777 Broadcast => Self::Broadcast,
1778 Reduce => Self::Reduce,
1779 Observe => Self::Observe,
1780 }
1781 }
1782}
1783
1784impl From<core::ConvergencePointType> for ConvergencePointType {
1785 fn from(v: core::ConvergencePointType) -> Self {
1786 use core::ConvergencePointType as C;
1787 match v {
1788 C::Transform => Self::Transform,
1789 C::Fork => Self::Fork,
1790 C::Join => Self::Join,
1791 C::Gate => Self::Gate,
1792 C::Select => Self::Select,
1793 C::Broadcast => Self::Broadcast,
1794 C::Reduce => Self::Reduce,
1795 C::Observe => Self::Observe,
1796 }
1797 }
1798}
1799
1800impl From<SubstrateType> for core::SubstrateType {
1801 fn from(v: SubstrateType) -> Self {
1802 use SubstrateType::*;
1803 match v {
1804 Financial => Self::Financial,
1805 Compute => Self::Compute,
1806 Network => Self::Network,
1807 Storage => Self::Storage,
1808 Security => Self::Security,
1809 Identity => Self::Identity,
1810 Observability => Self::Observability,
1811 Regulatory => Self::Regulatory,
1812 }
1813 }
1814}
1815
1816impl From<core::SubstrateType> for SubstrateType {
1817 fn from(v: core::SubstrateType) -> Self {
1818 use core::SubstrateType as C;
1819 match v {
1820 C::Financial => Self::Financial,
1821 C::Compute => Self::Compute,
1822 C::Network => Self::Network,
1823 C::Storage => Self::Storage,
1824 C::Security => Self::Security,
1825 C::Identity => Self::Identity,
1826 C::Observability => Self::Observability,
1827 C::Regulatory => Self::Regulatory,
1828 }
1829 }
1830}
1831
1832impl From<OptimizationDirection> for core::OptimizationDirection {
1833 fn from(v: OptimizationDirection) -> Self {
1834 match v {
1835 OptimizationDirection::Minimize => Self::Minimize,
1836 OptimizationDirection::Maximize => Self::Maximize,
1837 }
1838 }
1839}
1840
1841impl From<core::OptimizationDirection> for OptimizationDirection {
1842 fn from(v: core::OptimizationDirection) -> Self {
1843 use core::OptimizationDirection as C;
1844 match v {
1845 C::Minimize => Self::Minimize,
1846 C::Maximize => Self::Maximize,
1847 }
1848 }
1849}
1850
1851impl From<Horizon> for core::ConvergenceHorizon {
1852 fn from(v: Horizon) -> Self {
1853 match v.kind {
1854 HorizonKind::Bounded => Self::Bounded,
1855 HorizonKind::Asymptotic => Self::Asymptotic {
1856 metric: v.metric.unwrap_or_default(),
1857 direction: v.direction.unwrap_or_default().into(),
1858 healthy_rate_threshold: v.healthy_rate_threshold.unwrap_or_default(),
1859 },
1860 }
1861 }
1862}
1863
1864impl From<CalmClassification> for core::CalmClassification {
1865 fn from(v: CalmClassification) -> Self {
1866 match v {
1867 CalmClassification::Monotone => Self::Monotone,
1868 CalmClassification::NonMonotone => Self::NonMonotone,
1869 }
1870 }
1871}
1872
1873impl From<core::CalmClassification> for CalmClassification {
1874 fn from(v: core::CalmClassification) -> Self {
1875 use core::CalmClassification as C;
1876 match v {
1877 C::Monotone => Self::Monotone,
1878 C::NonMonotone => Self::NonMonotone,
1879 }
1880 }
1881}
1882
1883impl From<DataClassification> for core_compl::DataClassification {
1884 fn from(v: DataClassification) -> Self {
1885 use DataClassification::*;
1886 match v {
1887 Public => Self::Public,
1888 Internal => Self::Internal,
1889 Confidential => Self::Confidential,
1890 Pii => Self::Pii,
1891 Phi => Self::Phi,
1892 Pci => Self::Pci,
1893 }
1894 }
1895}
1896
1897impl From<core_compl::DataClassification> for DataClassification {
1898 fn from(v: core_compl::DataClassification) -> Self {
1899 use core_compl::DataClassification as C;
1900 match v {
1901 C::Public => Self::Public,
1902 C::Internal => Self::Internal,
1903 C::Confidential => Self::Confidential,
1904 C::Pii => Self::Pii,
1905 C::Phi => Self::Phi,
1906 C::Pci => Self::Pci,
1907 }
1908 }
1909}
1910
1911#[cfg(test)]
1912mod tests {
1913 use super::*;
1914 // The closed-set tests below call `T::from_str(bad)` via the
1915 // derive-generated `FromStr` impls — bring the trait into scope at
1916 // the test module so the lib body doesn't carry an otherwise-unused
1917 // `use std::str::FromStr;` at the file head.
1918 use std::str::FromStr;
1919
1920 #[test]
1921 fn bridges_roundtrip() {
1922 let pt: core::ConvergencePointType = ConvergencePointType::Gate.into();
1923 let back: ConvergencePointType = pt.into();
1924 assert_eq!(back, ConvergencePointType::Gate);
1925
1926 let sub: core::SubstrateType = SubstrateType::Observability.into();
1927 let back: SubstrateType = sub.into();
1928 assert_eq!(back, SubstrateType::Observability);
1929 }
1930
1931 #[test]
1932 fn data_classification_ordering() {
1933 assert!(DataClassification::Public < DataClassification::Pii);
1934 assert!(DataClassification::Internal < DataClassification::Confidential);
1935 }
1936
1937 #[test]
1938 fn horizon_default_is_bounded() {
1939 assert_eq!(Horizon::default().kind, HorizonKind::Bounded);
1940 }
1941
1942 // ── Classification::gate_compute substrate pins ─────────────────────
1943 //
1944 // The six-line `Classification { point_type: Gate, substrate: Compute,
1945 // horizon: Default::default(), calm: Default::default(),
1946 // data_classification: Default::default() }` struct-literal was
1947 // open-coded verbatim at ten hand-authored callsites before the
1948 // primitive closed it. These pins bind the composed shape at
1949 // fail-before-pass-after granularity so a regression that flipped a
1950 // baseline axis, drifted a sibling default, or leaked a non-baseline
1951 // slot into the substrate composer surfaces HERE rather than as
1952 // silent operator-visible drift at every unadorned ephemeral env
1953 // (the one production consumer, `default_ephemeral_class`) AND every
1954 // downstream test fixture that keys assertions on the shape.
1955
1956 #[test]
1957 fn gate_compute_composes_the_five_baseline_axes() {
1958 // Primary shape: every axis parked at the workspace baseline.
1959 // A regression that flipped `point_type` off `Gate` or
1960 // `substrate` off `Compute` — the two axes with no `Default` —
1961 // surfaces here.
1962 let c = Classification::gate_compute();
1963 assert_eq!(c.point_type, ConvergencePointType::Gate);
1964 assert_eq!(c.substrate, SubstrateType::Compute);
1965 assert_eq!(c.horizon, Horizon::default());
1966 assert_eq!(c.calm, CalmClassification::default());
1967 assert_eq!(c.data_classification, DataClassification::default());
1968 }
1969
1970 #[test]
1971 fn gate_compute_defaulted_axes_ride_sibling_closed_set_defaults() {
1972 // Pins the sibling-default correspondence the doc comment
1973 // names — a regression that flipped a sibling default (a new
1974 // `HorizonKind` variant promoted to `#[default]`, a rename of
1975 // `CalmClassification::Monotone`, a promotion of `Pii` above
1976 // `Internal` in the `DataClassification` ordering) would move
1977 // the baseline HERE rather than at every downstream consumer.
1978 let c = Classification::gate_compute();
1979 assert_eq!(c.horizon.kind, HorizonKind::Bounded);
1980 assert_eq!(c.calm, CalmClassification::Monotone);
1981 assert_eq!(c.data_classification, DataClassification::Internal);
1982 }
1983
1984 #[test]
1985 fn gate_compute_matches_hand_authored_pre_lift_bytewise() {
1986 // Byte-identical parity with the pre-lift six-line struct-literal
1987 // that recurred at ten hand-authored sites. A regression that
1988 // reshaped the primitive would diverge from the pre-lift block
1989 // HERE rather than at every downstream fixture that keys on the
1990 // shape.
1991 let composed = Classification::gate_compute();
1992 let hand_authored = Classification {
1993 point_type: ConvergencePointType::Gate,
1994 substrate: SubstrateType::Compute,
1995 horizon: Horizon::default(),
1996 calm: CalmClassification::default(),
1997 data_classification: DataClassification::default(),
1998 };
1999 assert_eq!(composed, hand_authored);
2000 }
2001
2002 #[test]
2003 fn gate_compute_is_call_time_construction_not_a_shared_singleton() {
2004 // Two independent calls produce structurally-equal but distinct
2005 // values — pins that the primitive is a plain constructor
2006 // rather than a `lazy_static` clone (which would leak a shared
2007 // singleton whose in-place mutation at one consumer would
2008 // silently mutate the shape at every other consumer). The `!=`
2009 // check on `&mut _`-obtained pointer addresses is intentional:
2010 // a shared singleton would collide, and the pin catches the
2011 // regression at the primitive rather than at the operator-facing
2012 // shape-drift downstream.
2013 let a = Classification::gate_compute();
2014 let b = Classification::gate_compute();
2015 assert_eq!(a, b);
2016 assert!(!std::ptr::eq(&a, &b));
2017 }
2018
2019 // ── closed-set algebra contracts for DataClassification
2020 // (ALL × as_str × FromStr × rank × predicate pair) ────────────
2021
2022 /// Structural well-formedness of [`DataClassification`] as a
2023 /// [`tatara_lisp::ClosedSet`] implementor — the workspace-wide
2024 /// testkit lift that pins all three structural invariants (`ALL`
2025 /// is non-empty, every variant round-trips through
2026 /// `label ↔ parse_label`, labels are pairwise distinct, `""` is
2027 /// outside the closed set) at ONE call site. Replaces the hand-
2028 /// derived `data_classification_all_is_unique_and_complete` +
2029 /// `data_classification_roundtrip_via_as_str` + the empty-input arm
2030 /// of `unknown_data_classification_errors`. `FromStr` delegates to
2031 /// `<Self as tatara_closed_set::ClosedSet>::parse_label`, so this helper
2032 /// exercises the same code path the reconciler hits when parsing a
2033 /// CRD `enum:`-validated `dataClassification` value back to the
2034 /// typed classification.
2035 #[test]
2036 fn data_classification_is_well_formed_closed_set() {
2037 tatara_closed_set::assert_closed_set_well_formed::<DataClassification>();
2038 }
2039
2040 /// CANONICAL-KEY CONTRACT: `as_str` matches serde's PascalCase
2041 /// output verbatim for every variant. A future variant rename (or
2042 /// an `as_str` arm typo) lands here at one site, instead of
2043 /// drifting between the typed surface, the CRD enum, and the YAML
2044 /// wire format the reconciler stamps on
2045 /// `spec.classification.dataClassification`.
2046 #[test]
2047 fn data_classification_as_str_matches_serde() {
2048 crate::tagged_union::assert_label_matches_serde_serialization::<DataClassification>();
2049 }
2050
2051 /// The Display impl IS `as_str` — pinning this lets future callers
2052 /// reach for either projection without drift. Any operator-facing
2053 /// "dataClassification={class}" diagnostic that composes through
2054 /// Display inherits the canonical wire-format string automatically.
2055 #[test]
2056 fn data_classification_display_matches_as_str() {
2057 crate::tagged_union::assert_display_matches_label::<DataClassification>();
2058 }
2059
2060 /// `FromStr` rejects strings that aren't in the canonical
2061 /// projection — lowercased / typo / cross-axis-leaked — and the
2062 /// error echoes the input verbatim so the operator-facing
2063 /// diagnostic carries the offending value, not a normalized form.
2064 /// The empty-input arm is pinned by
2065 /// [`data_classification_is_well_formed_closed_set`] via the
2066 /// `tatara_lisp::ClosedSet` testkit; the cases here pin the
2067 /// verbatim-echo contract on the [`UnknownDataClassification`]
2068 /// newtype, which the trait's `make_unknown` can't see.
2069 #[test]
2070 fn unknown_data_classification_errors() {
2071 for bad in [
2072 "pii", // lowercased
2073 "PII", // uppercased
2074 "PersonalData", // typo
2075 "internal_data",
2076 "Steady", // PoolPhase-axis leak
2077 "Replace", // ReturnPolicy-axis leak
2078 "Attested", // ProcessPhase-axis leak
2079 "Compute", // SubstrateType-axis leak
2080 "Gate", // ConvergencePointType-axis leak
2081 "Monotone", // CalmClassification-axis leak
2082 ] {
2083 let err = DataClassification::from_str(bad).unwrap_err();
2084 assert_eq!(err.0, bad, "error payload should echo input verbatim");
2085 }
2086 }
2087
2088 // `unknown_data_classification_message_matches_substrate_convention`
2089 // removed — clause (5) of
2090 // `tatara_closed_set::assert_closed_set_well_formed::<DataClassification>()`
2091 // verifies the substrate-wide `"unknown {SET_LABEL}: {input}"`
2092 // shape generically (called from
2093 // `data_classification_is_well_formed_closed_set` above); the
2094 // `SET_LABEL` projection is pinned by
2095 // `tatara_lisp_derive::pascal_to_spaced_lowercase_tests`.
2096
2097 /// TRUTH-TABLE CONTRACT: the predicate pair agrees with the
2098 /// documented per-variant compliance role. Pinning this table at
2099 /// one site means any future compliance-baseline auto-selector
2100 /// reads the same projection that the reconciler writes.
2101 #[test]
2102 fn data_classification_predicate_truth_tables() {
2103 assert!(!DataClassification::Public.is_restricted());
2104 assert!(!DataClassification::Public.is_regulated());
2105
2106 assert!(DataClassification::Internal.is_restricted());
2107 assert!(!DataClassification::Internal.is_regulated());
2108
2109 assert!(DataClassification::Confidential.is_restricted());
2110 assert!(!DataClassification::Confidential.is_regulated());
2111
2112 assert!(DataClassification::Pii.is_restricted());
2113 assert!(DataClassification::Pii.is_regulated());
2114
2115 assert!(DataClassification::Phi.is_restricted());
2116 assert!(DataClassification::Phi.is_regulated());
2117
2118 assert!(DataClassification::Pci.is_restricted());
2119 assert!(DataClassification::Pci.is_regulated());
2120 }
2121
2122 /// IMPLICATION CONTRACT: every regulated classification is also
2123 /// restricted. The impossible bucket (regulated AND
2124 /// freely-distributable) is pinned empty so a future variant that
2125 /// returned `(true, false)` from the predicate pair would FAIL
2126 /// here, forcing the author to either flip `is_restricted` or
2127 /// extend the consumer dispatch sites (compliance-baseline
2128 /// auto-selector, audit-log mandatory-fields validator)
2129 /// deliberately rather than silently producing a regulated class
2130 /// the API server would accept as freely-distributable. Encoded as
2131 /// material implication `is_regulated → is_restricted` so the
2132 /// boolean reads as the documented contract, not its NAND form.
2133 #[test]
2134 fn data_classification_regulated_implies_restricted() {
2135 for class in DataClassification::ALL {
2136 assert!(
2137 !class.is_regulated() || class.is_restricted(),
2138 "{class:?} is regulated but not restricted — \
2139 regulated data is by definition not freely distributable",
2140 );
2141 }
2142 }
2143
2144 /// COVERAGE CONTRACT: every variant lands in exactly one of three
2145 /// compliance buckets — freely distributable (`Public`),
2146 /// restricted-only (`Internal | Confidential`), or regulated
2147 /// (`Pii | Phi | Pci`). Pins the three buckets at their declared
2148 /// cardinalities (1, 2, 3 — sum to `ALL.len()`) so a future
2149 /// variant lands somewhere deliberately.
2150 #[test]
2151 fn data_classification_buckets_cover_every_variant() {
2152 let mut free = 0u32;
2153 let mut restricted_only = 0u32;
2154 let mut regulated = 0u32;
2155 for class in DataClassification::ALL {
2156 match (class.is_restricted(), class.is_regulated()) {
2157 (false, false) => free += 1,
2158 (true, false) => restricted_only += 1,
2159 (true, true) => regulated += 1,
2160 (false, true) => {
2161 panic!("regulated_implies_restricted already pins this empty for {class:?}")
2162 }
2163 }
2164 }
2165 assert_eq!(free, 1, "free bucket: Public");
2166 assert_eq!(
2167 restricted_only, 2,
2168 "restricted-only bucket: Internal + Confidential"
2169 );
2170 assert_eq!(regulated, 3, "regulated bucket: Pii + Phi + Pci");
2171 assert_eq!(
2172 free + restricted_only + regulated,
2173 DataClassification::ALL.len() as u32
2174 );
2175 }
2176
2177 /// MONOTONE-RANK CONTRACT: `sensitivity_rank` is strictly
2178 /// monotone over `ALL`'s declared order, so the lattice ordering
2179 /// `Public < Internal < Confidential < Pii < Phi < Pci` is sealed
2180 /// at one site (this enum's projection) instead of riding on the
2181 /// silent `as u8` cast in [`tatara_lattice`]. A future variant
2182 /// inserted in the middle would either preserve strict monotonicity
2183 /// here (and the lattice keeps working) or FAIL here at compile or
2184 /// test time (and the author has to renumber deliberately). Also
2185 /// pins the rank codomain at `0..ALL.len()` so no variant can
2186 /// silently outrank the documented top.
2187 #[test]
2188 fn data_classification_rank_is_strictly_monotone_over_all() {
2189 let ranks: Vec<u8> = DataClassification::ALL
2190 .into_iter()
2191 .map(DataClassification::sensitivity_rank)
2192 .collect();
2193 for win in ranks.windows(2) {
2194 assert!(win[0] < win[1], "ranks not strictly monotone: {ranks:?}");
2195 }
2196 assert_eq!(*ranks.first().unwrap(), 0, "bottom rank must be 0");
2197 assert_eq!(
2198 *ranks.last().unwrap(),
2199 (DataClassification::ALL.len() as u8) - 1,
2200 "top rank must be ALL.len() - 1"
2201 );
2202 }
2203
2204 /// RANK-AGREES-WITH-ORD CONTRACT: the typed `sensitivity_rank`
2205 /// projection agrees with the derived `PartialOrd` / `Ord` for
2206 /// every pair in `ALL × ALL`. This is the bridge that lets
2207 /// [`tatara_lattice`]'s total-order `Lattice for DataClassification`
2208 /// impl call `sensitivity_rank` instead of `as u8` without changing
2209 /// any observable lattice behavior — and it lets a future
2210 /// reordering of the enum's variant declarations land at this test
2211 /// site (forcing the rank arms to be renumbered) rather than
2212 /// silently shifting the lattice's `leq` relation.
2213 #[test]
2214 fn data_classification_rank_agrees_with_partial_ord() {
2215 for a in DataClassification::ALL {
2216 for b in DataClassification::ALL {
2217 assert_eq!(
2218 a.sensitivity_rank() <= b.sensitivity_rank(),
2219 a <= b,
2220 "rank vs. PartialOrd drift on ({a:?}, {b:?})"
2221 );
2222 }
2223 }
2224 }
2225
2226 /// DEFAULT-AGREEMENT CONTRACT: `DataClassification::default()`
2227 /// returns `Internal` (the variant tagged `#[default]`), AND that
2228 /// variant lands in the restricted-only bucket — neither freely
2229 /// distributable nor externally regulated. A future `#[default]`
2230 /// rename without flipping the predicates fails here.
2231 #[test]
2232 fn data_classification_default_is_internal_in_restricted_only_bucket() {
2233 let d = DataClassification::default();
2234 assert_eq!(d, DataClassification::Internal);
2235 assert!(d.is_restricted());
2236 assert!(!d.is_regulated());
2237 assert_eq!(d.sensitivity_rank(), 1);
2238 }
2239
2240 /// BRIDGE ROUND-TRIP CONTRACT: every variant survives the
2241 /// CRD-facing (`PascalCase`) ↔ tatara-core (`snake_case`)
2242 /// `From` hop. Today the bridge is two hand-written 6-arm matches
2243 /// in this file; pinning the round-trip over `ALL` means a future
2244 /// variant added without extending the bridge fails here at one
2245 /// site instead of drifting between the CRD wire format and the
2246 /// `core_compl::DataClassification` selector axis.
2247 #[test]
2248 fn data_classification_bridge_roundtrip_over_all() {
2249 for class in DataClassification::ALL {
2250 let core: core_compl::DataClassification = class.into();
2251 let back: DataClassification = core.into();
2252 assert_eq!(back, class, "bridge round-trip failed for {class:?}");
2253 }
2254 }
2255
2256 // ── closed-set algebra contracts for ConvergencePointType
2257 // (ALL × as_str × FromStr × arity-pair × predicate triple) ────
2258
2259 /// Structural well-formedness of [`ConvergencePointType`] as a
2260 /// [`tatara_lisp::ClosedSet`] implementor — the workspace-wide
2261 /// testkit lift that pins all three structural invariants (`ALL`
2262 /// is non-empty, every variant round-trips through `label ↔
2263 /// parse_label`, labels are pairwise distinct, `""` is outside
2264 /// the closed set) at ONE call site. Replaces the hand-derived
2265 /// `convergence_point_type_all_is_unique_and_complete` +
2266 /// `convergence_point_type_roundtrip_via_as_str` + the empty-
2267 /// input arm of `unknown_convergence_point_type_errors`.
2268 /// `FromStr` delegates to `<Self as tatara_closed_set::ClosedSet>::parse_label`,
2269 /// so this helper exercises the same code path the reconciler
2270 /// hits when parsing a CRD `enum:`-validated value back to the
2271 /// typed point-type. The forced `[Self; 8]` array literal on
2272 /// `ConvergencePointType::ALL` still pins the cardinality at the
2273 /// declaration site.
2274 #[test]
2275 fn convergence_point_type_is_well_formed_closed_set() {
2276 tatara_closed_set::assert_closed_set_well_formed::<ConvergencePointType>();
2277 }
2278
2279 /// CANONICAL-KEY CONTRACT: `as_str` matches serde's PascalCase
2280 /// output verbatim for every variant. A future variant rename (or
2281 /// an `as_str` arm typo) lands here at one site, instead of
2282 /// drifting between the typed surface, the CRD enum, and the YAML
2283 /// wire format the reconciler reads from
2284 /// `spec.classification.pointType`.
2285 #[test]
2286 fn convergence_point_type_as_str_matches_serde() {
2287 crate::tagged_union::assert_label_matches_serde_serialization::<ConvergencePointType>();
2288 }
2289
2290 /// The Display impl IS `as_str` — pinning this lets future callers
2291 /// reach for either projection without drift.
2292 #[test]
2293 fn convergence_point_type_display_matches_as_str() {
2294 crate::tagged_union::assert_display_matches_label::<ConvergencePointType>();
2295 }
2296
2297 /// `FromStr` rejects strings outside the canonical projection —
2298 /// lowercased / typo / cross-axis-leaked — and the error echoes
2299 /// the input verbatim so the operator-facing diagnostic surfaces
2300 /// the bad value, not a normalized form. The empty-input arm is
2301 /// pinned by [`convergence_point_type_is_well_formed_closed_set`]
2302 /// via the `tatara_lisp::ClosedSet` testkit; the cases here pin
2303 /// the verbatim-echo contract on the
2304 /// [`UnknownConvergencePointType`] newtype, which the trait's
2305 /// `make_unknown` can't see.
2306 #[test]
2307 fn unknown_convergence_point_type_errors() {
2308 for bad in [
2309 "gate", // lowercased
2310 "GATE", // uppercased
2311 "Transformr", // typo
2312 "Filter",
2313 "Steady", // PoolPhase-axis leak
2314 "Pii", // DataClassification-axis leak
2315 "Attested", // ProcessPhase-axis leak
2316 "Compute", // SubstrateType-axis leak
2317 "Monotone", // CalmClassification-axis leak
2318 "PromQL", // ConditionKind-axis leak
2319 ] {
2320 let err = ConvergencePointType::from_str(bad).unwrap_err();
2321 assert_eq!(err.0, bad, "error payload should echo input verbatim");
2322 }
2323 }
2324
2325 // `unknown_convergence_point_type_message_matches_substrate_convention`
2326 // removed — clause (5) of
2327 // `tatara_closed_set::assert_closed_set_well_formed::<ConvergencePointType>()`
2328 // verifies the substrate-wide `"unknown {SET_LABEL}: {input}"`
2329 // shape generically (called from
2330 // `convergence_point_type_is_well_formed_closed_set` above); the
2331 // `SET_LABEL` projection is pinned by
2332 // `tatara_lisp_derive::pascal_to_spaced_lowercase_tests`.
2333
2334 /// TRUTH-TABLE CONTRACT: the predicate triple agrees with the
2335 /// documented per-variant topology role. Pinning this table at
2336 /// one site means any future DAG validator reads the same
2337 /// projection that compliance bindings dispatch against.
2338 #[test]
2339 fn convergence_point_type_predicate_truth_tables() {
2340 // Endomorphic: 1→1
2341 assert!(ConvergencePointType::Transform.is_endomorphic());
2342 assert!(!ConvergencePointType::Transform.is_diffusive());
2343 assert!(!ConvergencePointType::Transform.is_convergent());
2344
2345 assert!(ConvergencePointType::Observe.is_endomorphic());
2346 assert!(!ConvergencePointType::Observe.is_diffusive());
2347 assert!(!ConvergencePointType::Observe.is_convergent());
2348
2349 // Diffusive: 1→N
2350 assert!(!ConvergencePointType::Fork.is_endomorphic());
2351 assert!(ConvergencePointType::Fork.is_diffusive());
2352 assert!(!ConvergencePointType::Fork.is_convergent());
2353
2354 assert!(!ConvergencePointType::Broadcast.is_endomorphic());
2355 assert!(ConvergencePointType::Broadcast.is_diffusive());
2356 assert!(!ConvergencePointType::Broadcast.is_convergent());
2357
2358 // Convergent: N→1
2359 for t in [
2360 ConvergencePointType::Join,
2361 ConvergencePointType::Gate,
2362 ConvergencePointType::Select,
2363 ConvergencePointType::Reduce,
2364 ] {
2365 assert!(!t.is_endomorphic(), "{t:?} should not be endomorphic");
2366 assert!(!t.is_diffusive(), "{t:?} should not be diffusive");
2367 assert!(t.is_convergent(), "{t:?} should be convergent");
2368 }
2369 }
2370
2371 /// COVERAGE CONTRACT: every variant lands in *exactly one* of the
2372 /// three topology buckets — endomorphic, diffusive, or convergent.
2373 /// Pins the three buckets at their declared cardinalities (2, 2, 4
2374 /// — sum to `ALL.len()`) so a future variant lands somewhere
2375 /// deliberately. No variant returns true from more than one
2376 /// predicate; no variant returns false from all three.
2377 #[test]
2378 fn convergence_point_type_buckets_cover_every_variant() {
2379 let mut endomorphic = 0u32;
2380 let mut diffusive = 0u32;
2381 let mut convergent = 0u32;
2382 for t in ConvergencePointType::ALL {
2383 let buckets = [t.is_endomorphic(), t.is_diffusive(), t.is_convergent()];
2384 let hits: u32 = buckets.iter().map(|b| u32::from(*b)).sum();
2385 assert_eq!(
2386 hits, 1,
2387 "{t:?} landed in {hits} buckets: {buckets:?} (must be exactly one)"
2388 );
2389 if t.is_endomorphic() {
2390 endomorphic += 1;
2391 }
2392 if t.is_diffusive() {
2393 diffusive += 1;
2394 }
2395 if t.is_convergent() {
2396 convergent += 1;
2397 }
2398 }
2399 assert_eq!(endomorphic, 2, "endomorphic bucket: Transform + Observe");
2400 assert_eq!(diffusive, 2, "diffusive bucket: Fork + Broadcast");
2401 assert_eq!(
2402 convergent, 4,
2403 "convergent bucket: Join + Gate + Select + Reduce"
2404 );
2405 assert_eq!(
2406 endomorphic + diffusive + convergent,
2407 ConvergencePointType::ALL.len() as u32
2408 );
2409 }
2410
2411 /// ARITY-PAIR ⇔ BUCKET CONTRACT: the `(input_arity, output_arity)`
2412 /// projection names the same topology partition as the
2413 /// `is_endomorphic` / `is_diffusive` / `is_convergent` predicate
2414 /// triple. `(One, One) ⇒ endomorphic`; `(One, Many) ⇒ diffusive`;
2415 /// `(Many, One) ⇒ convergent`. The impossible `(Many, Many)`
2416 /// bucket is pinned empty here — a `(Many, Many)` point would
2417 /// have no convergence semantics (many independent inputs
2418 /// replicated across many independent outputs) and every future
2419 /// DAG-composition validator would have to special-case it. This
2420 /// seal is the bridge that lets a future graph validator dispatch
2421 /// on either projection (arity pair OR bucket predicates) without
2422 /// drift — and a future variant that wants `(Many, Many)` must
2423 /// extend the bucket carving deliberately rather than silently
2424 /// shipping a fourth topology class.
2425 #[test]
2426 fn convergence_point_type_arity_pair_agrees_with_bucket() {
2427 for t in ConvergencePointType::ALL {
2428 match (t.input_arity(), t.output_arity()) {
2429 (Arity::One, Arity::One) => assert!(
2430 t.is_endomorphic(),
2431 "{t:?} has (One, One) arity but is not endomorphic"
2432 ),
2433 (Arity::One, Arity::Many) => assert!(
2434 t.is_diffusive(),
2435 "{t:?} has (One, Many) arity but is not diffusive"
2436 ),
2437 (Arity::Many, Arity::One) => assert!(
2438 t.is_convergent(),
2439 "{t:?} has (Many, One) arity but is not convergent"
2440 ),
2441 (Arity::Many, Arity::Many) => panic!(
2442 "{t:?} has (Many, Many) arity — pinned empty; \
2443 extend the topology carving before adding a variant here"
2444 ),
2445 }
2446 }
2447 }
2448
2449 /// BRIDGE ROUND-TRIP CONTRACT: every variant survives the
2450 /// CRD-facing (`PascalCase`) ↔ tatara-core (`snake_case`)
2451 /// `From` hop. Today the bridge is two hand-written 8-arm
2452 /// matches in this file; pinning the round-trip over `ALL`
2453 /// means a future variant added without extending the bridge
2454 /// fails here at one site instead of drifting between the CRD
2455 /// wire format and the
2456 /// `core::ConvergencePointType` selector axis that
2457 /// `compliance_binding::PointSelector::ByType` already
2458 /// dispatches against.
2459 #[test]
2460 fn convergence_point_type_bridge_roundtrip_over_all() {
2461 for t in ConvergencePointType::ALL {
2462 let core_t: core::ConvergencePointType = t.into();
2463 let back: ConvergencePointType = core_t.into();
2464 assert_eq!(back, t, "bridge round-trip failed for {t:?}");
2465 }
2466 }
2467
2468 // ── closed-set algebra contracts for Arity ───────────────────
2469
2470 /// `ALL` is the source of truth — pin its closure so a variant
2471 /// added without an `ALL` entry fails here. The arity is asserted
2472 /// by the `[Self; 2]` array type itself.
2473 #[test]
2474 fn arity_all_is_unique_and_complete() {
2475 let mut seen = std::collections::HashSet::new();
2476 for a in Arity::ALL {
2477 assert!(seen.insert(a), "duplicate variant in ALL: {a:?}");
2478 }
2479 assert_eq!(seen.len(), Arity::ALL.len());
2480 }
2481
2482 /// The Display impl IS `as_str` — pinning this lets future
2483 /// callers reach for either projection without drift. No serde
2484 /// matching here because `Arity` is a typed projection, not a
2485 /// CRD-facing enum — it never crosses the wire. Routed through
2486 /// the substrate-wide [`crate::tagged_union::assert_display_matches_label`]
2487 /// primitive so the sweep body lives at ONE substrate site rather
2488 /// than restated per-implementor. Also exercised through the
2489 /// substrate-wide `every_production_display_impl_binds_through_the_testkit_primitive`
2490 /// sweep so a per-crate test-site drop cannot silently disable the
2491 /// check.
2492 #[test]
2493 fn arity_display_matches_as_str() {
2494 crate::tagged_union::assert_display_matches_label::<Arity>();
2495 }
2496
2497 /// PREDICATE CONTRACT: `is_one` is true exactly for `Arity::One`.
2498 /// The disjointness against `Many` is structural (only two
2499 /// variants) but pinning the codomain here means a future
2500 /// `Arity::Zero` variant must declare its own `is_one` arm
2501 /// deliberately rather than silently defaulting through a
2502 /// non-closed-set match.
2503 #[test]
2504 fn arity_is_one_predicate_truth_table() {
2505 assert!(Arity::One.is_one());
2506 assert!(!Arity::Many.is_one());
2507 }
2508
2509 // ── closed-set algebra contracts for SubstrateType
2510 // (ALL × as_str × FromStr × predicate triple × bridge) ─────────
2511
2512 /// Structural well-formedness of [`SubstrateType`] as a
2513 /// [`tatara_lisp::ClosedSet`] implementor — see
2514 /// [`convergence_point_type_is_well_formed_closed_set`] for the
2515 /// canonical lift narrative. Replaces
2516 /// `substrate_type_all_is_unique_and_complete` +
2517 /// `substrate_type_roundtrip_via_as_str` + the empty-input arm
2518 /// of `unknown_substrate_type_errors`.
2519 #[test]
2520 fn substrate_type_is_well_formed_closed_set() {
2521 tatara_closed_set::assert_closed_set_well_formed::<SubstrateType>();
2522 }
2523
2524 /// CANONICAL-KEY CONTRACT: `as_str` matches serde's PascalCase
2525 /// output verbatim for every variant. A future variant rename
2526 /// (or an `as_str` arm typo) lands here at one site, instead of
2527 /// drifting between the typed surface, the CRD enum, and the
2528 /// YAML wire format the reconciler reads from
2529 /// `spec.classification.substrate`.
2530 #[test]
2531 fn substrate_type_as_str_matches_serde() {
2532 crate::tagged_union::assert_label_matches_serde_serialization::<SubstrateType>();
2533 }
2534
2535 /// The Display impl IS `as_str` — pinning this lets future
2536 /// callers reach for either projection without drift. Any
2537 /// operator-facing `substrate={kind}` diagnostic that composes
2538 /// through Display inherits the canonical wire-format string
2539 /// automatically.
2540 #[test]
2541 fn substrate_type_display_matches_as_str() {
2542 crate::tagged_union::assert_display_matches_label::<SubstrateType>();
2543 }
2544
2545 /// `FromStr` rejects strings outside the canonical projection —
2546 /// lowercased / typo / cross-axis-leaked — and the error echoes
2547 /// the input verbatim so the operator-facing diagnostic surfaces
2548 /// the bad value, not a normalized form. The empty-input arm is
2549 /// pinned by [`substrate_type_is_well_formed_closed_set`] via
2550 /// the `tatara_lisp::ClosedSet` testkit; the cases here pin the
2551 /// verbatim-echo contract on the [`UnknownSubstrateType`]
2552 /// newtype, which the trait's `make_unknown` can't see.
2553 #[test]
2554 fn unknown_substrate_type_errors() {
2555 for bad in [
2556 "compute", // lowercased
2557 "COMPUTE", // uppercased
2558 "Computte", // typo
2559 "Database", "Steady", // PoolPhase-axis leak
2560 "Pii", // DataClassification-axis leak
2561 "Attested", // ProcessPhase-axis leak
2562 "Gate", // ConvergencePointType-axis leak
2563 "Monotone", // CalmClassification-axis leak
2564 "PromQL", // ConditionKind-axis leak
2565 ] {
2566 let err = SubstrateType::from_str(bad).unwrap_err();
2567 assert_eq!(err.0, bad, "error payload should echo input verbatim");
2568 }
2569 }
2570
2571 // `unknown_substrate_type_message_matches_substrate_convention`
2572 // removed — clause (5) of
2573 // `tatara_closed_set::assert_closed_set_well_formed::<SubstrateType>()`
2574 // verifies the substrate-wide `"unknown {SET_LABEL}: {input}"`
2575 // shape generically (called from
2576 // `substrate_type_is_well_formed_closed_set` above); the
2577 // `SET_LABEL` projection is pinned by
2578 // `tatara_lisp_derive::pascal_to_spaced_lowercase_tests`.
2579
2580 /// TRUTH-TABLE CONTRACT: the predicate triple agrees with the
2581 /// documented per-variant plane role. Pinning this table at one
2582 /// site means any future compliance-baseline selector reads the
2583 /// same projection that the reconciler stamps on the CRD.
2584 #[test]
2585 fn substrate_type_predicate_truth_tables() {
2586 // Resource plane: you allocate budgets from it.
2587 for t in [
2588 SubstrateType::Financial,
2589 SubstrateType::Compute,
2590 SubstrateType::Network,
2591 SubstrateType::Storage,
2592 ] {
2593 assert!(t.is_resource(), "{t:?} should be a resource substrate");
2594 assert!(!t.is_policy(), "{t:?} should not be a policy substrate");
2595 assert!(
2596 !t.is_telemetry(),
2597 "{t:?} should not be a telemetry substrate"
2598 );
2599 }
2600
2601 // Policy plane: it gates access for other workloads.
2602 for t in [
2603 SubstrateType::Security,
2604 SubstrateType::Identity,
2605 SubstrateType::Regulatory,
2606 ] {
2607 assert!(!t.is_resource(), "{t:?} should not be a resource substrate");
2608 assert!(t.is_policy(), "{t:?} should be a policy substrate");
2609 assert!(
2610 !t.is_telemetry(),
2611 "{t:?} should not be a telemetry substrate"
2612 );
2613 }
2614
2615 // Telemetry plane: it observes other workloads.
2616 assert!(!SubstrateType::Observability.is_resource());
2617 assert!(!SubstrateType::Observability.is_policy());
2618 assert!(SubstrateType::Observability.is_telemetry());
2619 }
2620
2621 /// COVERAGE CONTRACT: every variant lands in *exactly one* of
2622 /// the three plane buckets — resource, policy, or telemetry.
2623 /// Pins the three buckets at their declared cardinalities (4,
2624 /// 3, 1 — sum to `ALL.len()`) so a future variant lands
2625 /// somewhere deliberately. No variant returns true from more
2626 /// than one predicate; no variant returns false from all three.
2627 #[test]
2628 fn substrate_type_buckets_cover_every_variant() {
2629 let mut resource = 0u32;
2630 let mut policy = 0u32;
2631 let mut telemetry = 0u32;
2632 for t in SubstrateType::ALL {
2633 let buckets = [t.is_resource(), t.is_policy(), t.is_telemetry()];
2634 let hits: u32 = buckets.iter().map(|b| u32::from(*b)).sum();
2635 assert_eq!(
2636 hits, 1,
2637 "{t:?} landed in {hits} buckets: {buckets:?} (must be exactly one)"
2638 );
2639 if t.is_resource() {
2640 resource += 1;
2641 }
2642 if t.is_policy() {
2643 policy += 1;
2644 }
2645 if t.is_telemetry() {
2646 telemetry += 1;
2647 }
2648 }
2649 assert_eq!(
2650 resource, 4,
2651 "resource bucket: Financial + Compute + Network + Storage"
2652 );
2653 assert_eq!(policy, 3, "policy bucket: Security + Identity + Regulatory");
2654 assert_eq!(telemetry, 1, "telemetry bucket: Observability");
2655 assert_eq!(
2656 resource + policy + telemetry,
2657 SubstrateType::ALL.len() as u32
2658 );
2659 }
2660
2661 /// BRIDGE ROUND-TRIP CONTRACT: every variant survives the
2662 /// CRD-facing (`PascalCase`) ↔ tatara-core (`snake_case`)
2663 /// `From` hop. Today the bridge is two hand-written 8-arm
2664 /// matches in this file; pinning the round-trip over `ALL`
2665 /// means a future variant added without extending the bridge
2666 /// fails here at one site instead of drifting between the CRD
2667 /// wire format and the `core::SubstrateType` selector axis
2668 /// that `compliance_binding::PointSelector::BySubstrate`
2669 /// already dispatches against.
2670 #[test]
2671 fn substrate_type_bridge_roundtrip_over_all() {
2672 for t in SubstrateType::ALL {
2673 let core_t: core::SubstrateType = t.into();
2674 let back: SubstrateType = core_t.into();
2675 assert_eq!(back, t, "bridge round-trip failed for {t:?}");
2676 }
2677 }
2678
2679 // ── closed-set algebra contracts for CalmClassification
2680 // (ALL × as_str × FromStr × requires_coordination × bridge) ─────
2681
2682 /// Structural well-formedness of [`CalmClassification`] as a
2683 /// [`tatara_lisp::ClosedSet`] implementor — see
2684 /// [`convergence_point_type_is_well_formed_closed_set`] for the
2685 /// canonical lift narrative. Replaces
2686 /// `calm_classification_all_is_unique_and_complete` +
2687 /// `calm_classification_roundtrip_via_as_str` + the empty-input
2688 /// arm of `unknown_calm_classification_errors`.
2689 #[test]
2690 fn calm_classification_is_well_formed_closed_set() {
2691 tatara_closed_set::assert_closed_set_well_formed::<CalmClassification>();
2692 }
2693
2694 /// CANONICAL-KEY CONTRACT: `as_str` matches serde's PascalCase
2695 /// output verbatim for every variant. A future variant rename
2696 /// (or an `as_str` arm typo) lands here at one site, instead of
2697 /// drifting between the typed surface, the CRD enum, and the
2698 /// YAML wire format the reconciler reads from
2699 /// `spec.classification.calm`.
2700 #[test]
2701 fn calm_classification_as_str_matches_serde() {
2702 crate::tagged_union::assert_label_matches_serde_serialization::<CalmClassification>();
2703 }
2704
2705 /// The Display impl IS `as_str` — pinning this lets future
2706 /// callers reach for either projection without drift. Any
2707 /// operator-facing `calm={kind}` diagnostic that composes
2708 /// through Display inherits the canonical wire-format string
2709 /// automatically.
2710 #[test]
2711 fn calm_classification_display_matches_as_str() {
2712 crate::tagged_union::assert_display_matches_label::<CalmClassification>();
2713 }
2714
2715 /// `FromStr` rejects strings outside the canonical projection —
2716 /// lowercased / typo / cross-axis-leaked — and the error echoes
2717 /// the input verbatim so the operator-facing diagnostic surfaces
2718 /// the bad value, not a normalized form. The empty-input arm is
2719 /// pinned by [`calm_classification_is_well_formed_closed_set`]
2720 /// via the `tatara_lisp::ClosedSet` testkit; the cases here pin
2721 /// the verbatim-echo contract on the
2722 /// [`UnknownCalmClassification`] newtype, which the trait's
2723 /// `make_unknown` can't see.
2724 #[test]
2725 fn unknown_calm_classification_errors() {
2726 for bad in [
2727 "monotone", // lowercased
2728 "MONOTONE", // uppercased
2729 "Mono", // typo
2730 "non_monotone", // core's snake_case form (must not cross axes)
2731 "non-monotone", // dashed
2732 "Monotonic", // close-typo
2733 "Steady", // PoolPhase-axis leak
2734 "Pii", // DataClassification-axis leak
2735 "Attested", // ProcessPhase-axis leak
2736 "Compute", // SubstrateType-axis leak
2737 "Gate", // ConvergencePointType-axis leak
2738 "PromQL", // ConditionKind-axis leak
2739 ] {
2740 let err = CalmClassification::from_str(bad).unwrap_err();
2741 assert_eq!(err.0, bad, "error payload should echo input verbatim");
2742 }
2743 }
2744
2745 // `unknown_calm_classification_message_matches_substrate_convention`
2746 // removed — clause (5) of
2747 // `tatara_closed_set::assert_closed_set_well_formed::<CalmClassification>()`
2748 // verifies the substrate-wide `"unknown {SET_LABEL}: {input}"`
2749 // shape generically (called from
2750 // `calm_classification_is_well_formed_closed_set` above); the
2751 // `SET_LABEL` projection is pinned by
2752 // `tatara_lisp_derive::pascal_to_spaced_lowercase_tests`.
2753
2754 /// CALM-THEOREM TRUTH-TABLE CONTRACT: `requires_coordination`
2755 /// implements the biconditional half of Hellerstein's CALM
2756 /// theorem — `Monotone ⇒ false` and `NonMonotone ⇒ true`.
2757 /// Pinning this table at one site means any future reconciler
2758 /// dispatch that picks between Raft writes and gossip
2759 /// propagation reads the same projection the lattice ordering
2760 /// (`Monotone ≤ NonMonotone`) does. A future variant that
2761 /// flipped this mapping would have to renumber every consumer
2762 /// deliberately rather than silently shipping a non-monotone
2763 /// operation onto the no-coordination path.
2764 #[test]
2765 fn calm_classification_requires_coordination_truth_table() {
2766 assert!(!CalmClassification::Monotone.requires_coordination());
2767 assert!(CalmClassification::NonMonotone.requires_coordination());
2768 }
2769
2770 /// COVERAGE CONTRACT: every variant lands in exactly one of two
2771 /// coordination buckets — no-coordination (`Monotone`) or
2772 /// requires-coordination (`NonMonotone`). Pins the two buckets
2773 /// at their declared cardinalities (1, 1 — sum to `ALL.len()`)
2774 /// so a future variant lands somewhere deliberately. The
2775 /// biconditional structure of the CALM theorem makes this
2776 /// partition exhaustive by construction.
2777 #[test]
2778 fn calm_classification_buckets_cover_every_variant() {
2779 let mut no_coord = 0u32;
2780 let mut coord = 0u32;
2781 for c in CalmClassification::ALL {
2782 if c.requires_coordination() {
2783 coord += 1;
2784 } else {
2785 no_coord += 1;
2786 }
2787 }
2788 assert_eq!(no_coord, 1, "no-coordination bucket: Monotone");
2789 assert_eq!(coord, 1, "requires-coordination bucket: NonMonotone");
2790 assert_eq!(no_coord + coord, CalmClassification::ALL.len() as u32);
2791 }
2792
2793 /// DEFAULT-AGREEMENT CONTRACT: `CalmClassification::default()`
2794 /// returns `Monotone` (the variant tagged `#[default]`) AND that
2795 /// variant lands in the no-coordination bucket. A future
2796 /// `#[default]` rename without flipping the predicate fails
2797 /// here — the default for an under-specified Process must
2798 /// remain the no-coordination side so that an unannotated
2799 /// Process can't silently demand Raft writes the reconciler
2800 /// isn't configured to provide.
2801 #[test]
2802 fn calm_classification_default_is_monotone_no_coordination() {
2803 let c = CalmClassification::default();
2804 assert_eq!(c, CalmClassification::Monotone);
2805 assert!(!c.requires_coordination());
2806 }
2807
2808 /// BRIDGE ROUND-TRIP CONTRACT: every variant survives the
2809 /// CRD-facing (`PascalCase`) ↔ tatara-core (`snake_case`)
2810 /// `From` hop. Today the bridge is two hand-written 2-arm
2811 /// matches in this file; pinning the round-trip over `ALL`
2812 /// means a future variant added without extending the bridge
2813 /// fails here at one site instead of drifting between the CRD
2814 /// wire format and the `core::CalmClassification` selector
2815 /// axis. Closes the asymmetry that pre-lift had a
2816 /// `From<CalmClassification> for core::CalmClassification`
2817 /// forward bridge but no reverse — symmetric to every other
2818 /// classification-axis bridge in this file.
2819 #[test]
2820 fn calm_classification_bridge_roundtrip_over_all() {
2821 for c in CalmClassification::ALL {
2822 let core_c: core::CalmClassification = c.into();
2823 let back: CalmClassification = core_c.into();
2824 assert_eq!(back, c, "bridge round-trip failed for {c:?}");
2825 }
2826 }
2827
2828 // ── closed-set algebra contracts for OptimizationDirection
2829 // (ALL × as_str × FromStr × prefers_lower × is_improvement) ───
2830
2831 /// Structural well-formedness of [`OptimizationDirection`] as a
2832 /// [`tatara_lisp::ClosedSet`] implementor — see
2833 /// [`convergence_point_type_is_well_formed_closed_set`] for the
2834 /// canonical lift narrative. Replaces
2835 /// `optimization_direction_all_is_unique_and_complete` +
2836 /// `optimization_direction_roundtrip_via_as_str` + the empty-
2837 /// input arm of `unknown_optimization_direction_errors`.
2838 #[test]
2839 fn optimization_direction_is_well_formed_closed_set() {
2840 tatara_closed_set::assert_closed_set_well_formed::<OptimizationDirection>();
2841 }
2842
2843 /// CANONICAL-KEY CONTRACT: `as_str` matches serde's PascalCase
2844 /// output verbatim for every variant. A future variant rename
2845 /// (or an `as_str` arm typo) lands here at one site, instead of
2846 /// drifting between the typed surface, the CRD enum, and the
2847 /// YAML wire format the reconciler reads from
2848 /// `spec.classification.horizon.direction`.
2849 #[test]
2850 fn optimization_direction_as_str_matches_serde() {
2851 crate::tagged_union::assert_label_matches_serde_serialization::<OptimizationDirection>();
2852 }
2853
2854 /// The Display impl IS `as_str` — pinning this lets future
2855 /// callers reach for either projection without drift. Any
2856 /// operator-facing `direction={kind}` diagnostic that composes
2857 /// through Display inherits the canonical wire-format string
2858 /// automatically.
2859 #[test]
2860 fn optimization_direction_display_matches_as_str() {
2861 crate::tagged_union::assert_display_matches_label::<OptimizationDirection>();
2862 }
2863
2864 /// `FromStr` rejects strings outside the canonical projection —
2865 /// lowercased / typo / cross-axis-leaked — and the error echoes
2866 /// the input verbatim so the operator-facing diagnostic surfaces
2867 /// the bad value, not a normalized form. The empty-input arm is
2868 /// pinned by [`optimization_direction_is_well_formed_closed_set`]
2869 /// via the `tatara_lisp::ClosedSet` testkit; the cases here pin
2870 /// the verbatim-echo contract on the
2871 /// [`UnknownOptimizationDirection`] newtype, which the trait's
2872 /// `make_unknown` can't see.
2873 #[test]
2874 fn unknown_optimization_direction_errors() {
2875 for bad in [
2876 "minimize", // lowercased
2877 "MINIMIZE", // uppercased
2878 "Minimze", // typo
2879 "Lower", // synonym, not canonical
2880 "Higher", // synonym, not canonical
2881 "Asc", // wire-leak from sort-order axis
2882 "Desc", // wire-leak from sort-order axis
2883 "Bounded", // HorizonKind-axis leak
2884 "Monotone", // CalmClassification-axis leak
2885 "Steady", // PoolPhase-axis leak
2886 "Pii", // DataClassification-axis leak
2887 "Attested", // ProcessPhase-axis leak
2888 "Compute", // SubstrateType-axis leak
2889 "Gate", // ConvergencePointType-axis leak
2890 "PromQL", // ConditionKind-axis leak
2891 ] {
2892 let err = OptimizationDirection::from_str(bad).unwrap_err();
2893 assert_eq!(err.0, bad, "error payload should echo input verbatim");
2894 }
2895 }
2896
2897 // `unknown_optimization_direction_message_matches_substrate_convention`
2898 // removed — clause (5) of
2899 // `tatara_closed_set::assert_closed_set_well_formed::<OptimizationDirection>()`
2900 // verifies the substrate-wide `"unknown {SET_LABEL}: {input}"`
2901 // shape generically (called from
2902 // `optimization_direction_is_well_formed_closed_set` above); the
2903 // `SET_LABEL` projection is pinned by
2904 // `tatara_lisp_derive::pascal_to_spaced_lowercase_tests`.
2905
2906 /// TRUTH-TABLE CONTRACT: `prefers_lower` is the boolean
2907 /// partition `Minimize ⇒ true`, `Maximize ⇒ false`. Pinning this
2908 /// table at one site means any future dispatch on per-direction
2909 /// polarity (rate-window evaluator, breathe-band regression
2910 /// detector) reads the same projection rather than re-deriving
2911 /// from the variant name. Mirrors
2912 /// [`CalmClassification::requires_coordination`]'s truth-table
2913 /// shape.
2914 #[test]
2915 fn optimization_direction_prefers_lower_truth_table() {
2916 assert!(OptimizationDirection::Minimize.prefers_lower());
2917 assert!(!OptimizationDirection::Maximize.prefers_lower());
2918 }
2919
2920 /// COVERAGE CONTRACT: every variant lands in exactly one of two
2921 /// polarity buckets — prefers-lower (`Minimize`) or
2922 /// prefers-higher (`Maximize`). Pins the two buckets at their
2923 /// declared cardinalities (1, 1 — sum to `ALL.len()`) so a
2924 /// future variant lands somewhere deliberately.
2925 #[test]
2926 fn optimization_direction_buckets_cover_every_variant() {
2927 let mut lower = 0u32;
2928 let mut higher = 0u32;
2929 for d in OptimizationDirection::ALL {
2930 if d.prefers_lower() {
2931 lower += 1;
2932 } else {
2933 higher += 1;
2934 }
2935 }
2936 assert_eq!(lower, 1, "prefers-lower bucket: Minimize");
2937 assert_eq!(higher, 1, "prefers-higher bucket: Maximize");
2938 assert_eq!(lower + higher, OptimizationDirection::ALL.len() as u32);
2939 }
2940
2941 /// LOAD-BEARING TRUTH-TABLE: `is_improvement` answers "is `after`
2942 /// strictly better than `before` under this direction?" for the
2943 /// canonical samples. Pins the strict-improvement semantic at
2944 /// one site so a future rate-window evaluator or breathe-band
2945 /// regression detector reads the same projection that the
2946 /// asymptotic-health probe writes.
2947 #[test]
2948 fn optimization_direction_is_improvement_truth_table() {
2949 // Minimize: lower-is-better
2950 assert!(OptimizationDirection::Minimize.is_improvement(10.0, 5.0));
2951 assert!(!OptimizationDirection::Minimize.is_improvement(5.0, 10.0));
2952
2953 // Maximize: higher-is-better
2954 assert!(OptimizationDirection::Maximize.is_improvement(5.0, 10.0));
2955 assert!(!OptimizationDirection::Maximize.is_improvement(10.0, 5.0));
2956 }
2957
2958 /// NO-OP CONTRACT: a sample equal to the previous one is NOT an
2959 /// improvement under either direction. Pinning this guarantees
2960 /// a flatlined rate-window evaluator doesn't silently keep
2961 /// claiming "still improving" forever and skipping the
2962 /// healthy-rate-threshold gate.
2963 #[test]
2964 fn optimization_direction_no_op_is_not_improvement() {
2965 for d in OptimizationDirection::ALL {
2966 assert!(
2967 !d.is_improvement(7.0, 7.0),
2968 "{d:?}: equal samples must not count as improvement",
2969 );
2970 assert!(
2971 !d.is_improvement(0.0, 0.0),
2972 "{d:?}: zero/zero must not count as improvement",
2973 );
2974 }
2975 }
2976
2977 /// NaN CONTRACT: NaN on either operand short-circuits to `false`
2978 /// (no improvement claim from indeterminate data) via the
2979 /// standard `PartialOrd` behavior. Without this, a rate-window
2980 /// evaluator that sampled a NaN partway through (a transient
2981 /// metric-scrape failure) would either panic on an `Ord`
2982 /// comparison or — worse — silently claim improvement on the
2983 /// next valid sample by treating NaN as the worst case.
2984 #[test]
2985 fn optimization_direction_nan_is_not_improvement() {
2986 let nan = f64::NAN;
2987 for d in OptimizationDirection::ALL {
2988 assert!(
2989 !d.is_improvement(nan, 1.0),
2990 "{d:?}: NaN before must not count as improvement",
2991 );
2992 assert!(
2993 !d.is_improvement(1.0, nan),
2994 "{d:?}: NaN after must not count as improvement",
2995 );
2996 assert!(
2997 !d.is_improvement(nan, nan),
2998 "{d:?}: NaN/NaN must not count as improvement",
2999 );
3000 }
3001 }
3002
3003 /// ANTISYMMETRY CONTRACT: for distinct finite samples,
3004 /// `is_improvement(a, b)` xor `is_improvement(b, a)` —
3005 /// exactly one direction of the pair counts as improvement.
3006 /// This is the algebraic shape every asymptotic-health
3007 /// rate-window evaluator depends on to avoid double-counting
3008 /// an improvement as a regression on the reverse traversal.
3009 /// A future variant that returned `true` for both directions
3010 /// (or `false` for both, the equal-sample case) would FAIL
3011 /// here, forcing the author to extend the consumer dispatch
3012 /// deliberately.
3013 #[test]
3014 fn optimization_direction_is_improvement_is_antisymmetric() {
3015 let pairs = [(1.0_f64, 2.0_f64), (0.0, 100.0), (-3.5, 3.5), (1e9, 1e-9)];
3016 for d in OptimizationDirection::ALL {
3017 for (a, b) in pairs {
3018 assert!(a != b, "test fixture requires distinct samples");
3019 assert!(
3020 d.is_improvement(a, b) ^ d.is_improvement(b, a),
3021 "{d:?}: antisymmetry violated on ({a}, {b})",
3022 );
3023 }
3024 }
3025 }
3026
3027 /// DEFAULT-AGREEMENT CONTRACT:
3028 /// `OptimizationDirection::default()` returns `Minimize` (the
3029 /// variant tagged `#[default]`), AND that variant lands in the
3030 /// prefers-lower bucket. A future `#[default]` rename without
3031 /// flipping the predicate fails here — `Minimize` is the
3032 /// canonical default for distributed-systems asymptotic
3033 /// optimization (cost / latency / error rate), so an
3034 /// unannotated metric must not silently flip the rate-window
3035 /// evaluator's polarity. This is also the same value the
3036 /// `Horizon → ConvergenceHorizon` bridge falls back to when
3037 /// `direction` is unset, so pinning the default here pins the
3038 /// bridge's behavior at one site.
3039 #[test]
3040 fn optimization_direction_default_is_minimize_prefers_lower() {
3041 let d = OptimizationDirection::default();
3042 assert_eq!(d, OptimizationDirection::Minimize);
3043 assert!(d.prefers_lower());
3044 }
3045
3046 /// BRIDGE ROUND-TRIP CONTRACT: every variant survives the
3047 /// CRD-facing (`PascalCase`) ↔ tatara-core (`snake_case`)
3048 /// `From` hop. Pre-lift the bridge was a one-way
3049 /// `From<OptimizationDirection> for core::OptimizationDirection`
3050 /// with no reverse — asymmetric to every other classification-
3051 /// axis bridge in this file. Pinning the round-trip over `ALL`
3052 /// means a future variant added without extending the bridge
3053 /// fails here at one site instead of drifting between the CRD
3054 /// wire format and `core::OptimizationDirection`.
3055 #[test]
3056 fn optimization_direction_bridge_roundtrip_over_all() {
3057 for d in OptimizationDirection::ALL {
3058 let core_d: core::OptimizationDirection = d.into();
3059 let back: OptimizationDirection = core_d.into();
3060 assert_eq!(back, d, "bridge round-trip failed for {d:?}");
3061 }
3062 }
3063
3064 // ── closed-set algebra contracts for HorizonKind
3065 // (ALL × as_str × FromStr × terminates × requires_metric_axes) ──
3066
3067 /// Structural well-formedness of [`HorizonKind`] as a
3068 /// [`tatara_lisp::ClosedSet`] implementor — see
3069 /// [`convergence_point_type_is_well_formed_closed_set`] for the
3070 /// canonical lift narrative. Replaces
3071 /// `horizon_kind_all_is_unique_and_complete` +
3072 /// `horizon_kind_roundtrip_via_as_str` + the empty-input arm of
3073 /// `unknown_horizon_kind_errors`.
3074 #[test]
3075 fn horizon_kind_is_well_formed_closed_set() {
3076 tatara_closed_set::assert_closed_set_well_formed::<HorizonKind>();
3077 }
3078
3079 /// CANONICAL-KEY CONTRACT: `as_str` matches serde's PascalCase
3080 /// output verbatim for every variant. A future variant rename
3081 /// (or an `as_str` arm typo) lands here at one site, instead of
3082 /// drifting between the typed surface, the CRD enum, and the
3083 /// YAML wire format the reconciler stamps on
3084 /// `spec.classification.horizon.kind`.
3085 #[test]
3086 fn horizon_kind_as_str_matches_serde() {
3087 crate::tagged_union::assert_label_matches_serde_serialization::<HorizonKind>();
3088 }
3089
3090 /// The Display impl IS `as_str` — pinning this lets future
3091 /// callers reach for either projection without drift. Any
3092 /// operator-facing `horizon.kind={kind}` diagnostic that
3093 /// composes through Display inherits the canonical wire-format
3094 /// string automatically.
3095 #[test]
3096 fn horizon_kind_display_matches_as_str() {
3097 crate::tagged_union::assert_display_matches_label::<HorizonKind>();
3098 }
3099
3100 /// `FromStr` rejects strings outside the canonical projection —
3101 /// lowercased / typo / cross-axis-leaked — and the error echoes
3102 /// the input verbatim so the operator-facing diagnostic surfaces
3103 /// the bad value, not a normalized form. The empty-input arm is
3104 /// pinned by [`horizon_kind_is_well_formed_closed_set`] via the
3105 /// `tatara_lisp::ClosedSet` testkit; the cases here pin the
3106 /// verbatim-echo contract on the [`UnknownHorizonKind`] newtype,
3107 /// which the trait's `make_unknown` can't see.
3108 #[test]
3109 fn unknown_horizon_kind_errors() {
3110 for bad in [
3111 "bounded", // lowercased
3112 "BOUNDED", // uppercased
3113 "Boundd", // typo
3114 "Finite", // synonym, not canonical
3115 "Perpetual", // synonym, not canonical
3116 "Infinite", // synonym, not canonical
3117 "Minimize", // OptimizationDirection-axis leak
3118 "Monotone", // CalmClassification-axis leak
3119 "Pii", // DataClassification-axis leak
3120 "Steady", // PoolPhase-axis leak
3121 "Attested", // ProcessPhase-axis leak
3122 "Compute", // SubstrateType-axis leak
3123 "Gate", // ConvergencePointType-axis leak
3124 "PromQL", // ConditionKind-axis leak
3125 ] {
3126 let err = HorizonKind::from_str(bad).unwrap_err();
3127 assert_eq!(err.0, bad, "error payload should echo input verbatim");
3128 }
3129 }
3130
3131 // `unknown_horizon_kind_message_matches_substrate_convention`
3132 // removed — clause (5) of
3133 // `tatara_closed_set::assert_closed_set_well_formed::<HorizonKind>()`
3134 // verifies the substrate-wide `"unknown {SET_LABEL}: {input}"`
3135 // shape generically (called from
3136 // `horizon_kind_is_well_formed_closed_set` above); the
3137 // `SET_LABEL` projection is pinned by
3138 // `tatara_lisp_derive::pascal_to_spaced_lowercase_tests`.
3139
3140 /// LOAD-BEARING TRUTH-TABLE: `terminates` is the boolean
3141 /// partition `Bounded ⇒ true`, `Asymptotic ⇒ false`. Pinning
3142 /// this table at one site means any future scheduler asking
3143 /// "will this Process reach `Reaped` via natural termination?"
3144 /// reads the same projection that the lattice ordering encodes
3145 /// (Bounded ≤ Asymptotic BECAUSE the bounded horizon strictly
3146 /// refines the asymptotic one by also terminating).
3147 #[test]
3148 fn horizon_kind_terminates_truth_table() {
3149 assert!(HorizonKind::Bounded.terminates());
3150 assert!(!HorizonKind::Asymptotic.terminates());
3151 }
3152
3153 /// LOAD-BEARING TRUTH-TABLE: `requires_metric_axes` is the
3154 /// boolean partition `Bounded ⇒ false`, `Asymptotic ⇒ true` —
3155 /// the typed image of the optionality the [`Horizon`] struct
3156 /// encodes via its three `Option<…>` fields (`metric`,
3157 /// `direction`, `healthy_rate_threshold`). The implicit
3158 /// "Asymptotic only" invariant in the field docs is now a
3159 /// checkable per-kind predicate. Pinning this table at one site
3160 /// means any future horizon-shape validator (CRD admission,
3161 /// `tatara-check` form linter, Lisp authoring-time predicate)
3162 /// reads the same projection.
3163 #[test]
3164 fn horizon_kind_requires_metric_axes_truth_table() {
3165 assert!(!HorizonKind::Bounded.requires_metric_axes());
3166 assert!(HorizonKind::Asymptotic.requires_metric_axes());
3167 }
3168
3169 /// COVERAGE CONTRACT: every variant lands in exactly one of two
3170 /// termination buckets — terminating (`Bounded`) or perpetual
3171 /// (`Asymptotic`). Pins the two buckets at their declared
3172 /// cardinalities (1, 1 — sum to `ALL.len()`) so a future variant
3173 /// lands somewhere deliberately.
3174 #[test]
3175 fn horizon_kind_buckets_cover_every_variant() {
3176 let mut terminating = 0u32;
3177 let mut perpetual = 0u32;
3178 for k in HorizonKind::ALL {
3179 if k.terminates() {
3180 terminating += 1;
3181 } else {
3182 perpetual += 1;
3183 }
3184 }
3185 assert_eq!(terminating, 1, "terminating bucket: Bounded");
3186 assert_eq!(perpetual, 1, "perpetual bucket: Asymptotic");
3187 assert_eq!(terminating + perpetual, HorizonKind::ALL.len() as u32);
3188 }
3189
3190 /// ANTISYMMETRY CONTRACT: for every variant, exactly one of
3191 /// `(terminates, requires_metric_axes)` is true — the two
3192 /// predicates carve the variants into complementary buckets
3193 /// (terminating ↔ no metric axes; perpetual ↔ requires metric
3194 /// axes). A future variant that returned `true` for both (a
3195 /// terminating horizon that nonetheless tracks an asymptotic
3196 /// metric) or `false` for both (an inert horizon with no
3197 /// termination AND no metric signal — there'd be nothing to
3198 /// observe) would fail here, forcing the author to extend
3199 /// either the predicates or the [`Horizon`] struct's
3200 /// optionality contract deliberately.
3201 #[test]
3202 fn horizon_kind_terminate_xor_requires_metric_axes() {
3203 for k in HorizonKind::ALL {
3204 assert!(
3205 k.terminates() ^ k.requires_metric_axes(),
3206 "{k:?}: terminates() XOR requires_metric_axes() must hold",
3207 );
3208 }
3209 }
3210
3211 /// DEFAULT-AGREEMENT CONTRACT: `HorizonKind::default()` returns
3212 /// `Bounded` (the variant tagged `#[default]`), AND that
3213 /// variant lands in the terminating bucket. A future
3214 /// `#[default]` rename without flipping the predicate fails
3215 /// here — `Bounded` is the canonical default for a convergence
3216 /// horizon (a point with no asymptotic axes declared should
3217 /// terminate naturally, not silently flip into a perpetual
3218 /// rate-window evaluator with zero threshold). This is also
3219 /// the same value `Horizon::default()` carries, so pinning the
3220 /// default here pins the struct-default behavior at one site.
3221 #[test]
3222 fn horizon_kind_default_is_bounded_terminates() {
3223 let k = HorizonKind::default();
3224 assert_eq!(k, HorizonKind::Bounded);
3225 assert!(k.terminates());
3226 assert!(!k.requires_metric_axes());
3227 }
3228
3229 /// HORIZON ↔ KIND AGREEMENT: every variant in `HorizonKind::ALL`
3230 /// composes with the existing [`Horizon::bounded`] /
3231 /// [`Horizon::asymptotic`] constructors to produce a `Horizon`
3232 /// whose `kind` matches AND whose `Option<…>` fields agree
3233 /// with `requires_metric_axes`. Pins the implicit contract
3234 /// between the kind discriminator and the optionality at one
3235 /// site — a future kind added without extending either the
3236 /// constructors or `requires_metric_axes` fails here before
3237 /// drifting between the typed surface and the documented
3238 /// "Asymptotic only" field invariant.
3239 #[test]
3240 fn horizon_kind_agrees_with_struct_optionality() {
3241 let bounded = Horizon::bounded();
3242 assert_eq!(bounded.kind, HorizonKind::Bounded);
3243 assert!(!bounded.kind.requires_metric_axes());
3244 assert!(bounded.metric.is_none());
3245 assert!(bounded.direction.is_none());
3246 assert!(bounded.healthy_rate_threshold.is_none());
3247
3248 let asymp = Horizon::asymptotic("p99_latency", OptimizationDirection::Minimize, 0.1);
3249 assert_eq!(asymp.kind, HorizonKind::Asymptotic);
3250 assert!(asymp.kind.requires_metric_axes());
3251 assert!(asymp.metric.is_some());
3252 assert!(asymp.direction.is_some());
3253 assert!(asymp.healthy_rate_threshold.is_some());
3254 }
3255
3256 // ── scalar-carrier presence probe on Classification × ConvergencePointType ──
3257 //
3258 // Fail-before-pass-after granularity: [`Classification::has_point_type`]
3259 // did not exist before this commit — every consumer of the
3260 // `(Classification, ConvergencePointType) -> bool` scalar-carrier
3261 // probe shape restated the `classification.point_type == kind`
3262 // equality body at its own callsite. Post-lift the shape lives at
3263 // ONE substrate owner and every downstream (the `point-type-<kind>`
3264 // require-tag family in `tatara-check`, future audit dispatchers
3265 // walking [`ConvergencePointType::ALL`], any future CRD-facing
3266 // closed-set discriminator on a required scalar `ProcessSpec` field
3267 // such as `has_substrate`/`has_calm`/`has_data_classification`)
3268 // binds through the SAME `has(kind)` shape the Option-slot
3269 // (`Intent::has`, `Lifetime::has`), slice-level
3270 // (`ConditionSliceExt::has_kind`, `DependsOnSliceExt::has_must_reach`,
3271 // `ComplianceBindingSliceExt::has_verification_phase`,
3272 // `ExportSpecSliceExt::has_{when,channel_kind,report_format,artifact_kind}`),
3273 // and prior scalar-carrier
3274 // (`SignalPolicy::has_sighup_strategy`,
3275 // `EncapsulatesSpec::has_mode`) peers publish.
3276
3277 /// DIAGONAL — for every [`ConvergencePointType`] variant, a
3278 /// [`Classification`] whose `point_type` field is set to that
3279 /// variant returns `true` from `has_point_type` on that same
3280 /// variant AND `false` on every other variant. Sweep the
3281 /// [`ConvergencePointType::ALL`] × ALL cross so a regression that
3282 /// hard-coded the arm to a single variant (silently returning
3283 /// `true` on every populated classification regardless of query
3284 /// kind) or wired the equality to a fixed unrelated field fails
3285 /// HERE at the substrate primitive before landing at the
3286 /// operator-facing checks.lisp surface.
3287 #[test]
3288 fn classification_has_point_type_returns_true_iff_variant_matches() {
3289 for populated in ConvergencePointType::ALL {
3290 let c = Classification {
3291 point_type: populated,
3292 substrate: SubstrateType::Compute,
3293 horizon: Horizon::default(),
3294 calm: CalmClassification::default(),
3295 data_classification: DataClassification::default(),
3296 };
3297 for query in ConvergencePointType::ALL {
3298 assert_eq!(
3299 c.has_point_type(query),
3300 query == populated,
3301 "point_type={populated:?}: query {query:?} classification drifted",
3302 );
3303 }
3304 }
3305 }
3306
3307 /// GATE-COMPUTE BASELINE — the workspace-baseline
3308 /// [`Classification::gate_compute`] shape carries
3309 /// `point_type: Gate`, so `has_point_type` returns `true` on
3310 /// [`ConvergencePointType::Gate`] and `false` on every other of
3311 /// the eight variants. Pins the composition of the substrate's
3312 /// baseline-constructor primitive with the scalar-carrier
3313 /// presence probe — a regression that flipped
3314 /// `gate_compute().point_type` off `Gate` (or wired
3315 /// `has_point_type` to a fixed variant answer) fails here at ONE
3316 /// narrow site before drifting across every unadorned ephemeral
3317 /// env (`default_ephemeral_class`) and every downstream test
3318 /// fixture that keys assertions on the shape.
3319 #[test]
3320 fn classification_gate_compute_has_point_type_gate_only() {
3321 let c = Classification::gate_compute();
3322 for kind in ConvergencePointType::ALL {
3323 let expected = kind == ConvergencePointType::Gate;
3324 assert_eq!(
3325 c.has_point_type(kind),
3326 expected,
3327 "gate_compute (point_type=Gate) must return {expected} for {kind:?}",
3328 );
3329 }
3330 }
3331
3332 // ── scalar-carrier presence probe on Classification × SubstrateType ──
3333 //
3334 // Fail-before-pass-after granularity: [`Classification::has_substrate`]
3335 // did not exist before this commit — every consumer of the
3336 // `(Classification, SubstrateType) -> bool` scalar-carrier probe
3337 // shape restated the `classification.substrate == kind` equality
3338 // body at its own callsite. Post-lift the shape lives at ONE
3339 // substrate owner and every downstream (the `substrate-<kind>`
3340 // require-tag family in `tatara-check`, future audit dispatchers
3341 // walking [`SubstrateType::ALL`], any future CRD-facing closed-set
3342 // discriminator on a required scalar `ProcessSpec` field such as
3343 // `has_calm`/`has_data_classification`) binds through the SAME
3344 // `has(kind)` shape the Option-slot (`Intent::has`, `Lifetime::has`),
3345 // slice-level (`ConditionSliceExt::has_kind`,
3346 // `DependsOnSliceExt::has_must_reach`,
3347 // `ComplianceBindingSliceExt::has_verification_phase`,
3348 // `ExportSpecSliceExt::has_{when,channel_kind,report_format,artifact_kind}`),
3349 // and prior scalar-carrier
3350 // (`SignalPolicy::has_sighup_strategy`,
3351 // `EncapsulatesSpec::has_mode`, `Classification::has_point_type`)
3352 // peers publish.
3353
3354 /// DIAGONAL — for every [`SubstrateType`] variant, a
3355 /// [`Classification`] whose `substrate` field is set to that
3356 /// variant returns `true` from `has_substrate` on that same
3357 /// variant AND `false` on every other variant. Sweep the
3358 /// [`SubstrateType::ALL`] × ALL cross so a regression that
3359 /// hard-coded the arm to a single variant (silently returning
3360 /// `true` on every populated classification regardless of query
3361 /// kind) or wired the equality to a fixed unrelated field (a
3362 /// stray probe on `classification.point_type`) fails HERE at the
3363 /// substrate primitive before landing at the operator-facing
3364 /// checks.lisp surface.
3365 #[test]
3366 fn classification_has_substrate_returns_true_iff_variant_matches() {
3367 for populated in SubstrateType::ALL {
3368 let c = Classification {
3369 point_type: ConvergencePointType::Gate,
3370 substrate: populated,
3371 horizon: Horizon::default(),
3372 calm: CalmClassification::default(),
3373 data_classification: DataClassification::default(),
3374 };
3375 for query in SubstrateType::ALL {
3376 assert_eq!(
3377 c.has_substrate(query),
3378 query == populated,
3379 "substrate={populated:?}: query {query:?} classification drifted",
3380 );
3381 }
3382 }
3383 }
3384
3385 /// GATE-COMPUTE BASELINE — the workspace-baseline
3386 /// [`Classification::gate_compute`] shape carries
3387 /// `substrate: Compute`, so `has_substrate` returns `true` on
3388 /// [`SubstrateType::Compute`] and `false` on every other of the
3389 /// eight variants. Pins the composition of the substrate's
3390 /// baseline-constructor primitive with the fourth scalar-carrier
3391 /// presence probe — a regression that flipped
3392 /// `gate_compute().substrate` off `Compute` (or wired
3393 /// `has_substrate` to a fixed variant answer, or crossed the
3394 /// wires to `point_type`) fails here at ONE narrow site before
3395 /// drifting across every unadorned ephemeral env
3396 /// (`default_ephemeral_class`) and every downstream test fixture
3397 /// that keys assertions on the shape. Byte-symmetric with the
3398 /// peer `classification_gate_compute_has_point_type_gate_only`
3399 /// pin on the third scalar-carrier — the two co-tenants on the
3400 /// (required-parent × required-scalar-child) corner walk their
3401 /// own required axis independently.
3402 #[test]
3403 fn classification_gate_compute_has_substrate_compute_only() {
3404 let c = Classification::gate_compute();
3405 for kind in SubstrateType::ALL {
3406 let expected = kind == SubstrateType::Compute;
3407 assert_eq!(
3408 c.has_substrate(kind),
3409 expected,
3410 "gate_compute (substrate=Compute) must return {expected} for {kind:?}",
3411 );
3412 }
3413 }
3414
3415 /// TWO-AXIS INDEPENDENCE — the two co-tenants on the (required-
3416 /// parent × required-scalar-child) corner of the presence-probe
3417 /// algebra ([`Classification::has_point_type`] and
3418 /// [`Classification::has_substrate`]) probe distinct required
3419 /// scalar slots on the SAME [`Classification`] parent, so a
3420 /// carrier with `point_type: Fork` AND `substrate: Storage`
3421 /// answers `true` on both fine tags simultaneously and `false`
3422 /// on every off-diagonal probe of either axis. Pins the two
3423 /// probes' independence at ONE narrow site — a regression that
3424 /// collapsed either onto the other's field (a stray probe of
3425 /// `has_substrate` reading `self.point_type`, or of
3426 /// `has_point_type` reading `self.substrate`) would fail HERE
3427 /// before landing at any consumer. The audit `every Fork-topology
3428 /// Storage-plane point handles SIGHUP by Restart` composes this
3429 /// exact two-axis conjunction on the required scalars of the
3430 /// six-axis classification lattice.
3431 #[test]
3432 fn classification_has_point_type_and_has_substrate_are_independent() {
3433 let c = Classification {
3434 point_type: ConvergencePointType::Fork,
3435 substrate: SubstrateType::Storage,
3436 horizon: Horizon::default(),
3437 calm: CalmClassification::default(),
3438 data_classification: DataClassification::default(),
3439 };
3440 assert!(c.has_point_type(ConvergencePointType::Fork));
3441 assert!(c.has_substrate(SubstrateType::Storage));
3442 assert!(!c.has_point_type(ConvergencePointType::Gate));
3443 assert!(!c.has_substrate(SubstrateType::Compute));
3444 // Cross-wiring probe: `has_point_type(Storage-as-if-Point)` and
3445 // `has_substrate(Fork-as-if-Substrate)` cannot even typecheck
3446 // — the closed-set enums are disjoint types — but a stray
3447 // implementation reading the WRONG required field would flip
3448 // both diagonal answers off. The four asserts above pin the
3449 // independence at ONE narrow site.
3450 }
3451
3452 // ── scalar-carrier presence probe on Classification × CalmClassification ──
3453 //
3454 // Fail-before-pass-after granularity: [`Classification::has_calm`]
3455 // did not exist before this commit — every consumer of the
3456 // `(Classification, CalmClassification) -> bool` scalar-carrier
3457 // probe shape restated the `classification.calm == kind` equality
3458 // body at its own callsite. Post-lift the shape lives at ONE
3459 // substrate owner and every downstream (the `calm-<kind>`
3460 // require-tag family in `tatara-check`, future audit dispatchers
3461 // walking [`CalmClassification::ALL`], any future CRD-facing
3462 // closed-set discriminator on a defaulted scalar `ProcessSpec`
3463 // field such as `has_data_classification`) binds through the SAME
3464 // `has(kind)` shape the Option-slot (`Intent::has`, `Lifetime::has`),
3465 // slice-level (`ConditionSliceExt::has_kind`,
3466 // `DependsOnSliceExt::has_must_reach`,
3467 // `ComplianceBindingSliceExt::has_verification_phase`,
3468 // `ExportSpecSliceExt::has_{when,channel_kind,report_format,artifact_kind}`),
3469 // and prior scalar-carrier
3470 // (`SignalPolicy::has_sighup_strategy`,
3471 // `EncapsulatesSpec::has_mode`, `Classification::has_point_type`,
3472 // `Classification::has_substrate`) peers publish. FIRST occupant
3473 // on the (required-parent × defaulted-scalar-child) corner of the
3474 // presence-probe algebra — a fresh corner distinct from all four
3475 // prior scalar-carrier peers.
3476
3477 /// DIAGONAL — for every [`CalmClassification`] variant, a
3478 /// [`Classification`] whose `calm` field is set to that variant
3479 /// returns `true` from `has_calm` on that same variant AND
3480 /// `false` on every other variant. Sweep the
3481 /// [`CalmClassification::ALL`] × ALL cross so a regression that
3482 /// hard-coded the arm to a single variant (silently returning
3483 /// `true` on every populated classification regardless of query
3484 /// kind) or wired the equality to a fixed unrelated field (a
3485 /// stray probe on `classification.point_type` or
3486 /// `classification.substrate`) fails HERE at the substrate
3487 /// primitive before landing at the operator-facing checks.lisp
3488 /// surface.
3489 #[test]
3490 fn classification_has_calm_returns_true_iff_variant_matches() {
3491 for populated in CalmClassification::ALL {
3492 let c = Classification {
3493 point_type: ConvergencePointType::Gate,
3494 substrate: SubstrateType::Compute,
3495 horizon: Horizon::default(),
3496 calm: populated,
3497 data_classification: DataClassification::default(),
3498 };
3499 for query in CalmClassification::ALL {
3500 assert_eq!(
3501 c.has_calm(query),
3502 query == populated,
3503 "calm={populated:?}: query {query:?} classification drifted",
3504 );
3505 }
3506 }
3507 }
3508
3509 /// GATE-COMPUTE BASELINE — the workspace-baseline
3510 /// [`Classification::gate_compute`] shape carries
3511 /// `calm: CalmClassification::default()` which is
3512 /// [`CalmClassification::Monotone`] via `#[default]`, so
3513 /// `has_calm` returns `true` on [`CalmClassification::Monotone`]
3514 /// and `false` on [`CalmClassification::NonMonotone`]. Pins the
3515 /// composition of the substrate's baseline-constructor primitive
3516 /// with the FIFTH scalar-carrier presence probe AND the sibling-
3517 /// default correspondence documented on [`Classification::gate_compute`]
3518 /// (which pins the three defaulted axes to the sibling closed-set
3519 /// defaults `HorizonKind::Bounded` / `CalmClassification::Monotone`
3520 /// / `DataClassification::Internal`) — a regression that flipped
3521 /// `gate_compute().calm` off `Monotone` (or promoted a different
3522 /// variant to `#[default]` on the closed set, or wired `has_calm`
3523 /// to a fixed variant answer, or crossed the wires to
3524 /// `point_type` / `substrate`) fails here at ONE narrow site
3525 /// before drifting across every unadorned ephemeral env
3526 /// (`default_ephemeral_class`) and every downstream test fixture
3527 /// that keys assertions on the shape. FIRST occupant on the
3528 /// (required-parent × defaulted-scalar-child) corner — locks the
3529 /// corner's characteristic "default-arm short-circuit" property
3530 /// at ONE narrow classifier site: a bare classification answers
3531 /// `true` on the default variant (distinct from the
3532 /// required-child corner peers, where a bare classification must
3533 /// name a variant deliberately to answer `true`).
3534 #[test]
3535 fn classification_gate_compute_has_calm_monotone_only() {
3536 let c = Classification::gate_compute();
3537 for kind in CalmClassification::ALL {
3538 let expected = kind == CalmClassification::Monotone;
3539 assert_eq!(
3540 c.has_calm(kind),
3541 expected,
3542 "gate_compute (calm=Monotone) must return {expected} for {kind:?}",
3543 );
3544 }
3545 }
3546
3547 /// THREE-AXIS INDEPENDENCE — the three co-tenants on the
3548 /// [`Classification`] parent
3549 /// ([`Classification::has_point_type`] +
3550 /// [`Classification::has_substrate`] on the (required-parent ×
3551 /// required-scalar-child) corner AND [`Classification::has_calm`]
3552 /// on the fresh (required-parent × defaulted-scalar-child)
3553 /// corner) probe distinct scalar slots on the SAME parent, so a
3554 /// carrier with `point_type: Fork` AND `substrate: Storage` AND
3555 /// `calm: NonMonotone` answers `true` on all three fine tags
3556 /// simultaneously and `false` on every off-diagonal probe of any
3557 /// axis. Pins the three probes' independence at ONE narrow site
3558 /// — a regression that collapsed any of the three onto another's
3559 /// field (a stray probe of `has_calm` reading `self.point_type`
3560 /// or `self.substrate`, or of either required-axis probe reading
3561 /// `self.calm`) would fail HERE before landing at any consumer.
3562 /// The audit `every Fork-topology Storage-plane NonMonotone-CALM
3563 /// point declares a Raft-guarded write path` composes this exact
3564 /// three-axis conjunction on the required + defaulted scalars of
3565 /// the six-axis classification lattice.
3566 #[test]
3567 fn classification_has_point_type_and_has_substrate_and_has_calm_are_independent() {
3568 let c = Classification {
3569 point_type: ConvergencePointType::Fork,
3570 substrate: SubstrateType::Storage,
3571 horizon: Horizon::default(),
3572 calm: CalmClassification::NonMonotone,
3573 data_classification: DataClassification::default(),
3574 };
3575 assert!(c.has_point_type(ConvergencePointType::Fork));
3576 assert!(c.has_substrate(SubstrateType::Storage));
3577 assert!(c.has_calm(CalmClassification::NonMonotone));
3578 assert!(!c.has_point_type(ConvergencePointType::Gate));
3579 assert!(!c.has_substrate(SubstrateType::Compute));
3580 assert!(!c.has_calm(CalmClassification::Monotone));
3581 }
3582
3583 // ── scalar-carrier presence probe on Classification × DataClassification ──
3584 //
3585 // Fail-before-pass-after granularity:
3586 // [`Classification::has_data_classification`] did not exist before
3587 // this commit — every consumer of the
3588 // `(Classification, DataClassification) -> bool` scalar-carrier
3589 // probe shape would have to restate the
3590 // `classification.data_classification == kind` equality body at
3591 // its own callsite. Post-lift the shape lives at ONE substrate
3592 // owner and every downstream (the `data-classification-<kind>`
3593 // require-tag family in `tatara-check`, future audit dispatchers
3594 // walking [`DataClassification::ALL`], any future CRD-facing
3595 // closed-set discriminator on a defaulted scalar `ProcessSpec`
3596 // field) binds through the SAME `has(kind)` shape the four prior
3597 // scalar-carrier peers on [`Classification`]
3598 // ([`Classification::has_point_type`],
3599 // [`Classification::has_substrate`],
3600 // [`Classification::has_calm`]) plus
3601 // [`crate::spec::SignalPolicy::has_sighup_strategy`] and
3602 // [`crate::encapsulates::EncapsulatesSpec::has_mode`] publish.
3603 // SECOND occupant on the (required-parent × defaulted-scalar-
3604 // child) corner of the presence-probe algebra after
3605 // [`Classification::has_calm`] opened it — pins the corner as a
3606 // proven-repeatable primitive shape rather than a single-example
3607 // curiosity and closes the four-scalar-carrier corner-coverage
3608 // contract on the six-axis classification lattice.
3609
3610 /// DIAGONAL — for every [`DataClassification`] variant, a
3611 /// [`Classification`] whose `data_classification` field is set to
3612 /// that variant returns `true` from `has_data_classification` on
3613 /// that same variant AND `false` on every other variant. Sweep
3614 /// the [`DataClassification::ALL`] × ALL cross so a regression
3615 /// that hard-coded the arm to a single variant (silently returning
3616 /// `true` on every populated classification regardless of query
3617 /// kind) or wired the equality to a fixed unrelated field (a
3618 /// stray probe on `classification.point_type` /
3619 /// `classification.substrate` / `classification.calm`) fails HERE
3620 /// at the substrate primitive before landing at the operator-
3621 /// facing checks.lisp surface.
3622 #[test]
3623 fn classification_has_data_classification_returns_true_iff_variant_matches() {
3624 for populated in DataClassification::ALL {
3625 let c = Classification {
3626 point_type: ConvergencePointType::Gate,
3627 substrate: SubstrateType::Compute,
3628 horizon: Horizon::default(),
3629 calm: CalmClassification::default(),
3630 data_classification: populated,
3631 };
3632 for query in DataClassification::ALL {
3633 assert_eq!(
3634 c.has_data_classification(query),
3635 query == populated,
3636 "data_classification={populated:?}: query {query:?} classification drifted",
3637 );
3638 }
3639 }
3640 }
3641
3642 /// GATE-COMPUTE BASELINE — the workspace-baseline
3643 /// [`Classification::gate_compute`] shape carries
3644 /// `data_classification: DataClassification::default()` which is
3645 /// [`DataClassification::Internal`] via `#[default]`, so
3646 /// `has_data_classification` returns `true` on
3647 /// [`DataClassification::Internal`] and `false` on every other
3648 /// variant ([`DataClassification::Public`],
3649 /// [`DataClassification::Confidential`],
3650 /// [`DataClassification::Pii`], [`DataClassification::Phi`],
3651 /// [`DataClassification::Pci`]). Pins the composition of the
3652 /// substrate's baseline-constructor primitive with the SIXTH
3653 /// scalar-carrier presence probe AND the sibling-default
3654 /// correspondence documented on [`Classification::gate_compute`]
3655 /// (which pins the three defaulted axes to the sibling closed-set
3656 /// defaults `HorizonKind::Bounded` / `CalmClassification::Monotone`
3657 /// / `DataClassification::Internal`) — a regression that flipped
3658 /// `gate_compute().data_classification` off `Internal` (or
3659 /// promoted a different variant to `#[default]` on the closed
3660 /// set, or wired `has_data_classification` to a fixed variant
3661 /// answer, or crossed the wires to `point_type` / `substrate` /
3662 /// `calm`) fails here at ONE narrow site before drifting across
3663 /// every unadorned ephemeral env (`default_ephemeral_class`) and
3664 /// every downstream test fixture that keys assertions on the
3665 /// shape. SECOND occupant on the (required-parent × defaulted-
3666 /// scalar-child) corner — pins the corner's characteristic
3667 /// "default-arm short-circuit" property on its second occupant
3668 /// (peer to `classification_gate_compute_has_calm_monotone_only`
3669 /// which pins the same shape on the corner's first occupant).
3670 #[test]
3671 fn classification_gate_compute_has_data_classification_internal_only() {
3672 let c = Classification::gate_compute();
3673 for kind in DataClassification::ALL {
3674 let expected = kind == DataClassification::Internal;
3675 assert_eq!(
3676 c.has_data_classification(kind),
3677 expected,
3678 "gate_compute (data_classification=Internal) must return {expected} for {kind:?}",
3679 );
3680 }
3681 }
3682
3683 /// FOUR-AXIS INDEPENDENCE — the four scalar-carrier co-tenants
3684 /// on the [`Classification`] parent
3685 /// ([`Classification::has_point_type`] plus
3686 /// [`Classification::has_substrate`] on the (required-parent ×
3687 /// required-scalar-child) corner AND [`Classification::has_calm`]
3688 /// plus [`Classification::has_data_classification`] on the
3689 /// (required-parent × defaulted-scalar-child) corner) probe
3690 /// distinct scalar slots on the SAME parent, so a carrier with
3691 /// `point_type: Fork` AND `substrate: Storage` AND
3692 /// `calm: NonMonotone` AND `data_classification: Pii` answers
3693 /// `true` on all four fine tags simultaneously and `false` on
3694 /// every off-diagonal probe of any axis. Pins the four probes'
3695 /// independence at ONE narrow site — a regression that collapsed
3696 /// any of the four onto another's field (a stray probe of
3697 /// `has_data_classification` reading `self.point_type` /
3698 /// `self.substrate` / `self.calm`, or of any prior probe reading
3699 /// `self.data_classification`) would fail HERE before landing at
3700 /// any consumer. The audit `every Fork-topology Storage-plane
3701 /// NonMonotone-CALM Pii-classification point declares a
3702 /// Raft-guarded write path AND a downstream PII-scrub sink`
3703 /// composes this exact four-axis conjunction on the required +
3704 /// defaulted scalars of the six-axis classification lattice.
3705 /// Closes the four-scalar-carrier corner-coverage contract on
3706 /// [`Classification`] — its two required-scalar-child slots
3707 /// (`point_type`, `substrate`) AND its two defaulted-scalar-
3708 /// child slots (`calm`, `data_classification`) all publish
3709 /// independent presence probes through the same shape.
3710 #[test]
3711 fn classification_four_scalar_carrier_probes_are_independent() {
3712 let c = Classification {
3713 point_type: ConvergencePointType::Fork,
3714 substrate: SubstrateType::Storage,
3715 horizon: Horizon::default(),
3716 calm: CalmClassification::NonMonotone,
3717 data_classification: DataClassification::Pii,
3718 };
3719 assert!(c.has_point_type(ConvergencePointType::Fork));
3720 assert!(c.has_substrate(SubstrateType::Storage));
3721 assert!(c.has_calm(CalmClassification::NonMonotone));
3722 assert!(c.has_data_classification(DataClassification::Pii));
3723 assert!(!c.has_point_type(ConvergencePointType::Gate));
3724 assert!(!c.has_substrate(SubstrateType::Compute));
3725 assert!(!c.has_calm(CalmClassification::Monotone));
3726 assert!(!c.has_data_classification(DataClassification::Internal));
3727 assert!(!c.has_data_classification(DataClassification::Public));
3728 assert!(!c.has_data_classification(DataClassification::Phi));
3729 }
3730
3731 // ── nested-struct-scalar-carrier presence probe on Classification × HorizonKind ──
3732 //
3733 // Fail-before-pass-after granularity:
3734 // [`Classification::has_horizon_kind`] did not exist before this
3735 // commit — every consumer of the `(Classification, HorizonKind) ->
3736 // bool` two-hop `self.horizon.kind == kind` probe shape would have
3737 // to restate the nested-struct field walk at its own callsite.
3738 // Post-lift the shape lives at ONE substrate owner and every
3739 // downstream (the `horizon-<kind>` require-tag family in
3740 // `tatara-check`, future audit dispatchers walking
3741 // [`HorizonKind::ALL`], any future CRD-facing nested-struct-scalar
3742 // discriminator on `ProcessSpec`) binds through the SAME
3743 // `has(kind)` shape the four prior scalar-carrier peers on
3744 // [`Classification`] ([`Classification::has_point_type`],
3745 // [`Classification::has_substrate`], [`Classification::has_calm`],
3746 // [`Classification::has_data_classification`]) plus
3747 // [`crate::spec::SignalPolicy::has_sighup_strategy`] and
3748 // [`crate::encapsulates::EncapsulatesSpec::has_mode`] publish.
3749 // FIRST occupant on the (required-parent × nested-struct-scalar-
3750 // child) corner of the presence-probe algebra — a fresh corner
3751 // distinct from the four corner-property-exhaustive scalar-carrier
3752 // peers on [`Classification`] (whose bodies read a closed-set
3753 // discriminator directly off a scalar slot without an intermediate
3754 // struct hop).
3755
3756 /// DIAGONAL — for every [`HorizonKind`] variant, a
3757 /// [`Classification`] whose `horizon.kind` field is set to that
3758 /// variant returns `true` from `has_horizon_kind` on that same
3759 /// variant AND `false` on every other variant. Sweep the
3760 /// [`HorizonKind::ALL`] × ALL cross so a regression that
3761 /// hard-coded the arm to a single variant (silently returning
3762 /// `true` on every populated classification regardless of query
3763 /// kind) or wired the equality to a fixed unrelated field (a
3764 /// stray probe on `classification.point_type` /
3765 /// `classification.substrate` / `classification.calm` /
3766 /// `classification.data_classification`, or a direct probe on the
3767 /// nested [`Horizon`] struct that ignored the discriminator arm)
3768 /// fails HERE at the substrate primitive before landing at the
3769 /// operator-facing checks.lisp surface. The nested-struct hop
3770 /// distinguishes this corner from the four scalar-carrier peers:
3771 /// the probe walks `self.horizon.kind` not `self.<field>`, so a
3772 /// regression that mis-routed the field walk (a stray
3773 /// `self.horizon == kind` that could not typecheck, or a stray
3774 /// `self.horizon.direction == kind` that would trip a different
3775 /// closed-set discriminator) fails at the compiler before the
3776 /// runtime diagonal even runs.
3777 #[test]
3778 fn classification_has_horizon_kind_returns_true_iff_variant_matches() {
3779 for populated in HorizonKind::ALL {
3780 let c = Classification {
3781 point_type: ConvergencePointType::Gate,
3782 substrate: SubstrateType::Compute,
3783 horizon: Horizon {
3784 kind: populated,
3785 ..Horizon::default()
3786 },
3787 calm: CalmClassification::default(),
3788 data_classification: DataClassification::default(),
3789 };
3790 for query in HorizonKind::ALL {
3791 assert_eq!(
3792 c.has_horizon_kind(query),
3793 query == populated,
3794 "horizon.kind={populated:?}: query {query:?} classification drifted",
3795 );
3796 }
3797 }
3798 }
3799
3800 /// GATE-COMPUTE BASELINE — the workspace-baseline
3801 /// [`Classification::gate_compute`] shape carries
3802 /// `horizon: Horizon::default()` whose `kind` field defaults to
3803 /// [`HorizonKind::Bounded`] via `#[default]`, so `has_horizon_kind`
3804 /// returns `true` on [`HorizonKind::Bounded`] and `false` on
3805 /// [`HorizonKind::Asymptotic`]. Pins the composition of the
3806 /// substrate's baseline-constructor primitive with the SEVENTH
3807 /// presence-probe peer AND the sibling-default correspondence
3808 /// documented on [`Classification::gate_compute`] (which pins the
3809 /// three defaulted axes to the sibling closed-set defaults
3810 /// `HorizonKind::Bounded` / `CalmClassification::Monotone` /
3811 /// `DataClassification::Internal`) — a regression that flipped
3812 /// `Horizon::default().kind` off `Bounded` (or promoted
3813 /// `Asymptotic` to `#[default]` on [`HorizonKind`], or wired
3814 /// `has_horizon_kind` to a fixed variant answer, or crossed the
3815 /// wires through the wrong nested struct) fails here at ONE
3816 /// narrow site before drifting across every unadorned ephemeral
3817 /// env (`default_ephemeral_class`) and every downstream test
3818 /// fixture that keys assertions on the shape. FIRST occupant on
3819 /// the (required-parent × nested-struct-scalar-child) corner —
3820 /// locks the corner's characteristic "default-arm short-circuit
3821 /// reaches through the nested struct's own default" property at
3822 /// ONE narrow site.
3823 #[test]
3824 fn classification_gate_compute_has_horizon_kind_bounded_only() {
3825 let c = Classification::gate_compute();
3826 for kind in HorizonKind::ALL {
3827 let expected = kind == HorizonKind::Bounded;
3828 assert_eq!(
3829 c.has_horizon_kind(kind),
3830 expected,
3831 "gate_compute (horizon.kind=Bounded) must return {expected} for {kind:?}",
3832 );
3833 }
3834 }
3835
3836 /// FIVE-AXIS INDEPENDENCE — the FIVE presence-probe co-tenants on
3837 /// the [`Classification`] parent
3838 /// ([`Classification::has_point_type`] plus
3839 /// [`Classification::has_substrate`] on the (required-parent ×
3840 /// required-scalar-child) corner AND
3841 /// [`Classification::has_calm`] plus
3842 /// [`Classification::has_data_classification`] on the (required-
3843 /// parent × defaulted-scalar-child) corner AND
3844 /// [`Classification::has_horizon_kind`] on the fresh (required-
3845 /// parent × nested-struct-scalar-child) corner) probe distinct
3846 /// slots on the SAME parent, so a carrier with `point_type: Fork`
3847 /// AND `substrate: Storage` AND `calm: NonMonotone` AND
3848 /// `data_classification: Pii` AND `horizon.kind: Asymptotic`
3849 /// answers `true` on all five fine tags simultaneously and
3850 /// `false` on every off-diagonal probe of any axis. Pins the five
3851 /// probes' independence at ONE narrow site — a regression that
3852 /// collapsed any of the five onto another's field (a stray probe
3853 /// of `has_horizon_kind` reading `self.point_type` /
3854 /// `self.substrate` / `self.calm` / `self.data_classification`,
3855 /// or of any prior probe reading through `self.horizon.kind`)
3856 /// would fail HERE before landing at any consumer. The audit
3857 /// `every Fork-topology Storage-plane NonMonotone-CALM
3858 /// Pii-classification Asymptotic-horizon point declares a
3859 /// Raft-guarded write path AND a downstream PII-scrub sink AND a
3860 /// rate-window healthy-threshold metric` composes this exact
3861 /// five-axis conjunction on the five classification-axis
3862 /// discriminators of the six-axis classification lattice — opens
3863 /// the five-way corner-coverage contract on [`Classification`],
3864 /// straddling THREE distinct corners of the (parent-shape ×
3865 /// child-shape) algebra (the required-child corner
3866 /// `has_point_type` + `has_substrate` share, the defaulted-child
3867 /// corner `has_calm` + `has_data_classification` share, and the
3868 /// nested-struct-child corner `has_horizon_kind` opens).
3869 #[test]
3870 fn classification_five_presence_probes_are_independent() {
3871 let c = Classification {
3872 point_type: ConvergencePointType::Fork,
3873 substrate: SubstrateType::Storage,
3874 horizon: Horizon {
3875 kind: HorizonKind::Asymptotic,
3876 ..Horizon::default()
3877 },
3878 calm: CalmClassification::NonMonotone,
3879 data_classification: DataClassification::Pii,
3880 };
3881 assert!(c.has_point_type(ConvergencePointType::Fork));
3882 assert!(c.has_substrate(SubstrateType::Storage));
3883 assert!(c.has_calm(CalmClassification::NonMonotone));
3884 assert!(c.has_data_classification(DataClassification::Pii));
3885 assert!(c.has_horizon_kind(HorizonKind::Asymptotic));
3886 assert!(!c.has_point_type(ConvergencePointType::Gate));
3887 assert!(!c.has_substrate(SubstrateType::Compute));
3888 assert!(!c.has_calm(CalmClassification::Monotone));
3889 assert!(!c.has_data_classification(DataClassification::Internal));
3890 assert!(!c.has_horizon_kind(HorizonKind::Bounded));
3891 }
3892
3893 // ── nested-struct-Option-scalar-carrier presence probe on Classification × OptimizationDirection ──
3894 //
3895 // Fail-before-pass-after granularity:
3896 // [`Classification::has_optimization_direction`] did not exist
3897 // before this commit — every consumer of the
3898 // `(Classification, OptimizationDirection) -> bool` two-hop
3899 // `self.horizon.direction.unwrap_or_default() == kind` probe
3900 // shape would have to restate the nested-struct-Option field
3901 // walk at its own callsite. Post-lift the shape lives at ONE
3902 // substrate owner and every downstream (the
3903 // `optimization-direction-<kind>` require-tag family in
3904 // `tatara-check`, future audit dispatchers walking
3905 // [`OptimizationDirection::ALL`], any future CRD-facing nested-
3906 // struct-Option-scalar discriminator on `ProcessSpec`) binds
3907 // through the SAME `has(kind)` shape the six prior presence
3908 // probes on [`Classification`] plus its cousins on
3909 // [`crate::spec::SignalPolicy`] and
3910 // [`crate::encapsulates::EncapsulatesSpec`] publish. SECOND
3911 // occupant on the (required-parent × nested-struct-scalar-
3912 // child) corner of the presence-probe algebra — the FIRST
3913 // occupant [`Classification::has_horizon_kind`] read the nested
3914 // scalar `horizon.kind: HorizonKind` DIRECTLY; this probe adds
3915 // the `Option`-hop through `direction: Option<OptimizationDirection>`
3916 // via `Option::unwrap_or_default`, pinning the corner as a
3917 // proven-repeatable primitive shape rather than a single-example
3918 // curiosity.
3919
3920 /// DIAGONAL — for every [`OptimizationDirection`] variant, a
3921 /// [`Classification`] whose `horizon.direction` field is set to
3922 /// `Some(that variant)` returns `true` from
3923 /// `has_optimization_direction` on that same variant AND
3924 /// `false` on every other variant. Sweep the
3925 /// [`OptimizationDirection::ALL`] × ALL cross so a regression
3926 /// that hard-coded the arm to a single variant (silently
3927 /// returning `true` on every populated classification regardless
3928 /// of query kind) or wired the equality to a fixed unrelated
3929 /// field (a stray probe on `classification.point_type` /
3930 /// `classification.substrate` / `classification.calm` /
3931 /// `classification.data_classification` /
3932 /// `classification.horizon.kind`, or a direct probe on the
3933 /// nested [`Horizon`] struct that ignored the `direction` arm)
3934 /// fails HERE at the substrate primitive before landing at the
3935 /// operator-facing checks.lisp surface. The `Option`-hop
3936 /// distinguishes this method from the direct-nested-scalar
3937 /// peer [`Classification::has_horizon_kind`]: the probe walks
3938 /// `self.horizon.direction.unwrap_or_default()` not
3939 /// `self.horizon.kind`, so a regression that mis-routed the
3940 /// field walk (a stray `self.horizon.kind == kind` that could
3941 /// not typecheck, or a stray `self.horizon == kind` that also
3942 /// could not typecheck) fails at the compiler before the
3943 /// runtime diagonal even runs.
3944 #[test]
3945 fn classification_has_optimization_direction_returns_true_iff_variant_matches() {
3946 for populated in OptimizationDirection::ALL {
3947 let c = Classification {
3948 point_type: ConvergencePointType::Gate,
3949 substrate: SubstrateType::Compute,
3950 horizon: Horizon {
3951 kind: HorizonKind::Asymptotic,
3952 direction: Some(populated),
3953 ..Horizon::default()
3954 },
3955 calm: CalmClassification::default(),
3956 data_classification: DataClassification::default(),
3957 };
3958 for query in OptimizationDirection::ALL {
3959 assert_eq!(
3960 c.has_optimization_direction(query),
3961 query == populated,
3962 "horizon.direction=Some({populated:?}): query {query:?} classification drifted",
3963 );
3964 }
3965 }
3966 }
3967
3968 /// GATE-COMPUTE BASELINE — the workspace-baseline
3969 /// [`Classification::gate_compute`] shape carries
3970 /// `horizon: Horizon::default()` whose `direction` field defaults
3971 /// to `None`. Under [`Option::unwrap_or_default`] the probe
3972 /// answers as if the field were `OptimizationDirection::default()`
3973 /// = [`OptimizationDirection::Minimize`] via `#[default]`, so
3974 /// `has_optimization_direction` returns `true` on
3975 /// [`OptimizationDirection::Minimize`] and `false` on
3976 /// [`OptimizationDirection::Maximize`]. Pins the composition of
3977 /// the substrate's baseline-constructor primitive with the
3978 /// EIGHTH presence-probe peer AND the closed-set-default
3979 /// correspondence documented on [`OptimizationDirection`] —
3980 /// a regression that flipped `OptimizationDirection::default()`
3981 /// off `Minimize` (which would silently invert every unadorned
3982 /// `Asymptotic` Process's rate-window evaluator polarity), or
3983 /// wired `has_optimization_direction` to a fixed variant answer,
3984 /// or crossed the wires through the wrong nested struct or the
3985 /// wrong Option-slot, fails here at ONE narrow site before
3986 /// drifting across every unadorned ephemeral env
3987 /// (`default_ephemeral_class`) and every downstream test fixture
3988 /// that keys assertions on the shape. SECOND occupant on the
3989 /// (required-parent × nested-struct-scalar-child) corner —
3990 /// locks the corner's Option-hop default-arm short-circuit
3991 /// property at ONE narrow site (the Option `None` folds onto
3992 /// the closed set's `#[default]` via `unwrap_or_default`,
3993 /// mirroring the direct-nested-scalar's default-arm short-
3994 /// circuit through the nested struct's own default).
3995 #[test]
3996 fn classification_gate_compute_has_optimization_direction_minimize_only() {
3997 let c = Classification::gate_compute();
3998 for kind in OptimizationDirection::ALL {
3999 let expected = kind == OptimizationDirection::Minimize;
4000 assert_eq!(
4001 c.has_optimization_direction(kind),
4002 expected,
4003 "gate_compute (horizon.direction=None ⇒ default Minimize) must return {expected} for {kind:?}",
4004 );
4005 }
4006 }
4007
4008 /// SIX-AXIS INDEPENDENCE — the SIX presence-probe co-tenants on
4009 /// the [`Classification`] parent
4010 /// ([`Classification::has_point_type`] plus
4011 /// [`Classification::has_substrate`] on the (required-parent ×
4012 /// required-scalar-child) corner AND
4013 /// [`Classification::has_calm`] plus
4014 /// [`Classification::has_data_classification`] on the (required-
4015 /// parent × defaulted-scalar-child) corner AND
4016 /// [`Classification::has_horizon_kind`] plus
4017 /// [`Classification::has_optimization_direction`] on the
4018 /// (required-parent × nested-struct-scalar-child) corner) probe
4019 /// distinct slots on the SAME parent, so a carrier with
4020 /// `point_type: Fork` AND `substrate: Storage` AND
4021 /// `calm: NonMonotone` AND `data_classification: Pii` AND
4022 /// `horizon.kind: Asymptotic` AND
4023 /// `horizon.direction: Some(Maximize)` answers `true` on all six
4024 /// fine tags simultaneously and `false` on every off-diagonal
4025 /// probe of any axis. Pins the six probes' independence at ONE
4026 /// narrow site — a regression that collapsed any of the six
4027 /// onto another's field (a stray probe of
4028 /// `has_optimization_direction` reading `self.point_type` /
4029 /// `self.substrate` / `self.calm` /
4030 /// `self.data_classification` / `self.horizon.kind`, or of any
4031 /// prior probe reading through `self.horizon.direction`) would
4032 /// fail HERE before landing at any consumer. The audit
4033 /// `every Fork-topology Storage-plane NonMonotone-CALM
4034 /// Pii-classification Asymptotic-horizon Maximize-direction
4035 /// point declares a rate-window healthy-threshold metric and a
4036 /// throughput-oriented SLO` composes this exact six-axis
4037 /// conjunction on the six classification-axis discriminators of
4038 /// the six-axis classification lattice — populates the six-way
4039 /// corner-coverage contract on [`Classification`], now
4040 /// straddling THREE distinct corners of the (parent-shape ×
4041 /// child-shape) algebra with TWO co-tenants each on the
4042 /// nested-struct-child corner: direct-nested-scalar
4043 /// (`has_horizon_kind`) and Option-nested-scalar
4044 /// (`has_optimization_direction`).
4045 #[test]
4046 fn classification_six_presence_probes_are_independent() {
4047 let c = Classification {
4048 point_type: ConvergencePointType::Fork,
4049 substrate: SubstrateType::Storage,
4050 horizon: Horizon {
4051 kind: HorizonKind::Asymptotic,
4052 direction: Some(OptimizationDirection::Maximize),
4053 ..Horizon::default()
4054 },
4055 calm: CalmClassification::NonMonotone,
4056 data_classification: DataClassification::Pii,
4057 };
4058 assert!(c.has_point_type(ConvergencePointType::Fork));
4059 assert!(c.has_substrate(SubstrateType::Storage));
4060 assert!(c.has_calm(CalmClassification::NonMonotone));
4061 assert!(c.has_data_classification(DataClassification::Pii));
4062 assert!(c.has_horizon_kind(HorizonKind::Asymptotic));
4063 assert!(c.has_optimization_direction(OptimizationDirection::Maximize));
4064 assert!(!c.has_point_type(ConvergencePointType::Gate));
4065 assert!(!c.has_substrate(SubstrateType::Compute));
4066 assert!(!c.has_calm(CalmClassification::Monotone));
4067 assert!(!c.has_data_classification(DataClassification::Internal));
4068 assert!(!c.has_horizon_kind(HorizonKind::Bounded));
4069 assert!(!c.has_optimization_direction(OptimizationDirection::Minimize));
4070 }
4071}