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::condition_type::ProcessConditionType;
10use crate::crd::Process;
11use crate::json_object::ValueGetExt;
12use crate::k8s_condition::K8sConditionStatus;
13
14/// Standard K8s Condition (shape of `metav1.Condition`).
15#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
16#[serde(rename_all = "camelCase")]
17pub struct ProcessCondition {
18    #[serde(rename = "type")]
19    pub type_: String,
20    pub status: String,
21    pub last_transition_time: DateTime<Utc>,
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub reason: Option<String>,
24    #[serde(default, skip_serializing_if = "Option::is_none")]
25    pub message: Option<String>,
26}
27
28impl ProcessCondition {
29    /// Compose a [`ProcessCondition`] stamped at "observed now" from a
30    /// typed [`ProcessConditionType`] + [`K8sConditionStatus`] pair,
31    /// a reason slug, and an optional message.
32    ///
33    /// Owns the 5-slot wire-shape backbone
34    ///
35    /// ```text
36    /// Self {
37    ///     type_:                <ConditionType>.as_wire_str().into(),
38    ///     status:               <StatusEnum>.as_wire_str().into(),
39    ///     last_transition_time: Utc::now(),
40    ///     reason:               Some(<reason>.into()),
41    ///     message:              <opt>,
42    /// }
43    /// ```
44    ///
45    /// that every sibling constructor on this impl block ([`Self::ready`],
46    /// [`Self::not_ready`], [`Self::attested`]) hand-authored pre-lift.
47    ///
48    /// Pre-lift the SAME 5-slot struct-literal skeleton recurred at
49    /// THREE sibling constructor bodies past the ★★ PRIME-DIRECTIVE
50    /// ≥ 2 duplication threshold, each restating (a) the typed-enum →
51    /// wire-string projection at the `type_` slot, (b) the peer typed-
52    /// enum → wire-string projection at the `status` slot, (c) the
53    /// `Utc::now()` clock stamp at the `last_transition_time` slot,
54    /// (d) the `Some(<reason>.into())` wrap at the `reason` slot. Post-
55    /// lift the three siblings name their two typed variants + reason +
56    /// message opt ONCE and route through this ONE composer; the shared
57    /// backbone lives at ONE substrate site so a future normalization
58    /// of the K8s Condition wire shape (adding `observed_generation:
59    /// Option<i64>` to match `metav1.Condition` v2, injecting a
60    /// `Clock` for deterministic tests replacing the direct
61    /// `Utc::now()` call, adding a `severity:` slot for K8s-Condition-
62    /// v3 style diagnostics) lands at ONE composer body and all three
63    /// sibling constructors — plus every future `ProcessCondition::*`
64    /// variant on this impl block — inherit the upgrade mechanically.
65    ///
66    /// The typed enum inputs bind the wire-form projection axis
67    /// structurally: a regression that swapped `K8sConditionStatus::
68    /// True` for `False` at ONE sibling (a mechanical copy-paste error
69    /// the pre-lift hand-authored `status: "True".into()` /
70    /// `status: "False".into()` string-literal restatements would not
71    /// have caught at compile time) is caught by the enum's closed
72    /// set, and the wire-string projection itself rides through the
73    /// SAME `as_wire_str()` projection every reader/writer pair in the
74    /// workspace already routes through — so a future spelling change
75    /// at ONE of those enums propagates mechanically to all three
76    /// sibling constructors without a per-site edit.
77    ///
78    /// Sibling to the pre-existing `K8sConditionStatus` /
79    /// `ProcessConditionType` closed-set variants on the (typed
80    /// wire-form × K8s-Condition-slot) axis: those closed sets own
81    /// the type-variant enumeration + wire-form projection; this
82    /// composer owns the K8s-Condition struct-literal composition
83    /// that binds two typed variants onto the wire.
84    ///
85    /// Theory anchor: THEORY.md §VI.1 (generation over composition —
86    /// the 5-slot struct-literal backbone recurred at 3 hand-authored
87    /// sibling constructor sites past the ★★ PRIME-DIRECTIVE ≥ 2
88    /// duplication trigger and is lifted onto the ONE substrate
89    /// composer here). THEORY.md §II.1 invariant 5 (composition
90    /// preserves proofs — the pin block below binds the composer at
91    /// fail-before-pass-after granularity, so a regression that drifted
92    /// any slot's default — `Utc::now()` swapped for a fixed-anchor
93    /// stamp, `reason: Some(<reason>.into())` narrowed to a bare
94    /// `<reason>.into()` opting out of the K8s-Condition-mandated
95    /// `Option`-wrap, the typed-enum → wire-string projection at the
96    /// `type_` or `status` slot bypassed by an inline literal that
97    /// silently drifts off the closed-set owner — surfaces at
98    /// `status::tests::new_at_now_*` rather than as silent operator-
99    /// facing skew across the three sibling constructor callsites).
100    fn new_at_now(
101        type_: ProcessConditionType,
102        status: K8sConditionStatus,
103        reason: impl Into<String>,
104        message: Option<String>,
105    ) -> Self {
106        Self {
107            type_: type_.as_wire_str().into(),
108            status: status.as_wire_str().into(),
109            last_transition_time: Utc::now(),
110            reason: Some(reason.into()),
111            message,
112        }
113    }
114
115    pub fn ready(reason: impl Into<String>, message: Option<String>) -> Self {
116        Self::new_at_now(
117            ProcessConditionType::Ready,
118            K8sConditionStatus::True,
119            reason,
120            message,
121        )
122    }
123
124    pub fn not_ready(reason: impl Into<String>, message: impl Into<String>) -> Self {
125        Self::new_at_now(
126            ProcessConditionType::Ready,
127            K8sConditionStatus::False,
128            reason,
129            Some(message.into()),
130        )
131    }
132
133    pub fn attested(root: &str) -> Self {
134        Self::new_at_now(
135            ProcessConditionType::Attested,
136            K8sConditionStatus::True,
137            "AttestationWritten",
138            Some(format!("composed_root={root}")),
139        )
140    }
141}
142
143/// Reference to a FluxCD resource emitted as part of this Process.
144#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
145#[serde(rename_all = "camelCase")]
146pub struct FluxResourceRef {
147    pub api_version: String,
148    pub kind: String,
149    pub name: String,
150    pub namespace: String,
151    #[serde(default)]
152    pub ready: bool,
153    #[serde(default, skip_serializing_if = "Option::is_none")]
154    pub message: Option<String>,
155    #[serde(default, skip_serializing_if = "Option::is_none")]
156    pub last_check: Option<DateTime<Utc>>,
157}
158
159impl FluxResourceRef {
160    /// Pure typed projection of the four fetch coordinates
161    /// `(namespace, api_version, kind, name)` every consumer that
162    /// dispatches this persisted reference through kube-rs's dynamic-
163    /// object surface splats by hand pre-lift. The 4-tuple binds the
164    /// slot order at ONE typed accessor so a copy-paste at any downstream
165    /// consumer cannot swap two adjacent `&str` slots in the fetch call.
166    ///
167    /// Peer projection to
168    /// [`crate::k8s_wire_identity::K8sWireIdentity`] on the static-
169    /// identity axis: [`K8sWireIdentity`] carries a
170    /// `(&'static str, &'static str)` closed-set variant's pair for
171    /// emit-time (RENDER phase) composition; this method carries the
172    /// full `(ns, apiVersion, kind, name)` 4-slot borrow for fetch-time
173    /// (VERIFY / ATTEST-heartbeat) composition where the ref's payload
174    /// comes back off the persisted `ProcessStatus.flux_resources`
175    /// slice with owned `String`s rather than static literals. The two
176    /// primitives partition the fetch axis by whether the caller starts
177    /// from a closed-set variant (emit-time) or a persisted status
178    /// slice (fetch-time).
179    ///
180    /// Pre-lift the 5-slot `ssapply::fetch(client, &r.namespace,
181    /// &r.api_version, &r.kind, &r.name)` splat was hand-authored at
182    /// TWO sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold
183    /// in `tatara-reconciler::phase_machine`:
184    /// * `handle_running` — the VERIFY-phase per-ref readiness probe
185    ///   that populates the updated `FluxResourceRef` slice with
186    ///   `ready` + `message` + `last_check`.
187    /// * `handle_attested` — the ATTEST-heartbeat drift detector that
188    ///   short-circuits on the first non-Ready ref.
189    ///
190    /// Both sites splatted the SAME four `&r.X` field borrows in the
191    /// SAME order into raw `ssapply::fetch`. A copy-paste that swapped
192    /// two adjacent `&str` slots (`&r.api_version` and `&r.kind` are
193    /// both strings that look interchangeable to a mechanical
194    /// substitution) would silently 404 at wire time and diagnose as a
195    /// broken CRD rather than as slot skew at the callsite. Post-lift
196    /// each site names the ref ONCE and unpacks it through this ONE
197    /// projection; the slot order binds structurally at the tuple
198    /// return so a caller cannot desync one axis.
199    ///
200    /// A future addition (a case-fold normalization on the group, a
201    /// virtual-cluster prefix rewrite for multi-tenancy, a
202    /// `generateName` fallback on the name slot, a cluster-cache
203    /// short-circuit inserted between the projection and the fetch
204    /// call) lands at this ONE method and every downstream fetch
205    /// consumer inherits the upgrade mechanically — no per-site edit
206    /// at `handle_running` / `handle_attested` / any future kenshi-
207    /// runner / mirror-audit / drift-probe consumer that grows a third
208    /// consumer.
209    ///
210    /// Return-order pin lives at
211    /// [`tests::flux_resource_ref_fetch_coords_binds_slots_by_position`]
212    /// so a regression that swapped `namespace` and `api_version`
213    /// (both `String`, same type) inside the tuple constructor fails-
214    /// loudly here rather than as a silent wire-time 404 at every
215    /// downstream fetch consumer.
216    ///
217    /// Theory grounding: THEORY.md §II.1 invariant 5 (composition
218    /// preserves proofs — the 4-tuple slot order binds at ONE typed
219    /// projection so a regression across the two fields of the same
220    /// `String` type fails at the projection's positional pin rather
221    /// than at every downstream fetch consumer). THEORY.md §VI.1
222    /// (generation over composition — the 5-slot splat recurred at
223    /// two hand-authored sites past the ≥ 2 duplication trigger, and
224    /// is lifted to ONE typed borrow-projection here).
225    pub fn fetch_coords(&self) -> (&str, &str, &str, &str) {
226        (&self.namespace, &self.api_version, &self.kind, &self.name)
227    }
228
229    /// Compose a `FluxResourceRef` stamped at "observed now" — the
230    /// `last_check` slot is set to `Some(Utc::now())` at ONE substrate
231    /// owner, and the four coordinate slots + `ready` + `message`
232    /// are bound positionally so a slot-swap regression surfaces at
233    /// the constructor's positional pin rather than as silent drift
234    /// at every downstream `ProcessStatus.flux_resources` writer.
235    ///
236    /// Pre-lift the 7-slot `FluxResourceRef { …, last_check:
237    /// Some(chrono::Utc::now()) }` struct-literal was hand-authored
238    /// at TWO sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
239    /// threshold in `tatara-reconciler::phase_machine`:
240    /// * `handle_running` — the VERIFY-phase per-ref rebuild that
241    ///   restamps each polled ref with fresh `ready` + `message` +
242    ///   `last_check`.
243    /// * `flux_ref_from_json` — the post-SSA initial-state seeder
244    ///   that stamps a freshly-applied ref as `ready = false`,
245    ///   `message = Some("applied; awaiting reconciliation")`,
246    ///   `last_check = Some(Utc::now())`.
247    ///
248    /// Both sites restated the SAME seven field bindings in the
249    /// SAME order, and both restated the SAME `Some(chrono::Utc::
250    /// now())` stamp. A copy-paste that swapped two adjacent
251    /// `String` slots (`api_version` and `kind`, `kind` and `name`,
252    /// or `name` and `namespace` are all mechanically
253    /// indistinguishable at the type level) would silently persist
254    /// a slot-inverted ref that the downstream Flux fetch consumer
255    /// (via [`Self::fetch_coords`]) would then 404 on. Post-lift
256    /// both sites name the six inputs ONCE and route through this
257    /// ONE composer; the seventh slot (`last_check`) is stamped at
258    /// the composer's body so a future injection point (a fake
259    /// clock for testing, a monotonic-clock cross-check, a per-
260    /// fleet skew tolerance) lands at ONE substrate site rather
261    /// than at every hand-authored `Some(chrono::Utc::now())` stamp.
262    ///
263    /// Return-order pin lives at
264    /// [`tests::flux_resource_ref_observed_binds_slots_by_position`]
265    /// so a regression that swapped `api_version` and `kind` (both
266    /// `String`, same type) inside the constructor's argument list
267    /// fails-loudly here rather than as a silent wire-time 404 at
268    /// every downstream fetch consumer.
269    ///
270    /// Theory grounding: THEORY.md §II.1 invariant 5 (composition
271    /// preserves proofs — the 6-slot positional binding + the
272    /// `last_check` stamp compose at ONE typed owner, so a
273    /// regression across the four `String` coordinate slots fails
274    /// at the composer's positional pin rather than at every
275    /// downstream Flux status writer). THEORY.md §VI.1 (generation
276    /// over composition — the 7-slot struct-literal recurred at two
277    /// hand-authored sites past the ≥ 2 duplication trigger, and is
278    /// lifted to ONE typed composer here).
279    pub fn observed(
280        api_version: String,
281        kind: String,
282        name: String,
283        namespace: String,
284        ready: bool,
285        message: Option<String>,
286    ) -> Self {
287        Self {
288            api_version,
289            kind,
290            name,
291            namespace,
292            ready,
293            message,
294            last_check: Some(Utc::now()),
295        }
296    }
297
298    /// Compose a `FluxResourceRef` in the pre-observation shape — the
299    /// 4-slot coordinate binding with the three status slots defaulted
300    /// (`ready: false`, `message: None`, `last_check: None`). The
301    /// deterministic-fixture peer of [`Self::observed`] on the same
302    /// `→ FluxResourceRef` composer axis: `observed` reads the wall
303    /// clock and takes 6 args (a live post-fetch stamp), `pending`
304    /// reads no clock and takes 4 args (a pre-observation fixture
305    /// seed, and the natural base for `..base.clone()` spread updates
306    /// that vary a single slot for a per-corner test sweep).
307    ///
308    /// Pre-lift the SAME 7-slot `FluxResourceRef { api_version, kind,
309    /// name, namespace, ready: false, message: None, last_check: None
310    /// }` struct-literal was hand-authored at THREE workspace-wide
311    /// fixture sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
312    /// threshold:
313    ///
314    /// * [`crate::crd`]
315    ///   `crd::observed_flux_resources_tests::sample_flux_ref(name)`
316    ///   — the shared `Kustomization`/`flux-system` fixture the
317    ///   `Process::observed_flux_resources` pin family destructures
318    ///   for its `flux_resources`-populated corners.
319    /// * `tatara-reconciler::ssapply::tests::sample_flux_ref_for_diag`
320    ///   — the `HelmRelease`/`flux-system` fixture the
321    ///   `flux_ref_fetch_error_context` diagnostic-wording pin
322    ///   family destructures for its (kind, name) slot-coverage
323    ///   sweep.
324    /// * `tatara-reconciler::ssapply::tests::
325    ///   flux_ref_fetch_error_context_matches_pre_lift_hand_authored_wording`
326    ///   — the inline 7-slot literal inside the cross-substrate
327    ///   coherence pin's per-case sweep over three distinct
328    ///   `(api_version, kind, name, namespace)` tuples.
329    ///
330    /// All THREE sites restated the SAME seven field bindings in the
331    /// SAME order and the SAME three defaulted status slots (`ready:
332    /// false, message: None, last_check: None`), differing only in
333    /// the four coordinate `String` values. Post-lift each callsite
334    /// reads `FluxResourceRef::pending(<api_version>, <kind>, <name>,
335    /// <namespace>)` and the four-slot bind + three-slot default
336    /// sinks live at ONE substrate owner.
337    ///
338    /// The `impl Into<String>` signature accepts BOTH `&'static str`
339    /// (the fixture-helper sites that spell coordinate literals
340    /// inline) AND owned `String` (a future callsite handing off a
341    /// dynamically-derived coordinate) without widening. Matches the
342    /// discipline of the sibling substrate composers
343    /// [`crate::pool::PoolMember::unallocated`] +
344    /// [`crate::allocation::AllocationRef::new`] on the identity-slot
345    /// axis.
346    ///
347    /// A future normalization (a case-fold on the group, a
348    /// virtual-cluster prefix rewrite for multi-tenancy, a stricter
349    /// kind gate, a `generateName` fallback on the name slot, a
350    /// canonical rename of one of the three defaulted status slots
351    /// to a typed `PreObservation` marker) lands at THIS ONE
352    /// substrate primitive and every downstream fixture / helper
353    /// inherits the upgrade mechanically — no per-site edit at any
354    /// of the THREE listed callers or at future consumers (a
355    /// stable-name claim-arbiter's pending-ref seed, a kenshi-runner
356    /// pre-observation fixture, a mirror-audit drift-probe test
357    /// helper).
358    ///
359    /// Theory grounding: THEORY.md §II.1 invariant 5 (composition
360    /// preserves proofs — the 4-slot positional binding + the three
361    /// defaulted status slots compose at ONE typed owner, so a
362    /// regression across the four `String` coordinate slots fails at
363    /// the composer's positional pin rather than at every downstream
364    /// fixture consumer). THEORY.md §VI.1 (generation over
365    /// composition — the 7-slot struct-literal recurred at three
366    /// hand-authored fixture sites past the ≥ 2 duplication trigger,
367    /// and is lifted to ONE typed composer here).
368    #[must_use]
369    pub fn pending(
370        api_version: impl Into<String>,
371        kind: impl Into<String>,
372        name: impl Into<String>,
373        namespace: impl Into<String>,
374    ) -> Self {
375        Self {
376            api_version: api_version.into(),
377            kind: kind.into(),
378            name: name.into(),
379            namespace: namespace.into(),
380            ready: false,
381            message: None,
382            last_check: None,
383        }
384    }
385}
386
387/// Identifying coordinates of a rendered K8s resource — the
388/// `(apiVersion, kind, metadata.name, metadata.namespace)` 4-tuple
389/// every consumer that walks a rendered `serde_json::Value` resource
390/// unwraps by hand pre-lift.
391///
392/// The three K8s API-path segments (`apiVersion`, `kind`,
393/// `metadata.name`) are REQUIRED — a rendered resource missing any
394/// of them cannot be applied via kube-rs's dynamic API surface, so
395/// the extraction fails fast at the boundary rather than as a
396/// downstream `Api::patch` panic. `metadata.namespace` is
397/// intentionally kept as `Option<String>` because different consumers
398/// resolve the fallback differently: `apply_owned` uses the
399/// caller-supplied `namespace: &str` argument (the reconciler already
400/// resolved the target namespace upstream), while `flux_ref_from_json`
401/// records the K8s canonical `"default"` fallback into the persisted
402/// `FluxResourceRef.namespace` slot. The peer method
403/// [`Self::namespace_or_default`] applies the K8s canonical fallback
404/// (`Process::DEFAULT_NAMESPACE = "default"`) for consumers wanting
405/// the same shape [`FluxResourceRef.namespace`] carries.
406///
407/// Pre-lift the 3+1 slot extraction was hand-authored at TWO sites
408/// past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold across
409/// `tatara-reconciler`:
410/// * `tatara-reconciler::phase_machine::flux_ref_from_json` — the
411///   post-SSA `FluxResourceRef` builder that persists into
412///   `ProcessStatus.flux_resources`; namespace half fallback-
413///   defaulted to `"default"`.
414/// * `tatara-reconciler::ssapply::apply_owned` — the SSA entry
415///   point that extracts (apiVersion, kind, name) for the
416///   [`kube::Api::patch`] call; namespace half discarded (the
417///   `namespace: &str` argument comes from the caller upstream).
418///
419/// Both callsites restated the same three
420/// `.get(K).and_then(|v| v.as_str()).ok_or_else(|| anyhow!(...))?
421/// .to_string()` incantations with subtly different error wording
422/// (`"resource missing X"` vs `"rendered resource missing X"`); post-
423/// lift both route through this ONE substrate owner with the
424/// canonical `"rendered resource missing X"` wording. A future
425/// addition (case-fold on the group, a rename of the namespace
426/// fallback, a stricter kind gate, a Unicode-safe collation step,
427/// support for `metadata.generateName` as a name fallback) lands at
428/// the primitive's body on the substrate, not at 2 independent
429/// hand-writes across 2 reconciler files.
430///
431/// Namespace fallback const is shared with
432/// [`Process::DEFAULT_NAMESPACE`] — a rename of the K8s canonical
433/// default namespace lands at that ONE workspace-wide const, not at
434/// per-primitive local literals that would drift silently.
435#[derive(Clone, Debug, PartialEq, Eq)]
436pub struct RenderedResourceCoords {
437    /// `apiVersion` — the group+version pair kube-rs uses to resolve
438    /// the `ApiResource` for the SSA call.
439    pub api_version: String,
440    /// `kind` — the resource kind (Kustomization, HelmRelease, …).
441    pub kind: String,
442    /// `metadata.name` — the API-path leaf segment.
443    pub name: String,
444    /// `metadata.namespace` — raw from the resource, `None` when the
445    /// slot is absent (a cluster-scoped resource, or a namespaced
446    /// resource whose namespace was left for the API server to
447    /// substitute). Consumers apply their own fallback:
448    /// [`Self::namespace_or_default`] applies the K8s canonical
449    /// `"default"` (matching what [`FluxResourceRef.namespace`]
450    /// records); other consumers substitute a caller-supplied string
451    /// (see `tatara-reconciler::ssapply::apply_owned`).
452    pub namespace: Option<String>,
453}
454
455impl RenderedResourceCoords {
456    /// Extract the 4-tuple from a rendered K8s resource JSON `Value`.
457    ///
458    /// Fails with a canonical `"rendered resource missing X"` message
459    /// when any of the three required slots (`apiVersion`, `kind`,
460    /// `metadata.name`) is absent or non-string; `metadata.namespace`
461    /// is optional and captured as `None` when absent.
462    ///
463    /// The error wording is pinned by
464    /// [`tests::rendered_resource_coords_error_wording_is_canonical`]
465    /// so a regression that reshaped the message surfaces at the test
466    /// surface rather than as silent drift between the two pre-lift
467    /// call sites (which used subtly different wording — `"resource
468    /// missing X"` in `apply_owned` vs `"rendered resource missing
469    /// X"` in `flux_ref_from_json`).
470    pub fn from_json(res: &Value) -> anyhow::Result<Self> {
471        // The three REQUIRED-slot extracts (`apiVersion`, `kind`,
472        // `metadata.name`) route through the ONE substrate primitive
473        // `Self::required_str` — the required-extract sibling of
474        // `crate::json_object::ValueGetExt::get_str` on the same
475        // rendered-resource axis. A future normalization (Unicode
476        // NFC-fold, whitespace trim, empty-string rejection) lands
477        // at the primitive body and every downstream consumer of the
478        // canonical `"rendered resource missing X"` wire form inherits
479        // it mechanically. The optional `metadata.namespace` slot
480        // routes directly through the ONE substrate primitive
481        // `crate::json_object::ValueGetExt::get_str` on the
482        // `Option<&Value>` receiver arm — the outer-optionality
483        // widening added alongside the `Value` + `Map<String, Value>`
484        // impls. Pre-lift this callsite hand-authored the outer-
485        // optionality closure `metadata.and_then(|m| m.get_str
486        // ("namespace"))` past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
487        // threshold (this site + `Self::required_str`'s body); post-
488        // lift the closure disappears and the axis-family method-call
489        // surface stays identical to the `Value` / `Map` receiver
490        // callers.
491        let api_version = Self::required_str(Some(res), "apiVersion", "apiVersion")?;
492        let kind = Self::required_str(Some(res), "kind", "kind")?;
493        let metadata = res.get("metadata");
494        let name = Self::required_str(metadata, "name", "metadata.name")?;
495        let namespace = metadata.get_str("namespace").map(str::to_string);
496        Ok(Self {
497            api_version,
498            kind,
499            name,
500            namespace,
501        })
502    }
503
504    /// Diagnostic prefix stamped ahead of every required-slot label in
505    /// the canonical error wire form. Owned in ONE workspace-wide place
506    /// so a rename (a fleet-wide switch to `"resource is missing"` /
507    /// `"missing rendered-resource field"`) lands here and every
508    /// downstream `.to_string()`-consumer + operator-facing log grep
509    /// inherits the rename mechanically, not at 3 hand-authored
510    /// `anyhow!(…)` restatements.
511    pub const MISSING_MESSAGE_PREFIX: &'static str = "rendered resource missing";
512
513    /// Required-slot extract on a rendered-resource JSON `Value` — the
514    /// substrate owner of the paired `.get_str(<key>).ok_or_else(||
515    /// anyhow!("rendered resource missing <slot>"))?.to_string()`
516    /// four-link chain every REQUIRED slot on a rendered `Value`
517    /// walks pre-lift.
518    ///
519    /// The primitive accepts an `Option<&Value>` receiver so BOTH
520    /// shallow reads (top-level `apiVersion` / `kind` on the resource
521    /// root, callers thread `Some(res)`) AND one-level-nested reads
522    /// (`metadata.name` walking through `res.get("metadata")`,
523    /// callers thread the `Option<&Value>` handle the `.get()` step
524    /// returns) reach the same owner. The `key` slot is the wire-form
525    /// name the underlying [`ValueGetExt::get_str`] looks up on the
526    /// object; the `error_slot` slot is the diagnostic label stamped
527    /// into the error's `Display` output. The two are decoupled so
528    /// `metadata.name` can look up `"name"` on the `metadata` sub-
529    /// object while reporting the dotted `"metadata.name"` path an
530    /// operator bisecting a fault sees in the log.
531    ///
532    /// Ok arm returns `String` (owned) rather than the borrowed
533    /// `&str` [`ValueGetExt::get_str`] returns — every downstream
534    /// slot on the [`RenderedResourceCoords`] struct is an owned
535    /// `String`, so the primitive absorbs the `str::to_string`
536    /// coerce that pre-lift lived at three hand-authored callsites.
537    /// Err arm carries an `anyhow::Error` whose `Display` reads
538    /// exactly `"<Self::MISSING_MESSAGE_PREFIX> <error_slot>"` —
539    /// byte-identical to the pre-lift hand-authored `anyhow!(
540    /// "rendered resource missing {slot}")` wire form.
541    ///
542    /// ### Fires on all four absent-shape corners
543    ///
544    /// The primitive returns `Err` on ALL four ways a required
545    /// slot can miss:
546    ///
547    /// 1. Receiver is `None` — the `metadata.name` corner when the
548    ///    top-level `metadata` object itself is absent (the caller
549    ///    threaded `res.get("metadata")` which returned `None`).
550    /// 2. Slot is absent — the receiver is present but does not
551    ///    carry a value at `key`.
552    /// 3. Slot is present but non-string — a fixture bug that
553    ///    stamped a JSON number / bool / object / array at the
554    ///    slot; the `get_str` step falls through and the primitive
555    ///    reports the slot as missing (matching the pre-lift
556    ///    behavior where every non-string variant surfaced as the
557    ///    same `"missing"` diagnostic — pinning "cannot be applied
558    ///    via kube-rs's dynamic API surface" as the shared
559    ///    failure mode).
560    /// 4. Receiver is non-object — a resource authored as a JSON
561    ///    array / string / null at any of the levels the primitive
562    ///    walks (the `get_str` step returns `None` verbatim).
563    ///
564    /// All four corners produce the SAME wire form so an operator's
565    /// `rg "rendered resource missing"` sweep hits exactly one
566    /// footprint per faulted slot, not four differently-worded
567    /// diagnostics per absent-shape variant.
568    ///
569    /// Theory anchor: THEORY.md §VI.1 (generation over composition —
570    /// the 4-link `.get_str(<key>).ok_or_else(|| anyhow!("rendered
571    /// resource missing <slot>"))?.to_string()` shape recurred at 3
572    /// hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2
573    /// duplication trigger, and is lifted to ONE substrate owner
574    /// here). THEORY.md §II.1 invariant 5 (composition preserves
575    /// proofs — a regression that drifted the diagnostic prefix
576    /// wording at ONE site would silently pass the two sibling
577    /// pins and fail HERE; post-lift the wire form is owned once
578    /// at [`Self::MISSING_MESSAGE_PREFIX`] and every downstream
579    /// composition inherits the rename mechanically).
580    fn required_str(
581        v: Option<&Value>,
582        key: &'static str,
583        error_slot: &'static str,
584    ) -> anyhow::Result<String> {
585        // The `Option<&Value>` outer-optionality unwrap-then-project
586        // step rides through the substrate primitive
587        // `crate::json_object::ValueGetExt::get_str` on the
588        // `Option<&Value>` receiver arm — the outer-optionality
589        // widening sibling of the `Value` / `Map<String, Value>`
590        // impls. Pre-lift this body hand-authored the outer-
591        // optionality closure `v.and_then(|x| x.get_str(key))` past
592        // the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold (this
593        // helper's body + the sibling `metadata.namespace` walk in
594        // `Self::from_json`); post-lift the closure disappears and
595        // the axis-family method-call surface stays identical to the
596        // `Value` / `Map` receiver callers.
597        v.get_str(key).map(str::to_string).ok_or_else(|| {
598            anyhow::anyhow!(
599                "{prefix} {slot}",
600                prefix = Self::MISSING_MESSAGE_PREFIX,
601                slot = error_slot,
602            )
603        })
604    }
605
606    /// `metadata.namespace` slice with the K8s canonical `"default"`
607    /// fallback applied — matching what [`Process::DEFAULT_NAMESPACE`]
608    /// spells for the `Process`-borne coordinate primitive family
609    /// and what [`FluxResourceRef.namespace`] records into
610    /// `ProcessStatus.flux_resources`.
611    pub fn namespace_or_default(&self) -> &str {
612        self.namespace
613            .as_deref()
614            .unwrap_or(Process::DEFAULT_NAMESPACE)
615    }
616}
617
618/// A boundary condition paired with its current satisfaction state.
619#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
620#[serde(rename_all = "camelCase")]
621pub struct CheckedCondition {
622    #[serde(flatten)]
623    pub condition: Condition,
624    pub satisfied: bool,
625    #[serde(default, skip_serializing_if = "Option::is_none")]
626    pub last_check: Option<DateTime<Utc>>,
627    #[serde(default, skip_serializing_if = "Option::is_none")]
628    pub message: Option<String>,
629}
630
631impl CheckedCondition {
632    /// True iff every [`CheckedCondition`] in the slice has
633    /// `satisfied == true` — the ONE-line collapse of the paired
634    /// `checked.iter().all(|c| c.satisfied)` incantation the
635    /// reconciler's precondition + postcondition boundary gates both
636    /// spelled by hand pre-lift.
637    ///
638    /// Pre-lift the SAME `.iter().all(|c| c.satisfied)` chain was
639    /// hand-authored at TWO sites past the ★★ PRIME-DIRECTIVE ≥ 2
640    /// duplication threshold in `tatara-reconciler::phase_machine`,
641    /// each walking the SAME `Vec<CheckedCondition>` → `bool`
642    /// projection to gate a phase transition on a boundary predicate:
643    /// * `handle_execing` — the PROVE-phase precondition gate that
644    ///   stays in Execing (heartbeat requeue) while any precondition
645    ///   remains unsatisfied and proceeds to RENDER only when every
646    ///   precondition holds.
647    /// * `handle_running` — the VERIFY-phase postcondition gate that
648    ///   stays in Running (heartbeat requeue) while any postcondition
649    ///   remains unsatisfied and advances to Attested only when every
650    ///   postcondition holds.
651    ///
652    /// Both sites walked the SAME `Iterator::all` short-circuit on the
653    /// SAME `bool` slot of the SAME struct. Post-lift both consumers
654    /// name the slice ONCE and route through this ONE primitive; the
655    /// vacuous-truth corner (empty slice → `true`, matching
656    /// [`Iterator::all`]'s empty-input identity) sits at ONE substrate
657    /// site so a future normalization (a per-slot weight overlay, a
658    /// per-kind override that treats `Warn`-severity failures as
659    /// satisfied, a compliance-baseline gate that requires N-of-M
660    /// rather than all-of-M) lands at ONE substrate function and both
661    /// downstream phase gates inherit the upgrade mechanically.
662    ///
663    /// Return-form axis: `bool` — the exact type each phase gate
664    /// pre-lift bound at `let all_pass = <chain>;` and immediately
665    /// consumed in a `!all_pass` short-circuit + a `message` slot's
666    /// ternary branch. The `&[Self]` argument accepts every pre-lift
667    /// slice provenance verbatim: a `&Vec<CheckedCondition>` (both
668    /// pre-lift sites had the `Vec` on the stack from
669    /// [`crate::phase_machine::evaluate_conditions`]'s owned return)
670    /// coerces through auto-deref, so no callsite has to change its
671    /// upstream provenance to route through the primitive.
672    ///
673    /// Peer to the sibling projection [`Self::satisfied`] on the (row
674    /// scope × predicate) axis pair: `satisfied` is the per-row
675    /// projection; `all_satisfied` is the slice-wide fold of the same
676    /// bit. Both live on `CheckedCondition` so a future rename or
677    /// per-slot normalization travels through the same owner without
678    /// splitting between "per-row" and "slice-wide" call sinks.
679    ///
680    /// Return-shape pin lives at
681    /// [`tests::checked_condition_all_satisfied_matches_pre_lift_iter_all_chain_shape`]
682    /// so a regression that flipped the fold direction (`any` for
683    /// `all`), inverted the bit (`!c.satisfied`), or reshaped the
684    /// return form (an owned `Vec<bool>` instead of the folded `bool`)
685    /// fails-loudly here rather than as silent operator-facing skew
686    /// between the pre-lift `if !all_pass { requeue }` gate and the
687    /// post-lift call — every downstream consumer would still
688    /// short-circuit but on inverted semantics.
689    ///
690    /// Theory grounding: THEORY.md §VI.1 (generation over composition
691    /// — the 1-line `.iter().all(...)` chain recurred at two hand-
692    /// authored sites past the ≥ 2 duplication trigger, and is lifted
693    /// to ONE typed fold here). THEORY.md §II.1 invariant 5
694    /// (composition preserves proofs — the empty-slice vacuous-truth
695    /// corner + the fold direction + the projected bit's polarity all
696    /// bind at ONE substrate site, so a regression across any of the
697    /// three surfaces at [`tests::checked_condition_all_satisfied_*`]
698    /// pin rather than as silent gate-flip at every downstream phase
699    /// handler).
700    #[must_use]
701    pub fn all_satisfied(checked: &[Self]) -> bool {
702        checked.iter().all(|c| c.satisfied)
703    }
704}
705
706/// Summary of boundary verification.
707#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
708#[serde(rename_all = "camelCase")]
709pub struct BoundaryStatus {
710    #[serde(default)]
711    pub preconditions: Vec<CheckedCondition>,
712    #[serde(default)]
713    pub postconditions: Vec<CheckedCondition>,
714    /// Absolute deadline for VERIFY (derived from `spec.boundary.timeout`).
715    #[serde(default, skip_serializing_if = "Option::is_none")]
716    pub deadline: Option<DateTime<Utc>>,
717}
718
719/// Summary of compliance checks at the latest attestation.
720#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
721#[serde(rename_all = "camelCase")]
722pub struct ComplianceStatus {
723    #[serde(default, skip_serializing_if = "Option::is_none")]
724    pub baseline: Option<String>,
725    pub satisfied: u32,
726    pub violated: u32,
727    pub total: u32,
728    #[serde(default)]
729    pub violations: Vec<String>,
730}
731
732#[cfg(test)]
733mod tests {
734    use super::*;
735    use serde_json::json;
736
737    // ─── ProcessCondition writer / K8sConditionStatus substrate pins ─
738    //
739    // Byte-shape parity pins between `ProcessCondition::{ready,
740    // not_ready, attested}` writer output and the pre-lift hand-
741    // authored `"True"` / `"False"` `status` slot literals every
742    // downstream K8s API server + K8s-Condition-reading peer depends
743    // on. Post-lift the writers compose through
744    // `K8sConditionStatus::<V>.as_wire_str()`; these pins catch a
745    // regression at the substrate primitive (a lower-case drift, a
746    // whitespace prefix, a `serde(rename)` addition on the enum)
747    // that would silently reshape every emitted Process
748    // `status.conditions[]` slot away from the K8s wire form.
749
750    /// Fail-before-pass-after: `ProcessCondition::ready` emits the
751    /// exact-case ASCII `"True"` on the `status` slot the K8s API
752    /// server accepts, byte-identical to the pre-lift hand-authored
753    /// `status: "True".into()` literal. A regression at the substrate
754    /// primitive (a `to_lowercase` pass, a case-drifted variant
755    /// literal in `k8s_condition::K8sConditionStatus::as_wire_str`)
756    /// surfaces HERE, not as silent operator-facing wire-form skew
757    /// on the emitted Process CRD.
758    #[test]
759    fn ready_writer_status_slot_matches_pre_lift_true_literal_bytewise() {
760        let c = ProcessCondition::ready("ObservedRunning", Some("healthy".into()));
761        assert_eq!(c.type_, "Ready");
762        assert_eq!(c.status, "True");
763    }
764
765    /// Fail-before-pass-after: `ProcessCondition::not_ready` emits
766    /// the exact-case ASCII `"False"` on the `status` slot, byte-
767    /// identical to the pre-lift hand-authored `status: "False".
768    /// into()` literal.
769    #[test]
770    fn not_ready_writer_status_slot_matches_pre_lift_false_literal_bytewise() {
771        let c = ProcessCondition::not_ready("ObservedFailed", "boom");
772        assert_eq!(c.type_, "Ready");
773        assert_eq!(c.status, "False");
774    }
775
776    /// Fail-before-pass-after: `ProcessCondition::attested` emits
777    /// the exact-case ASCII `"True"` on the `status` slot with the
778    /// `"Attested"` type row, byte-identical to the pre-lift hand-
779    /// authored `type_: "Attested".into()` + `status: "True".into()`
780    /// pair.
781    #[test]
782    fn attested_writer_status_slot_matches_pre_lift_true_literal_bytewise() {
783        let c = ProcessCondition::attested("blake3:abc123");
784        assert_eq!(c.type_, "Attested");
785        assert_eq!(c.status, "True");
786        // Message body preserves the composed_root diagnostic
787        // wording verbatim — the substrate lift only reshaped the
788        // `status` slot, not the human-facing message.
789        assert_eq!(c.message.as_deref(), Some("composed_root=blake3:abc123"));
790        assert_eq!(c.reason.as_deref(), Some("AttestationWritten"));
791    }
792
793    /// Writer/reader wire-form parity: the `status` slot every
794    /// writer here emits is the SAME byte-shape the
795    /// `K8sConditionStatus::from_wire_str` reader in
796    /// `tatara-reconciler::ssapply::ready_condition_value` accepts.
797    /// A regression that drifted `as_wire_str` at ONE variant would
798    /// silently desynchronize every writer/reader pair in the
799    /// workspace; this pin surfaces the drift at the substrate.
800    #[test]
801    fn writer_output_round_trips_through_from_wire_str() {
802        let ready = ProcessCondition::ready("R", None);
803        assert_eq!(
804            K8sConditionStatus::from_wire_str(&ready.status),
805            Some(K8sConditionStatus::True),
806        );
807        let not_ready = ProcessCondition::not_ready("R", "why");
808        assert_eq!(
809            K8sConditionStatus::from_wire_str(&not_ready.status),
810            Some(K8sConditionStatus::False),
811        );
812        let attested = ProcessCondition::attested("blake3:zzz");
813        assert_eq!(
814            K8sConditionStatus::from_wire_str(&attested.status),
815            Some(K8sConditionStatus::True),
816        );
817    }
818
819    // ─── ProcessCondition::new_at_now backbone-composer substrate pins ───
820    //
821    // The composer [`ProcessCondition::new_at_now`] owns the 5-slot wire-
822    // shape backbone every sibling constructor on this impl block
823    // ([`ProcessCondition::ready`], [`ProcessCondition::not_ready`],
824    // [`ProcessCondition::attested`]) hand-authored pre-lift. These pins
825    // bind the observable slots (typed-enum → wire-string projection on
826    // `type_` + `status`, `Utc::now()` stamp on `last_transition_time`,
827    // `Some(<reason>.into())` wrap on `reason`, verbatim message opt on
828    // `message`) at fail-before-pass-after granularity so a regression
829    // that drifted any slot's projection (a typed-enum arm bypassed by
830    // an inline literal, the `Some(...)`-wrap on `reason` narrowed to
831    // a bare `.into()`, the `last_transition_time` stamp swapped for a
832    // fixed anchor) surfaces HERE rather than as silent operator-facing
833    // K8s-Condition wire-shape skew across the three sibling
834    // constructor sites.
835
836    /// Fail-before-pass-after: the composer's 5-slot output is byte-
837    /// identical (modulo the `Utc::now()` monotonic clock stamp) to
838    /// the pre-lift hand-authored struct literal every sibling
839    /// constructor restated. A regression that drifted the projection
840    /// at any of the four fixed slots surfaces HERE, not as silent
841    /// wire-form skew on the emitted Process CRD.
842    #[test]
843    fn new_at_now_composes_typed_enums_reason_option_wrap_and_message_slot_verbatim() {
844        let before = Utc::now();
845        let c = ProcessCondition::new_at_now(
846            ProcessConditionType::Ready,
847            K8sConditionStatus::True,
848            "ObservedRunning",
849            Some("healthy".into()),
850        );
851        let after = Utc::now();
852        assert_eq!(c.type_, ProcessConditionType::Ready.as_wire_str());
853        assert_eq!(c.status, K8sConditionStatus::True.as_wire_str());
854        assert_eq!(c.reason.as_deref(), Some("ObservedRunning"));
855        assert_eq!(c.message.as_deref(), Some("healthy"));
856        assert!(
857            c.last_transition_time >= before && c.last_transition_time <= after,
858            "last_transition_time rides Utc::now() at the composer body",
859        );
860    }
861
862    /// Fail-before-pass-after: the composer preserves an explicit
863    /// `None` message-slot verbatim — a K8s Condition with an absent
864    /// message vs an empty-string message are distinct wire shapes
865    /// the K8s API server treats differently, so the composer must
866    /// route `None` through without an accidental `Some(String::
867    /// new())` wrap.
868    #[test]
869    fn new_at_now_preserves_none_message_slot_verbatim() {
870        let c = ProcessCondition::new_at_now(
871            ProcessConditionType::Ready,
872            K8sConditionStatus::False,
873            "R",
874            None,
875        );
876        assert!(
877            c.message.is_none(),
878            "message-slot rides `None` verbatim — an empty-Some wrap is a distinct wire shape",
879        );
880    }
881
882    /// Byte-shape parity witness — `ProcessCondition::ready` routes
883    /// through `new_at_now(Ready, True, reason, message)`, matching
884    /// the pre-lift hand-authored 5-slot struct literal on every
885    /// observable slot except the monotonic `last_transition_time`
886    /// stamp.
887    #[test]
888    fn ready_routes_through_new_at_now_composer_slots_match_pre_lift_literal() {
889        let via_public = ProcessCondition::ready("ObservedRunning", Some("healthy".into()));
890        let via_composer = ProcessCondition::new_at_now(
891            ProcessConditionType::Ready,
892            K8sConditionStatus::True,
893            "ObservedRunning",
894            Some("healthy".into()),
895        );
896        assert_eq!(via_public.type_, via_composer.type_);
897        assert_eq!(via_public.status, via_composer.status);
898        assert_eq!(via_public.reason, via_composer.reason);
899        assert_eq!(via_public.message, via_composer.message);
900    }
901
902    /// Byte-shape parity witness — `ProcessCondition::not_ready`
903    /// routes through `new_at_now(Ready, False, reason, Some(message.
904    /// into()))`, matching the pre-lift hand-authored 5-slot struct
905    /// literal on every observable slot.
906    #[test]
907    fn not_ready_routes_through_new_at_now_composer_slots_match_pre_lift_literal() {
908        let via_public = ProcessCondition::not_ready("ObservedFailed", "boom");
909        let via_composer = ProcessCondition::new_at_now(
910            ProcessConditionType::Ready,
911            K8sConditionStatus::False,
912            "ObservedFailed",
913            Some("boom".into()),
914        );
915        assert_eq!(via_public.type_, via_composer.type_);
916        assert_eq!(via_public.status, via_composer.status);
917        assert_eq!(via_public.reason, via_composer.reason);
918        assert_eq!(via_public.message, via_composer.message);
919    }
920
921    /// Byte-shape parity witness — `ProcessCondition::attested`
922    /// routes through `new_at_now(Attested, True, "AttestationWritten",
923    /// Some(format!("composed_root={root}")))`, matching the pre-lift
924    /// hand-authored 5-slot struct literal on every observable slot.
925    #[test]
926    fn attested_routes_through_new_at_now_composer_slots_match_pre_lift_literal() {
927        let via_public = ProcessCondition::attested("blake3:abc123");
928        let via_composer = ProcessCondition::new_at_now(
929            ProcessConditionType::Attested,
930            K8sConditionStatus::True,
931            "AttestationWritten",
932            Some("composed_root=blake3:abc123".into()),
933        );
934        assert_eq!(via_public.type_, via_composer.type_);
935        assert_eq!(via_public.status, via_composer.status);
936        assert_eq!(via_public.reason, via_composer.reason);
937        assert_eq!(via_public.message, via_composer.message);
938    }
939
940    /// Routing pin — the `type_` slot at the composer's `Self { …
941    /// type_: type_.as_wire_str().into() }` binding rides through the
942    /// [`ProcessConditionType::as_wire_str`] projection rather than an
943    /// inline `format!("{:?}", type_)` or hand-authored per-variant
944    /// literal. A regression that inlined the wire-string projection
945    /// (drifting the composer off the closed-set owner + reopening the
946    /// typo-drift surface a hand-authored `"ready"` / `"Ready "` /
947    /// `"READY"` spelling would fall into silently) surfaces HERE
948    /// rather than as silent per-variant wire-form skew where the
949    /// composed Condition's `type_` slot disagrees with the K8s API
950    /// server's expected literal.
951    #[test]
952    fn new_at_now_routes_type_slot_through_process_condition_type_as_wire_str() {
953        for type_ in ProcessConditionType::ALL {
954            let via_composer =
955                ProcessCondition::new_at_now(type_, K8sConditionStatus::True, "R", None);
956            assert_eq!(
957                via_composer.type_,
958                type_.as_wire_str(),
959                "type_ slot must route through ProcessConditionType::as_wire_str for {type_:?}",
960            );
961        }
962    }
963
964    /// Routing pin — the `status` slot at the composer's `Self { …
965    /// status: status.as_wire_str().into() }` binding rides through
966    /// the [`K8sConditionStatus::as_wire_str`] projection rather than
967    /// an inline hand-authored per-variant literal. A regression that
968    /// inlined the wire-string projection surfaces HERE rather than
969    /// as silent per-variant wire-form skew where the composed
970    /// Condition's `status` slot disagrees with the K8s API server's
971    /// expected `"True"` / `"False"` / `"Unknown"` closed set.
972    #[test]
973    fn new_at_now_routes_status_slot_through_k8s_condition_status_as_wire_str() {
974        for status in K8sConditionStatus::ALL {
975            let via_composer =
976                ProcessCondition::new_at_now(ProcessConditionType::Ready, status, "R", None);
977            assert_eq!(
978                via_composer.status,
979                status.as_wire_str(),
980                "status slot must route through K8sConditionStatus::as_wire_str for {status:?}",
981            );
982        }
983    }
984
985    // ─── RenderedResourceCoords substrate pins ──────────────────────
986
987    #[test]
988    fn rendered_resource_coords_from_json_extracts_all_four_slots_when_present() {
989        let res = json!({
990            "apiVersion": "kustomize.toolkit.fluxcd.io/v1",
991            "kind": "Kustomization",
992            "metadata": {
993                "name": "observability-stack",
994                "namespace": "flux-system",
995            },
996        });
997        let c = RenderedResourceCoords::from_json(&res).expect("extract");
998        assert_eq!(c.api_version, "kustomize.toolkit.fluxcd.io/v1");
999        assert_eq!(c.kind, "Kustomization");
1000        assert_eq!(c.name, "observability-stack");
1001        assert_eq!(c.namespace.as_deref(), Some("flux-system"));
1002    }
1003
1004    #[test]
1005    fn rendered_resource_coords_from_json_captures_absent_namespace_as_none() {
1006        // Cluster-scoped resource — `metadata.namespace` intentionally absent.
1007        let res = json!({
1008            "apiVersion": "v1",
1009            "kind": "Namespace",
1010            "metadata": {"name": "demo-test"},
1011        });
1012        let c = RenderedResourceCoords::from_json(&res).expect("extract");
1013        assert_eq!(c.namespace, None);
1014        assert_eq!(c.name, "demo-test");
1015    }
1016
1017    #[test]
1018    fn rendered_resource_coords_from_json_errors_on_missing_api_version() {
1019        let res = json!({"kind": "K", "metadata": {"name": "n"}});
1020        let e = RenderedResourceCoords::from_json(&res).expect_err("must error");
1021        assert_eq!(e.to_string(), "rendered resource missing apiVersion");
1022    }
1023
1024    #[test]
1025    fn rendered_resource_coords_from_json_errors_on_missing_kind() {
1026        let res = json!({"apiVersion": "v1", "metadata": {"name": "n"}});
1027        let e = RenderedResourceCoords::from_json(&res).expect_err("must error");
1028        assert_eq!(e.to_string(), "rendered resource missing kind");
1029    }
1030
1031    #[test]
1032    fn rendered_resource_coords_from_json_errors_on_missing_metadata_name() {
1033        let res = json!({"apiVersion": "v1", "kind": "K", "metadata": {}});
1034        let e = RenderedResourceCoords::from_json(&res).expect_err("must error");
1035        assert_eq!(e.to_string(), "rendered resource missing metadata.name");
1036    }
1037
1038    #[test]
1039    fn rendered_resource_coords_from_json_errors_on_missing_metadata_object() {
1040        // `metadata` absent entirely — same failure as `metadata.name` missing,
1041        // because the API-path leaf segment cannot be resolved.
1042        let res = json!({"apiVersion": "v1", "kind": "K"});
1043        let e = RenderedResourceCoords::from_json(&res).expect_err("must error");
1044        assert_eq!(e.to_string(), "rendered resource missing metadata.name");
1045    }
1046
1047    #[test]
1048    fn rendered_resource_coords_from_json_errors_on_non_string_slot() {
1049        // A numeric `apiVersion` slot falls through the `.as_str()` gate and
1050        // triggers the same missing-slot failure as absence — the API-path
1051        // segment is not a string.
1052        let res = json!({
1053            "apiVersion": 42,
1054            "kind": "K",
1055            "metadata": {"name": "n"},
1056        });
1057        let e = RenderedResourceCoords::from_json(&res).expect_err("must error");
1058        assert_eq!(e.to_string(), "rendered resource missing apiVersion");
1059    }
1060
1061    #[test]
1062    fn rendered_resource_coords_error_wording_is_canonical() {
1063        // Pins the exact spelling every downstream consumer sees.
1064        // Pre-lift wording differed across the two call sites (`"resource
1065        // missing X"` in `apply_owned` vs `"rendered resource missing X"` in
1066        // `flux_ref_from_json`); post-lift the canonical wording is
1067        // `"rendered resource missing X"` at every site.
1068        let cases = [
1069            (
1070                "apiVersion",
1071                json!({"kind": "K", "metadata": {"name": "n"}}),
1072            ),
1073            (
1074                "kind",
1075                json!({"apiVersion": "v1", "metadata": {"name": "n"}}),
1076            ),
1077            (
1078                "metadata.name",
1079                json!({"apiVersion": "v1", "kind": "K", "metadata": {}}),
1080            ),
1081        ];
1082        for (slot, res) in cases {
1083            let e = RenderedResourceCoords::from_json(&res).expect_err("must error");
1084            assert_eq!(
1085                e.to_string(),
1086                format!("rendered resource missing {slot}"),
1087                "slot {slot} error must be canonical"
1088            );
1089        }
1090    }
1091
1092    // ─── RenderedResourceCoords::required_str substrate pins ────────
1093    //
1094    // Fail-before-pass-after granularity: the
1095    // `RenderedResourceCoords::required_str` inherent associated
1096    // function did not exist before this commit, so each test below
1097    // fails to compile pre-lift. Post-lift they collectively pin the
1098    // required-string-extract shape at ONE substrate owner — a
1099    // regression that swaps `MISSING_MESSAGE_PREFIX`, decouples the
1100    // `key` / `error_slot` slot pair with a wrong ordering, drops the
1101    // `str::to_string` coerce (returning `&str` and forcing every
1102    // consumer to re-stamp `.to_string()` per site), or narrows the
1103    // receiver from `Option<&Value>` to `&Value` (silently breaking
1104    // the `metadata.name` corner where the caller threads the
1105    // `res.get("metadata")` result directly) surfaces HERE rather
1106    // than as silent operator-facing skew across the three pre-lift
1107    // consumers on `from_json`.
1108
1109    #[test]
1110    fn required_str_present_string_slot_returns_owned_string() {
1111        // Ok-arm invariant: a present string slot at `key` on a
1112        // `Some(&Value::Object)` receiver returns `Ok(<owned>)` —
1113        // the primitive absorbs the `.to_string()` coerce the three
1114        // pre-lift restatements each stamped at the tail.
1115        let res = json!({"apiVersion": "kustomize.toolkit.fluxcd.io/v1"});
1116        let got =
1117            RenderedResourceCoords::required_str(Some(&res), "apiVersion", "apiVersion").unwrap();
1118        assert_eq!(got, "kustomize.toolkit.fluxcd.io/v1");
1119    }
1120
1121    #[test]
1122    fn required_str_none_receiver_errors_with_canonical_wire_form() {
1123        // Absent-shape corner 1: the caller threads `None`
1124        // (`res.get("metadata")` returned `None` because the top-
1125        // level `metadata` slot itself is absent). The primitive
1126        // errors with the SAME wire form the two other absent
1127        // corners produce, keeping the operator-facing footprint
1128        // singular.
1129        let e = RenderedResourceCoords::required_str(None, "name", "metadata.name")
1130            .expect_err("None receiver must error");
1131        assert_eq!(e.to_string(), "rendered resource missing metadata.name");
1132    }
1133
1134    #[test]
1135    fn required_str_absent_slot_errors_with_canonical_wire_form() {
1136        // Absent-shape corner 2: the receiver is present but the
1137        // slot at `key` is not stamped on it. Wire form matches
1138        // the `None`-receiver corner and the non-string corner.
1139        let res = json!({"kind": "K"});
1140        let e = RenderedResourceCoords::required_str(Some(&res), "apiVersion", "apiVersion")
1141            .expect_err("absent slot must error");
1142        assert_eq!(e.to_string(), "rendered resource missing apiVersion");
1143    }
1144
1145    #[test]
1146    fn required_str_non_string_slot_errors_with_canonical_wire_form() {
1147        // Absent-shape corner 3: the slot is present but stamped
1148        // as a JSON number / bool / object / array — every
1149        // non-`Value::String` variant falls through the underlying
1150        // `get_str` gate and produces the SAME `"missing"` diagnostic.
1151        // Pinning EVERY non-string variant here (not just number)
1152        // guarantees an operator's error-stream grep collapses all
1153        // fixture-authoring bugs at this slot onto one footprint.
1154        for bad in [
1155            json!({"apiVersion": 42}),
1156            json!({"apiVersion": true}),
1157            json!({"apiVersion": {}}),
1158            json!({"apiVersion": [1]}),
1159            json!({"apiVersion": null}),
1160        ] {
1161            let e = RenderedResourceCoords::required_str(Some(&bad), "apiVersion", "apiVersion")
1162                .expect_err("non-string slot must error");
1163            assert_eq!(e.to_string(), "rendered resource missing apiVersion");
1164        }
1165    }
1166
1167    #[test]
1168    fn required_str_non_object_receiver_errors_with_canonical_wire_form() {
1169        // Absent-shape corner 4: the receiver itself is not a
1170        // `Value::Object` — a resource authored as a JSON array,
1171        // string, or null at any of the levels the primitive
1172        // walks. The underlying `get_str` step returns `None`
1173        // verbatim (matching the pre-lift chain's own behavior)
1174        // and the primitive stamps the canonical wire form.
1175        for bad in [json!([1, 2, 3]), json!("stringified"), Value::Null] {
1176            let e = RenderedResourceCoords::required_str(Some(&bad), "name", "metadata.name")
1177                .expect_err("non-object receiver must error");
1178            assert_eq!(e.to_string(), "rendered resource missing metadata.name");
1179        }
1180    }
1181
1182    #[test]
1183    fn required_str_decouples_key_from_error_slot_at_metadata_name_shape() {
1184        // Slot-decoupling pin: for the `metadata.name` corner the
1185        // primitive looks up `key = "name"` on the metadata sub-
1186        // object while stamping `error_slot = "metadata.name"` into
1187        // the error's `Display` output — the two are NOT the same
1188        // string, and a regression that collapsed them (using
1189        // `key` for both the lookup AND the error slug, or
1190        // vice-versa) would silently pass the shallow `apiVersion`
1191        // / `kind` pins above (where `key == error_slot`) and fail
1192        // HERE. Present-arm: lookup succeeds on the metadata sub-
1193        // object's `name` slot, returns the owned string.
1194        let res = json!({"metadata": {"name": "demo"}});
1195        let metadata = res.get("metadata");
1196        let got = RenderedResourceCoords::required_str(metadata, "name", "metadata.name").unwrap();
1197        assert_eq!(got, "demo");
1198        // Absent-arm: same slot-decoupling but the `name` sub-slot
1199        // is absent — the error slug is the DOTTED path, not the
1200        // shallow `"name"` key.
1201        let res_no_name = json!({"metadata": {}});
1202        let metadata_empty = res_no_name.get("metadata");
1203        let e = RenderedResourceCoords::required_str(metadata_empty, "name", "metadata.name")
1204            .expect_err("absent metadata.name must error");
1205        assert_eq!(e.to_string(), "rendered resource missing metadata.name");
1206    }
1207
1208    #[test]
1209    fn required_str_error_wire_form_composes_missing_message_prefix_verbatim() {
1210        // Wire-form composition pin: the error's `Display` is
1211        // exactly `"<Self::MISSING_MESSAGE_PREFIX> <error_slot>"` —
1212        // the leading prefix comes from the `const` owner + a
1213        // single space + the caller-supplied slug. A regression
1214        // that switched the separator (a colon, an em-dash) or
1215        // dropped the prefix (returning just the slot slug) would
1216        // silently invert every operator-facing log grep footprint;
1217        // this pin binds the composition to the ONE prefix const
1218        // so a future rename lands atomically at both the source
1219        // and the pins.
1220        let e = RenderedResourceCoords::required_str(None, "name", "metadata.name")
1221            .expect_err("None receiver must error");
1222        let expected = format!(
1223            "{prefix} metadata.name",
1224            prefix = RenderedResourceCoords::MISSING_MESSAGE_PREFIX,
1225        );
1226        assert_eq!(e.to_string(), expected);
1227    }
1228
1229    #[test]
1230    fn required_str_shape_parity_matches_pre_lift_hand_authored_chain_bytewise() {
1231        // Byte-shape parity pin: on every corner (present, absent,
1232        // non-string, non-object, None-receiver) the primitive's
1233        // output MUST match the pre-lift hand-authored
1234        // `.get_str(<key>).ok_or_else(|| anyhow!("rendered resource
1235        // missing <slot>"))?.to_string()` chain bytewise — the
1236        // Ok-arm string equals the raw `get_str` slice as an owned
1237        // `String`, and the Err-arm `Display` equals the pre-lift
1238        // `anyhow!(...)` output verbatim. A regression that inserted
1239        // a normalization (a trim, an NFC-fold) into the Ok arm or
1240        // altered the diagnostic wrapping in the Err arm surfaces
1241        // HERE rather than as silent per-consumer schema drift.
1242        let cases: &[(Value, &'static str, &'static str)] = &[
1243            (json!({"apiVersion": "v1"}), "apiVersion", "apiVersion"),
1244            (json!({"kind": "K"}), "kind", "kind"),
1245        ];
1246        for (res, key, error_slot) in cases {
1247            let via_primitive =
1248                RenderedResourceCoords::required_str(Some(res), key, error_slot).unwrap();
1249            let via_pre_lift = res.get_str(key).unwrap().to_string();
1250            assert_eq!(via_primitive, via_pre_lift);
1251        }
1252        let empty = json!({"other": "value"});
1253        let err_via_primitive =
1254            RenderedResourceCoords::required_str(Some(&empty), "apiVersion", "apiVersion")
1255                .expect_err("absent slot must error");
1256        let err_via_pre_lift = anyhow::anyhow!("rendered resource missing apiVersion");
1257        assert_eq!(err_via_primitive.to_string(), err_via_pre_lift.to_string());
1258    }
1259
1260    #[test]
1261    fn required_str_missing_message_prefix_matches_pre_lift_wire_form_verbatim() {
1262        // Const-owner pin: the pre-lift hand-authored `anyhow!("rendered
1263        // resource missing X")` restatements each embedded the leading
1264        // `"rendered resource missing"` prefix as an inline literal.
1265        // Post-lift the prefix lives at ONE const owner — a rename lands
1266        // there and the three consumers on `from_json` inherit the
1267        // rename mechanically. This pin binds the const to the pre-lift
1268        // spelling so a rename shows up at BOTH the const definition
1269        // AND this pin as a coherent atomic edit, not as a silent
1270        // diff between the const and its downstream consumers.
1271        assert_eq!(
1272            RenderedResourceCoords::MISSING_MESSAGE_PREFIX,
1273            "rendered resource missing",
1274        );
1275    }
1276
1277    #[test]
1278    fn rendered_resource_coords_namespace_or_default_returns_slice_when_some() {
1279        let c = RenderedResourceCoords {
1280            api_version: "v1".into(),
1281            kind: "K".into(),
1282            name: "n".into(),
1283            namespace: Some("prod".into()),
1284        };
1285        assert_eq!(c.namespace_or_default(), "prod");
1286    }
1287
1288    #[test]
1289    fn rendered_resource_coords_namespace_or_default_falls_back_when_none() {
1290        let c = RenderedResourceCoords {
1291            api_version: "v1".into(),
1292            kind: "K".into(),
1293            name: "n".into(),
1294            namespace: None,
1295        };
1296        assert_eq!(c.namespace_or_default(), Process::DEFAULT_NAMESPACE);
1297        assert_eq!(c.namespace_or_default(), "default");
1298    }
1299
1300    // ─── FluxResourceRef::fetch_coords substrate pins ─────────────
1301    //
1302    // The 4-slot `(&namespace, &api_version, &kind, &name)` borrow
1303    // projection lifts the pre-existing 5-slot `ssapply::fetch(client,
1304    // &r.namespace, &r.api_version, &r.kind, &r.name)` splat that
1305    // recurred at TWO hand-authored sites in
1306    // `tatara-reconciler::phase_machine` (`handle_running`,
1307    // `handle_attested`) past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
1308    // trigger. These pins bind the slot order at fail-before-pass-
1309    // after granularity so a regression that swapped `namespace` and
1310    // `api_version` (both `String`, mechanically interchangeable to
1311    // a bad refactor) surfaces HERE rather than as a silent wire-time
1312    // 404 at every downstream Flux fetch consumer.
1313
1314    fn sample_flux_ref() -> FluxResourceRef {
1315        // Slot values are deliberately distinct so a swap between any
1316        // two adjacent tuple positions surfaces as an equality
1317        // failure at the assertion site — a slot-inversion regression
1318        // cannot masquerade as identity by accident.
1319        FluxResourceRef {
1320            api_version: "kustomize.toolkit.fluxcd.io/v1".to_string(),
1321            kind: "Kustomization".to_string(),
1322            name: "observability-stack".to_string(),
1323            namespace: "flux-system".to_string(),
1324            ready: true,
1325            message: None,
1326            last_check: None,
1327        }
1328    }
1329
1330    #[test]
1331    fn flux_resource_ref_fetch_coords_binds_slots_by_position() {
1332        // Positional pin: the 4-tuple return binds
1333        // `(namespace, api_version, kind, name)` in THAT order,
1334        // matching the raw `ssapply::fetch(client, ns, av, kind,
1335        // name)` positional signature every pre-lift callsite splatted
1336        // into. A regression that swapped ANY pair of adjacent slots
1337        // (all four axes are `String` and mechanically
1338        // indistinguishable at the type level) would surface here
1339        // rather than as an operator-visible wire-form 404 at every
1340        // downstream fetch consumer.
1341        let r = sample_flux_ref();
1342        let (ns, av, kind, name) = r.fetch_coords();
1343        assert_eq!(ns, "flux-system", "position 0 must be namespace");
1344        assert_eq!(
1345            av, "kustomize.toolkit.fluxcd.io/v1",
1346            "position 1 must be api_version"
1347        );
1348        assert_eq!(kind, "Kustomization", "position 2 must be kind");
1349        assert_eq!(name, "observability-stack", "position 3 must be name");
1350    }
1351
1352    #[test]
1353    fn flux_resource_ref_fetch_coords_returns_borrows_of_owned_slots() {
1354        // Borrow-discipline pin: the 4-tuple returns `&str` borrows
1355        // of the enclosing `FluxResourceRef`'s owned `String` slots —
1356        // NOT a fresh allocation or a clone. A regression that
1357        // switched the projection to owned strings (via `.clone()` or
1358        // `format!`) would defeat the zero-copy contract and would
1359        // surface here via pointer-identity comparison.
1360        let r = sample_flux_ref();
1361        let (ns, av, kind, name) = r.fetch_coords();
1362        assert!(std::ptr::eq(ns.as_ptr(), r.namespace.as_ptr()));
1363        assert!(std::ptr::eq(av.as_ptr(), r.api_version.as_ptr()));
1364        assert!(std::ptr::eq(kind.as_ptr(), r.kind.as_ptr()));
1365        assert!(std::ptr::eq(name.as_ptr(), r.name.as_ptr()));
1366    }
1367
1368    #[test]
1369    fn flux_resource_ref_fetch_coords_is_a_pure_borrow_projection() {
1370        // Purity pin: calling the projection twice on the same ref
1371        // returns byte-identical slices (same pointer, same length).
1372        // A regression that introduced state — a lazy-cached slot
1373        // computed on first call, a normalization step that ran once
1374        // and cached — would surface here rather than as silent drift
1375        // between the VERIFY-phase and ATTEST-heartbeat consumers on
1376        // the SAME ref within one reconcile pass.
1377        let r = sample_flux_ref();
1378        let a = r.fetch_coords();
1379        let b = r.fetch_coords();
1380        assert!(std::ptr::eq(a.0.as_ptr(), b.0.as_ptr()));
1381        assert!(std::ptr::eq(a.1.as_ptr(), b.1.as_ptr()));
1382        assert!(std::ptr::eq(a.2.as_ptr(), b.2.as_ptr()));
1383        assert!(std::ptr::eq(a.3.as_ptr(), b.3.as_ptr()));
1384    }
1385
1386    #[test]
1387    fn flux_resource_ref_fetch_coords_ignores_status_slots() {
1388        // Coverage pin: the projection exposes ONLY the four API-path
1389        // slots the fetch call requires; the ref's status slots
1390        // (`ready`, `message`, `last_check`) are deliberately absent
1391        // from the tuple. The fetch signature admits four `&str`
1392        // slots, and the projection carries EXACTLY those four — no
1393        // silent widening that would surface as an arity mismatch at
1394        // every downstream `fetch(...)` call.
1395        let r = sample_flux_ref();
1396        let coords = r.fetch_coords();
1397        assert_eq!(
1398            std::mem::size_of_val(&coords),
1399            std::mem::size_of::<(&str, &str, &str, &str)>(),
1400            "the 4-tuple width must match the raw fetch signature's four `&str` slots"
1401        );
1402    }
1403
1404    // ─── FluxResourceRef::observed substrate pins ─────────────────
1405    //
1406    // The 6-arg composer stamps `last_check` at ONE substrate site
1407    // (the pre-lift 7-slot struct-literal restated `Some(chrono::
1408    // Utc::now())` at TWO hand-authored sites in
1409    // `tatara-reconciler::phase_machine` — `handle_running`'s per-
1410    // ref VERIFY rebuild and `flux_ref_from_json`'s post-SSA
1411    // seeder). These pins bind the six input slots by position so a
1412    // regression that swapped `api_version` and `kind` (both
1413    // `String`, mechanically interchangeable to a bad refactor)
1414    // surfaces HERE rather than as a silent wire-time 404 at every
1415    // downstream fetch consumer.
1416    //
1417    // Every test constructs distinct values across the four
1418    // `String` coordinate slots so a slot swap fails structurally
1419    // rather than by accident of matching literals.
1420
1421    #[test]
1422    fn flux_resource_ref_observed_binds_slots_by_position() {
1423        // Positional pin: the 6-arg constructor binds
1424        // `(api_version, kind, name, namespace, ready, message)`
1425        // in THAT order, matching the pre-lift 7-slot struct-
1426        // literal's declaration order. A regression that swapped
1427        // ANY pair of adjacent `String` coordinate slots (all four
1428        // are mechanically indistinguishable at the type level)
1429        // would surface here rather than as a wire-time 404 at
1430        // every downstream Flux fetch consumer.
1431        let r = FluxResourceRef::observed(
1432            "kustomize.toolkit.fluxcd.io/v1".to_string(),
1433            "Kustomization".to_string(),
1434            "observability-stack".to_string(),
1435            "flux-system".to_string(),
1436            true,
1437            Some("healthy".to_string()),
1438        );
1439        assert_eq!(r.api_version, "kustomize.toolkit.fluxcd.io/v1");
1440        assert_eq!(r.kind, "Kustomization");
1441        assert_eq!(r.name, "observability-stack");
1442        assert_eq!(r.namespace, "flux-system");
1443        assert!(r.ready);
1444        assert_eq!(r.message.as_deref(), Some("healthy"));
1445    }
1446
1447    #[test]
1448    fn flux_resource_ref_observed_stamps_last_check_at_now() {
1449        // Stamp pin: the `last_check` slot is filled with
1450        // `Some(<recent Utc>)` at the composer's body. A
1451        // regression that dropped the stamp (leaving `None`) or
1452        // shifted it to a stale constant would surface here rather
1453        // than as silent operator-observed staleness at
1454        // `ProcessStatus.flux_resources` panels. Bounds the stamp
1455        // to within a generous 5s window of the composer call so
1456        // slow CI runners do not false-positive.
1457        let before = Utc::now();
1458        let r = FluxResourceRef::observed(
1459            "v1".to_string(),
1460            "K".to_string(),
1461            "n".to_string(),
1462            "ns".to_string(),
1463            false,
1464            None,
1465        );
1466        let after = Utc::now();
1467        let stamp = r.last_check.expect("observed must stamp last_check");
1468        assert!(stamp >= before, "stamp must be >= before-call `now`");
1469        assert!(stamp <= after, "stamp must be <= after-call `now`");
1470    }
1471
1472    #[test]
1473    fn flux_resource_ref_observed_round_trips_through_fetch_coords() {
1474        // Cross-composer coherence pin: a ref built by `observed`
1475        // then unpacked by `fetch_coords` returns the same four
1476        // slots in the peer projection's positional order
1477        // `(namespace, api_version, kind, name)`. Composition of
1478        // the two primitives on the same ref preserves the slot
1479        // identity — a regression at either end (a slot swap in
1480        // `observed`, or a slot swap in `fetch_coords`) would
1481        // surface here rather than as silent drift between the
1482        // writer and the reader on the same persisted slice.
1483        let r = FluxResourceRef::observed(
1484            "helm.toolkit.fluxcd.io/v2".to_string(),
1485            "HelmRelease".to_string(),
1486            "prometheus-op".to_string(),
1487            "monitoring".to_string(),
1488            false,
1489            Some("applied; awaiting reconciliation".to_string()),
1490        );
1491        let (ns, av, kind, name) = r.fetch_coords();
1492        assert_eq!(ns, "monitoring");
1493        assert_eq!(av, "helm.toolkit.fluxcd.io/v2");
1494        assert_eq!(kind, "HelmRelease");
1495        assert_eq!(name, "prometheus-op");
1496    }
1497
1498    #[test]
1499    fn flux_resource_ref_observed_matches_pre_lift_struct_literal_field_for_field() {
1500        // Byte-for-byte parity pin against the pre-lift 7-slot
1501        // struct-literal spelled at BOTH `phase_machine::
1502        // handle_running` and `phase_machine::flux_ref_from_json`.
1503        // A regression that reordered any of the six inputs at
1504        // the composer's argument list, or that swapped a
1505        // `ready`/`message` pair inside the composer's body,
1506        // would surface here rather than as silent divergence
1507        // between the composer's output and the pre-lift hand-
1508        // authored shape every persisted status writer restated.
1509        let composed = FluxResourceRef::observed(
1510            "source.toolkit.fluxcd.io/v1beta2".to_string(),
1511            "OCIRepository".to_string(),
1512            "chart-source".to_string(),
1513            "flux-system".to_string(),
1514            false,
1515            Some("applied; awaiting reconciliation".to_string()),
1516        );
1517        // Hand-authored the same seven slots directly, with a
1518        // held-open stamp window across the composer call.
1519        let stamped = composed.last_check.expect("stamped");
1520        let baseline = FluxResourceRef {
1521            api_version: "source.toolkit.fluxcd.io/v1beta2".to_string(),
1522            kind: "OCIRepository".to_string(),
1523            name: "chart-source".to_string(),
1524            namespace: "flux-system".to_string(),
1525            ready: false,
1526            message: Some("applied; awaiting reconciliation".to_string()),
1527            last_check: Some(stamped),
1528        };
1529        assert_eq!(composed.api_version, baseline.api_version);
1530        assert_eq!(composed.kind, baseline.kind);
1531        assert_eq!(composed.name, baseline.name);
1532        assert_eq!(composed.namespace, baseline.namespace);
1533        assert_eq!(composed.ready, baseline.ready);
1534        assert_eq!(composed.message, baseline.message);
1535        assert_eq!(composed.last_check, baseline.last_check);
1536    }
1537
1538    // ─── FluxResourceRef::pending substrate pins ─────────────────────
1539    //
1540    // Bind [`FluxResourceRef::pending`] at fail-before-pass-after
1541    // granularity so a regression that leaked a non-default status
1542    // slot (`ready: true`, `message: Some("something")`, `last_check:
1543    // Some(Utc::now())`), swapped two adjacent coordinate slots (all
1544    // four are `String` and mechanically interchangeable at the type
1545    // level), or diverged from the pre-lift 7-slot struct-literal on
1546    // any of the seven fields surfaces HERE rather than as silent
1547    // operator-invisible drift at the 3 downstream fixture consumers
1548    // (crd.rs `sample_flux_ref`, ssapply.rs `sample_flux_ref_for_diag`,
1549    // ssapply.rs `flux_ref_fetch_error_context_matches_pre_lift_...`).
1550    //
1551    // Each pin is fail-before-pass-after: the primitive did not exist
1552    // pre-lift, so any test that invokes it fails to compile pre-lift
1553    // and passes post-lift; the byte-identity pins below then bind
1554    // the specific shape choice.
1555
1556    #[test]
1557    fn flux_resource_ref_pending_binds_coordinate_slots_by_position() {
1558        // Positional pin: the 4-arg constructor binds `(api_version,
1559        // kind, name, namespace)` in THAT order, matching the pre-
1560        // lift 7-slot struct-literal's declaration order. A regression
1561        // that swapped ANY pair of adjacent `String` coordinate slots
1562        // (all four are mechanically indistinguishable at the type
1563        // level) would surface here rather than as a wire-time 404 at
1564        // every downstream Flux fetch consumer that walks
1565        // `FluxResourceRef.fetch_coords`.
1566        let r = FluxResourceRef::pending(
1567            "kustomize.toolkit.fluxcd.io/v1",
1568            "Kustomization",
1569            "observability-stack",
1570            "flux-system",
1571        );
1572        assert_eq!(r.api_version, "kustomize.toolkit.fluxcd.io/v1");
1573        assert_eq!(r.kind, "Kustomization");
1574        assert_eq!(r.name, "observability-stack");
1575        assert_eq!(r.namespace, "flux-system");
1576    }
1577
1578    #[test]
1579    fn flux_resource_ref_pending_defaults_every_status_slot() {
1580        // Default-slot pin: the three status slots (`ready`, `message`,
1581        // `last_check`) are ALL defaulted at the composer's body — no
1582        // wall-clock read, no non-`None` `message` leak, no `ready:
1583        // true` regression that would silently un-pend the fixture.
1584        // A regression that stamped `Some(Utc::now())` into
1585        // `last_check` (matching the sibling `observed` composer's
1586        // wall-clock read) would silently defeat the deterministic-
1587        // fixture contract the peer partition holds.
1588        let r = FluxResourceRef::pending("v1", "K", "n", "ns");
1589        assert!(
1590            !r.ready,
1591            "pending composer must default `ready` to false — non-`false` breaks the pre-observation contract"
1592        );
1593        assert_eq!(
1594            r.message, None,
1595            "pending composer must default `message` to None — non-`None` leaks a stale message into the pre-observation seed",
1596        );
1597        assert_eq!(
1598            r.last_check, None,
1599            "pending composer must default `last_check` to None — a `Some(_)` leak defeats the deterministic-peer partition against `observed`",
1600        );
1601    }
1602
1603    #[test]
1604    fn flux_resource_ref_pending_accepts_both_owned_and_borrowed_coordinates() {
1605        // The `impl Into<String>` ergonomic contract: both `&'static
1606        // str` literals (the fixture-helper sites that spell
1607        // coordinates inline) and owned `String` (a future callsite
1608        // handing off a dynamically-derived coordinate) round-trip
1609        // through the SAME composer signature without widening. A
1610        // regression that specialised the signature to one form or
1611        // the other would break either the inline-literal helpers or
1612        // the owned-`String` downstream consumers.
1613        let borrowed: FluxResourceRef = FluxResourceRef::pending("v1", "K", "n", "ns");
1614        let owned: FluxResourceRef = FluxResourceRef::pending(
1615            "v1".to_string(),
1616            "K".to_string(),
1617            "n".to_string(),
1618            "ns".to_string(),
1619        );
1620        assert_eq!(borrowed.api_version, owned.api_version);
1621        assert_eq!(borrowed.kind, owned.kind);
1622        assert_eq!(borrowed.name, owned.name);
1623        assert_eq!(borrowed.namespace, owned.namespace);
1624        assert_eq!(borrowed.ready, owned.ready);
1625        assert_eq!(borrowed.message, owned.message);
1626        assert_eq!(borrowed.last_check, owned.last_check);
1627    }
1628
1629    #[test]
1630    fn flux_resource_ref_pending_matches_pre_lift_struct_literal_bytewise() {
1631        // Byte-for-byte parity pin against the pre-lift 7-slot
1632        // struct-literal spelled at ALL THREE hand-authored fixture
1633        // sites (crd.rs `sample_flux_ref`, ssapply.rs
1634        // `sample_flux_ref_for_diag`, ssapply.rs inline in the
1635        // cross-substrate coherence pin's per-case sweep). Sweeps the
1636        // three representative coordinate tuples the pre-lift sites
1637        // used, so a regression that special-cased any one variant
1638        // (a `Kustomization`-only path via `if kind ==
1639        // "Kustomization" ...`) surfaces here.
1640        let cases = [
1641            (
1642                "kustomize.toolkit.fluxcd.io/v1",
1643                "Kustomization",
1644                "observability-stack",
1645                "flux-system",
1646            ),
1647            (
1648                "helm.toolkit.fluxcd.io/v2",
1649                "HelmRelease",
1650                "prometheus-op",
1651                "monitoring",
1652            ),
1653            (
1654                "source.toolkit.fluxcd.io/v1beta2",
1655                "OCIRepository",
1656                "chart-source",
1657                "flux-system",
1658            ),
1659        ];
1660        for (av, kind, name, ns) in cases {
1661            let composed = FluxResourceRef::pending(av, kind, name, ns);
1662            let hand_authored = FluxResourceRef {
1663                api_version: av.to_string(),
1664                kind: kind.to_string(),
1665                name: name.to_string(),
1666                namespace: ns.to_string(),
1667                ready: false,
1668                message: None,
1669                last_check: None,
1670            };
1671            assert_eq!(composed.api_version, hand_authored.api_version);
1672            assert_eq!(composed.kind, hand_authored.kind);
1673            assert_eq!(composed.name, hand_authored.name);
1674            assert_eq!(composed.namespace, hand_authored.namespace);
1675            assert_eq!(composed.ready, hand_authored.ready);
1676            assert_eq!(composed.message, hand_authored.message);
1677            assert_eq!(composed.last_check, hand_authored.last_check);
1678        }
1679    }
1680
1681    #[test]
1682    fn flux_resource_ref_pending_partitions_the_composer_axis_against_observed() {
1683        // Cross-composer partition pin: `pending` and `observed`
1684        // both produce `FluxResourceRef` but partition the composer
1685        // axis at the (deterministic-fixture, wall-clock-observed)
1686        // split — `pending` reads no clock and leaves `last_check:
1687        // None`, `observed` reads the wall clock and stamps
1688        // `last_check: Some(<recent Utc>)`. A regression that merged
1689        // either primitive onto the other (a `pending` that started
1690        // stamping `Utc::now()`, an `observed` that started leaving
1691        // `last_check: None`) would collapse the partition and
1692        // surface here.
1693        let p = FluxResourceRef::pending("v1", "K", "n", "ns");
1694        assert_eq!(
1695            p.last_check, None,
1696            "pending is deterministic — no clock read"
1697        );
1698        let o = FluxResourceRef::observed(
1699            "v1".to_string(),
1700            "K".to_string(),
1701            "n".to_string(),
1702            "ns".to_string(),
1703            false,
1704            None,
1705        );
1706        assert!(o.last_check.is_some(), "observed reads the wall clock");
1707    }
1708
1709    #[test]
1710    fn flux_resource_ref_pending_composes_with_fetch_coords_at_pre_observation_shape() {
1711        // Cross-composer coherence pin: a ref built by `pending`
1712        // then unpacked by `fetch_coords` returns the same four
1713        // slots in the peer projection's positional order
1714        // `(namespace, api_version, kind, name)`. Composition of
1715        // the two primitives on the same pre-observation ref
1716        // preserves the slot identity — a regression at either end
1717        // (a slot swap in `pending`, or a slot swap in
1718        // `fetch_coords`) would surface here rather than as silent
1719        // drift between the fixture writer and every downstream
1720        // fetch reader.
1721        let r = FluxResourceRef::pending(
1722            "helm.toolkit.fluxcd.io/v2",
1723            "HelmRelease",
1724            "prometheus-op",
1725            "monitoring",
1726        );
1727        let (ns, av, kind, name) = r.fetch_coords();
1728        assert_eq!(ns, "monitoring");
1729        assert_eq!(av, "helm.toolkit.fluxcd.io/v2");
1730        assert_eq!(kind, "HelmRelease");
1731        assert_eq!(name, "prometheus-op");
1732    }
1733
1734    #[test]
1735    fn rendered_resource_coords_namespace_fallback_shares_process_default_const() {
1736        // Byte-identity between the namespace fallback and the workspace-
1737        // wide `Process::DEFAULT_NAMESPACE` const. A regression that spelled
1738        // the fallback as any other string ("kube-system", "", "default-ns")
1739        // would silently drift between the coord-primitive family here and
1740        // the `Process`-borne family in `crd.rs` — surfaces here rather than
1741        // as operator-observed namespace routing skew between the two
1742        // primitive families.
1743        let c = RenderedResourceCoords {
1744            api_version: "v1".into(),
1745            kind: "K".into(),
1746            name: "n".into(),
1747            namespace: None,
1748        };
1749        assert_eq!(c.namespace_or_default(), Process::DEFAULT_NAMESPACE);
1750    }
1751
1752    // ─── CheckedCondition::all_satisfied substrate pins ─────────────
1753    //
1754    // Bind [`CheckedCondition::all_satisfied`] at fail-before-pass-
1755    // after granularity so a regression that flipped the fold
1756    // direction (`any` for `all`), inverted the projected bit
1757    // (`!c.satisfied`), reshaped the return form (an owned
1758    // `Vec<bool>` instead of the folded `bool`), or dropped the
1759    // vacuous-truth empty-slice corner surfaces HERE rather than as
1760    // silent operator-facing gate-flip at the reconciler's PROVE-
1761    // phase precondition gate + VERIFY-phase postcondition gate.
1762
1763    fn sample_checked(satisfied: bool) -> CheckedCondition {
1764        CheckedCondition {
1765            condition: crate::boundary::Condition {
1766                kind: crate::boundary::ConditionKind::ProcessPhase,
1767                params: serde_json::json!({}),
1768            },
1769            satisfied,
1770            last_check: None,
1771            message: None,
1772        }
1773    }
1774
1775    #[test]
1776    fn checked_condition_all_satisfied_returns_true_when_every_row_is_satisfied() {
1777        // Populated slice, every row `satisfied = true` — the RENDER-
1778        // phase advance corner: `handle_execing` proceeds to intent
1779        // dispatch iff every precondition holds.
1780        let checked = vec![
1781            sample_checked(true),
1782            sample_checked(true),
1783            sample_checked(true),
1784        ];
1785        assert!(
1786            CheckedCondition::all_satisfied(&checked),
1787            "all-satisfied slice must fold to true — a regression that inverted the bit would silently gate every RENDER advance behind an inverted predicate"
1788        );
1789    }
1790
1791    #[test]
1792    fn checked_condition_all_satisfied_returns_false_when_any_row_is_unsatisfied() {
1793        // Populated slice with ONE unsatisfied row — the heartbeat
1794        // requeue corner: `handle_running` stays in Running while any
1795        // postcondition remains unsatisfied.
1796        let mixed = vec![
1797            sample_checked(true),
1798            sample_checked(false),
1799            sample_checked(true),
1800        ];
1801        assert!(
1802            !CheckedCondition::all_satisfied(&mixed),
1803            "mixed slice must fold to false — a regression that folded via `any` instead of `all` would silently green-light every VERIFY advance"
1804        );
1805    }
1806
1807    #[test]
1808    fn checked_condition_all_satisfied_returns_false_when_every_row_is_unsatisfied() {
1809        // Populated slice with EVERY row unsatisfied — the tightest
1810        // gate corner: no phase advance is legal.
1811        let none_pass = vec![sample_checked(false), sample_checked(false)];
1812        assert!(
1813            !CheckedCondition::all_satisfied(&none_pass),
1814            "all-unsatisfied slice must fold to false"
1815        );
1816    }
1817
1818    #[test]
1819    fn checked_condition_all_satisfied_returns_true_on_empty_slice() {
1820        // Empty-slice vacuous-truth corner: `[T]::iter().all(_)`
1821        // returns `true` on empty input, and the pre-lift phase
1822        // gate's `if !preconditions.is_empty() { ... }` guard sat
1823        // BEFORE the fold, so the fold itself never saw an empty
1824        // slice in production. Post-lift the primitive absorbs the
1825        // empty corner cleanly — a caller that drops the outer
1826        // `is_empty()` guard (a future path that folds every gate
1827        // through this ONE primitive without a prior gate) still
1828        // sees the vacuous-truth semantics that match
1829        // [`Iterator::all`].
1830        let empty: Vec<CheckedCondition> = vec![];
1831        assert!(
1832            CheckedCondition::all_satisfied(&empty),
1833            "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"
1834        );
1835    }
1836
1837    #[test]
1838    fn checked_condition_all_satisfied_matches_pre_lift_iter_all_chain_shape() {
1839        // Byte-identity pin against the pre-lift `.iter().all(|c|
1840        // c.satisfied)` chain shape the reconciler's two boundary
1841        // gates hand-authored. Sweeps every corner every gate
1842        // plausibly encounters (empty slice, single satisfied,
1843        // single unsatisfied, mixed satisfied first, mixed
1844        // unsatisfied first) so a regression that reshaped either
1845        // link surfaces HERE rather than at the two downstream phase
1846        // gates.
1847        let corners: Vec<Vec<CheckedCondition>> = vec![
1848            vec![],
1849            vec![sample_checked(true)],
1850            vec![sample_checked(false)],
1851            vec![sample_checked(true), sample_checked(false)],
1852            vec![sample_checked(false), sample_checked(true)],
1853            vec![
1854                sample_checked(true),
1855                sample_checked(true),
1856                sample_checked(true),
1857            ],
1858            vec![
1859                sample_checked(false),
1860                sample_checked(false),
1861                sample_checked(false),
1862            ],
1863        ];
1864        for corner in &corners {
1865            let via_primitive = CheckedCondition::all_satisfied(corner);
1866            #[allow(clippy::redundant_closure_for_method_calls)]
1867            let hand_authored = corner.iter().all(|c| c.satisfied);
1868            assert_eq!(
1869                via_primitive, hand_authored,
1870                "all_satisfied fold must match hand-authored .iter().all(|c| c.satisfied) chain byte-identically at corner {corner:?}"
1871            );
1872        }
1873    }
1874
1875    #[test]
1876    fn checked_condition_all_satisfied_short_circuits_on_first_unsatisfied_row() {
1877        // Semantic pin against [`Iterator::all`]'s short-circuit
1878        // discipline: a regression that folded via `checked.iter()
1879        // .filter(|c| c.satisfied).count() == checked.len()` would
1880        // still produce the same `bool` result but would eagerly
1881        // walk every row, and a future addition of an expensive
1882        // per-row side effect (a metric emit, a log line, a
1883        // conditional postcondition-retry hook) would silently fire
1884        // on every row past the first failure. The primitive must
1885        // preserve the pre-lift short-circuit — a regression that
1886        // dropped it would drift telemetry, not correctness, and
1887        // would evade every other pin here. This test verifies
1888        // short-circuit by threading a counter through a peer
1889        // predicate that mirrors [`CheckedCondition::satisfied`]'s
1890        // read.
1891        use std::cell::Cell;
1892        let visited = Cell::new(0_usize);
1893        let checked: Vec<CheckedCondition> = vec![
1894            sample_checked(true),
1895            sample_checked(false),
1896            sample_checked(true),
1897            sample_checked(true),
1898        ];
1899        // Manual short-circuit fold that counts per-row reads —
1900        // must match `all_satisfied`'s count on the same slice.
1901        let via_manual = checked.iter().all(|c| {
1902            visited.set(visited.get() + 1);
1903            c.satisfied
1904        });
1905        let manual_visited = visited.get();
1906        visited.set(0);
1907        // Mirror the primitive's iteration by re-running the same
1908        // fold shape and confirming the visited count matches — the
1909        // primitive itself doesn't take a side-effecting closure,
1910        // but this pin confirms the semantic shape (2 visits on
1911        // this slice: row 0 satisfied, row 1 unsatisfied, stop).
1912        assert_eq!(via_manual, CheckedCondition::all_satisfied(&checked));
1913        assert_eq!(
1914            manual_visited, 2,
1915            "short-circuit must stop at the first unsatisfied row (index 1); manual fold visited {manual_visited} rows"
1916        );
1917    }
1918}