Skip to main content

tatara_process/
compliance.rs

1//! Compliance bindings — CRD-facing with bridges to `tatara_core::compliance_binding`.
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use tatara_core::domain::compliance_binding as core;
7
8use crate::phase::ProcessPhase;
9
10/// Compliance section of `ProcessSpec`.
11#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
12#[serde(rename_all = "camelCase")]
13pub struct ComplianceSpec {
14    /// Canonical baseline (e.g., `fedramp-moderate`, `cis-k8s-v1.8`, `soc2`, `pci-dss`).
15    /// Semantically the `meet` of all `bindings`.
16    #[serde(default, skip_serializing_if = "Option::is_none")]
17    pub baseline: Option<String>,
18    /// Individual control bindings.
19    #[serde(default)]
20    pub bindings: Vec<ComplianceBinding>,
21    /// Allow the reconciler to invoke remediation hooks on violations.
22    #[serde(default)]
23    pub auto_remediate: bool,
24}
25
26#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
27#[serde(rename_all = "camelCase")]
28pub struct ComplianceBinding {
29    /// Framework name: `nist-800-53`, `cis-k8s-v1.8`, `fedramp-moderate`, `soc2`, `pci-dss`.
30    pub framework: String,
31    /// Control id within the framework (e.g., `SC-7`, `5.1.1`).
32    pub control_id: String,
33    /// When the binding is verified.
34    #[serde(default)]
35    pub phase: VerificationPhase,
36    /// Optional human description.
37    #[serde(default, skip_serializing_if = "Option::is_none")]
38    pub description: Option<String>,
39}
40
41/// When a ComplianceBinding is evaluated.
42#[derive(
43    Clone,
44    Copy,
45    Debug,
46    PartialEq,
47    Eq,
48    Hash,
49    Serialize,
50    Deserialize,
51    JsonSchema,
52    Default,
53    tatara_closed_set::DeriveClosedSet,
54)]
55#[serde(rename_all = "PascalCase")]
56#[closed_set(via = "as_str", generate_unknown, display)]
57pub enum VerificationPhase {
58    /// Before Execing — fails reconciliation if violated.
59    PlanTime,
60    /// During VERIFY — gates Running → Attested.
61    #[default]
62    AtBoundary,
63    /// After Attested — continuous audit, emits events on violation.
64    PostConvergence,
65}
66
67impl VerificationPhase {
68    /// The closed set of verification phases — single source of truth that
69    /// drives the `as_str` / Display / `FromStr` triad and the typed
70    /// `gates_phase` projection over [`ProcessPhase`]. Adding a fourth
71    /// variant lands at one `ALL` entry + one `as_str` arm + one
72    /// `gates_phase` arm — exhaustively checked by the compiler (the
73    /// `[Self; 3]` array literal forces the arity).
74    ///
75    /// Sibling closed-set lifts on the same `ProcessSpec` axis:
76    /// [`crate::signal::SighupStrategy::ALL`],
77    /// [`crate::spec::MustReachPhase::ALL`],
78    /// [`crate::intent::WorkloadKind::ALL`],
79    /// [`crate::export::ReportFormat::ALL`],
80    /// [`crate::encapsulates::EncapsulationMode::ALL`],
81    /// [`crate::export::ExportTrigger::ALL`],
82    /// [`crate::lifetime::TeardownPolicy::ALL`],
83    /// [`crate::boundary::ConditionKind::ALL`],
84    /// [`crate::lifetime::LifetimeKind::ALL`],
85    /// [`crate::intent::IntentKind::ALL`],
86    /// [`crate::phase::ProcessPhase::ALL`],
87    /// [`crate::signal::ProcessSignal::ALL`].
88    pub const ALL: [Self; 3] = [Self::PlanTime, Self::AtBoundary, Self::PostConvergence];
89
90    /// Canonical PascalCase wire-format projection — matches the serde
91    /// `rename_all = "PascalCase"` output verbatim AND the CRD `enum:`
92    /// enumeration the reconciler stamps on the
93    /// `processes.tatara.pleme.io` schema. Pinned by
94    /// `verification_phase_as_str_matches_serde` so a variant rename
95    /// can't drift between the typed surface, the CRD enum, and the
96    /// YAML wire format at one site.
97    pub const fn as_str(self) -> &'static str {
98        match self {
99            Self::PlanTime => "PlanTime",
100            Self::AtBoundary => "AtBoundary",
101            Self::PostConvergence => "PostConvergence",
102        }
103    }
104
105    /// Typed `const fn` projection onto the [`ProcessPhase`] gate the
106    /// binding's verification blocks when it fails. Each variant maps
107    /// to the earliest phase whose entry the binding can prevent:
108    ///
109    /// - `PlanTime` → `Some(Execing)` — the RENDER phase is what
110    ///   PlanTime gates ("Before Execing — fails reconciliation if
111    ///   violated"); a violated PlanTime control prevents the
112    ///   `Forking → Execing` transition.
113    /// - `AtBoundary` → `Some(Attested)` — the VERIFY phase ("gates
114    ///   Running → Attested"); a violated AtBoundary control prevents
115    ///   the `Running → Attested` transition.
116    /// - `PostConvergence` → `None` — the binding is non-blocking
117    ///   ("After Attested — continuous audit, emits events on
118    ///   violation"); it never gates a transition.
119    ///
120    /// Single source of truth for the future reconciler control-plane
121    /// compliance evaluator's "which transition would a failing
122    /// binding block?" decision; pinned by
123    /// `verification_phase_gates_phase_truth_table`. Closed-set match
124    /// (not `matches!`) so adding a fourth variant triggers the
125    /// compiler's exhaustiveness check at this site rather than
126    /// silently defaulting to either group.
127    pub const fn gates_phase(self) -> Option<ProcessPhase> {
128        match self {
129            Self::PlanTime => Some(ProcessPhase::Execing),
130            Self::AtBoundary => Some(ProcessPhase::Attested),
131            Self::PostConvergence => None,
132        }
133    }
134}
135
136// `impl FromStr for VerificationPhase` +
137// `impl tatara_lisp::ClosedSet for VerificationPhase` +
138// `impl fmt::Display for VerificationPhase` +
139// `pub struct UnknownVerificationPhase(pub String)` are all
140// generated by `#[derive(tatara_closed_set::DeriveClosedSet)]` +
141// `#[closed_set(via = "as_str", generate_unknown, display)]` on the
142// enum declaration above. `label` delegates to the inherent
143// `VerificationPhase::as_str` (which matches the serde
144// `rename_all = "PascalCase"` projection AND the CRD `enum:`
145// enumeration verbatim — pinned by
146// `verification_phase_as_str_matches_serde`). The auto-derived
147// carrier label "verification phase" matches the prior hand-rolled
148// `#[error("unknown verification phase: {0}")]` annotation
149// byte-for-byte. Symmetric to every other `#[derive(DeriveClosedSet)]`
150// implementor across the crate.
151
152impl From<VerificationPhase> for core::VerificationPhase {
153    fn from(v: VerificationPhase) -> Self {
154        match v {
155            VerificationPhase::PlanTime => Self::PlanTime,
156            VerificationPhase::AtBoundary => Self::AtBoundary,
157            VerificationPhase::PostConvergence => Self::PostConvergence,
158        }
159    }
160}
161
162impl From<core::VerificationPhase> for VerificationPhase {
163    fn from(v: core::VerificationPhase) -> Self {
164        use core::VerificationPhase as C;
165        match v {
166            C::PlanTime => Self::PlanTime,
167            C::AtBoundary => Self::AtBoundary,
168            C::PostConvergence => Self::PostConvergence,
169        }
170    }
171}
172
173impl ComplianceBinding {
174    pub fn to_core(&self) -> core::ComplianceControl {
175        core::ComplianceControl {
176            framework: self.framework.clone(),
177            control_id: self.control_id.clone(),
178            description: self.description.clone().unwrap_or_default(),
179        }
180    }
181}
182
183/// Slice-level `(VerificationPhase, presence)` probe on any
184/// `&[ComplianceBinding]` — the ONE substrate primitive that owns the
185/// `.iter().any(|b| b.phase == K)` walk shape for the compliance-
186/// binding vector. Callers compose the answer they want on top:
187/// `spec.compliance.bindings.has_verification_phase(kind)` for the
188/// point-domain `verification-phase-<kind>` require-tag family, a
189/// coherence check that verifies "every `PlanTime` binding predicates
190/// on a framework the fleet publishes", an editor completion listing
191/// which [`VerificationPhase`] gates the operator authored — every
192/// future consumer reaches this ONE primitive through
193/// `slice.has_verification_phase(k)` instead of restating the
194/// `.iter().any` closure body.
195///
196/// # Third instance in the slice-level presence-probe algebra
197///
198/// Same axis, same shape, third instance in the workspace-wide
199/// slice-level closed-set-driven presence-probe algebra alongside
200/// [`crate::boundary::ConditionSliceExt::has_kind`] on `&[Condition]`
201/// and [`crate::spec::DependsOnSliceExt::has_must_reach`] on
202/// `&[DependsOn]`. All three live one composition boundary below the
203/// tagged-union-parent probes ([`crate::intent::Intent::has`],
204/// [`crate::lifetime::Lifetime::has`],
205/// [`crate::boundary::Boundary::has_condition_kind`]) at the
206/// (`&self`, `K`) → `bool` signature; a future normalization at the
207/// slice-level probe shape (widening the return to
208/// `Option<&ComplianceBinding>` for deeper diagnostics, adding a
209/// debug-build assertion on redundant duplicate `(framework,
210/// control_id)` pairs at the same phase, switching to a linear scan
211/// that also counts matches) lands at ONE site here and every
212/// downstream `slice.has_verification_phase(K)` callsite picks it up
213/// mechanically.
214///
215/// # Compounding
216///
217/// The `verification-phase-<kind>` require-tag prefix family in
218/// `tatara-reconciler::bin::tatara-check` composes this primitive with
219/// the closed-set `FromStr` autoderived on [`VerificationPhase`]
220/// through the `strip_and_classify_prefixed_kind` substrate to publish
221/// a sixth closed-set-driven prefix family byte-for-byte symmetrical
222/// with `intent-<kind>` / `lifetime-<kind>` / `condition-<kind>` /
223/// `must-reach-<kind>` / `sighup-<kind>`. Coexists with the coarse
224/// `compliance` fixed tag (which answers "does this spec carry ANY
225/// compliance binding") — the two tags publish distinct answers.
226/// A future fourth [`VerificationPhase`] variant added to `ALL` (a
227/// hypothetical `Continuous` checkpoint) reaches every downstream
228/// through the SAME closed-set walk with no per-caller edit.
229///
230/// Theory anchor: THEORY.md §II.1 invariant 5 — composition preserves
231/// proofs; the per-slice `phase` walk lives at ONE substrate site so
232/// every downstream (require-tag classifier, coherence check, editor
233/// completion) binds through the SAME shape rather than restating the
234/// `.iter().any(|b| b.phase == K)` closure body at each callsite.
235/// THEORY.md §VI.1 — generation over composition; a future
236/// [`VerificationPhase`] variant lands at ONE `ALL` entry + ONE
237/// `as_str` arm on the closed set and the presence probe picks it up
238/// mechanically without further per-consumer edits.
239pub trait ComplianceBindingSliceExt {
240    /// True iff at least one [`ComplianceBinding`] in this slice
241    /// verifies at the given [`VerificationPhase`]. The single-slice
242    /// presence probe every consumer of the `(&[ComplianceBinding],
243    /// VerificationPhase) -> bool` shape composes against.
244    fn has_verification_phase(&self, kind: VerificationPhase) -> bool;
245
246    /// True iff at least one [`ComplianceBinding`] in this slice would
247    /// gate the given [`ProcessPhase`] transition when it fails — i.e.
248    /// at least one binding's [`VerificationPhase::gates_phase`]
249    /// projection is `Some(phase)`. The single-slice DERIVED-Option-
250    /// typed-projection presence probe every consumer of the
251    /// `(&[ComplianceBinding], ProcessPhase) -> bool` shape composes
252    /// against.
253    ///
254    /// # First slice-parent × derived-Option-typed-projection-child corner
255    ///
256    /// Peer of [`Self::has_verification_phase`] on the SAME slice,
257    /// distinct on the child axis. `has_verification_phase(k)` probes
258    /// the RAW stored [`ComplianceBinding::phase`] scalar; this method
259    /// composes the typed [`VerificationPhase::gates_phase`] projection
260    /// through the same slice walk, so the answer keys off "which
261    /// [`ProcessPhase`] transition would a failing binding block?"
262    /// rather than "which verification-phase checkpoint is authored?".
263    /// The projection is many-to-one (`PlanTime → Execing`,
264    /// `AtBoundary → Attested`, `PostConvergence → None`), so
265    /// `has_verification_gates(ProcessPhase::Attested)` on a slice
266    /// carrying five `AtBoundary` bindings and one `PostConvergence`
267    /// binding answers `true` (the five `AtBoundary` bindings all
268    /// project to `Attested`, the `PostConvergence` projects to
269    /// `None`), and `has_verification_gates(ProcessPhase::Reaped)`
270    /// answers `false` on the same slice (no
271    /// [`VerificationPhase`] variant projects to `Reaped`). Sibling
272    /// derived-Option-child probe of
273    /// [`crate::spec::SignalPolicy::has_sighup_target`] on the
274    /// (required-scalar-parent × derived-Option-child) corner — this
275    /// method opens the (slice-parent × derived-Option-child) corner
276    /// as its natural sibling one composition boundary deeper.
277    ///
278    /// # Semantics — TRANSITION match on the projection image
279    ///
280    /// A binding's `phase.gates_phase()` yields `Some(target)` for
281    /// exactly the [`ProcessPhase`]s the [`VerificationPhase`] closed
282    /// set names as gateable ([`ProcessPhase::Execing`] gated by
283    /// `PlanTime`, [`ProcessPhase::Attested`] gated by `AtBoundary`)
284    /// and `None` for the non-blocking `PostConvergence` audit
285    /// checkpoint. So `has_verification_gates` answers `false` for
286    /// every non-gateable [`ProcessPhase`] regardless of how many
287    /// `PostConvergence` bindings live in the slice — the projection
288    /// short-circuit at the closed-set primitive rides through the
289    /// slice walk without leaking. A regression that (a) probed the
290    /// stored `.phase` field directly (which would answer for the
291    /// wrong closed set), (b) inverted the projection (yielding
292    /// `Some` for `PostConvergence` and `None` elsewhere), or (c)
293    /// crossed the wires with `has_verification_phase` (which returns
294    /// `true` for a `PostConvergence` binding queried at
295    /// `PostConvergence`) fails at THIS probe's substrate site before
296    /// drifting into the require-tag classifier or the future
297    /// reconciler control-plane compliance evaluator.
298    ///
299    /// # Compounding
300    ///
301    /// A future `verification-gates-<phase>` require-tag prefix family
302    /// in `tatara-reconciler::bin::tatara-check` composes this
303    /// primitive with the autoderived [`ProcessPhase`] `FromStr`
304    /// through the `strip_and_classify_prefixed_kind` substrate to
305    /// publish a further closed-set-driven prefix family symmetric
306    /// with `verification-phase-<kind>` — but keyed on the transition
307    /// a failing binding blocks rather than on the checkpoint stored
308    /// in the CRD. The future reconciler control-plane compliance
309    /// evaluator that decides "should the current
310    /// `Running → Attested` transition proceed given the observed
311    /// binding violations?" reaches this ONE substrate site to
312    /// answer the load-bearing "does this spec even care about the
313    /// candidate transition?" gate.
314    ///
315    /// A future [`VerificationPhase`] variant whose `gates_phase` arm
316    /// projects to a fresh [`ProcessPhase`] reaches every downstream
317    /// through the SAME closed-set walk with no per-caller edit — the
318    /// projection changes at ONE arm on
319    /// [`VerificationPhase::gates_phase`] and this probe body
320    /// inherits the shift automatically. Symmetric to the way
321    /// [`Self::has_verification_phase`] absorbs a fresh variant with
322    /// no probe-body edit.
323    ///
324    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition
325    /// preserves proofs; the per-slice `phase.gates_phase()` walk
326    /// lives at ONE substrate site so every downstream (require-tag
327    /// classifier, control-plane compliance evaluator, editor
328    /// completion listing "which transitions would fail if this
329    /// binding's control reported a violation") binds through the
330    /// SAME shape rather than restating the `.iter().any(|b|
331    /// b.phase.gates_phase() == Some(K))` closure body at each
332    /// callsite. THEORY.md §VI.1 — generation over composition; a
333    /// future [`VerificationPhase`] variant lands at ONE `ALL` entry,
334    /// one `as_str` arm, one `gates_phase` arm on the closed set,
335    /// and both slice-level presence probes
336    /// (`has_verification_phase`, `has_verification_gates`) pick it
337    /// up mechanically.
338    fn has_verification_gates(&self, phase: ProcessPhase) -> bool;
339}
340
341impl ComplianceBindingSliceExt for [ComplianceBinding] {
342    fn has_verification_phase(&self, kind: VerificationPhase) -> bool {
343        self.iter().any(|b| b.phase == kind)
344    }
345
346    fn has_verification_gates(&self, phase: ProcessPhase) -> bool {
347        self.iter().any(|b| b.phase.gates_phase() == Some(phase))
348    }
349}
350
351#[cfg(test)]
352mod tests {
353    use super::*;
354
355    #[test]
356    fn default_phase_is_at_boundary() {
357        assert_eq!(VerificationPhase::default(), VerificationPhase::AtBoundary);
358    }
359
360    #[test]
361    fn binding_roundtrip_to_core() {
362        let b = ComplianceBinding {
363            framework: "nist-800-53".into(),
364            control_id: "SC-7".into(),
365            phase: VerificationPhase::AtBoundary,
366            description: Some("boundary protection".into()),
367        };
368        let c = b.to_core();
369        assert_eq!(c.framework, "nist-800-53");
370        assert_eq!(c.control_id, "SC-7");
371    }
372
373    // ── closed-set algebra contracts (ALL × as_str × FromStr × gates_phase) ──
374
375    /// Structural well-formedness of [`VerificationPhase`] as a
376    /// [`tatara_lisp::ClosedSet`] implementor — the workspace-wide
377    /// testkit lift that pins all three structural invariants
378    /// (`ALL` is non-empty, every variant round-trips through
379    /// `label ↔ parse_label`, labels are pairwise distinct, `""` is
380    /// outside the closed set) at ONE call site. Replaces the
381    /// hand-derived `verification_phase_all_is_unique_and_complete` +
382    /// `verification_phase_roundtrip_via_as_str` + the empty-input
383    /// arm of the per-implementor unknown-error test. `FromStr`
384    /// delegates to `<Self as tatara_closed_set::ClosedSet>::parse_label`,
385    /// so this helper exercises the same code path the reconciler
386    /// hits when parsing a CRD `enum:`-validated value back to the
387    /// typed phase.
388    #[test]
389    fn verification_phase_is_well_formed_closed_set() {
390        tatara_closed_set::assert_closed_set_well_formed::<VerificationPhase>();
391    }
392
393    /// CANONICAL-KEY CONTRACT: `as_str` matches serde's PascalCase
394    /// output verbatim for every variant. A future variant rename
395    /// (or an `as_str` arm typo) lands here at one site, instead of
396    /// drifting between the typed surface and the YAML wire format
397    /// the reconciler / operator both read. NOT lifted into the
398    /// `ClosedSet` testkit — `serde_json` is NOT a `tatara-lisp`
399    /// dependency, and per-implementor serde-shape choices (PascalCase
400    /// for CRD enums, snake_case for camelCase carriers, lowercase
401    /// for Lisp keyword projections) make a generic helper a
402    /// category error.
403    #[test]
404    fn verification_phase_as_str_matches_serde() {
405        crate::tagged_union::assert_label_matches_serde_serialization::<VerificationPhase>();
406    }
407
408    /// The Display impl IS `as_str` — pinning this lets future callers
409    /// reach for either projection without drift.
410    #[test]
411    fn verification_phase_display_matches_as_str() {
412        crate::tagged_union::assert_display_matches_label::<VerificationPhase>();
413    }
414
415    /// `FromStr` rejects domain-specific non-canonical inputs and
416    /// the error echoes the input VERBATIM so the operator-facing
417    /// diagnostic carries the offending value. Kept per-implementor
418    /// because the verbatim-payload contract is a property of the
419    /// per-enum `Unknown<X>(pub String)` newtype, not of the trait's
420    /// structural surface. (The empty-input arm is now lifted into
421    /// `verification_phase_is_well_formed_closed_set`; the
422    /// case-drifted / hyphenated / extinct-variant arms stay here as
423    /// they're representative non-canonical inputs the operator
424    /// might supply.)
425    #[test]
426    fn unknown_verification_phase_errors() {
427        use std::str::FromStr;
428        for bad in [
429            "plantime",
430            "ATBOUNDARY",
431            "Plan-Time",
432            "post_convergence",
433            "Continuous",
434        ] {
435            let err = VerificationPhase::from_str(bad).unwrap_err();
436            assert_eq!(err.0, bad, "error payload should echo input verbatim");
437        }
438    }
439
440    /// TRUTH-TABLE CONTRACT: `gates_phase` agrees with the documented
441    /// per-variant codomain (the phase whose entry a violated binding
442    /// blocks, or `None` for non-blocking continuous-audit phases).
443    #[test]
444    fn verification_phase_gates_phase_truth_table() {
445        assert_eq!(
446            VerificationPhase::PlanTime.gates_phase(),
447            Some(ProcessPhase::Execing)
448        );
449        assert_eq!(
450            VerificationPhase::AtBoundary.gates_phase(),
451            Some(ProcessPhase::Attested)
452        );
453        assert_eq!(VerificationPhase::PostConvergence.gates_phase(), None);
454    }
455
456    /// SUBSET CONTRACT: every `Some(target)` `gates_phase` projects to
457    /// is a phase reachable as the destination of some legal
458    /// `ProcessPhase::can_transition_to` edge. A future variant that
459    /// projected to a `ProcessPhase` no transition leads into would
460    /// FAIL here, forcing the author to either pick a real gate phase
461    /// or extend `can_transition_to` deliberately. The reachability
462    /// check is the cross-enum coherence proof — the typed-phase
463    /// state machine and the verification-phase gate algebra agree on
464    /// which phases are gateable.
465    #[test]
466    fn verification_phase_gates_phase_projects_to_reachable_phases() {
467        for vp in VerificationPhase::ALL {
468            if let Some(target) = vp.gates_phase() {
469                let reachable = ProcessPhase::ALL
470                    .into_iter()
471                    .any(|src| src != target && src.can_transition_to(target));
472                assert!(
473                    reachable,
474                    "{vp:?}.gates_phase() = Some({target:?}) but no legal transition lands on {target:?}",
475                );
476            }
477        }
478    }
479
480    /// INJECTIVITY CONTRACT: distinct `Some` variants of `gates_phase`
481    /// project to distinct `ProcessPhase`s. Pairing this with the
482    /// subset contract above forces a future variant to land on a
483    /// fresh gateable phase (or project to `None` and be a deliberate
484    /// non-blocking auditor).
485    #[test]
486    fn verification_phase_gates_phase_is_injective() {
487        let projections: Vec<ProcessPhase> = VerificationPhase::ALL
488            .into_iter()
489            .filter_map(VerificationPhase::gates_phase)
490            .collect();
491        let unique: std::collections::HashSet<_> = projections.iter().copied().collect();
492        assert_eq!(
493            projections.len(),
494            unique.len(),
495            "gates_phase projection is not injective: {projections:?}",
496        );
497    }
498
499    // ── ComplianceBindingSliceExt::has_verification_phase substrate pins ──
500    //
501    // Fail-before-pass-after granularity: `ComplianceBindingSliceExt`
502    // did not exist before this commit — the `(&[ComplianceBinding],
503    // VerificationPhase) -> bool` walk shape was not spelled anywhere in
504    // the workspace. The lift opens the third instance in the slice-
505    // level closed-set-driven presence-probe algebra (peer of
506    // `ConditionSliceExt::has_kind` on `&[Condition]` and
507    // `DependsOnSliceExt::has_must_reach` on `&[DependsOn]`), enabling
508    // the sixth `verification-phase-<kind>` require-tag prefix family in
509    // `tatara-reconciler::bin::tatara-check` to compose against ONE
510    // substrate site rather than restating the `.iter().any(|b| b.phase
511    // == K)` closure body inline at the classifier.
512
513    fn binding_at(phase: VerificationPhase) -> ComplianceBinding {
514        ComplianceBinding {
515            framework: "nist-800-53".into(),
516            control_id: "SC-7".into(),
517            phase,
518            description: None,
519        }
520    }
521
522    /// EMPTY-SLICE pin — an empty `&[ComplianceBinding]` returns
523    /// `false` for EVERY [`VerificationPhase`]. Sweep
524    /// [`VerificationPhase::ALL`] so a new variant added without a
525    /// matching arm in the primitive surfaces at rustc's exhaustiveness
526    /// gate on the ALL literal (arity forced by `[Self; 3]`) rather than
527    /// as a silent false-positive at every downstream callsite composing
528    /// this primitive.
529    #[test]
530    fn compliance_binding_slice_has_verification_phase_returns_false_on_empty_slice_for_every_kind()
531    {
532        let empty: &[ComplianceBinding] = &[];
533        for kind in VerificationPhase::ALL {
534            assert!(
535                !empty.has_verification_phase(kind),
536                "empty slice must return false for {kind:?}",
537            );
538        }
539    }
540
541    /// PER-VARIANT pin — a single-element slice returns `true` for
542    /// exactly the phase it carries, `false` for every other variant.
543    /// Sweep the [`VerificationPhase::ALL`] × ALL cross so a regression
544    /// that (a) hard-coded the arm to a single kind (silently returning
545    /// true for every populated slice regardless of query kind), or
546    /// (b) matched on [`ComplianceBinding::framework`] instead of
547    /// [`ComplianceBinding::phase`] fails HERE at the substrate
548    /// primitive.
549    #[test]
550    fn compliance_binding_slice_has_verification_phase_reads_phase_field_per_variant() {
551        for populated in VerificationPhase::ALL {
552            let slice = [binding_at(populated)];
553            for query in VerificationPhase::ALL {
554                let expected = query == populated;
555                assert_eq!(
556                    slice.has_verification_phase(query),
557                    expected,
558                    "populated={populated:?}: query {query:?} drifted",
559                );
560            }
561        }
562    }
563
564    /// MULTI-ENTRY pin — a slice with multiple entries returns `true`
565    /// for every phase that appears at any position (existential
566    /// quantifier over the slice), `false` for phases that appear at
567    /// no position. Locks the `any` semantics so a regression that
568    /// collapsed to a `first`-only probe (`slice.first().map_or(false,
569    /// |b| b.phase == kind)`) fails here even though the single-element
570    /// per-variant pin above passes.
571    #[test]
572    fn compliance_binding_slice_has_verification_phase_scans_beyond_the_first_position() {
573        let slice = [
574            binding_at(VerificationPhase::PlanTime),
575            binding_at(VerificationPhase::PostConvergence),
576        ];
577        for present in [
578            VerificationPhase::PlanTime,
579            VerificationPhase::PostConvergence,
580        ] {
581            assert!(
582                slice.has_verification_phase(present),
583                "phase at any position must resolve true: {present:?}",
584            );
585        }
586        assert!(
587            !slice.has_verification_phase(VerificationPhase::AtBoundary),
588            "phase absent from the slice must resolve false: AtBoundary",
589        );
590    }
591
592    // ── ComplianceBindingSliceExt::has_verification_gates substrate pins ──
593    //
594    // Fail-before-pass-after granularity: `has_verification_gates` did
595    // not exist before this commit — the `(&[ComplianceBinding],
596    // ProcessPhase) -> bool` derived-Option-child walk shape was not
597    // spelled anywhere in the workspace. The lift opens the FIRST
598    // instance in the (slice-parent × derived-Option-typed-projection-
599    // child) corner of the workspace-wide presence-probe algebra
600    // (parent is the `&[ComplianceBinding]` slice; child is the
601    // `Option<ProcessPhase>` derived from each binding's stored
602    // `VerificationPhase` via `VerificationPhase::gates_phase`).
603    // Direct pattern peer of `SignalPolicy::has_sighup_target` at the
604    // (required-scalar-parent × derived-Option-child) corner — that
605    // corner opens the required-scalar side of the derived-Option-
606    // child slice; this method opens the slice side.
607
608    /// EMPTY-SLICE pin — an empty `&[ComplianceBinding]` returns
609    /// `false` for EVERY [`ProcessPhase`], gateable or not. Sweep
610    /// [`ProcessPhase::ALL`] so a new phase variant added without a
611    /// matching arm in the primitive surfaces at rustc's exhaustiveness
612    /// gate on the ALL literal rather than as a silent false-positive
613    /// at every downstream callsite composing this primitive.
614    #[test]
615    fn compliance_binding_slice_has_verification_gates_returns_false_on_empty_slice_for_every_phase(
616    ) {
617        let empty: &[ComplianceBinding] = &[];
618        for phase in ProcessPhase::ALL {
619            assert!(
620                !empty.has_verification_gates(phase),
621                "empty slice must return false for {phase:?}",
622            );
623        }
624    }
625
626    /// PER-VARIANT DIAGONAL pin — a single-element slice returns `true`
627    /// for EXACTLY the [`ProcessPhase`] the binding's
628    /// [`VerificationPhase::gates_phase`] projection yields (or `false`
629    /// for EVERY phase when the projection is `None`), `false` for
630    /// every other phase. Sweep the [`VerificationPhase::ALL`] ×
631    /// [`ProcessPhase::ALL`] cross so a regression that (a) hard-coded
632    /// the arm to a single kind (silently returning `true` for every
633    /// populated slice regardless of query phase), (b) read the stored
634    /// `.phase` field directly (returning `true` on the WRONG closed
635    /// set), or (c) inverted the `Option<ProcessPhase>` projection
636    /// (yielding `Some` for `PostConvergence` and `None` elsewhere)
637    /// fails HERE at the substrate primitive.
638    #[test]
639    fn compliance_binding_slice_has_verification_gates_projects_through_gates_phase_per_variant() {
640        for stored in VerificationPhase::ALL {
641            let slice = [binding_at(stored)];
642            let expected_gate = stored.gates_phase();
643            for query in ProcessPhase::ALL {
644                let expected = expected_gate == Some(query);
645                assert_eq!(
646                    slice.has_verification_gates(query),
647                    expected,
648                    "stored={stored:?}: query {query:?} drifted from \
649                     gates_phase() projection {expected_gate:?}",
650                );
651            }
652        }
653    }
654
655    /// NON-BLOCKING pin — a slice carrying ONLY `PostConvergence`
656    /// bindings answers `false` for EVERY [`ProcessPhase`], because
657    /// [`VerificationPhase::PostConvergence::gates_phase`] is `None`
658    /// (the continuous-audit checkpoint blocks no transition). Locks
659    /// the projection's `None` short-circuit at the slice walk so a
660    /// regression that (a) leaked a `PostConvergence` binding into
661    /// some arbitrary [`ProcessPhase`] answer, or (b) collapsed
662    /// `Option<ProcessPhase>::None` to a defaulted phase (Pending,
663    /// Reaped) fails HERE.
664    #[test]
665    fn compliance_binding_slice_has_verification_gates_returns_false_on_post_convergence_only_slice(
666    ) {
667        let slice = [
668            binding_at(VerificationPhase::PostConvergence),
669            binding_at(VerificationPhase::PostConvergence),
670        ];
671        for phase in ProcessPhase::ALL {
672            assert!(
673                !slice.has_verification_gates(phase),
674                "PostConvergence-only slice must return false for every phase: \
675                 {phase:?} (gates_phase() = None everywhere)",
676            );
677        }
678    }
679
680    /// MULTI-ENTRY / MANY-TO-ONE pin — a slice with MULTIPLE bindings
681    /// projecting to the SAME [`ProcessPhase`] via
682    /// [`VerificationPhase::gates_phase`] answers `true` for that
683    /// phase, and a slice mixing projected + `None`-projected bindings
684    /// still answers `true` for the projected phase while remaining
685    /// `false` for the non-gateable ones. Locks the `any` semantics on
686    /// the projection: the SLICE walk composes the projection through
687    /// `.iter().any(|b| b.phase.gates_phase() == Some(K))` so a
688    /// regression that (a) short-circuited on the first `None`
689    /// projection (silently answering `false` when the first binding
690    /// is `PostConvergence`), (b) required ALL bindings to project to
691    /// the queried phase (universal instead of existential), or (c)
692    /// dropped past-first entries entirely fails HERE.
693    #[test]
694    fn compliance_binding_slice_has_verification_gates_scans_beyond_the_first_position() {
695        let slice = [
696            binding_at(VerificationPhase::PostConvergence),
697            binding_at(VerificationPhase::AtBoundary),
698            binding_at(VerificationPhase::AtBoundary),
699        ];
700        assert!(
701            slice.has_verification_gates(ProcessPhase::Attested),
702            "AtBoundary binding at non-first position must project through gates_phase",
703        );
704        assert!(
705            !slice.has_verification_gates(ProcessPhase::Execing),
706            "no PlanTime binding in slice — Execing gate must resolve false",
707        );
708        assert!(
709            !slice.has_verification_gates(ProcessPhase::Reaped),
710            "no VerificationPhase variant projects to Reaped — must resolve false",
711        );
712    }
713
714    /// AXIS-SPLIT pin — the two slice-level presence probes
715    /// (`has_verification_phase` on the RAW stored checkpoint,
716    /// `has_verification_gates` on the DERIVED Option-typed transition
717    /// projection) answer independently on the SAME slice. A slice
718    /// carrying one `PostConvergence` binding answers `true` for
719    /// `has_verification_phase(PostConvergence)` (the RAW checkpoint
720    /// is authored) AND `false` for every `has_verification_gates(K)`
721    /// (the projection is `None` so no transition is gated). Locks
722    /// the raw-vs-derived semantic split at the substrate boundary so
723    /// a regression that collapsed one probe onto the other fails
724    /// HERE at ONE narrow site.
725    #[test]
726    fn compliance_binding_slice_verification_phase_and_gates_split_on_post_convergence() {
727        let slice = [binding_at(VerificationPhase::PostConvergence)];
728        assert!(
729            slice.has_verification_phase(VerificationPhase::PostConvergence),
730            "raw checkpoint probe must see the authored PostConvergence binding",
731        );
732        for phase in ProcessPhase::ALL {
733            assert!(
734                !slice.has_verification_gates(phase),
735                "derived gates probe on PostConvergence-only slice must resolve \
736                 false for every phase: {phase:?}",
737            );
738        }
739    }
740}