Skip to main content

tatara_process/
status.rs

1//! `ProcessStatus` sub-structures — conditions, checked boundaries, Flux refs.
2
3use chrono::{DateTime, Utc};
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8use crate::boundary::Condition;
9use crate::crd::Process;
10
11/// Standard K8s Condition (shape of `metav1.Condition`).
12#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
13#[serde(rename_all = "camelCase")]
14pub struct ProcessCondition {
15    #[serde(rename = "type")]
16    pub type_: String,
17    pub status: String,
18    pub last_transition_time: DateTime<Utc>,
19    #[serde(default, skip_serializing_if = "Option::is_none")]
20    pub reason: Option<String>,
21    #[serde(default, skip_serializing_if = "Option::is_none")]
22    pub message: Option<String>,
23}
24
25impl ProcessCondition {
26    pub fn ready(reason: impl Into<String>, message: Option<String>) -> Self {
27        Self {
28            type_: "Ready".into(),
29            status: "True".into(),
30            last_transition_time: Utc::now(),
31            reason: Some(reason.into()),
32            message,
33        }
34    }
35
36    pub fn not_ready(reason: impl Into<String>, message: impl Into<String>) -> Self {
37        Self {
38            type_: "Ready".into(),
39            status: "False".into(),
40            last_transition_time: Utc::now(),
41            reason: Some(reason.into()),
42            message: Some(message.into()),
43        }
44    }
45
46    pub fn attested(root: &str) -> Self {
47        Self {
48            type_: "Attested".into(),
49            status: "True".into(),
50            last_transition_time: Utc::now(),
51            reason: Some("AttestationWritten".into()),
52            message: Some(format!("composed_root={root}")),
53        }
54    }
55}
56
57/// Reference to a FluxCD resource emitted as part of this Process.
58#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
59#[serde(rename_all = "camelCase")]
60pub struct FluxResourceRef {
61    pub api_version: String,
62    pub kind: String,
63    pub name: String,
64    pub namespace: String,
65    #[serde(default)]
66    pub ready: bool,
67    #[serde(default, skip_serializing_if = "Option::is_none")]
68    pub message: Option<String>,
69    #[serde(default, skip_serializing_if = "Option::is_none")]
70    pub last_check: Option<DateTime<Utc>>,
71}
72
73impl FluxResourceRef {
74    /// Pure typed projection of the four fetch coordinates
75    /// `(namespace, api_version, kind, name)` every consumer that
76    /// dispatches this persisted reference through kube-rs's dynamic-
77    /// object surface splats by hand pre-lift. The 4-tuple binds the
78    /// slot order at ONE typed accessor so a copy-paste at any downstream
79    /// consumer cannot swap two adjacent `&str` slots in the fetch call.
80    ///
81    /// Peer projection to
82    /// [`crate::k8s_wire_identity::K8sWireIdentity`] on the static-
83    /// identity axis: [`K8sWireIdentity`] carries a
84    /// `(&'static str, &'static str)` closed-set variant's pair for
85    /// emit-time (RENDER phase) composition; this method carries the
86    /// full `(ns, apiVersion, kind, name)` 4-slot borrow for fetch-time
87    /// (VERIFY / ATTEST-heartbeat) composition where the ref's payload
88    /// comes back off the persisted `ProcessStatus.flux_resources`
89    /// slice with owned `String`s rather than static literals. The two
90    /// primitives partition the fetch axis by whether the caller starts
91    /// from a closed-set variant (emit-time) or a persisted status
92    /// slice (fetch-time).
93    ///
94    /// Pre-lift the 5-slot `ssapply::fetch(client, &r.namespace,
95    /// &r.api_version, &r.kind, &r.name)` splat was hand-authored at
96    /// TWO sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold
97    /// in `tatara-reconciler::phase_machine`:
98    /// * `handle_running` — the VERIFY-phase per-ref readiness probe
99    ///   that populates the updated `FluxResourceRef` slice with
100    ///   `ready` + `message` + `last_check`.
101    /// * `handle_attested` — the ATTEST-heartbeat drift detector that
102    ///   short-circuits on the first non-Ready ref.
103    ///
104    /// Both sites splatted the SAME four `&r.X` field borrows in the
105    /// SAME order into raw `ssapply::fetch`. A copy-paste that swapped
106    /// two adjacent `&str` slots (`&r.api_version` and `&r.kind` are
107    /// both strings that look interchangeable to a mechanical
108    /// substitution) would silently 404 at wire time and diagnose as a
109    /// broken CRD rather than as slot skew at the callsite. Post-lift
110    /// each site names the ref ONCE and unpacks it through this ONE
111    /// projection; the slot order binds structurally at the tuple
112    /// return so a caller cannot desync one axis.
113    ///
114    /// A future addition (a case-fold normalization on the group, a
115    /// virtual-cluster prefix rewrite for multi-tenancy, a
116    /// `generateName` fallback on the name slot, a cluster-cache
117    /// short-circuit inserted between the projection and the fetch
118    /// call) lands at this ONE method and every downstream fetch
119    /// consumer inherits the upgrade mechanically — no per-site edit
120    /// at `handle_running` / `handle_attested` / any future kenshi-
121    /// runner / mirror-audit / drift-probe consumer that grows a third
122    /// consumer.
123    ///
124    /// Return-order pin lives at
125    /// [`tests::flux_resource_ref_fetch_coords_binds_slots_by_position`]
126    /// so a regression that swapped `namespace` and `api_version`
127    /// (both `String`, same type) inside the tuple constructor fails-
128    /// loudly here rather than as a silent wire-time 404 at every
129    /// downstream fetch consumer.
130    ///
131    /// Theory grounding: THEORY.md §II.1 invariant 5 (composition
132    /// preserves proofs — the 4-tuple slot order binds at ONE typed
133    /// projection so a regression across the two fields of the same
134    /// `String` type fails at the projection's positional pin rather
135    /// than at every downstream fetch consumer). THEORY.md §VI.1
136    /// (generation over composition — the 5-slot splat recurred at
137    /// two hand-authored sites past the ≥ 2 duplication trigger, and
138    /// is lifted to ONE typed borrow-projection here).
139    pub fn fetch_coords(&self) -> (&str, &str, &str, &str) {
140        (&self.namespace, &self.api_version, &self.kind, &self.name)
141    }
142
143    /// Compose a `FluxResourceRef` stamped at "observed now" — the
144    /// `last_check` slot is set to `Some(Utc::now())` at ONE substrate
145    /// owner, and the four coordinate slots + `ready` + `message`
146    /// are bound positionally so a slot-swap regression surfaces at
147    /// the constructor's positional pin rather than as silent drift
148    /// at every downstream `ProcessStatus.flux_resources` writer.
149    ///
150    /// Pre-lift the 7-slot `FluxResourceRef { …, last_check:
151    /// Some(chrono::Utc::now()) }` struct-literal was hand-authored
152    /// at TWO sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
153    /// threshold in `tatara-reconciler::phase_machine`:
154    /// * `handle_running` — the VERIFY-phase per-ref rebuild that
155    ///   restamps each polled ref with fresh `ready` + `message` +
156    ///   `last_check`.
157    /// * `flux_ref_from_json` — the post-SSA initial-state seeder
158    ///   that stamps a freshly-applied ref as `ready = false`,
159    ///   `message = Some("applied; awaiting reconciliation")`,
160    ///   `last_check = Some(Utc::now())`.
161    ///
162    /// Both sites restated the SAME seven field bindings in the
163    /// SAME order, and both restated the SAME `Some(chrono::Utc::
164    /// now())` stamp. A copy-paste that swapped two adjacent
165    /// `String` slots (`api_version` and `kind`, `kind` and `name`,
166    /// or `name` and `namespace` are all mechanically
167    /// indistinguishable at the type level) would silently persist
168    /// a slot-inverted ref that the downstream Flux fetch consumer
169    /// (via [`Self::fetch_coords`]) would then 404 on. Post-lift
170    /// both sites name the six inputs ONCE and route through this
171    /// ONE composer; the seventh slot (`last_check`) is stamped at
172    /// the composer's body so a future injection point (a fake
173    /// clock for testing, a monotonic-clock cross-check, a per-
174    /// fleet skew tolerance) lands at ONE substrate site rather
175    /// than at every hand-authored `Some(chrono::Utc::now())` stamp.
176    ///
177    /// Return-order pin lives at
178    /// [`tests::flux_resource_ref_observed_binds_slots_by_position`]
179    /// so a regression that swapped `api_version` and `kind` (both
180    /// `String`, same type) inside the constructor's argument list
181    /// fails-loudly here rather than as a silent wire-time 404 at
182    /// every downstream fetch consumer.
183    ///
184    /// Theory grounding: THEORY.md §II.1 invariant 5 (composition
185    /// preserves proofs — the 6-slot positional binding + the
186    /// `last_check` stamp compose at ONE typed owner, so a
187    /// regression across the four `String` coordinate slots fails
188    /// at the composer's positional pin rather than at every
189    /// downstream Flux status writer). THEORY.md §VI.1 (generation
190    /// over composition — the 7-slot struct-literal recurred at two
191    /// hand-authored sites past the ≥ 2 duplication trigger, and is
192    /// lifted to ONE typed composer here).
193    pub fn observed(
194        api_version: String,
195        kind: String,
196        name: String,
197        namespace: String,
198        ready: bool,
199        message: Option<String>,
200    ) -> Self {
201        Self {
202            api_version,
203            kind,
204            name,
205            namespace,
206            ready,
207            message,
208            last_check: Some(Utc::now()),
209        }
210    }
211
212    /// Compose a `FluxResourceRef` in the pre-observation shape — the
213    /// 4-slot coordinate binding with the three status slots defaulted
214    /// (`ready: false`, `message: None`, `last_check: None`). The
215    /// deterministic-fixture peer of [`Self::observed`] on the same
216    /// `→ FluxResourceRef` composer axis: `observed` reads the wall
217    /// clock and takes 6 args (a live post-fetch stamp), `pending`
218    /// reads no clock and takes 4 args (a pre-observation fixture
219    /// seed, and the natural base for `..base.clone()` spread updates
220    /// that vary a single slot for a per-corner test sweep).
221    ///
222    /// Pre-lift the SAME 7-slot `FluxResourceRef { api_version, kind,
223    /// name, namespace, ready: false, message: None, last_check: None
224    /// }` struct-literal was hand-authored at THREE workspace-wide
225    /// fixture sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
226    /// threshold:
227    ///
228    /// * [`crate::crd`]
229    ///   `crd::observed_flux_resources_tests::sample_flux_ref(name)`
230    ///   — the shared `Kustomization`/`flux-system` fixture the
231    ///   `Process::observed_flux_resources` pin family destructures
232    ///   for its `flux_resources`-populated corners.
233    /// * `tatara-reconciler::ssapply::tests::sample_flux_ref_for_diag`
234    ///   — the `HelmRelease`/`flux-system` fixture the
235    ///   `flux_ref_fetch_error_context` diagnostic-wording pin
236    ///   family destructures for its (kind, name) slot-coverage
237    ///   sweep.
238    /// * `tatara-reconciler::ssapply::tests::
239    ///   flux_ref_fetch_error_context_matches_pre_lift_hand_authored_wording`
240    ///   — the inline 7-slot literal inside the cross-substrate
241    ///   coherence pin's per-case sweep over three distinct
242    ///   `(api_version, kind, name, namespace)` tuples.
243    ///
244    /// All THREE sites restated the SAME seven field bindings in the
245    /// SAME order and the SAME three defaulted status slots (`ready:
246    /// false, message: None, last_check: None`), differing only in
247    /// the four coordinate `String` values. Post-lift each callsite
248    /// reads `FluxResourceRef::pending(<api_version>, <kind>, <name>,
249    /// <namespace>)` and the four-slot bind + three-slot default
250    /// sinks live at ONE substrate owner.
251    ///
252    /// The `impl Into<String>` signature accepts BOTH `&'static str`
253    /// (the fixture-helper sites that spell coordinate literals
254    /// inline) AND owned `String` (a future callsite handing off a
255    /// dynamically-derived coordinate) without widening. Matches the
256    /// discipline of the sibling substrate composers
257    /// [`crate::pool::PoolMember::unallocated`] +
258    /// [`crate::allocation::AllocationRef::new`] on the identity-slot
259    /// axis.
260    ///
261    /// A future normalization (a case-fold on the group, a
262    /// virtual-cluster prefix rewrite for multi-tenancy, a stricter
263    /// kind gate, a `generateName` fallback on the name slot, a
264    /// canonical rename of one of the three defaulted status slots
265    /// to a typed `PreObservation` marker) lands at THIS ONE
266    /// substrate primitive and every downstream fixture / helper
267    /// inherits the upgrade mechanically — no per-site edit at any
268    /// of the THREE listed callers or at future consumers (a
269    /// stable-name claim-arbiter's pending-ref seed, a kenshi-runner
270    /// pre-observation fixture, a mirror-audit drift-probe test
271    /// helper).
272    ///
273    /// Theory grounding: THEORY.md §II.1 invariant 5 (composition
274    /// preserves proofs — the 4-slot positional binding + the three
275    /// defaulted status slots compose at ONE typed owner, so a
276    /// regression across the four `String` coordinate slots fails at
277    /// the composer's positional pin rather than at every downstream
278    /// fixture consumer). THEORY.md §VI.1 (generation over
279    /// composition — the 7-slot struct-literal recurred at three
280    /// hand-authored fixture sites past the ≥ 2 duplication trigger,
281    /// and is lifted to ONE typed composer here).
282    #[must_use]
283    pub fn pending(
284        api_version: impl Into<String>,
285        kind: impl Into<String>,
286        name: impl Into<String>,
287        namespace: impl Into<String>,
288    ) -> Self {
289        Self {
290            api_version: api_version.into(),
291            kind: kind.into(),
292            name: name.into(),
293            namespace: namespace.into(),
294            ready: false,
295            message: None,
296            last_check: None,
297        }
298    }
299}
300
301/// Identifying coordinates of a rendered K8s resource — the
302/// `(apiVersion, kind, metadata.name, metadata.namespace)` 4-tuple
303/// every consumer that walks a rendered `serde_json::Value` resource
304/// unwraps by hand pre-lift.
305///
306/// The three K8s API-path segments (`apiVersion`, `kind`,
307/// `metadata.name`) are REQUIRED — a rendered resource missing any
308/// of them cannot be applied via kube-rs's dynamic API surface, so
309/// the extraction fails fast at the boundary rather than as a
310/// downstream `Api::patch` panic. `metadata.namespace` is
311/// intentionally kept as `Option<String>` because different consumers
312/// resolve the fallback differently: `apply_owned` uses the
313/// caller-supplied `namespace: &str` argument (the reconciler already
314/// resolved the target namespace upstream), while `flux_ref_from_json`
315/// records the K8s canonical `"default"` fallback into the persisted
316/// `FluxResourceRef.namespace` slot. The peer method
317/// [`Self::namespace_or_default`] applies the K8s canonical fallback
318/// (`Process::DEFAULT_NAMESPACE = "default"`) for consumers wanting
319/// the same shape [`FluxResourceRef.namespace`] carries.
320///
321/// Pre-lift the 3+1 slot extraction was hand-authored at TWO sites
322/// past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold across
323/// `tatara-reconciler`:
324/// * `tatara-reconciler::phase_machine::flux_ref_from_json` — the
325///   post-SSA `FluxResourceRef` builder that persists into
326///   `ProcessStatus.flux_resources`; namespace half fallback-
327///   defaulted to `"default"`.
328/// * `tatara-reconciler::ssapply::apply_owned` — the SSA entry
329///   point that extracts (apiVersion, kind, name) for the
330///   [`kube::Api::patch`] call; namespace half discarded (the
331///   `namespace: &str` argument comes from the caller upstream).
332///
333/// Both callsites restated the same three
334/// `.get(K).and_then(|v| v.as_str()).ok_or_else(|| anyhow!(...))?
335/// .to_string()` incantations with subtly different error wording
336/// (`"resource missing X"` vs `"rendered resource missing X"`); post-
337/// lift both route through this ONE substrate owner with the
338/// canonical `"rendered resource missing X"` wording. A future
339/// addition (case-fold on the group, a rename of the namespace
340/// fallback, a stricter kind gate, a Unicode-safe collation step,
341/// support for `metadata.generateName` as a name fallback) lands at
342/// the primitive's body on the substrate, not at 2 independent
343/// hand-writes across 2 reconciler files.
344///
345/// Namespace fallback const is shared with
346/// [`Process::DEFAULT_NAMESPACE`] — a rename of the K8s canonical
347/// default namespace lands at that ONE workspace-wide const, not at
348/// per-primitive local literals that would drift silently.
349#[derive(Clone, Debug, PartialEq, Eq)]
350pub struct RenderedResourceCoords {
351    /// `apiVersion` — the group+version pair kube-rs uses to resolve
352    /// the `ApiResource` for the SSA call.
353    pub api_version: String,
354    /// `kind` — the resource kind (Kustomization, HelmRelease, …).
355    pub kind: String,
356    /// `metadata.name` — the API-path leaf segment.
357    pub name: String,
358    /// `metadata.namespace` — raw from the resource, `None` when the
359    /// slot is absent (a cluster-scoped resource, or a namespaced
360    /// resource whose namespace was left for the API server to
361    /// substitute). Consumers apply their own fallback:
362    /// [`Self::namespace_or_default`] applies the K8s canonical
363    /// `"default"` (matching what [`FluxResourceRef.namespace`]
364    /// records); other consumers substitute a caller-supplied string
365    /// (see `tatara-reconciler::ssapply::apply_owned`).
366    pub namespace: Option<String>,
367}
368
369impl RenderedResourceCoords {
370    /// Extract the 4-tuple from a rendered K8s resource JSON `Value`.
371    ///
372    /// Fails with a canonical `"rendered resource missing X"` message
373    /// when any of the three required slots (`apiVersion`, `kind`,
374    /// `metadata.name`) is absent or non-string; `metadata.namespace`
375    /// is optional and captured as `None` when absent.
376    ///
377    /// The error wording is pinned by
378    /// [`tests::rendered_resource_coords_error_wording_is_canonical`]
379    /// so a regression that reshaped the message surfaces at the test
380    /// surface rather than as silent drift between the two pre-lift
381    /// call sites (which used subtly different wording — `"resource
382    /// missing X"` in `apply_owned` vs `"rendered resource missing
383    /// X"` in `flux_ref_from_json`).
384    pub fn from_json(res: &Value) -> anyhow::Result<Self> {
385        let api_version = res
386            .get("apiVersion")
387            .and_then(|v| v.as_str())
388            .ok_or_else(|| anyhow::anyhow!("rendered resource missing apiVersion"))?
389            .to_string();
390        let kind = res
391            .get("kind")
392            .and_then(|v| v.as_str())
393            .ok_or_else(|| anyhow::anyhow!("rendered resource missing kind"))?
394            .to_string();
395        let metadata = res.get("metadata");
396        let name = metadata
397            .and_then(|m| m.get("name"))
398            .and_then(|v| v.as_str())
399            .ok_or_else(|| anyhow::anyhow!("rendered resource missing metadata.name"))?
400            .to_string();
401        let namespace = metadata
402            .and_then(|m| m.get("namespace"))
403            .and_then(|v| v.as_str())
404            .map(str::to_string);
405        Ok(Self {
406            api_version,
407            kind,
408            name,
409            namespace,
410        })
411    }
412
413    /// `metadata.namespace` slice with the K8s canonical `"default"`
414    /// fallback applied — matching what [`Process::DEFAULT_NAMESPACE`]
415    /// spells for the `Process`-borne coordinate primitive family
416    /// and what [`FluxResourceRef.namespace`] records into
417    /// `ProcessStatus.flux_resources`.
418    pub fn namespace_or_default(&self) -> &str {
419        self.namespace
420            .as_deref()
421            .unwrap_or(Process::DEFAULT_NAMESPACE)
422    }
423}
424
425/// A boundary condition paired with its current satisfaction state.
426#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
427#[serde(rename_all = "camelCase")]
428pub struct CheckedCondition {
429    #[serde(flatten)]
430    pub condition: Condition,
431    pub satisfied: bool,
432    #[serde(default, skip_serializing_if = "Option::is_none")]
433    pub last_check: Option<DateTime<Utc>>,
434    #[serde(default, skip_serializing_if = "Option::is_none")]
435    pub message: Option<String>,
436}
437
438impl CheckedCondition {
439    /// True iff every [`CheckedCondition`] in the slice has
440    /// `satisfied == true` — the ONE-line collapse of the paired
441    /// `checked.iter().all(|c| c.satisfied)` incantation the
442    /// reconciler's precondition + postcondition boundary gates both
443    /// spelled by hand pre-lift.
444    ///
445    /// Pre-lift the SAME `.iter().all(|c| c.satisfied)` chain was
446    /// hand-authored at TWO sites past the ★★ PRIME-DIRECTIVE ≥ 2
447    /// duplication threshold in `tatara-reconciler::phase_machine`,
448    /// each walking the SAME `Vec<CheckedCondition>` → `bool`
449    /// projection to gate a phase transition on a boundary predicate:
450    /// * `handle_execing` — the PROVE-phase precondition gate that
451    ///   stays in Execing (heartbeat requeue) while any precondition
452    ///   remains unsatisfied and proceeds to RENDER only when every
453    ///   precondition holds.
454    /// * `handle_running` — the VERIFY-phase postcondition gate that
455    ///   stays in Running (heartbeat requeue) while any postcondition
456    ///   remains unsatisfied and advances to Attested only when every
457    ///   postcondition holds.
458    ///
459    /// Both sites walked the SAME `Iterator::all` short-circuit on the
460    /// SAME `bool` slot of the SAME struct. Post-lift both consumers
461    /// name the slice ONCE and route through this ONE primitive; the
462    /// vacuous-truth corner (empty slice → `true`, matching
463    /// [`Iterator::all`]'s empty-input identity) sits at ONE substrate
464    /// site so a future normalization (a per-slot weight overlay, a
465    /// per-kind override that treats `Warn`-severity failures as
466    /// satisfied, a compliance-baseline gate that requires N-of-M
467    /// rather than all-of-M) lands at ONE substrate function and both
468    /// downstream phase gates inherit the upgrade mechanically.
469    ///
470    /// Return-form axis: `bool` — the exact type each phase gate
471    /// pre-lift bound at `let all_pass = <chain>;` and immediately
472    /// consumed in a `!all_pass` short-circuit + a `message` slot's
473    /// ternary branch. The `&[Self]` argument accepts every pre-lift
474    /// slice provenance verbatim: a `&Vec<CheckedCondition>` (both
475    /// pre-lift sites had the `Vec` on the stack from
476    /// [`crate::phase_machine::evaluate_conditions`]'s owned return)
477    /// coerces through auto-deref, so no callsite has to change its
478    /// upstream provenance to route through the primitive.
479    ///
480    /// Peer to the sibling projection [`Self::satisfied`] on the (row
481    /// scope × predicate) axis pair: `satisfied` is the per-row
482    /// projection; `all_satisfied` is the slice-wide fold of the same
483    /// bit. Both live on `CheckedCondition` so a future rename or
484    /// per-slot normalization travels through the same owner without
485    /// splitting between "per-row" and "slice-wide" call sinks.
486    ///
487    /// Return-shape pin lives at
488    /// [`tests::checked_condition_all_satisfied_matches_pre_lift_iter_all_chain_shape`]
489    /// so a regression that flipped the fold direction (`any` for
490    /// `all`), inverted the bit (`!c.satisfied`), or reshaped the
491    /// return form (an owned `Vec<bool>` instead of the folded `bool`)
492    /// fails-loudly here rather than as silent operator-facing skew
493    /// between the pre-lift `if !all_pass { requeue }` gate and the
494    /// post-lift call — every downstream consumer would still
495    /// short-circuit but on inverted semantics.
496    ///
497    /// Theory grounding: THEORY.md §VI.1 (generation over composition
498    /// — the 1-line `.iter().all(...)` chain recurred at two hand-
499    /// authored sites past the ≥ 2 duplication trigger, and is lifted
500    /// to ONE typed fold here). THEORY.md §II.1 invariant 5
501    /// (composition preserves proofs — the empty-slice vacuous-truth
502    /// corner + the fold direction + the projected bit's polarity all
503    /// bind at ONE substrate site, so a regression across any of the
504    /// three surfaces at [`tests::checked_condition_all_satisfied_*`]
505    /// pin rather than as silent gate-flip at every downstream phase
506    /// handler).
507    #[must_use]
508    pub fn all_satisfied(checked: &[Self]) -> bool {
509        checked.iter().all(|c| c.satisfied)
510    }
511}
512
513/// Summary of boundary verification.
514#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
515#[serde(rename_all = "camelCase")]
516pub struct BoundaryStatus {
517    #[serde(default)]
518    pub preconditions: Vec<CheckedCondition>,
519    #[serde(default)]
520    pub postconditions: Vec<CheckedCondition>,
521    /// Absolute deadline for VERIFY (derived from `spec.boundary.timeout`).
522    #[serde(default, skip_serializing_if = "Option::is_none")]
523    pub deadline: Option<DateTime<Utc>>,
524}
525
526/// Summary of compliance checks at the latest attestation.
527#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
528#[serde(rename_all = "camelCase")]
529pub struct ComplianceStatus {
530    #[serde(default, skip_serializing_if = "Option::is_none")]
531    pub baseline: Option<String>,
532    pub satisfied: u32,
533    pub violated: u32,
534    pub total: u32,
535    #[serde(default)]
536    pub violations: Vec<String>,
537}
538
539#[cfg(test)]
540mod tests {
541    use super::*;
542    use serde_json::json;
543
544    // ─── RenderedResourceCoords substrate pins ──────────────────────
545
546    #[test]
547    fn rendered_resource_coords_from_json_extracts_all_four_slots_when_present() {
548        let res = json!({
549            "apiVersion": "kustomize.toolkit.fluxcd.io/v1",
550            "kind": "Kustomization",
551            "metadata": {
552                "name": "observability-stack",
553                "namespace": "flux-system",
554            },
555        });
556        let c = RenderedResourceCoords::from_json(&res).expect("extract");
557        assert_eq!(c.api_version, "kustomize.toolkit.fluxcd.io/v1");
558        assert_eq!(c.kind, "Kustomization");
559        assert_eq!(c.name, "observability-stack");
560        assert_eq!(c.namespace.as_deref(), Some("flux-system"));
561    }
562
563    #[test]
564    fn rendered_resource_coords_from_json_captures_absent_namespace_as_none() {
565        // Cluster-scoped resource — `metadata.namespace` intentionally absent.
566        let res = json!({
567            "apiVersion": "v1",
568            "kind": "Namespace",
569            "metadata": {"name": "demo-test"},
570        });
571        let c = RenderedResourceCoords::from_json(&res).expect("extract");
572        assert_eq!(c.namespace, None);
573        assert_eq!(c.name, "demo-test");
574    }
575
576    #[test]
577    fn rendered_resource_coords_from_json_errors_on_missing_api_version() {
578        let res = json!({"kind": "K", "metadata": {"name": "n"}});
579        let e = RenderedResourceCoords::from_json(&res).expect_err("must error");
580        assert_eq!(e.to_string(), "rendered resource missing apiVersion");
581    }
582
583    #[test]
584    fn rendered_resource_coords_from_json_errors_on_missing_kind() {
585        let res = json!({"apiVersion": "v1", "metadata": {"name": "n"}});
586        let e = RenderedResourceCoords::from_json(&res).expect_err("must error");
587        assert_eq!(e.to_string(), "rendered resource missing kind");
588    }
589
590    #[test]
591    fn rendered_resource_coords_from_json_errors_on_missing_metadata_name() {
592        let res = json!({"apiVersion": "v1", "kind": "K", "metadata": {}});
593        let e = RenderedResourceCoords::from_json(&res).expect_err("must error");
594        assert_eq!(e.to_string(), "rendered resource missing metadata.name");
595    }
596
597    #[test]
598    fn rendered_resource_coords_from_json_errors_on_missing_metadata_object() {
599        // `metadata` absent entirely — same failure as `metadata.name` missing,
600        // because the API-path leaf segment cannot be resolved.
601        let res = json!({"apiVersion": "v1", "kind": "K"});
602        let e = RenderedResourceCoords::from_json(&res).expect_err("must error");
603        assert_eq!(e.to_string(), "rendered resource missing metadata.name");
604    }
605
606    #[test]
607    fn rendered_resource_coords_from_json_errors_on_non_string_slot() {
608        // A numeric `apiVersion` slot falls through the `.as_str()` gate and
609        // triggers the same missing-slot failure as absence — the API-path
610        // segment is not a string.
611        let res = json!({
612            "apiVersion": 42,
613            "kind": "K",
614            "metadata": {"name": "n"},
615        });
616        let e = RenderedResourceCoords::from_json(&res).expect_err("must error");
617        assert_eq!(e.to_string(), "rendered resource missing apiVersion");
618    }
619
620    #[test]
621    fn rendered_resource_coords_error_wording_is_canonical() {
622        // Pins the exact spelling every downstream consumer sees.
623        // Pre-lift wording differed across the two call sites (`"resource
624        // missing X"` in `apply_owned` vs `"rendered resource missing X"` in
625        // `flux_ref_from_json`); post-lift the canonical wording is
626        // `"rendered resource missing X"` at every site.
627        let cases = [
628            (
629                "apiVersion",
630                json!({"kind": "K", "metadata": {"name": "n"}}),
631            ),
632            (
633                "kind",
634                json!({"apiVersion": "v1", "metadata": {"name": "n"}}),
635            ),
636            (
637                "metadata.name",
638                json!({"apiVersion": "v1", "kind": "K", "metadata": {}}),
639            ),
640        ];
641        for (slot, res) in cases {
642            let e = RenderedResourceCoords::from_json(&res).expect_err("must error");
643            assert_eq!(
644                e.to_string(),
645                format!("rendered resource missing {slot}"),
646                "slot {slot} error must be canonical"
647            );
648        }
649    }
650
651    #[test]
652    fn rendered_resource_coords_namespace_or_default_returns_slice_when_some() {
653        let c = RenderedResourceCoords {
654            api_version: "v1".into(),
655            kind: "K".into(),
656            name: "n".into(),
657            namespace: Some("prod".into()),
658        };
659        assert_eq!(c.namespace_or_default(), "prod");
660    }
661
662    #[test]
663    fn rendered_resource_coords_namespace_or_default_falls_back_when_none() {
664        let c = RenderedResourceCoords {
665            api_version: "v1".into(),
666            kind: "K".into(),
667            name: "n".into(),
668            namespace: None,
669        };
670        assert_eq!(c.namespace_or_default(), Process::DEFAULT_NAMESPACE);
671        assert_eq!(c.namespace_or_default(), "default");
672    }
673
674    // ─── FluxResourceRef::fetch_coords substrate pins ─────────────
675    //
676    // The 4-slot `(&namespace, &api_version, &kind, &name)` borrow
677    // projection lifts the pre-existing 5-slot `ssapply::fetch(client,
678    // &r.namespace, &r.api_version, &r.kind, &r.name)` splat that
679    // recurred at TWO hand-authored sites in
680    // `tatara-reconciler::phase_machine` (`handle_running`,
681    // `handle_attested`) past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
682    // trigger. These pins bind the slot order at fail-before-pass-
683    // after granularity so a regression that swapped `namespace` and
684    // `api_version` (both `String`, mechanically interchangeable to
685    // a bad refactor) surfaces HERE rather than as a silent wire-time
686    // 404 at every downstream Flux fetch consumer.
687
688    fn sample_flux_ref() -> FluxResourceRef {
689        // Slot values are deliberately distinct so a swap between any
690        // two adjacent tuple positions surfaces as an equality
691        // failure at the assertion site — a slot-inversion regression
692        // cannot masquerade as identity by accident.
693        FluxResourceRef {
694            api_version: "kustomize.toolkit.fluxcd.io/v1".to_string(),
695            kind: "Kustomization".to_string(),
696            name: "observability-stack".to_string(),
697            namespace: "flux-system".to_string(),
698            ready: true,
699            message: None,
700            last_check: None,
701        }
702    }
703
704    #[test]
705    fn flux_resource_ref_fetch_coords_binds_slots_by_position() {
706        // Positional pin: the 4-tuple return binds
707        // `(namespace, api_version, kind, name)` in THAT order,
708        // matching the raw `ssapply::fetch(client, ns, av, kind,
709        // name)` positional signature every pre-lift callsite splatted
710        // into. A regression that swapped ANY pair of adjacent slots
711        // (all four axes are `String` and mechanically
712        // indistinguishable at the type level) would surface here
713        // rather than as an operator-visible wire-form 404 at every
714        // downstream fetch consumer.
715        let r = sample_flux_ref();
716        let (ns, av, kind, name) = r.fetch_coords();
717        assert_eq!(ns, "flux-system", "position 0 must be namespace");
718        assert_eq!(
719            av, "kustomize.toolkit.fluxcd.io/v1",
720            "position 1 must be api_version"
721        );
722        assert_eq!(kind, "Kustomization", "position 2 must be kind");
723        assert_eq!(name, "observability-stack", "position 3 must be name");
724    }
725
726    #[test]
727    fn flux_resource_ref_fetch_coords_returns_borrows_of_owned_slots() {
728        // Borrow-discipline pin: the 4-tuple returns `&str` borrows
729        // of the enclosing `FluxResourceRef`'s owned `String` slots —
730        // NOT a fresh allocation or a clone. A regression that
731        // switched the projection to owned strings (via `.clone()` or
732        // `format!`) would defeat the zero-copy contract and would
733        // surface here via pointer-identity comparison.
734        let r = sample_flux_ref();
735        let (ns, av, kind, name) = r.fetch_coords();
736        assert!(std::ptr::eq(ns.as_ptr(), r.namespace.as_ptr()));
737        assert!(std::ptr::eq(av.as_ptr(), r.api_version.as_ptr()));
738        assert!(std::ptr::eq(kind.as_ptr(), r.kind.as_ptr()));
739        assert!(std::ptr::eq(name.as_ptr(), r.name.as_ptr()));
740    }
741
742    #[test]
743    fn flux_resource_ref_fetch_coords_is_a_pure_borrow_projection() {
744        // Purity pin: calling the projection twice on the same ref
745        // returns byte-identical slices (same pointer, same length).
746        // A regression that introduced state — a lazy-cached slot
747        // computed on first call, a normalization step that ran once
748        // and cached — would surface here rather than as silent drift
749        // between the VERIFY-phase and ATTEST-heartbeat consumers on
750        // the SAME ref within one reconcile pass.
751        let r = sample_flux_ref();
752        let a = r.fetch_coords();
753        let b = r.fetch_coords();
754        assert!(std::ptr::eq(a.0.as_ptr(), b.0.as_ptr()));
755        assert!(std::ptr::eq(a.1.as_ptr(), b.1.as_ptr()));
756        assert!(std::ptr::eq(a.2.as_ptr(), b.2.as_ptr()));
757        assert!(std::ptr::eq(a.3.as_ptr(), b.3.as_ptr()));
758    }
759
760    #[test]
761    fn flux_resource_ref_fetch_coords_ignores_status_slots() {
762        // Coverage pin: the projection exposes ONLY the four API-path
763        // slots the fetch call requires; the ref's status slots
764        // (`ready`, `message`, `last_check`) are deliberately absent
765        // from the tuple. The fetch signature admits four `&str`
766        // slots, and the projection carries EXACTLY those four — no
767        // silent widening that would surface as an arity mismatch at
768        // every downstream `fetch(...)` call.
769        let r = sample_flux_ref();
770        let coords = r.fetch_coords();
771        assert_eq!(
772            std::mem::size_of_val(&coords),
773            std::mem::size_of::<(&str, &str, &str, &str)>(),
774            "the 4-tuple width must match the raw fetch signature's four `&str` slots"
775        );
776    }
777
778    // ─── FluxResourceRef::observed substrate pins ─────────────────
779    //
780    // The 6-arg composer stamps `last_check` at ONE substrate site
781    // (the pre-lift 7-slot struct-literal restated `Some(chrono::
782    // Utc::now())` at TWO hand-authored sites in
783    // `tatara-reconciler::phase_machine` — `handle_running`'s per-
784    // ref VERIFY rebuild and `flux_ref_from_json`'s post-SSA
785    // seeder). These pins bind the six input slots by position so a
786    // regression that swapped `api_version` and `kind` (both
787    // `String`, mechanically interchangeable to a bad refactor)
788    // surfaces HERE rather than as a silent wire-time 404 at every
789    // downstream fetch consumer.
790    //
791    // Every test constructs distinct values across the four
792    // `String` coordinate slots so a slot swap fails structurally
793    // rather than by accident of matching literals.
794
795    #[test]
796    fn flux_resource_ref_observed_binds_slots_by_position() {
797        // Positional pin: the 6-arg constructor binds
798        // `(api_version, kind, name, namespace, ready, message)`
799        // in THAT order, matching the pre-lift 7-slot struct-
800        // literal's declaration order. A regression that swapped
801        // ANY pair of adjacent `String` coordinate slots (all four
802        // are mechanically indistinguishable at the type level)
803        // would surface here rather than as a wire-time 404 at
804        // every downstream Flux fetch consumer.
805        let r = FluxResourceRef::observed(
806            "kustomize.toolkit.fluxcd.io/v1".to_string(),
807            "Kustomization".to_string(),
808            "observability-stack".to_string(),
809            "flux-system".to_string(),
810            true,
811            Some("healthy".to_string()),
812        );
813        assert_eq!(r.api_version, "kustomize.toolkit.fluxcd.io/v1");
814        assert_eq!(r.kind, "Kustomization");
815        assert_eq!(r.name, "observability-stack");
816        assert_eq!(r.namespace, "flux-system");
817        assert!(r.ready);
818        assert_eq!(r.message.as_deref(), Some("healthy"));
819    }
820
821    #[test]
822    fn flux_resource_ref_observed_stamps_last_check_at_now() {
823        // Stamp pin: the `last_check` slot is filled with
824        // `Some(<recent Utc>)` at the composer's body. A
825        // regression that dropped the stamp (leaving `None`) or
826        // shifted it to a stale constant would surface here rather
827        // than as silent operator-observed staleness at
828        // `ProcessStatus.flux_resources` panels. Bounds the stamp
829        // to within a generous 5s window of the composer call so
830        // slow CI runners do not false-positive.
831        let before = Utc::now();
832        let r = FluxResourceRef::observed(
833            "v1".to_string(),
834            "K".to_string(),
835            "n".to_string(),
836            "ns".to_string(),
837            false,
838            None,
839        );
840        let after = Utc::now();
841        let stamp = r.last_check.expect("observed must stamp last_check");
842        assert!(stamp >= before, "stamp must be >= before-call `now`");
843        assert!(stamp <= after, "stamp must be <= after-call `now`");
844    }
845
846    #[test]
847    fn flux_resource_ref_observed_round_trips_through_fetch_coords() {
848        // Cross-composer coherence pin: a ref built by `observed`
849        // then unpacked by `fetch_coords` returns the same four
850        // slots in the peer projection's positional order
851        // `(namespace, api_version, kind, name)`. Composition of
852        // the two primitives on the same ref preserves the slot
853        // identity — a regression at either end (a slot swap in
854        // `observed`, or a slot swap in `fetch_coords`) would
855        // surface here rather than as silent drift between the
856        // writer and the reader on the same persisted slice.
857        let r = FluxResourceRef::observed(
858            "helm.toolkit.fluxcd.io/v2".to_string(),
859            "HelmRelease".to_string(),
860            "prometheus-op".to_string(),
861            "monitoring".to_string(),
862            false,
863            Some("applied; awaiting reconciliation".to_string()),
864        );
865        let (ns, av, kind, name) = r.fetch_coords();
866        assert_eq!(ns, "monitoring");
867        assert_eq!(av, "helm.toolkit.fluxcd.io/v2");
868        assert_eq!(kind, "HelmRelease");
869        assert_eq!(name, "prometheus-op");
870    }
871
872    #[test]
873    fn flux_resource_ref_observed_matches_pre_lift_struct_literal_field_for_field() {
874        // Byte-for-byte parity pin against the pre-lift 7-slot
875        // struct-literal spelled at BOTH `phase_machine::
876        // handle_running` and `phase_machine::flux_ref_from_json`.
877        // A regression that reordered any of the six inputs at
878        // the composer's argument list, or that swapped a
879        // `ready`/`message` pair inside the composer's body,
880        // would surface here rather than as silent divergence
881        // between the composer's output and the pre-lift hand-
882        // authored shape every persisted status writer restated.
883        let composed = FluxResourceRef::observed(
884            "source.toolkit.fluxcd.io/v1beta2".to_string(),
885            "OCIRepository".to_string(),
886            "chart-source".to_string(),
887            "flux-system".to_string(),
888            false,
889            Some("applied; awaiting reconciliation".to_string()),
890        );
891        // Hand-authored the same seven slots directly, with a
892        // held-open stamp window across the composer call.
893        let stamped = composed.last_check.expect("stamped");
894        let baseline = FluxResourceRef {
895            api_version: "source.toolkit.fluxcd.io/v1beta2".to_string(),
896            kind: "OCIRepository".to_string(),
897            name: "chart-source".to_string(),
898            namespace: "flux-system".to_string(),
899            ready: false,
900            message: Some("applied; awaiting reconciliation".to_string()),
901            last_check: Some(stamped),
902        };
903        assert_eq!(composed.api_version, baseline.api_version);
904        assert_eq!(composed.kind, baseline.kind);
905        assert_eq!(composed.name, baseline.name);
906        assert_eq!(composed.namespace, baseline.namespace);
907        assert_eq!(composed.ready, baseline.ready);
908        assert_eq!(composed.message, baseline.message);
909        assert_eq!(composed.last_check, baseline.last_check);
910    }
911
912    // ─── FluxResourceRef::pending substrate pins ─────────────────────
913    //
914    // Bind [`FluxResourceRef::pending`] at fail-before-pass-after
915    // granularity so a regression that leaked a non-default status
916    // slot (`ready: true`, `message: Some("something")`, `last_check:
917    // Some(Utc::now())`), swapped two adjacent coordinate slots (all
918    // four are `String` and mechanically interchangeable at the type
919    // level), or diverged from the pre-lift 7-slot struct-literal on
920    // any of the seven fields surfaces HERE rather than as silent
921    // operator-invisible drift at the 3 downstream fixture consumers
922    // (crd.rs `sample_flux_ref`, ssapply.rs `sample_flux_ref_for_diag`,
923    // ssapply.rs `flux_ref_fetch_error_context_matches_pre_lift_...`).
924    //
925    // Each pin is fail-before-pass-after: the primitive did not exist
926    // pre-lift, so any test that invokes it fails to compile pre-lift
927    // and passes post-lift; the byte-identity pins below then bind
928    // the specific shape choice.
929
930    #[test]
931    fn flux_resource_ref_pending_binds_coordinate_slots_by_position() {
932        // Positional pin: the 4-arg constructor binds `(api_version,
933        // kind, name, namespace)` in THAT order, matching the pre-
934        // lift 7-slot struct-literal's declaration order. A regression
935        // that swapped ANY pair of adjacent `String` coordinate slots
936        // (all four are mechanically indistinguishable at the type
937        // level) would surface here rather than as a wire-time 404 at
938        // every downstream Flux fetch consumer that walks
939        // `FluxResourceRef.fetch_coords`.
940        let r = FluxResourceRef::pending(
941            "kustomize.toolkit.fluxcd.io/v1",
942            "Kustomization",
943            "observability-stack",
944            "flux-system",
945        );
946        assert_eq!(r.api_version, "kustomize.toolkit.fluxcd.io/v1");
947        assert_eq!(r.kind, "Kustomization");
948        assert_eq!(r.name, "observability-stack");
949        assert_eq!(r.namespace, "flux-system");
950    }
951
952    #[test]
953    fn flux_resource_ref_pending_defaults_every_status_slot() {
954        // Default-slot pin: the three status slots (`ready`, `message`,
955        // `last_check`) are ALL defaulted at the composer's body — no
956        // wall-clock read, no non-`None` `message` leak, no `ready:
957        // true` regression that would silently un-pend the fixture.
958        // A regression that stamped `Some(Utc::now())` into
959        // `last_check` (matching the sibling `observed` composer's
960        // wall-clock read) would silently defeat the deterministic-
961        // fixture contract the peer partition holds.
962        let r = FluxResourceRef::pending("v1", "K", "n", "ns");
963        assert!(
964            !r.ready,
965            "pending composer must default `ready` to false — non-`false` breaks the pre-observation contract"
966        );
967        assert_eq!(
968            r.message, None,
969            "pending composer must default `message` to None — non-`None` leaks a stale message into the pre-observation seed",
970        );
971        assert_eq!(
972            r.last_check, None,
973            "pending composer must default `last_check` to None — a `Some(_)` leak defeats the deterministic-peer partition against `observed`",
974        );
975    }
976
977    #[test]
978    fn flux_resource_ref_pending_accepts_both_owned_and_borrowed_coordinates() {
979        // The `impl Into<String>` ergonomic contract: both `&'static
980        // str` literals (the fixture-helper sites that spell
981        // coordinates inline) and owned `String` (a future callsite
982        // handing off a dynamically-derived coordinate) round-trip
983        // through the SAME composer signature without widening. A
984        // regression that specialised the signature to one form or
985        // the other would break either the inline-literal helpers or
986        // the owned-`String` downstream consumers.
987        let borrowed: FluxResourceRef = FluxResourceRef::pending("v1", "K", "n", "ns");
988        let owned: FluxResourceRef = FluxResourceRef::pending(
989            "v1".to_string(),
990            "K".to_string(),
991            "n".to_string(),
992            "ns".to_string(),
993        );
994        assert_eq!(borrowed.api_version, owned.api_version);
995        assert_eq!(borrowed.kind, owned.kind);
996        assert_eq!(borrowed.name, owned.name);
997        assert_eq!(borrowed.namespace, owned.namespace);
998        assert_eq!(borrowed.ready, owned.ready);
999        assert_eq!(borrowed.message, owned.message);
1000        assert_eq!(borrowed.last_check, owned.last_check);
1001    }
1002
1003    #[test]
1004    fn flux_resource_ref_pending_matches_pre_lift_struct_literal_bytewise() {
1005        // Byte-for-byte parity pin against the pre-lift 7-slot
1006        // struct-literal spelled at ALL THREE hand-authored fixture
1007        // sites (crd.rs `sample_flux_ref`, ssapply.rs
1008        // `sample_flux_ref_for_diag`, ssapply.rs inline in the
1009        // cross-substrate coherence pin's per-case sweep). Sweeps the
1010        // three representative coordinate tuples the pre-lift sites
1011        // used, so a regression that special-cased any one variant
1012        // (a `Kustomization`-only path via `if kind ==
1013        // "Kustomization" ...`) surfaces here.
1014        let cases = [
1015            (
1016                "kustomize.toolkit.fluxcd.io/v1",
1017                "Kustomization",
1018                "observability-stack",
1019                "flux-system",
1020            ),
1021            (
1022                "helm.toolkit.fluxcd.io/v2",
1023                "HelmRelease",
1024                "prometheus-op",
1025                "monitoring",
1026            ),
1027            (
1028                "source.toolkit.fluxcd.io/v1beta2",
1029                "OCIRepository",
1030                "chart-source",
1031                "flux-system",
1032            ),
1033        ];
1034        for (av, kind, name, ns) in cases {
1035            let composed = FluxResourceRef::pending(av, kind, name, ns);
1036            let hand_authored = FluxResourceRef {
1037                api_version: av.to_string(),
1038                kind: kind.to_string(),
1039                name: name.to_string(),
1040                namespace: ns.to_string(),
1041                ready: false,
1042                message: None,
1043                last_check: None,
1044            };
1045            assert_eq!(composed.api_version, hand_authored.api_version);
1046            assert_eq!(composed.kind, hand_authored.kind);
1047            assert_eq!(composed.name, hand_authored.name);
1048            assert_eq!(composed.namespace, hand_authored.namespace);
1049            assert_eq!(composed.ready, hand_authored.ready);
1050            assert_eq!(composed.message, hand_authored.message);
1051            assert_eq!(composed.last_check, hand_authored.last_check);
1052        }
1053    }
1054
1055    #[test]
1056    fn flux_resource_ref_pending_partitions_the_composer_axis_against_observed() {
1057        // Cross-composer partition pin: `pending` and `observed`
1058        // both produce `FluxResourceRef` but partition the composer
1059        // axis at the (deterministic-fixture, wall-clock-observed)
1060        // split — `pending` reads no clock and leaves `last_check:
1061        // None`, `observed` reads the wall clock and stamps
1062        // `last_check: Some(<recent Utc>)`. A regression that merged
1063        // either primitive onto the other (a `pending` that started
1064        // stamping `Utc::now()`, an `observed` that started leaving
1065        // `last_check: None`) would collapse the partition and
1066        // surface here.
1067        let p = FluxResourceRef::pending("v1", "K", "n", "ns");
1068        assert_eq!(
1069            p.last_check, None,
1070            "pending is deterministic — no clock read"
1071        );
1072        let o = FluxResourceRef::observed(
1073            "v1".to_string(),
1074            "K".to_string(),
1075            "n".to_string(),
1076            "ns".to_string(),
1077            false,
1078            None,
1079        );
1080        assert!(o.last_check.is_some(), "observed reads the wall clock");
1081    }
1082
1083    #[test]
1084    fn flux_resource_ref_pending_composes_with_fetch_coords_at_pre_observation_shape() {
1085        // Cross-composer coherence pin: a ref built by `pending`
1086        // then unpacked by `fetch_coords` returns the same four
1087        // slots in the peer projection's positional order
1088        // `(namespace, api_version, kind, name)`. Composition of
1089        // the two primitives on the same pre-observation ref
1090        // preserves the slot identity — a regression at either end
1091        // (a slot swap in `pending`, or a slot swap in
1092        // `fetch_coords`) would surface here rather than as silent
1093        // drift between the fixture writer and every downstream
1094        // fetch reader.
1095        let r = FluxResourceRef::pending(
1096            "helm.toolkit.fluxcd.io/v2",
1097            "HelmRelease",
1098            "prometheus-op",
1099            "monitoring",
1100        );
1101        let (ns, av, kind, name) = r.fetch_coords();
1102        assert_eq!(ns, "monitoring");
1103        assert_eq!(av, "helm.toolkit.fluxcd.io/v2");
1104        assert_eq!(kind, "HelmRelease");
1105        assert_eq!(name, "prometheus-op");
1106    }
1107
1108    #[test]
1109    fn rendered_resource_coords_namespace_fallback_shares_process_default_const() {
1110        // Byte-identity between the namespace fallback and the workspace-
1111        // wide `Process::DEFAULT_NAMESPACE` const. A regression that spelled
1112        // the fallback as any other string ("kube-system", "", "default-ns")
1113        // would silently drift between the coord-primitive family here and
1114        // the `Process`-borne family in `crd.rs` — surfaces here rather than
1115        // as operator-observed namespace routing skew between the two
1116        // primitive families.
1117        let c = RenderedResourceCoords {
1118            api_version: "v1".into(),
1119            kind: "K".into(),
1120            name: "n".into(),
1121            namespace: None,
1122        };
1123        assert_eq!(c.namespace_or_default(), Process::DEFAULT_NAMESPACE);
1124    }
1125
1126    // ─── CheckedCondition::all_satisfied substrate pins ─────────────
1127    //
1128    // Bind [`CheckedCondition::all_satisfied`] at fail-before-pass-
1129    // after granularity so a regression that flipped the fold
1130    // direction (`any` for `all`), inverted the projected bit
1131    // (`!c.satisfied`), reshaped the return form (an owned
1132    // `Vec<bool>` instead of the folded `bool`), or dropped the
1133    // vacuous-truth empty-slice corner surfaces HERE rather than as
1134    // silent operator-facing gate-flip at the reconciler's PROVE-
1135    // phase precondition gate + VERIFY-phase postcondition gate.
1136
1137    fn sample_checked(satisfied: bool) -> CheckedCondition {
1138        CheckedCondition {
1139            condition: crate::boundary::Condition {
1140                kind: crate::boundary::ConditionKind::ProcessPhase,
1141                params: serde_json::json!({}),
1142            },
1143            satisfied,
1144            last_check: None,
1145            message: None,
1146        }
1147    }
1148
1149    #[test]
1150    fn checked_condition_all_satisfied_returns_true_when_every_row_is_satisfied() {
1151        // Populated slice, every row `satisfied = true` — the RENDER-
1152        // phase advance corner: `handle_execing` proceeds to intent
1153        // dispatch iff every precondition holds.
1154        let checked = vec![
1155            sample_checked(true),
1156            sample_checked(true),
1157            sample_checked(true),
1158        ];
1159        assert!(
1160            CheckedCondition::all_satisfied(&checked),
1161            "all-satisfied slice must fold to true — a regression that inverted the bit would silently gate every RENDER advance behind an inverted predicate"
1162        );
1163    }
1164
1165    #[test]
1166    fn checked_condition_all_satisfied_returns_false_when_any_row_is_unsatisfied() {
1167        // Populated slice with ONE unsatisfied row — the heartbeat
1168        // requeue corner: `handle_running` stays in Running while any
1169        // postcondition remains unsatisfied.
1170        let mixed = vec![
1171            sample_checked(true),
1172            sample_checked(false),
1173            sample_checked(true),
1174        ];
1175        assert!(
1176            !CheckedCondition::all_satisfied(&mixed),
1177            "mixed slice must fold to false — a regression that folded via `any` instead of `all` would silently green-light every VERIFY advance"
1178        );
1179    }
1180
1181    #[test]
1182    fn checked_condition_all_satisfied_returns_false_when_every_row_is_unsatisfied() {
1183        // Populated slice with EVERY row unsatisfied — the tightest
1184        // gate corner: no phase advance is legal.
1185        let none_pass = vec![sample_checked(false), sample_checked(false)];
1186        assert!(
1187            !CheckedCondition::all_satisfied(&none_pass),
1188            "all-unsatisfied slice must fold to false"
1189        );
1190    }
1191
1192    #[test]
1193    fn checked_condition_all_satisfied_returns_true_on_empty_slice() {
1194        // Empty-slice vacuous-truth corner: `[T]::iter().all(_)`
1195        // returns `true` on empty input, and the pre-lift phase
1196        // gate's `if !preconditions.is_empty() { ... }` guard sat
1197        // BEFORE the fold, so the fold itself never saw an empty
1198        // slice in production. Post-lift the primitive absorbs the
1199        // empty corner cleanly — a caller that drops the outer
1200        // `is_empty()` guard (a future path that folds every gate
1201        // through this ONE primitive without a prior gate) still
1202        // sees the vacuous-truth semantics that match
1203        // [`Iterator::all`].
1204        let empty: Vec<CheckedCondition> = vec![];
1205        assert!(
1206            CheckedCondition::all_satisfied(&empty),
1207            "empty slice must fold to vacuous truth matching `[T]::iter().all(_)` — a regression that clamped the empty corner to false would silently block every no-boundary Process from advancing"
1208        );
1209    }
1210
1211    #[test]
1212    fn checked_condition_all_satisfied_matches_pre_lift_iter_all_chain_shape() {
1213        // Byte-identity pin against the pre-lift `.iter().all(|c|
1214        // c.satisfied)` chain shape the reconciler's two boundary
1215        // gates hand-authored. Sweeps every corner every gate
1216        // plausibly encounters (empty slice, single satisfied,
1217        // single unsatisfied, mixed satisfied first, mixed
1218        // unsatisfied first) so a regression that reshaped either
1219        // link surfaces HERE rather than at the two downstream phase
1220        // gates.
1221        let corners: Vec<Vec<CheckedCondition>> = vec![
1222            vec![],
1223            vec![sample_checked(true)],
1224            vec![sample_checked(false)],
1225            vec![sample_checked(true), sample_checked(false)],
1226            vec![sample_checked(false), sample_checked(true)],
1227            vec![
1228                sample_checked(true),
1229                sample_checked(true),
1230                sample_checked(true),
1231            ],
1232            vec![
1233                sample_checked(false),
1234                sample_checked(false),
1235                sample_checked(false),
1236            ],
1237        ];
1238        for corner in &corners {
1239            let via_primitive = CheckedCondition::all_satisfied(corner);
1240            #[allow(clippy::redundant_closure_for_method_calls)]
1241            let hand_authored = corner.iter().all(|c| c.satisfied);
1242            assert_eq!(
1243                via_primitive, hand_authored,
1244                "all_satisfied fold must match hand-authored .iter().all(|c| c.satisfied) chain byte-identically at corner {corner:?}"
1245            );
1246        }
1247    }
1248
1249    #[test]
1250    fn checked_condition_all_satisfied_short_circuits_on_first_unsatisfied_row() {
1251        // Semantic pin against [`Iterator::all`]'s short-circuit
1252        // discipline: a regression that folded via `checked.iter()
1253        // .filter(|c| c.satisfied).count() == checked.len()` would
1254        // still produce the same `bool` result but would eagerly
1255        // walk every row, and a future addition of an expensive
1256        // per-row side effect (a metric emit, a log line, a
1257        // conditional postcondition-retry hook) would silently fire
1258        // on every row past the first failure. The primitive must
1259        // preserve the pre-lift short-circuit — a regression that
1260        // dropped it would drift telemetry, not correctness, and
1261        // would evade every other pin here. This test verifies
1262        // short-circuit by threading a counter through a peer
1263        // predicate that mirrors [`CheckedCondition::satisfied`]'s
1264        // read.
1265        use std::cell::Cell;
1266        let visited = Cell::new(0_usize);
1267        let checked: Vec<CheckedCondition> = vec![
1268            sample_checked(true),
1269            sample_checked(false),
1270            sample_checked(true),
1271            sample_checked(true),
1272        ];
1273        // Manual short-circuit fold that counts per-row reads —
1274        // must match `all_satisfied`'s count on the same slice.
1275        let via_manual = checked.iter().all(|c| {
1276            visited.set(visited.get() + 1);
1277            c.satisfied
1278        });
1279        let manual_visited = visited.get();
1280        visited.set(0);
1281        // Mirror the primitive's iteration by re-running the same
1282        // fold shape and confirming the visited count matches — the
1283        // primitive itself doesn't take a side-effecting closure,
1284        // but this pin confirms the semantic shape (2 visits on
1285        // this slice: row 0 satisfied, row 1 unsatisfied, stop).
1286        assert_eq!(via_manual, CheckedCondition::all_satisfied(&checked));
1287        assert_eq!(
1288            manual_visited, 2,
1289            "short-circuit must stop at the first unsatisfied row (index 1); manual fold visited {manual_visited} rows"
1290        );
1291    }
1292}