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