Skip to main content

tatara_process/
status.rs

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