tatara_process/crd.rs
1//! The `Process` CRD — `tatara.pleme.io/v1alpha1`.
2
3use chrono::{DateTime, Utc};
4use kube::CustomResource;
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use tatara_lisp::DeriveTataraDomain;
8
9use crate::attestation::ProcessAttestation;
10use crate::boundary::Boundary;
11use crate::classification::Classification;
12use crate::compliance::ComplianceSpec;
13use crate::encapsulates::EncapsulatesSpec;
14use crate::identity::Identity;
15use crate::intent::Intent;
16use crate::lifetime::{EphemeralLifetime, Lifetime};
17use crate::phase::ProcessPhase;
18use crate::routing::RoutingSpec;
19use crate::signal::ProcessSignal;
20use crate::spec::{DependsOn, IdentitySpec, SignalPolicy};
21use crate::status::{BoundaryStatus, ComplianceStatus, FluxResourceRef, ProcessCondition};
22
23/// Process — one element of the tatara convergence lattice, reconciled as a Unix process.
24///
25/// ```yaml
26/// apiVersion: tatara.pleme.io/v1alpha1
27/// kind: Process
28/// metadata:
29/// name: observability-stack
30/// namespace: seph
31/// spec:
32/// identity:
33/// parent: seph.1
34/// classification:
35/// pointType: Gate
36/// substrate: Observability
37/// intent:
38/// nix:
39/// flakeRef: github:pleme-io/k8s?dir=shared/infrastructure
40/// attribute: observability
41/// compliance:
42/// baseline: fedramp-moderate
43/// bindings:
44/// - framework: nist-800-53
45/// controlId: SC-7
46/// phase: AtBoundary
47/// dependsOn:
48/// - name: secret-injection
49/// ```
50#[derive(CustomResource, DeriveTataraDomain, Clone, Debug, Deserialize, Serialize, JsonSchema)]
51#[kube(
52 group = "tatara.pleme.io",
53 version = "v1alpha1",
54 kind = "Process",
55 plural = "processes",
56 shortname = "proc",
57 namespaced,
58 status = "ProcessStatus",
59 printcolumn = r#"{"name":"PID","type":"string","jsonPath":".status.pid"}"#,
60 printcolumn = r#"{"name":"Phase","type":"string","jsonPath":".status.phase"}"#,
61 printcolumn = r#"{"name":"Type","type":"string","jsonPath":".spec.classification.pointType"}"#,
62 printcolumn = r#"{"name":"Substrate","type":"string","jsonPath":".spec.classification.substrate"}"#,
63 printcolumn = r#"{"name":"Gen","type":"integer","jsonPath":".status.attestation.generation"}"#,
64 printcolumn = r#"{"name":"Age","type":"date","jsonPath":".metadata.creationTimestamp"}"#
65)]
66#[serde(rename_all = "camelCase")]
67#[tatara(keyword = "defpoint")]
68pub struct ProcessSpec {
69 /// Identity (parent, name override).
70 #[serde(default)]
71 pub identity: IdentitySpec,
72
73 /// Lattice position (6 dimensions).
74 pub classification: Classification,
75
76 /// Where rendered artifacts come from. Exactly one variant must be set.
77 pub intent: Intent,
78
79 /// Boundary predicates (preconditions / postconditions).
80 #[serde(default)]
81 pub boundary: Boundary,
82
83 /// Compliance bindings + baseline.
84 #[serde(default)]
85 pub compliance: ComplianceSpec,
86
87 /// Lattice dependencies — must reach phase before we proceed.
88 #[serde(default)]
89 pub depends_on: Vec<DependsOn>,
90
91 /// Signal policy (grace, SIGHUP strategy, start-suspended).
92 #[serde(default)]
93 pub signals: SignalPolicy,
94
95 /// Lifetime — `Permanent` (default, re-converging) or `Ephemeral`
96 /// (auto-SIGTERM per `teardown_policy` + TTL clock).
97 #[serde(default, skip_serializing_if = "Lifetime::is_default")]
98 pub lifetime: Lifetime,
99
100 /// External edges — DNS + Ingress. When `None`, the Process is
101 /// internal-only (matches today's default). See
102 /// [`crate::routing`] for the full shape.
103 #[serde(default, skip_serializing_if = "Option::is_none")]
104 pub routing: Option<RoutingSpec>,
105
106 /// Pre-existing in-cluster state this Process wraps. When `None`,
107 /// the Process is greenfield (Manage mode implicitly applied to
108 /// nothing pre-existing). See [`crate::encapsulates`] for the
109 /// three modes (Manage / Adopt / Observe).
110 #[serde(default, skip_serializing_if = "Option::is_none")]
111 pub encapsulates: Option<EncapsulatesSpec>,
112
113 /// Soft-suspend marker — reconciler treats as SIGSTOP.
114 /// Same effect as delivering SIGSTOP, but persistent across restarts.
115 #[serde(default)]
116 pub suspended: bool,
117}
118
119// Coordinate primitives — the `(namespace, name)` pair every downstream
120// composer (annotation writers, claim arbiter, boundary evaluator,
121// render owner-metadata seed) pulled by hand from `Process.metadata`
122// pre-lift, each restating the same two `Option<String>`-to-`&str`
123// unwrap incantations with the same two workspace-wide fallback
124// strings sprayed inline. Post-lift the pair lives at ONE substrate
125// primitive on `Process` — a future normalization (case-fold,
126// unicode-safe collation, cross-cluster prefix, a rename of either
127// fallback) lands here and every downstream composer inherits the
128// upgrade mechanically. Peer to `qualified_process_ref` in
129// `tatara-reconciler::ssapply`, whose two `&str` arguments are
130// exactly the pair `Process::coordinates_or_defaults` returns.
131impl Process {
132 /// The K8s canonical default namespace — the fallback every
133 /// consumer of a `Process` whose `metadata.namespace` is `None`
134 /// substitutes. Matches the string K8s itself substitutes on
135 /// namespaced resource writes with no explicit namespace.
136 pub const DEFAULT_NAMESPACE: &'static str = "default";
137
138 /// Workspace-wide fallback for a `Process`'s `metadata.name` when
139 /// it is `None` — the sentinel every annotation writer, claim
140 /// arbiter, and owner-metadata seed substitutes so downstream
141 /// grepping / label-selecting sees a stable spelling rather than
142 /// a per-callsite ad-hoc placeholder (`""`, `"<unnamed>"`, or the
143 /// empty `unwrap_or_default()` fallback). A Process authored
144 /// through the reconciler's fork path always has a name; this
145 /// constant covers the surface where an untyped `Process` value
146 /// (test fixture, dynamic API response, adopted resource pre-
147 /// name-resolution) surfaces without one.
148 pub const UNNAMED_PLACEHOLDER: &'static str = "unnamed";
149
150 /// Namespace slice with the [`Self::DEFAULT_NAMESPACE`] fallback
151 /// applied — the ONE-line collapse of the `metadata.namespace
152 /// .as_deref().unwrap_or("default")` incantation every consumer
153 /// spelled by hand pre-lift.
154 ///
155 /// Peer to [`Self::name_or_placeholder`] on the (metadata slot ×
156 /// fallback shape) axis; both compose through
157 /// [`Self::coordinates_or_defaults`] when a consumer needs the
158 /// pair together (annotation writers, claim-arbiter row builders,
159 /// render owner-metadata seed).
160 pub fn namespace_or_default(&self) -> &str {
161 self.metadata
162 .namespace
163 .as_deref()
164 .unwrap_or(Self::DEFAULT_NAMESPACE)
165 }
166
167 /// Name slice with the [`Self::UNNAMED_PLACEHOLDER`] fallback
168 /// applied — the ONE-line collapse of the `metadata.name.as_deref
169 /// ().unwrap_or("unnamed")` incantation every consumer spelled by
170 /// hand pre-lift.
171 ///
172 /// Peer to [`Self::namespace_or_default`] on the (metadata slot ×
173 /// fallback shape) axis; both compose through
174 /// [`Self::coordinates_or_defaults`] when a consumer needs the
175 /// pair together.
176 pub fn name_or_placeholder(&self) -> &str {
177 self.metadata
178 .name
179 .as_deref()
180 .unwrap_or(Self::UNNAMED_PLACEHOLDER)
181 }
182
183 /// `(namespace, name)` coordinates with the workspace-wide default
184 /// fallbacks applied — the ONE-line collapse of the paired
185 /// `metadata.namespace.as_deref().unwrap_or("default")` +
186 /// `metadata.name.as_deref().unwrap_or("unnamed")` extraction
187 /// every downstream composer restated by hand pre-lift.
188 ///
189 /// Return-tuple order matches the axis order of the substrate's
190 /// paired-composer primitive
191 /// `tatara_reconciler::ssapply::qualified_process_ref(ns, name)`:
192 /// the (namespace, name) pair this method returns feeds that
193 /// primitive positionally without an axis-swap step.
194 pub fn coordinates_or_defaults(&self) -> (&str, &str) {
195 (self.namespace_or_default(), self.name_or_placeholder())
196 }
197
198 /// `(namespace, name)` coordinates as owned `String`s, with the
199 /// namespace half fallback-defaulted to [`Self::DEFAULT_NAMESPACE`]
200 /// but the name half REQUIRED — an [`anyhow::Error`] is returned
201 /// when `metadata.name` is absent, because "unnamed" is a display
202 /// placeholder, not a valid K8s API path segment. Fed straight into
203 /// kube-rs API calls (`Api::patch`, `Api::delete`, `Api::get`) that
204 /// take owned `String` arguments; the [`Self::DEFAULT_NAMESPACE`]
205 /// fallback matches what K8s itself substitutes on namespaced
206 /// resource writes with no explicit namespace, so the surface is
207 /// safe against a `Process` whose `metadata.namespace` slot is
208 /// absent (test fixture, dynamic API response pre-defaulting) but
209 /// refuses to guess a name.
210 ///
211 /// Peer to [`Self::coordinates_or_defaults`] on the (return-form ×
212 /// name gate) axis pair:
213 /// * borrow + name-defaulted → `coordinates_or_defaults` (display,
214 /// annotation writers, ownership-tag composers — every consumer
215 /// whose downstream drops `"unnamed"` in place of a missing name
216 /// without an operator-visible failure);
217 /// * owned + name-required → this method (kube-rs API calls —
218 /// every consumer whose downstream must NOT silently substitute
219 /// a placeholder for the API call target, because the caller is
220 /// about to `patch`/`delete`/`get` at `metadata.name`).
221 ///
222 /// The error wording is pinned by
223 /// [`tests::owned_coordinates_or_err_error_message_matches_pre_lift_reconciler_wording`]
224 /// to match the exact spelling every pre-lift `tatara-reconciler`
225 /// helper produced (`"Process has no metadata.name"`) so log-line
226 /// / test greps that anchored on that wording keep matching post-
227 /// lift, and no operator-visible message drift lands as a side
228 /// effect of the substrate move.
229 pub fn owned_coordinates_or_err(&self) -> anyhow::Result<(String, String)> {
230 let ns = self
231 .metadata
232 .namespace
233 .clone()
234 .unwrap_or_else(|| Self::DEFAULT_NAMESPACE.into());
235 let name = self
236 .metadata
237 .name
238 .clone()
239 .ok_or_else(|| anyhow::anyhow!("Process has no metadata.name"))?;
240 Ok((ns, name))
241 }
242
243 /// `(name, uid)` coordinates as owned `String`s — BOTH slots
244 /// REQUIRED, spelled with the workspace-canonical
245 /// `"Process has no metadata.<slot>"` wire-form message
246 /// [`Self::owned_coordinates_or_err`] pins on the name-gate.
247 ///
248 /// Sibling to [`Self::owned_coordinates_or_err`] on the
249 /// (metadata × pair × required) axis of the coordinate-primitive
250 /// family, partitioned by SLOT PAIR:
251 /// * [`Self::owned_coordinates_or_err`] → `(namespace, name)`, the
252 /// pair every kube-rs `Api::patch` / `Api::delete` / `Api::get`
253 /// call takes positionally on a namespaced-scoped `Api<Process>`
254 /// handle (namespace fallback-defaulted, name gate REQUIRED).
255 /// * this method → `(name, uid)`, the pair
256 /// [`crate::owner_reference_json`] / [`crate::owner_references_json`]
257 /// take positionally to compose a K8s OwnerReference entry
258 /// pointing at this `Process` (BOTH gates REQUIRED — an empty
259 /// `uid` string would collide with the `is_empty` gate on
260 /// [`crate::owner_references_json`] and silently drop the owner
261 /// reference, so the missing-uid corner errors here rather than
262 /// propagates as a silent orphan).
263 ///
264 /// Pre-lift the paired 2-slot required-extract shape was hand-
265 /// authored at `tatara-reconciler::ssapply::build_owner_reference`
266 /// past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold, restating
267 /// the SAME `.metadata.<slot>.clone().ok_or_else(|| anyhow!(
268 /// "<prefix> missing metadata.<slot>"))?` chain at BOTH the name
269 /// gate AND the uid gate. The pre-lift wire-form drifted from the
270 /// workspace-canonical spelling [`Self::owned_coordinates_or_err`]
271 /// pins (`"Process has no metadata.name"`) to a lowercase-verb
272 /// variant (`"process missing metadata.name"`) — post-lift both
273 /// gates route through THIS primitive and inherit the workspace-
274 /// canonical wire-form mechanically, closing a workspace-wide
275 /// operator-facing wire-form drift.
276 ///
277 /// Return-tuple axis order matches
278 /// [`crate::owner_reference_json`]'s positional-argument order
279 /// exactly (`fn owner_reference_json(name: &str, uid: &str) ->
280 /// Value`), so the caller composes without a per-callsite axis-
281 /// swap step.
282 ///
283 /// The name gate fires BEFORE the uid gate: on a `Process` fixture
284 /// missing BOTH slots, the returned error names `metadata.name`
285 /// (matches [`Self::owned_coordinates_or_err`]'s two-gate
286 /// ordering, so the two owned-required-extract primitives in the
287 /// family report the same "first-missing-slot" slug at the paired
288 /// missing-both corner).
289 ///
290 /// Theory anchor: THEORY.md §VI.1 (generation over composition —
291 /// the paired `.metadata.<slot>.clone().ok_or_else(...)` chain
292 /// restated two hand-authored gates past the ★★ PRIME-DIRECTIVE
293 /// ≥ 2 duplication trigger). THEORY.md §II.1 invariant 5
294 /// (composition preserves proofs — a regression that swapped the
295 /// two gates, drifted the wire-form back to the lowercase-verb
296 /// spelling, reshaped the axis order of the return tuple, or
297 /// relaxed either gate to a silent-string fallback surfaces at
298 /// the tests below rather than as silent operator-facing drift at
299 /// every downstream [`crate::owner_reference_json`] consumer that
300 /// ingests this pair positionally).
301 pub fn owned_name_and_uid_or_err(&self) -> anyhow::Result<(String, String)> {
302 let name = self
303 .metadata
304 .name
305 .clone()
306 .ok_or_else(|| anyhow::anyhow!("Process has no metadata.name"))?;
307 let uid = self
308 .metadata
309 .uid
310 .clone()
311 .ok_or_else(|| anyhow::anyhow!("Process has no metadata.uid"))?;
312 Ok((name, uid))
313 }
314
315 /// `(namespace, name)` coordinates in the BORROW + NAME-REQUIRED
316 /// corner of the primitive family — namespace half falls back to
317 /// [`Self::DEFAULT_NAMESPACE`], but the name half is REQUIRED
318 /// (`None` on a `Process` whose `metadata.name` is absent, so the
319 /// caller stops with an `else { continue; }` / `else { return
320 /// …; }` guard rather than proceeding with the empty-string
321 /// sentinel every pre-lift consumer had to spell inline).
322 ///
323 /// Peer to [`Self::coordinates_or_defaults`] +
324 /// [`Self::owned_coordinates_or_err`] on the (return-form ×
325 /// name-gate) axis pair — closes the corner the family previously
326 /// left open:
327 ///
328 /// * borrow + name-defaulted → [`Self::coordinates_or_defaults`]
329 /// (annotation writers, render owner-metadata seed — consumers
330 /// whose downstream tolerates the `"unnamed"` display placeholder
331 /// without operator-visible failure);
332 /// * borrow + name-required → **this method** (claim-arbiter
333 /// probes, child-Process delete-fan-out — consumers that need a
334 /// real API-path leaf and cleanly SKIP the row when the name is
335 /// absent rather than issuing a K8s call with an empty-string
336 /// name argument);
337 /// * owned + name-required → [`Self::owned_coordinates_or_err`]
338 /// (kube-rs API-path calls — consumers whose downstream requires
339 /// owned `String` arguments and rejects the missing-name corner
340 /// with a load-bearing error message).
341 ///
342 /// The primitive family's `None`-on-missing-name semantics
343 /// intentionally differs from [`Self::owned_coordinates_or_err`]'s
344 /// error-on-missing-name semantics: the caller sites for this form
345 /// (child-Process fan-out, claim-arbiter row probes) are non-fatal
346 /// SKIPS rather than reportable failures — an `Option::None` at
347 /// the primitive lets the caller thread that "skip" through a
348 /// let-else without stringifying / logging an anyhow chain per
349 /// missing-name occurrence.
350 ///
351 /// The namespace fallback matches [`Self::coordinates_or_defaults`]
352 /// (via [`Self::namespace_or_default`]), so a consumer that
353 /// switches between the two borrow-form primitives based on its
354 /// name-gate need never sees a different namespace-fallback string
355 /// as a side effect.
356 pub fn coordinates_or_none(&self) -> Option<(&str, &str)> {
357 let name = self.metadata.name.as_deref()?;
358 Some((self.namespace_or_default(), name))
359 }
360
361 /// Canonical `<ns>/<name>` **namespace-qualified process reference**
362 /// composed straight off the live [`Process`] — the ONE-liner
363 /// collapse of the paired
364 /// `let (ns, name) = process.coordinates_or_defaults(); let r =
365 /// qualified_process_ref(ns, name);` incantation every consumer
366 /// whose downstream keys a Process by "which cluster location owns
367 /// it" hand-authored at scattered sites across `tatara-reconciler`.
368 ///
369 /// Pre-lift the 2-step `coordinates_or_defaults() →
370 /// qualified_process_ref(ns, name)` composition was hand-authored
371 /// at THREE sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
372 /// threshold in `tatara-reconciler`, each restating the SAME
373 /// paired projection + `<ns>/<name>` shape:
374 /// * `render::render_routing` — routing-graph `PROCESS=<ref>`
375 /// annotation seed on every emitted Ingress / DNSEndpoint,
376 /// feeding [`crate::status::FluxResourceRef`] downstream.
377 /// * `render::render_export_jobs` — export-Job `PROCESS=<ref>`
378 /// annotation seed on every emitted export `batch/v1` Job.
379 /// * `table_controller::reconcile` — claim-arbiter row-key +
380 /// `Candidate.process_ref` seed on the stable-name claim
381 /// registry (the reference lands verbatim in
382 /// [`crate::table::ClaimRecord.holder`], where every downstream
383 /// claim query greps it).
384 ///
385 /// All THREE sites walked the SAME 2-step chain — pull the
386 /// `(ns, name)` pair through [`Self::coordinates_or_defaults`],
387 /// then feed the pair positionally into
388 /// [`crate::qualified_process_ref`]. Post-lift each caller reads
389 /// `process.qualified_ref()` — the paired projection + shape
390 /// composer now sit at ONE substrate owner, so a rename of either
391 /// workspace-wide fallback (`"default"` / `"unnamed"`), a swap of
392 /// the `<ns>/<name>` separator, a normalization pass inserted
393 /// between the paired projection and the shape composer, or a
394 /// future `<ns>/<name>@<gen>` / `<cluster>/<ns>/<name>` cross-
395 /// cluster extension lands here exactly once and every consumer
396 /// (annotation seed, claim-row key, holder-slot writer, export-
397 /// Job seed, `Candidate` composer) inherits the upgrade
398 /// mechanically.
399 ///
400 /// Peer to [`Self::coordinates_or_defaults`] on the (return-form ×
401 /// composition-depth) axis pair:
402 /// * pair + defaulted → [`Self::coordinates_or_defaults`]
403 /// (consumers that thread each half into a separate positional
404 /// slot — `Api::namespaced(client, &ns) + Api::patch(&name, …)`,
405 /// `one_export_job(ns, name, …)`, `EdgeContext { process_name,
406 /// process_namespace, … }`);
407 /// * shape + defaulted → **this method** (consumers that key on
408 /// the composed `<ns>/<name>` reference directly — the
409 /// `PROCESS=<ref>` annotation seed, the `ClaimRecord.holder`
410 /// slot, the label-selector composer).
411 ///
412 /// The namespace-fallback discipline matches
413 /// [`Self::coordinates_or_defaults`] (via
414 /// [`Self::namespace_or_default`]) and the name-fallback discipline
415 /// matches [`Self::name_or_placeholder`], so a consumer that
416 /// switches between the pair-returning primitive and this shape-
417 /// composing primitive never sees a different fallback string as
418 /// a side effect. The composed reference is byte-identical to the
419 /// pre-lift hand-authored `format!("{ns}/{name}")` with `ns` /
420 /// `name` supplied by the pair-returning primitive, so downstream
421 /// greps keyed on the reference shape (`PROCESS=<ref>` on emitted
422 /// resources, `holder = <ref>` on claim-registry queries) match
423 /// bytewise post-lift.
424 ///
425 /// Theory anchor: THEORY.md §VI.1 (generation over composition —
426 /// the 2-step paired-projection + shape-composer chain recurred at
427 /// three hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2
428 /// duplication trigger, and is lifted onto ONE workspace-wide
429 /// owner here). THEORY.md §II.1 invariant 5 (composition preserves
430 /// proofs — a regression that inserted a normalization step at
431 /// only two of three sites, or that drifted the fallback strings
432 /// between the paired projection and the shape composer, surfaces
433 /// at [`tests::qualified_ref_*`] rather than as silent operator-
434 /// visible skew across the three annotation / claim-key /
435 /// export-Job seed writers).
436 #[must_use]
437 pub fn qualified_ref(&self) -> String {
438 let (ns, name) = self.coordinates_or_defaults();
439 crate::qualified_process_ref(ns, name)
440 }
441
442 /// Borrowed lookup of ONE key in `metadata.annotations`, with
443 /// BOTH the missing-`annotations` corner AND the missing-key
444 /// corner collapsed to `None` — the ONE-liner collapse of the
445 /// paired `self.metadata.annotations.as_ref().and_then(|m|
446 /// m.get(key)).map(String::as_str)` incantation every consumer
447 /// restated by hand pre-lift.
448 ///
449 /// Pre-lift the 3-line `.metadata.annotations.as_ref().and_then
450 /// (|m| m.get(KEY))` chain (in three tail variants — `.cloned()`,
451 /// `.cloned().unwrap_or_default()`, `.map(String::as_str)`) was
452 /// hand-authored at THREE sites past the ★★ PRIME-DIRECTIVE ≥ 2
453 /// duplication threshold across the workspace:
454 /// * `tatara-reconciler::signals::ingest` — SIGNAL annotation
455 /// lookup (pre-lift `.cloned()` for owned parsing).
456 /// * `tatara-reconciler::phase_machine::released_from_annotation`
457 /// — RELEASED_FROM annotation lookup (pre-lift `.cloned()
458 /// .unwrap_or_default()` for `match v.as_str()`).
459 /// * `tatara-pool-reconciler::controller_pool::process_belongs_to_pool`
460 /// — POOL annotation lookup (pre-lift `.map(String::as_str)`
461 /// for `== Some(pool_name)`).
462 ///
463 /// All THREE sites walked the SAME 3-line chain — read the
464 /// annotations map, gate on presence, index by key — differing
465 /// only in the tail that shaped the result. Post-lift each
466 /// caller routes through the ONE substrate primitive here and
467 /// applies its own tail at its own site (`.map(str::to_string)`
468 /// / bare match / `==`).
469 ///
470 /// Return-form axis: `Option<&str>` mirrors the existing borrow-
471 /// first discipline of the peer metadata primitives
472 /// [`Self::namespace_or_default`], [`Self::name_or_placeholder`],
473 /// [`Self::coordinates_or_none`]. The two corners the chain
474 /// swallowed pre-lift (missing `metadata.annotations` map,
475 /// missing key inside the map) BOTH collapse to `None` so
476 /// `.is_some()` / `if let Some(_)` / `Option::map` behave
477 /// identically on a `Process` whose annotations block is `None`
478 /// and on one whose annotations block is populated but omits the
479 /// key — matching what the pre-lift `.and_then(...)` chain
480 /// produced.
481 ///
482 /// A future normalization step (a key-canonicalization pass,
483 /// a case-fold lookup, a per-key alias table for renamed
484 /// annotations across API versions, a per-namespace override
485 /// substrate) lands at ONE substrate method here and all three
486 /// downstream consumers pick up the upgrade mechanically — no
487 /// per-callsite hand-edit at `ingest` / `released_from_annotation`
488 /// / `process_belongs_to_pool`.
489 ///
490 /// Sibling to the peer metadata primitives
491 /// ([`Self::namespace_or_default`], [`Self::name_or_placeholder`],
492 /// [`Self::coordinates_or_defaults`], [`Self::coordinates_or_none`],
493 /// [`Self::owned_coordinates_or_err`]) on the metadata axis;
494 /// this method opens the borrow-form peer on the ANNOTATION
495 /// axis. Future annotation projections (a paired
496 /// `label(&str) -> Option<&str>` on `metadata.labels`, a
497 /// `has_annotation(&str) -> bool` boolean gate for presence-
498 /// only consumers) land as peer methods on this same axis.
499 ///
500 /// Theory anchor: THEORY.md §VI.1 (generation over composition
501 /// — the 3-line annotation-lookup chain recurred at three
502 /// hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2
503 /// duplication trigger, and is lifted to ONE owner here).
504 /// THEORY.md §II.1 invariant 5 (composition preserves proofs —
505 /// the pins bind the missing-`annotations` corner + the
506 /// missing-key corner + the borrow-form `&str` lifetime + the
507 /// byte-identical parity with the pre-lift 3-line chain, so a
508 /// regression that drifted any surface at
509 /// `tests::annotation_*` rather than as silent operator-facing
510 /// skew between the SIGNAL / RELEASED_FROM / POOL annotation
511 /// readers).
512 pub fn annotation(&self, key: &str) -> Option<&str> {
513 self.metadata
514 .annotations
515 .as_ref()
516 .and_then(|m| m.get(key))
517 .map(String::as_str)
518 }
519
520 /// Borrow-form metadata-projection primitive on the `metadata.uid`
521 /// axis: returns the K8s-API-server-assigned uid as a `&str`, with
522 /// the missing-uid corner collapsed to the load-bearing empty-string
523 /// sentinel — the ONE-liner collapse of the paired
524 /// `self.metadata.uid.as_deref().unwrap_or("")` incantation every
525 /// owner-reference-emitting consumer restated by hand pre-lift.
526 ///
527 /// The empty-string fallback is NOT arbitrary — it is the exact
528 /// sentinel value the sibling substrate composer
529 /// [`crate::owner_references_json`] gates on (`if uid.is_empty()
530 /// { vec![] } else { vec![owner_reference_json(name, uid)] }`) to
531 /// stamp `metadata.ownerReferences: []` on a resource whose owning
532 /// Process pre-dates the API server's `metadata.uid` assignment
533 /// (test fixture, mid-Forking snapshot before the first `patch`
534 /// round-trip, dynamic API response pre-uid-resolution). Pre-lift
535 /// each consumer spelled the fallback as `.unwrap_or("")` at its
536 /// callsite; the two literals in two files could drift silently to
537 /// `.unwrap_or_default()`, `.unwrap_or("<unknown>")`, or an
538 /// `if let Some(u) = &process.metadata.uid` gate that returned a
539 /// different owner-refs shape for the missing-uid corner. Post-lift
540 /// the sentinel value is composed at ONE substrate site so the
541 /// empty-uid gate at `owner_references_json` and its per-callsite
542 /// producers share the SAME `""` byte-string, and a rename of the
543 /// sentinel would land at ONE substrate site rather than at every
544 /// downstream `owner_references_json(name, uid)` call.
545 ///
546 /// Peer to [`Self::namespace_or_default`] +
547 /// [`Self::name_or_placeholder`] on the metadata-slot × fallback-
548 /// shape axis: `namespace_or_default` returns the K8s-canonical
549 /// `"default"` fallback (matching what the API server substitutes
550 /// on namespaced writes with no explicit namespace);
551 /// `name_or_placeholder` returns the workspace-wide `"unnamed"`
552 /// sentinel (a display placeholder for downstream grepping /
553 /// label-selecting); this method returns the empty-string sentinel
554 /// (a load-bearing gate value that composes with
555 /// [`crate::owner_references_json`]'s `is_empty` check). The three
556 /// primitives partition the metadata-slot family by whether the
557 /// consumer wants a K8s-canonical fallback (namespace), a display
558 /// placeholder (name), or a gate sentinel (uid).
559 ///
560 /// Pre-lift the `.metadata.uid.as_deref().unwrap_or("")` chain was
561 /// hand-authored at TWO sites past the ★★ PRIME-DIRECTIVE ≥ 2
562 /// duplication threshold in `tatara-reconciler::render`, both
563 /// feeding a downstream owner-reference emitter:
564 /// * `render_routing` — the routing-edge seed that binds
565 /// `process_uid` into every routing-form `EdgeContext` (Ingress +
566 /// DNSEndpoint) built inside the fanout loop over
567 /// `RoutingSpec::hostnames`; each `Edge::render` impl then walks
568 /// its `EdgeContext` through `build_owner_refs` →
569 /// [`crate::owner_references_json`] to stamp
570 /// `metadata.ownerReferences` on the emitted resource.
571 /// * `render_export_jobs` — the ephemeral-export Job builder that
572 /// passes the same uid slice to `tatara_process::
573 /// owner_references_json(name, uid)` per rendered Job, stamping
574 /// the export-Job's `metadata.ownerReferences` back at the
575 /// owning Process.
576 ///
577 /// Both sites walked the SAME `.as_deref().unwrap_or("")` chain and
578 /// both wanted the `&str` form the primitive returns — as the
579 /// second positional argument to `owner_references_json(name, uid)`
580 /// on the ownership-tag axis. Post-lift each callsite reads
581 /// `let uid = process.uid_or_empty();` and the produced slice feeds
582 /// the same downstream composer unchanged.
583 ///
584 /// Return-form axis: `&str` mirrors the existing borrow-first
585 /// discipline of the peer metadata-fallback primitives
586 /// ([`Self::namespace_or_default`], [`Self::name_or_placeholder`]);
587 /// all three return owned-metadata borrows with a slot-specific
588 /// fallback baked in so downstream consumers compose the slice
589 /// directly into their next call without re-spelling the fallback.
590 ///
591 /// A future normalization step (a canonicalization pass that
592 /// rejects a malformed uid before the owner-ref stamp, a cross-
593 /// cluster uid rewrite for multi-tenant control planes, a stale-
594 /// uid warning annotation for a Process whose uid changed under
595 /// the reconciler mid-generation) lands at ONE substrate method
596 /// here and both downstream `owner_references_json` consumers
597 /// pick up the upgrade mechanically — no per-callsite hand-edit
598 /// at `render_routing` / `render_export_jobs`.
599 ///
600 /// Theory anchor: THEORY.md §VI.1 (generation over composition —
601 /// the `.metadata.uid.as_deref().unwrap_or("")` chain recurred at
602 /// two hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2
603 /// duplication trigger, and is lifted to ONE owner here).
604 /// THEORY.md §II.1 invariant 5 (composition preserves proofs —
605 /// the pins bind the missing-uid corner + the empty-string
606 /// sentinel byte-shape + the borrow-form `&str` lifetime + the
607 /// byte-identical parity with the pre-lift chain + the composition
608 /// coherence with [`crate::owner_references_json`]'s `is_empty`
609 /// gate, so a regression that drifted any surface at
610 /// `tests::uid_or_empty_*` rather than as silent operator-facing
611 /// skew between the two owner-reference emitters on the SAME
612 /// Process).
613 pub fn uid_or_empty(&self) -> &str {
614 self.metadata.uid.as_deref().unwrap_or("")
615 }
616
617 /// Owned-form metadata-projection primitive on the `metadata.name`
618 /// axis: returns an owned `String` copy of the K8s object name, with
619 /// the missing-name corner collapsed to the load-bearing empty-string
620 /// sentinel — the ONE-liner collapse of the paired
621 /// `self.metadata.name.clone().unwrap_or_default()` incantation every
622 /// keying / row-builder consumer restated by hand pre-lift.
623 ///
624 /// Pre-lift the `.metadata.name.clone().unwrap_or_default()` chain
625 /// was hand-authored at TWO sites past the ★★ PRIME-DIRECTIVE ≥ 2
626 /// duplication threshold in `tatara-pool-reconciler::controller_pool`,
627 /// both stamping the `PoolMember` / `PoolMemberSnapshot`
628 /// `process_name: String` slot inside a struct-literal fanout over
629 /// pool-owned `Process`es:
630 /// * `reconcile_pool`'s pool-member seed (annotation-matched Process
631 /// list → `PoolMember { process_name, state, entered_state_at, .. }`)
632 /// — the row every operator sees on the pool's status page.
633 /// * `reconcile_pool`'s desired-count snapshot seed
634 /// (`PoolMemberSnapshot { process_name, phase, created_at }`)
635 /// — the row fed into `decide_pool_convergence`.
636 ///
637 /// Both sites walked the SAME `.clone().unwrap_or_default()` chain
638 /// and both wanted the `String` form the primitive returns — as the
639 /// owned-form `process_name: String` slot on a struct literal
640 /// composed inside a `.iter().map(...)` fanout over the same
641 /// pool-owned `Process` list. Post-lift each callsite reads
642 /// `process_name: p.owned_name_or_empty()` and the produced value
643 /// feeds the same struct-literal slot unchanged.
644 ///
645 /// The empty-string fallback is the SAME sentinel the sibling
646 /// borrow-form primitive [`Self::uid_or_empty`] returns — the two
647 /// primitives partition the owned-form × borrow-form corner of the
648 /// metadata-slot family on identical fallback semantics (empty
649 /// string means "the slot is unset"), so a consumer that switches
650 /// between them based on downstream ownership requirements never
651 /// sees a different missing-slot spelling as a side effect.
652 ///
653 /// Peer to [`Self::name_or_placeholder`] on the (return-form ×
654 /// fallback-value) axis pair — closes the corner the family
655 /// previously left open:
656 ///
657 /// * borrow + display placeholder → [`Self::name_or_placeholder`]
658 /// (log lines, annotation writers, ownership-tag composers —
659 /// consumers whose downstream drops `"unnamed"` in place of a
660 /// missing name without operator-visible failure);
661 /// * owned + empty sentinel → **this method** (row-builder /
662 /// HashMap-key / struct-literal fanout consumers whose downstream
663 /// fills a `String` field with the load-bearing `""` sentinel to
664 /// flag "no name to key by" rather than substituting a display
665 /// placeholder that would misalign a downstream lookup);
666 /// * owned + name-required → [`Self::owned_coordinates_or_err`] (kube-rs
667 /// API-path calls — consumers whose downstream must NOT silently
668 /// substitute a placeholder for the API call target).
669 ///
670 /// The primitive family's `""`-on-missing-name semantics
671 /// intentionally differs from [`Self::name_or_placeholder`]'s
672 /// `"unnamed"` semantics: the caller sites for this form (pool
673 /// membership row seeds, HashMap keys) are load-bearing keys — a
674 /// display placeholder like `"unnamed"` would silently alias every
675 /// missing-name Process to the same key, collapsing distinct rows
676 /// in the pool's member list. The empty-string sentinel keeps the
677 /// pre-lift byte-shape and lets downstream consumers gate on
678 /// `String::is_empty` if they need to filter the missing-name
679 /// corner explicitly.
680 ///
681 /// A future normalization step (a name-canonicalization pass, a
682 /// case-fold key builder, a per-pool alias table for renamed
683 /// Processes across generations) lands at ONE substrate method
684 /// here and both downstream `PoolMember` / `PoolMemberSnapshot`
685 /// seeds pick up the upgrade mechanically — no per-callsite hand-
686 /// edit at `reconcile_pool`.
687 ///
688 /// Theory anchor: THEORY.md §VI.1 (generation over composition —
689 /// the `.metadata.name.clone().unwrap_or_default()` chain recurred
690 /// at two hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2
691 /// duplication trigger, and is lifted to ONE owner here).
692 /// THEORY.md §II.1 invariant 5 (composition preserves proofs —
693 /// the pins bind the missing-name corner + the empty-string
694 /// sentinel byte-shape + the owned-form `String` return type +
695 /// the byte-identical parity with the pre-lift chain + the
696 /// fallback-value coherence with the sibling [`Self::uid_or_empty`]
697 /// on the metadata-slot × empty-sentinel axis, so a regression
698 /// that drifted any surface at `tests::owned_name_or_empty_*`
699 /// rather than as silent operator-facing skew between the pool-
700 /// member seed and the desired-count snapshot seed on the SAME
701 /// pool).
702 pub fn owned_name_or_empty(&self) -> String {
703 self.metadata.name.clone().unwrap_or_default()
704 }
705
706 /// Borrow-form spec-projection primitive on the declared parent-PID
707 /// axis: returns the hierarchical PID path (e.g. `"seph.1"`) the
708 /// author declared at `spec.identity.parent`, with the empty-slot
709 /// corner collapsed to `None` — the ONE-liner collapse of the
710 /// paired `self.spec.identity.parent.as_deref()` incantation every
711 /// consumer restated by hand pre-lift.
712 ///
713 /// Pre-lift the `.spec.identity.parent.as_deref()` chain was hand-
714 /// authored at TWO sites past the ★★ PRIME-DIRECTIVE ≥ 2
715 /// duplication threshold in `tatara-reconciler::phase_machine`:
716 /// * `handle_forking` — the ALLOCATE-PID composer that threads the
717 /// declared parent PID into [`pid::allocate_pid`] and also into
718 /// the status patch payload (`{ "pid": new_pid, "parent":
719 /// parent_pid }`), so the reconciler-observed
720 /// [`ProcessStatus::parent`] slot mirrors the author-declared
721 /// [`IdentitySpec::parent`] at fork time. The `info!` tracing
722 /// span also reads the same slice as the `parent` field on the
723 /// PID-assigned log line.
724 /// * `handle_exiting` — the SIGTERM cascade's child-fan-out filter
725 /// that enumerates every Process cluster-wide and picks children
726 /// whose `spec.identity.parent` equals this Process's currently-
727 /// observed PID (`.filter(|c| c.spec.identity.parent.as_deref()
728 /// == Some(pid))`). The filter runs per candidate child, so the
729 /// borrow-form projection avoids allocating one `String` clone
730 /// per non-matching row in the cluster-wide list.
731 ///
732 /// Both sites walked the SAME `.as_deref()` chain and both wanted
733 /// the `Option<&str>` form the primitive returns — the
734 /// `handle_forking` site to feed positionally into
735 /// `pid::allocate_pid(&identity, parent_pid, next_seq)` and the
736 /// tracing span's `parent = ?parent_pid` debug print + the JSON
737 /// payload's `"parent": parent_pid` slot; the `handle_exiting`
738 /// filter to compare directly against `Some(pid)` where `pid:
739 /// &str` came off the borrow-form peer [`Self::observed_pid`].
740 ///
741 /// Return-form axis: `Option<&str>` mirrors the borrow-first
742 /// discipline of every peer primitive on the metadata / status
743 /// slot family ([`Self::namespace_or_default`],
744 /// [`Self::name_or_placeholder`], [`Self::observed_pid`],
745 /// [`Self::annotation`]). The empty-slot corner
746 /// (`spec.identity.parent = None`, matching `init` / PID 1 with
747 /// no parent) collapses to `None` so `.is_some()` / `if let
748 /// Some(_)` / `.map(...)` behave identically on a `Process`
749 /// authored at cluster init (PID 1, parent absent) and on any
750 /// PID-N child (parent present) — matching the pre-lift
751 /// `.as_deref()` chain's `None` byte-identically.
752 ///
753 /// Peer to [`Self::observed_pid`] on the (spec-declared ×
754 /// status-observed) axis pair: `observed_pid` returns the PID
755 /// path this Process currently OWNS (the reconciler-persisted
756 /// child position in the hierarchy), while `declared_parent_pid`
757 /// returns the PID path this Process's parent OWNS (the author-
758 /// declared upstream position). The SIGTERM cascade at
759 /// `handle_exiting` composes both: it reads its own
760 /// [`Self::observed_pid`] and matches each candidate child's
761 /// [`Self::declared_parent_pid`] against that value — the child-
762 /// fan-out relation IS the spec-declared × status-observed axis
763 /// pair collapsed to a single comparator, both sides routed
764 /// through the same borrow-form skeleton.
765 ///
766 /// A future normalization step (a per-slot canonicalization pass
767 /// that rejects malformed hierarchical PIDs, a case-fold lookup
768 /// against a table of renamed identities, a cross-cluster prefix
769 /// stripper, an alias-table lookup that maps a legacy PID to its
770 /// current spelling) lands at ONE substrate method here and both
771 /// downstream consumers pick up the upgrade mechanically — no
772 /// per-callsite hand-edit at `handle_forking` / `handle_exiting`.
773 ///
774 /// Sibling to the peer metadata-projection primitives
775 /// ([`Self::namespace_or_default`], [`Self::name_or_placeholder`],
776 /// [`Self::coordinates_or_defaults`], [`Self::coordinates_or_none`],
777 /// [`Self::owned_coordinates_or_err`], [`Self::annotation`]) on the
778 /// metadata axis; this method opens the borrow-form peer on the
779 /// declared-identity axis. Future identity projections
780 /// (`declared_name_override` on the `spec.identity.name_override`
781 /// axis, a paired `declared_identity` composite that returns both
782 /// halves) land as peer methods on this same axis.
783 ///
784 /// Theory anchor: THEORY.md §VI.1 (generation over composition —
785 /// the `.spec.identity.parent.as_deref()` chain recurred at two
786 /// hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
787 /// trigger, and is lifted to ONE owner here). THEORY.md §II.1
788 /// invariant 5 (composition preserves proofs — the pins bind the
789 /// empty-slot corner + the borrow-form `&str` lifetime + the
790 /// byte-identical parity with the pre-lift `.as_deref()` chain,
791 /// so a regression that drifted any surface at
792 /// `tests::declared_parent_pid_*` rather than as silent operator-
793 /// facing skew between the ALLOCATE-PID composer and the SIGTERM
794 /// cascade's child-fan-out filter on the SAME parent-child pair).
795 pub fn declared_parent_pid(&self) -> Option<&str> {
796 self.spec.identity.parent.as_deref()
797 }
798
799 /// Borrow-form spec-projection primitive on the declared
800 /// name-override axis: returns the human name the author declared
801 /// at `spec.identity.name_override` (used verbatim instead of the
802 /// content-hash-derived name in [`derive_identity`]), with the
803 /// empty-slot corner collapsed to `None` — the ONE-liner collapse
804 /// of the paired `self.spec.identity.name_override.as_deref()`
805 /// incantation every consumer restated by hand pre-lift.
806 ///
807 /// Pre-lift the `.spec.identity.name_override.as_deref()` chain
808 /// was hand-authored at TWO sites past the ★★ PRIME-DIRECTIVE ≥ 2
809 /// duplication threshold in `tatara-reconciler::phase_machine`,
810 /// both feeding the second positional argument of
811 /// [`derive_identity`]:
812 /// * `handle_pending` — the DECLARE composer that computes the
813 /// Process's [`Identity`] on entry to the state machine (before
814 /// `patch::phase_status` writes it into `status.identity`).
815 /// * `handle_forking` — the ALLOCATE-PID composer that recomputes
816 /// the same [`Identity`] on a rehydration path (status may
817 /// already carry an identity from a prior reconcile, in which
818 /// case the `.and_then(|s| s.identity.clone())` short-circuit
819 /// takes it; otherwise this `.unwrap_or_else` branch fires and
820 /// recomputes the identity fresh from the spec) so `pid::
821 /// allocate_pid` sees the SAME [`Identity`] the DECLARE phase
822 /// produced.
823 ///
824 /// Both sites walked the SAME `.as_deref()` chain and both wanted
825 /// the `Option<&str>` form the primitive returns — as the second
826 /// positional argument to `derive_identity(&self.spec, …)`, which
827 /// internally trims + filters empty strings + dispatches on
828 /// `Some(non_empty)` (verbatim name, `name_override: true`) vs
829 /// `None | Some(empty | whitespace)` (content-hash-derived name,
830 /// `name_override: false`). The primitive itself preserves the
831 /// raw slot byte-identically (the trim happens IN
832 /// `derive_identity`, not at the borrow site), so the two live
833 /// paths compose through the SAME borrow-form skeleton.
834 ///
835 /// Return-form axis: `Option<&str>` mirrors the borrow-first
836 /// discipline of every peer primitive on the metadata / status /
837 /// spec-identity slot family ([`Self::namespace_or_default`],
838 /// [`Self::name_or_placeholder`], [`Self::observed_pid`],
839 /// [`Self::annotation`], [`Self::declared_parent_pid`]). The
840 /// empty-slot corner (`spec.identity.name_override = None`,
841 /// matching a Process authored WITHOUT the human-name-override
842 /// escape hatch — the default; `derive_identity` then computes
843 /// the name from the content hash) collapses to `None` so
844 /// `.is_some()` / `if let Some(_)` / `.map(...)` behave
845 /// identically on the two Process shapes an operator can author.
846 ///
847 /// Peer to [`Self::declared_parent_pid`] on the (parent × name-
848 /// override) sub-axis of the declared-identity axis: both
849 /// primitives project a `Option<String>` slot on `IdentitySpec`
850 /// through the SAME borrow-form skeleton, so a future
851 /// `declared_identity` composite that returns both halves
852 /// together (e.g. as a `(Option<&str>, Option<&str>)` tuple or a
853 /// borrow-form `DeclaredIdentityView<'_>` newtype) lands as ONE
854 /// method that COMPOSES the two peer primitives, not as three
855 /// hand-authored `.as_deref()` chains restated at each callsite.
856 ///
857 /// A future normalization step (a per-slot canonicalization pass
858 /// that rejects malformed names, a case-fold lookup against a
859 /// table of renamed identities, an alias-table lookup that maps
860 /// a legacy name-override to its current spelling, a whitespace-
861 /// trim lift OUT of `derive_identity` INTO the primitive so both
862 /// consumers see the trimmed form) lands at ONE substrate method
863 /// here and both downstream consumers pick up the upgrade
864 /// mechanically — no per-callsite hand-edit at `handle_pending` /
865 /// `handle_forking`.
866 ///
867 /// Sibling to the peer spec-identity projection
868 /// [`Self::declared_parent_pid`] on the declared-identity axis;
869 /// this method opens the borrow-form peer on the name-override
870 /// sub-axis of the same closed set (`IdentitySpec { parent,
871 /// name_override }`). Future identity projections (a paired
872 /// `declared_identity` composite that returns both halves
873 /// together) land as peer methods on this same axis.
874 ///
875 /// Theory anchor: THEORY.md §VI.1 (generation over composition —
876 /// the `.spec.identity.name_override.as_deref()` chain recurred
877 /// at two hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2
878 /// duplication trigger, and is lifted to ONE owner here).
879 /// THEORY.md §II.1 invariant 5 (composition preserves proofs —
880 /// the pins bind the empty-slot corner + the borrow-form `&str`
881 /// lifetime + the byte-identical parity with the pre-lift
882 /// `.as_deref()` chain + the invariance under
883 /// [`derive_identity`]'s internal trim/filter step, so a
884 /// regression that drifted any surface at
885 /// `tests::declared_name_override_*` rather than as silent
886 /// operator-facing skew between the DECLARE composer and the
887 /// ALLOCATE-PID rehydration branch on the SAME Process spec).
888 pub fn declared_name_override(&self) -> Option<&str> {
889 self.spec.identity.name_override.as_deref()
890 }
891
892 /// Borrowed slice of the FluxCD resources this Process's status
893 /// currently persists at `status.flux_resources`, with the
894 /// missing-`status` corner collapsed to an empty slice — the ONE-
895 /// line collapse of the paired `self.status.as_ref().map(|s|
896 /// s.flux_resources.clone()).unwrap_or_default()` incantation
897 /// every VERIFY-phase / ATTEST-heartbeat consumer restated by hand
898 /// pre-lift.
899 ///
900 /// Pre-lift the 5-line `.status.as_ref().map(|s| s.flux_resources
901 /// .clone()).unwrap_or_default()` chain was hand-authored at TWO
902 /// sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold in
903 /// `tatara-reconciler::phase_machine`:
904 /// * `handle_running` — the VERIFY-phase per-ref readiness probe
905 /// seed that walks every ref through
906 /// [`crate::status::FluxResourceRef::fetch_coords`] via
907 /// `ssapply::fetch_flux_ref` and rebuilds an updated
908 /// `Vec<FluxResourceRef>` with `ready` + `message` + `last_check`
909 /// observed at reconcile time.
910 /// * `handle_attested` — the ATTEST-heartbeat drift detector that
911 /// short-circuits on the first non-Ready ref via
912 /// `ssapply::fetch_flux_ref` + `ssapply::ready_condition`.
913 ///
914 /// Both sites walked the SAME 5-line chain — clone the vector
915 /// eagerly for the length of the reconcile pass, then iterate it
916 /// by reference — even though neither site ever mutates the vector
917 /// nor keeps it alive past the enclosing async fn. Post-lift both
918 /// callers borrow the slice directly from `self.status`; the two
919 /// pre-lift `.clone()` calls disappear because the slice lives for
920 /// the borrow of `&self`, and both call sites' subsequent
921 /// downstream calls (`ssapply::fetch_flux_ref` / the
922 /// `patch::patch_process_status` write) do not touch the borrowed
923 /// `p: &Process`, so the borrow lifetime holds.
924 ///
925 /// Return-form axis: `&[FluxResourceRef]` mirrors the existing
926 /// borrow-first discipline every pre-lift consumer already
927 /// iterated by reference (`for r in &refs`), and the shape of
928 /// [`crate::status::FluxResourceRef::fetch_coords`]'s per-ref
929 /// borrow projection extends mechanically to the slice-level
930 /// projection here. The missing-`status` corner collapses to the
931 /// empty slice `&[]` so `.is_empty()` / `.len()` / iteration all
932 /// behave identically on a `Process` whose status is `None` and
933 /// on one whose status carries an empty `flux_resources` slot —
934 /// matching what the pre-lift `.unwrap_or_default()` produced
935 /// (an empty `Vec`).
936 ///
937 /// A future normalization step (a per-ref canonicalization pass
938 /// that skips duplicated refs, an owner-filter that returns only
939 /// refs stamped with the CURRENT `metadata.generation`, a
940 /// staleness gate that drops refs whose `last_check` predates a
941 /// reconcile deadline) lands at ONE substrate method here and
942 /// both downstream consumers pick up the upgrade mechanically —
943 /// no per-callsite hand-edit at `handle_running` /
944 /// `handle_attested`.
945 ///
946 /// Sibling to the [`Self::coordinates_or_none`] borrow-first
947 /// primitive on the metadata axis; this method opens the
948 /// analogous borrow-first primitive on the status-projection
949 /// axis. Future status projections (`observed_attestation` on
950 /// the attestation-chain axis, `observed_pid` on the PID axis,
951 /// `observed_children` on the child-fan-out axis) land as peer
952 /// methods on this same axis.
953 ///
954 /// Theory anchor: THEORY.md §VI.1 (generation over composition —
955 /// the 5-line status-projection chain recurred at two hand-
956 /// authored sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
957 /// trigger, and is lifted to ONE owner here). THEORY.md §II.1
958 /// invariant 5 (composition preserves proofs — the pins bind the
959 /// missing-`status` corner + the slice-lifetime borrow discipline
960 /// + the byte-identical parity with the pre-lift 5-line chain, so
961 /// a regression that drifted any of the three surfaces at
962 /// `tests::observed_flux_resources_*` rather than as silent
963 /// operator-facing skew between the VERIFY-phase and ATTEST-
964 /// heartbeat consumers).
965 pub fn observed_flux_resources(&self) -> &[FluxResourceRef] {
966 self.status
967 .as_ref()
968 .map(|s| s.flux_resources.as_slice())
969 .unwrap_or(&[])
970 }
971
972 /// The borrow-form status-projection primitive on the PID axis:
973 /// returns the hierarchical PID path (e.g. `"seph.1.7"`) the
974 /// reconciler currently persists at `status.pid`, with BOTH the
975 /// missing-`status` corner AND the empty-slot corner collapsed
976 /// to `None` — the ONE-liner collapse of the paired
977 /// `self.status.as_ref().and_then(|s| s.pid.clone())` incantation
978 /// every consumer restated by hand pre-lift.
979 ///
980 /// Pre-lift the 3-line `.status.as_ref().and_then(|s| s.pid
981 /// .clone())` chain was hand-authored at TWO sites past the ★★
982 /// PRIME-DIRECTIVE ≥ 2 duplication threshold in
983 /// `tatara-reconciler::phase_machine`:
984 /// * `handle_forking` — the ALLOCATE-PID gate that short-
985 /// circuits the PID allocator when the reconciler already
986 /// assigned a PID on a prior reconcile pass (pre-lift the
987 /// chain composed with `.is_some()` and threw the clone away
988 /// without ever reading the string).
989 /// * `handle_exiting` — the SIGTERM cascade that enumerates
990 /// child Processes and terminates them by matching each
991 /// child's `spec.identity.parent` against the PID this Process
992 /// currently owns (pre-lift the chain bound an owned
993 /// `Option<String>` and threaded `pid.as_str()` into the
994 /// downstream `.as_deref() == Some(...)` comparator).
995 ///
996 /// Both sites walked the SAME 3-line chain — clone the `String`
997 /// eagerly, then either drop it (the `handle_forking` gate) or
998 /// re-borrow it through `.as_str()` (the `handle_exiting`
999 /// comparator) — even though neither site ever mutates the PID
1000 /// nor keeps it alive past the enclosing async fn. Post-lift
1001 /// both callers borrow the PID directly from `self.status`; the
1002 /// pre-lift `.clone()` at both sites disappears because the
1003 /// `&str` lives for the borrow of `&self`, and both call sites'
1004 /// subsequent downstream calls (the K8s API list/patch, the
1005 /// child-Process comparator) do not touch the borrowed
1006 /// `p: &Process`, so the borrow lifetime holds.
1007 ///
1008 /// Return-form axis: `Option<&str>` mirrors the existing
1009 /// borrow-first discipline every pre-lift consumer already
1010 /// re-borrowed through `.as_str()` before use, and the shape of
1011 /// [`Self::coordinates_or_none`]'s `Option<(&str, &str)>`
1012 /// projection extends mechanically to the single-slot
1013 /// projection here. The missing-`status` corner AND the
1014 /// populated-status-with-`pid=None` corner BOTH collapse to
1015 /// `None` so `.is_some()` / `if let Some(_)` / `.map(...)`
1016 /// behave identically on a `Process` whose status is `None`
1017 /// and on one whose status carries an unpopulated `pid` slot —
1018 /// matching what the pre-lift `.and_then(...)` chain produced.
1019 ///
1020 /// A future normalization step (a per-slot canonicalization
1021 /// pass that rejects malformed hierarchical PIDs, a
1022 /// generation-filter that returns `None` for a PID stamped
1023 /// with a stale `metadata.generation`, a staleness gate that
1024 /// drops a PID whose observing `phase_since` predates a
1025 /// reconcile deadline) lands at ONE substrate method here and
1026 /// both downstream consumers pick up the upgrade mechanically
1027 /// — no per-callsite hand-edit at `handle_forking` /
1028 /// `handle_exiting`.
1029 ///
1030 /// Sibling to the peer [`Self::observed_flux_resources`]
1031 /// borrow-first primitive on the flux-resources axis; both
1032 /// methods compose the same missing-`status` fallback +
1033 /// borrow-form return-shape skeleton on distinct
1034 /// `ProcessStatus` slots. Future status projections
1035 /// (`observed_parent` on the parent-pointer axis,
1036 /// `observed_message` on the human-readable-status axis,
1037 /// `observed_attestation` on the attestation-chain axis) land
1038 /// as peer methods on this same axis.
1039 ///
1040 /// Theory anchor: THEORY.md §VI.1 (generation over
1041 /// composition — the 3-line status-projection chain recurred
1042 /// at two hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2
1043 /// duplication trigger, and is lifted to ONE owner here).
1044 /// THEORY.md §II.1 invariant 5 (composition preserves proofs —
1045 /// the pins bind the missing-`status` corner + the empty-slot
1046 /// corner + the borrow-form `&str` lifetime + the
1047 /// byte-identical parity with the pre-lift 3-line chain, so a
1048 /// regression that drifted any surface at
1049 /// `tests::observed_pid_*` rather than as silent operator-
1050 /// facing skew between the ALLOCATE-PID gate and the SIGTERM
1051 /// cascade on the SAME `Process`).
1052 pub fn observed_pid(&self) -> Option<&str> {
1053 self.status.as_ref().and_then(|s| s.pid.as_deref())
1054 }
1055
1056 /// The borrow-form status-projection primitive on the
1057 /// attestation-chain axis: returns the last
1058 /// [`ProcessAttestation`] the reconciler persisted at
1059 /// `status.attestation`, with the missing-`status` corner AND the
1060 /// empty-slot corner BOTH collapsed to `None` — the ONE-liner
1061 /// collapse of the paired `self.status.as_ref().and_then(|s|
1062 /// s.attestation.as_ref())` incantation every consumer restated
1063 /// by hand pre-lift.
1064 ///
1065 /// Pre-lift the 3-line `.status.as_ref().and_then(|s| s
1066 /// .attestation.as_ref())` chain was hand-authored at TWO sites
1067 /// past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold in
1068 /// `tatara-reconciler`:
1069 /// * `phase_machine::advance_to_attested` — the ATTEST composer
1070 /// that chains `prior.next(pillars)` when a prior attestation
1071 /// is persisted and seeds with `ProcessAttestation::initial`
1072 /// otherwise.
1073 /// * `render::render_export_jobs` — the ephemeral-export Job
1074 /// builder that pulls the prior `composed_root` off the last
1075 /// persisted attestation and threads it into every rendered
1076 /// Job's `previousRoot` env var, so the export receipt chains
1077 /// into the Process's BLAKE3 attestation tree at the correct
1078 /// generation boundary.
1079 ///
1080 /// Both sites walked the SAME 3-line chain — the borrow-form
1081 /// `Option<&ProcessAttestation>` shape both consumers wanted
1082 /// already — even though neither site ever mutated the
1083 /// attestation nor kept it alive past the enclosing async fn.
1084 /// Post-lift both callers borrow the attestation directly from
1085 /// `self.status`; the pre-lift 3-line chain shrinks to a single
1086 /// method call at both sites, and both consumers' subsequent
1087 /// downstream calls (`ProcessAttestation::next` for the ATTEST
1088 /// composer, `.composed_root.clone()` for the export Job builder)
1089 /// do not touch the borrowed `p: &Process`, so the borrow
1090 /// lifetime holds.
1091 ///
1092 /// Return-form axis: `Option<&ProcessAttestation>` mirrors the
1093 /// existing borrow-first discipline every pre-lift consumer
1094 /// already re-borrowed through `.as_ref()`, and the shape of the
1095 /// peer [`Self::observed_pid`] projection extends mechanically
1096 /// to the whole-attestation-record projection here. The missing-
1097 /// `status` corner AND the populated-status-with-`attestation
1098 /// =None` corner BOTH collapse to `None` so `.is_some()` / `if
1099 /// let Some(_)` / `.map(...)` behave identically on a `Process`
1100 /// whose status is `None` and on one whose status carries an
1101 /// unpopulated `attestation` slot — matching what the pre-lift
1102 /// `.and_then(...)` chain produced.
1103 ///
1104 /// A future normalization step (a per-slot canonicalization pass
1105 /// that rejects a persisted attestation whose `composed_root`
1106 /// fails `verify`, a generation-filter that returns `None` for
1107 /// an attestation stamped with a stale `metadata.generation`, a
1108 /// staleness gate that drops an attestation whose `attested_at`
1109 /// predates a reconcile deadline) lands at ONE substrate method
1110 /// here and both downstream consumers pick up the upgrade
1111 /// mechanically — no per-callsite hand-edit at
1112 /// `advance_to_attested` / `render_export_jobs`.
1113 ///
1114 /// Sibling to the peer [`Self::observed_pid`] +
1115 /// [`Self::observed_flux_resources`] borrow-first primitives on
1116 /// the PID + flux-resources axes; all three methods compose the
1117 /// same missing-`status` fallback + borrow-form return-shape
1118 /// skeleton on distinct `ProcessStatus` slots. Future status
1119 /// projections (`observed_parent` on the parent-pointer axis,
1120 /// `observed_message` on the human-readable-status axis) land
1121 /// as peer methods on this same axis.
1122 ///
1123 /// Theory anchor: THEORY.md §VI.1 (generation over composition
1124 /// — the 3-line status-projection chain recurred at two hand-
1125 /// authored sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
1126 /// trigger, and is lifted to ONE owner here). THEORY.md §II.1
1127 /// invariant 5 (composition preserves proofs — the pins bind
1128 /// the missing-`status` corner + the empty-slot corner + the
1129 /// borrow-form `&ProcessAttestation` lifetime + the byte-
1130 /// identical parity with the pre-lift 3-line chain, so a
1131 /// regression that drifted any surface at
1132 /// `tests::observed_attestation_*` rather than as silent
1133 /// operator-facing skew between the ATTEST composer and the
1134 /// ephemeral-export receipt chain on the SAME `Process`).
1135 pub fn observed_attestation(&self) -> Option<&ProcessAttestation> {
1136 self.status.as_ref().and_then(|s| s.attestation.as_ref())
1137 }
1138
1139 /// The borrow-form status-projection primitive on the resolved-
1140 /// identity axis: returns the [`Identity`] the reconciler
1141 /// currently persists at `status.identity` (name + content hash +
1142 /// override flag), with the missing-`status` corner AND the
1143 /// empty-slot corner BOTH collapsed to `None` — the ONE-liner
1144 /// collapse of the paired `self.status.as_ref().and_then(|s|
1145 /// s.identity.as_ref())` incantation every consumer restated by
1146 /// hand pre-lift.
1147 ///
1148 /// Pre-lift the paired `.status.as_ref().and_then(|s|
1149 /// s.identity.<clone|as_ref>())` chain was hand-authored at TWO
1150 /// sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold
1151 /// in `tatara-reconciler`:
1152 /// * `phase_machine::handle_forking` — the FORK-time identity
1153 /// seed that reuses the reconciler-persisted `Identity` if
1154 /// present and falls back to a fresh `derive_identity(&spec,
1155 /// name_override)` otherwise. Pre-lift the site cloned the
1156 /// whole `Identity` off the borrow before threading it through
1157 /// `.unwrap_or_else(...)` even though the fallback path
1158 /// allocates its own owned `Identity` — the pre-lift clone
1159 /// allocated a fresh `Identity` on the happy path just so the
1160 /// `Option`'s shape matched the fallback's `Identity` return
1161 /// type.
1162 /// * `ssapply::inject_annotations` — the SSA-time annotation
1163 /// composer that stamps the content-hash annotation onto every
1164 /// owned resource. Pre-lift the site nested the identity
1165 /// borrow-form check inside a manual `if let Some(status) =
1166 /// &process.status { … }` guard alongside sibling `status.pid`
1167 /// and `status.attestation` accesses — three siblings the peer
1168 /// primitives [`Self::observed_pid`] and
1169 /// [`Self::observed_attestation`] already own, so the outer
1170 /// status guard was the last hand-authored `.status.as_ref()`
1171 /// destructure at this composer.
1172 ///
1173 /// Both sites walked the SAME 3-line chain (one via `.clone()`,
1174 /// one via `.as_ref()`) — the borrow-form
1175 /// `Option<&Identity>` shape both consumers wanted already, even
1176 /// though the FORK-time seed then had to `.clone()` off the
1177 /// borrow to compose with the owned-`Identity` fallback. Post-
1178 /// lift the seed calls `.observed_identity().cloned()` at the
1179 /// exact composition point where the owned value is required
1180 /// (the empty-borrow corner clones nothing, since
1181 /// `Option::cloned` on `None` is `None`), and the SSA-time
1182 /// consumer drops the outer status guard entirely — the
1183 /// three-sibling primitive family (pid + identity + attestation)
1184 /// now peers through `observed_pid` +
1185 /// `observed_identity` + `observed_attestation` at ONE call each
1186 /// with no shared status destructure between them.
1187 ///
1188 /// Return-form axis: `Option<&Identity>` mirrors the
1189 /// existing borrow-first discipline every pre-lift consumer
1190 /// already re-borrowed through `.as_ref()` / re-cloned through
1191 /// `.clone()`, and the shape of the peer
1192 /// [`Self::observed_attestation`] projection extends
1193 /// mechanically to the whole-`Identity`-record projection here.
1194 /// The missing-`status` corner AND the populated-status-with-
1195 /// `identity=None` corner BOTH collapse to `None` so
1196 /// `.is_some()` / `if let Some(_)` / `.map(...)` behave
1197 /// identically on a `Process` whose status is `None` and on one
1198 /// whose status carries an unpopulated `identity` slot —
1199 /// matching what the pre-lift `.and_then(...)` chain produced.
1200 ///
1201 /// A future normalization step (a per-slot canonicalization
1202 /// pass that rejects an `Identity` whose `content_hash` fails
1203 /// re-derivation against the current spec, a generation-filter
1204 /// that returns `None` for an identity stamped with a stale
1205 /// `metadata.generation`, a staleness gate that drops an
1206 /// identity whose observing `phase_since` predates a reconcile
1207 /// deadline) lands at ONE substrate method here and both
1208 /// downstream consumers pick up the upgrade mechanically — no
1209 /// per-callsite hand-edit at `handle_forking` /
1210 /// `inject_annotations`.
1211 ///
1212 /// Sibling to the peer [`Self::observed_pid`] +
1213 /// [`Self::observed_attestation`] +
1214 /// [`Self::observed_flux_resources`] borrow-first primitives on
1215 /// the PID + attestation-chain + flux-resources axes; all four
1216 /// methods compose the same missing-`status` fallback +
1217 /// borrow-form return-shape skeleton on distinct `ProcessStatus`
1218 /// slots. Future status projections (`observed_parent` on the
1219 /// parent-pointer axis, `observed_message` on the human-
1220 /// readable-status axis) land as peer methods on this same
1221 /// axis.
1222 ///
1223 /// Theory anchor: THEORY.md §VI.1 (generation over composition
1224 /// — the 3-line status-projection chain recurred at two hand-
1225 /// authored sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
1226 /// trigger, and is lifted to ONE owner here). THEORY.md §II.1
1227 /// invariant 5 (composition preserves proofs — the pins bind
1228 /// the missing-`status` corner + the empty-slot corner + the
1229 /// borrow-form `&Identity` lifetime + the byte-identical parity
1230 /// with the pre-lift 3-line chain, so a regression that drifted
1231 /// any surface at `tests::observed_identity_*` rather than as
1232 /// silent operator-facing skew between the FORK-time identity
1233 /// seed and the SSA-time content-hash annotation stamp on the
1234 /// SAME `Process`).
1235 pub fn observed_identity(&self) -> Option<&Identity> {
1236 self.status.as_ref().and_then(|s| s.identity.as_ref())
1237 }
1238
1239 /// The copy-form status-projection primitive on the phase axis:
1240 /// returns the [`ProcessPhase`] the reconciler currently persists
1241 /// at `status.phase`, wrapped in an `Option` so the missing-
1242 /// `status` corner collapses to `None` — the ONE-liner collapse
1243 /// of the paired `self.status.as_ref().map(|s| s.phase)`
1244 /// incantation every consumer restated by hand pre-lift.
1245 ///
1246 /// Peer to the borrow-form projections
1247 /// [`Self::observed_pid`] (PID axis, `Option<&str>`),
1248 /// [`Self::observed_flux_resources`] (flux-resources axis,
1249 /// `&[FluxResourceRef]`), and [`Self::observed_attestation`]
1250 /// (attestation-chain axis, `Option<&ProcessAttestation>`); this
1251 /// method opens the copy-form peer for `ProcessPhase` — a
1252 /// `Copy` scalar with a `Default` impl (`Pending`), so the
1253 /// return is `Option<ProcessPhase>` rather than
1254 /// `Option<&ProcessPhase>` (borrow would give the caller
1255 /// nothing over the copy for a 1-byte enum) and neither the
1256 /// missing-`status` corner nor a "empty slot" corner is
1257 /// meaningful — the underlying slot is a bare `ProcessPhase`,
1258 /// not `Option<ProcessPhase>`, so the primitive returns `None`
1259 /// iff `status: None`.
1260 ///
1261 /// Pre-lift the 3-line `.status.as_ref().map(|s| s.phase)`
1262 /// chain was hand-authored at FIVE sites past the ★★
1263 /// PRIME-DIRECTIVE ≥ 2 duplication threshold in
1264 /// `tatara-reconciler`:
1265 /// * `controller::reconcile` — the top-level dispatcher's
1266 /// `current_phase` seed that feeds the deletion-preempt +
1267 /// signal-ingestion gates + the per-phase handler dispatch.
1268 /// Pre-lift `.unwrap_or(ProcessPhase::Pending)`.
1269 /// * `boundary::evaluate_process_phase` — the boundary
1270 /// evaluator's `ProcessPhase` condition (a peer-Process
1271 /// `phase`-reached postcondition). Pre-lift
1272 /// `.unwrap_or(ProcessPhase::Pending)`.
1273 /// * `boundary::check_depends_on` — the `depends_on`
1274 /// pre-condition audit that stashes the observed phase into
1275 /// the `UnmetDependency::actual: Option<ProcessPhase>` slot
1276 /// (keeps the `Option` form). Pre-lift the raw
1277 /// `.map(|s| s.phase)` shape.
1278 /// * `phase_machine::p_current_phase_str` — the released-from
1279 /// annotation composer that emits `"Attested"` for every
1280 /// non-`Failed` phase (SIGSTOP/SIGCONT release gate).
1281 /// Pre-lift `.unwrap_or(ProcessPhase::Attested)` — the ONE
1282 /// site whose default is not `Pending`; the primitive
1283 /// returns the raw `Option` so the caller's `.unwrap_or`
1284 /// default choice stays local rather than baked in.
1285 /// * `table_controller::stable_name_group_key` — the routing-
1286 /// groupby seed that pairs the phase with the PID + creation
1287 /// timestamp when partitioning Processes claiming the same
1288 /// stable name. Pre-lift `.unwrap_or(ProcessPhase::Pending)`.
1289 ///
1290 /// All FIVE sites walked the SAME 3-line `.status.as_ref()
1291 /// .map(|s| s.phase)` chain — three closed with `unwrap_or
1292 /// (ProcessPhase::Pending)` (the `Default`), one closed with
1293 /// `unwrap_or(ProcessPhase::Attested)`, one kept the raw
1294 /// `Option<ProcessPhase>` — so the ONE substrate accessor
1295 /// returns the raw `Option<ProcessPhase>` and each consumer
1296 /// keeps its `.unwrap_or(...)` default choice at its own site.
1297 ///
1298 /// A future normalization step (a generation-filter that
1299 /// returns `None` for a phase stamped with a stale
1300 /// `metadata.generation`, a staleness gate that drops a phase
1301 /// whose observing `phase_since` predates a reconcile
1302 /// deadline, a canonicalization pass that maps a phase that
1303 /// no longer belongs to the CRD's closed set to `None`) lands
1304 /// at ONE substrate method here and all five consumers pick
1305 /// up the upgrade mechanically — no per-callsite hand-edit at
1306 /// `reconcile` / `evaluate_process_phase` / `check_depends_on`
1307 /// / `p_current_phase_str` / `stable_name_group_key`.
1308 ///
1309 /// Future status projections (`observed_parent` on the
1310 /// parent-pointer axis, `observed_message` on the human-
1311 /// readable-status axis, `observed_children` on the child
1312 /// fan-out axis, `observed_exit_code` on the terminal-exit
1313 /// axis) land as peer methods on this same axis.
1314 ///
1315 /// Theory anchor: THEORY.md §VI.1 (generation over
1316 /// composition — the 3-line status-projection chain recurred
1317 /// at FIVE hand-authored sites past the ★★ PRIME-DIRECTIVE
1318 /// ≥ 2 duplication trigger, and is lifted to ONE owner here).
1319 /// THEORY.md §II.1 invariant 5 (composition preserves proofs
1320 /// — the pins bind the missing-`status` corner + the
1321 /// per-variant enum round-trip + the byte-identical parity
1322 /// with the pre-lift 3-line chain, so a regression that
1323 /// drifted any surface at `tests::observed_phase_*` rather
1324 /// than as silent operator-facing skew between the
1325 /// controller's dispatch seed and the boundary evaluator's
1326 /// depends-on audit on the SAME `Process` within one
1327 /// reconcile pass).
1328 pub fn observed_phase(&self) -> Option<ProcessPhase> {
1329 self.status.as_ref().map(|s| s.phase)
1330 }
1331
1332 /// The copy-form status-projection primitive on the phase axis
1333 /// with the `Pending` sink applied — the ONE-liner collapse of
1334 /// the paired `self.observed_phase().unwrap_or(ProcessPhase::
1335 /// Pending)` incantation every reconciler consumer restated by
1336 /// hand at the `Option`-flattening tail of the `observed_phase`
1337 /// call. Sibling to [`Self::observed_phase`] on the (return-form
1338 /// × fallback shape) axis pair — the raw-`Option` corner stays
1339 /// as `observed_phase`, this method opens the `Pending`-defaulted
1340 /// corner that four of the five hand-authored `observed_phase`
1341 /// consumers chose (the fifth chose `Attested`; it keeps the raw
1342 /// `Option` accessor because a `Pending` sink would silently drop
1343 /// its released-from-annotation branch into the wrong label).
1344 ///
1345 /// The primitive returns [`ProcessPhase::Pending`] on any missing
1346 /// `status` slot — the same sentinel [`ProcessPhase::default`]
1347 /// returns, and the same fallback all four pre-lift consumers
1348 /// wrote by hand. `ProcessPhase::Pending` is load-bearing as the
1349 /// "not yet observed" default because the top-level dispatcher's
1350 /// `Pending → Forking` transition, the boundary evaluator's
1351 /// per-Process phase-reached postcondition, the routing groupby's
1352 /// stable-name claim-arbiter row seed, and the pool controller's
1353 /// desired-count snapshot all read a freshly-forked Process (no
1354 /// `status` yet stamped by the reconciler) as being at the
1355 /// entrypoint phase of the closed lifecycle. A caller with a
1356 /// different default choice (currently only the SIGSTOP/SIGCONT
1357 /// release gate's `Attested` fallback in
1358 /// `phase_machine::p_current_phase_str`) keeps the raw
1359 /// [`Self::observed_phase`] accessor at its own site.
1360 ///
1361 /// Pre-lift the two-link `.observed_phase().unwrap_or
1362 /// (ProcessPhase::Pending)` chain was hand-authored at FOUR
1363 /// sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold
1364 /// across the workspace:
1365 /// * `tatara-reconciler::controller::reconcile` — the top-level
1366 /// dispatcher's `current_phase` seed that feeds the
1367 /// deletion-preempt + signal-ingestion gates + the per-phase
1368 /// handler dispatch.
1369 /// * `tatara-reconciler::boundary::evaluate_process_phase` — the
1370 /// boundary evaluator's [`ConditionKind::ProcessPhase`]
1371 /// evaluator that compares a peer-Process's observed phase
1372 /// against the operator-declared `phase`-reached postcondition.
1373 /// * `tatara-reconciler::table_controller::stable_name_group_key`
1374 /// — the routing-groupby seed that pairs the phase with the
1375 /// PID + creation timestamp when partitioning Processes
1376 /// claiming the same stable name.
1377 /// * `tatara-pool-reconciler::controller_pool::reconcile_pool` —
1378 /// the desired-count loop's per-member snapshot seed that feeds
1379 /// `decide_pool_convergence` with each owned Process's
1380 /// `(phase, created_at)` pair.
1381 ///
1382 /// All FOUR sites walked the SAME two-link chain and all four
1383 /// closed with `ProcessPhase::Pending` as the sink; post-lift
1384 /// each callsite reads `process.observed_phase_or_pending()` and
1385 /// the produced `ProcessPhase` feeds the same downstream branch
1386 /// (dispatch on the `current_phase` value, comparison against a
1387 /// declared threshold, groupby-key composition, member-state
1388 /// snapshot construction) unchanged.
1389 ///
1390 /// Return-form axis: `ProcessPhase` matches the copy discipline
1391 /// of [`Self::observed_phase`] (a `Copy` scalar one byte wide),
1392 /// with the [`Option`] wrapper collapsed at the primitive rather
1393 /// than at every consumer. A caller that needs the missing-`status`
1394 /// corner as a distinguishable value keeps the raw
1395 /// [`Self::observed_phase`] accessor.
1396 ///
1397 /// A future normalization step (a generation-filter that
1398 /// treats a phase stamped with a stale `metadata.generation` as
1399 /// unobserved and therefore `Pending`, a staleness gate that
1400 /// drops a phase whose observing `phase_since` predates a
1401 /// reconcile deadline, a canonicalization pass that maps a phase
1402 /// that no longer belongs to the CRD's closed set to `Pending`)
1403 /// lands at ONE substrate method here — because this primitive
1404 /// composes on top of [`Self::observed_phase`], the normalization
1405 /// applies to both the raw-`Option` and the `Pending`-sinked
1406 /// return through the SAME upstream body — and all four
1407 /// downstream consumers pick up the upgrade mechanically.
1408 ///
1409 /// Peer to the sibling defaulted-fallback primitive family
1410 /// [`Self::namespace_or_default`] +
1411 /// [`Self::name_or_placeholder`] + [`Self::uid_or_empty`] on the
1412 /// (return-shape × fallback-value) axis — those three open the
1413 /// borrow-form defaulted corner for the metadata slots; this
1414 /// method opens the copy-form defaulted corner for the phase
1415 /// slot on `status`. Future defaulted-fallback status
1416 /// projections (an `observed_pid_or_empty` on the PID axis, an
1417 /// `observed_exit_code_or_zero` on the terminal-exit axis) land
1418 /// as peer methods on this same axis.
1419 ///
1420 /// Theory anchor: THEORY.md §VI.1 (generation over composition —
1421 /// the two-link `.observed_phase().unwrap_or(Pending)` chain
1422 /// recurred at four hand-authored sites past the ★★
1423 /// PRIME-DIRECTIVE ≥ 2 duplication trigger, and is lifted to ONE
1424 /// owner here). THEORY.md §II.1 invariant 5 (composition
1425 /// preserves proofs — the pins bind the missing-`status` sink to
1426 /// `Pending` + populated-status pass-through + every
1427 /// `ProcessPhase` variant round-trip + byte-identical parity
1428 /// with the pre-lift two-link chain, so a regression that
1429 /// drifted any surface at `tests::observed_phase_or_pending_*`
1430 /// rather than as silent operator-facing skew between the
1431 /// top-level dispatcher's `Pending → Forking` seed and the
1432 /// boundary evaluator's per-Process phase-reached postcondition
1433 /// on the SAME `Process` within one reconcile pass).
1434 pub fn observed_phase_or_pending(&self) -> ProcessPhase {
1435 self.observed_phase().unwrap_or(ProcessPhase::Pending)
1436 }
1437
1438 /// The copy-form status-projection primitive on the
1439 /// `status.phase_since` axis: returns the [`DateTime<Utc>`] the
1440 /// reconciler stamped when this Process last transitioned into its
1441 /// current [`ProcessPhase`], wrapped in an `Option` so BOTH the
1442 /// missing-`status` corner AND the empty-slot corner
1443 /// (`ProcessStatus.phase_since == None` — a freshly-forked Process
1444 /// whose reconciler has not yet stamped a first transition) collapse
1445 /// to `None` — the ONE-liner collapse of the paired
1446 /// `self.status.as_ref().and_then(|s| s.phase_since)` incantation
1447 /// the pool reconciler's per-owned-Process member-seed builder
1448 /// restated by hand pre-lift.
1449 ///
1450 /// Pre-lift the 5-line
1451 /// ```rust,ignore
1452 /// p.status
1453 /// .as_ref()
1454 /// .and_then(|s| s.phase_since)
1455 /// .unwrap_or_else(Utc::now)
1456 /// ```
1457 /// chain was hand-authored at
1458 /// `tatara-pool-reconciler::controller_pool::reconcile_inner`'s
1459 /// per-owned-Process `PoolMember { entered_state_at: … }` seed —
1460 /// the row-builder that feeds `pool_phase_from_members` +
1461 /// `apply_pool_reconcile_decision` with each owned Process's
1462 /// last-observed transition instant. Post-lift the callsite reads
1463 /// `p.observed_phase_since().unwrap_or_else(Utc::now)`, a
1464 /// one-liner symmetric to the peer `p.created_at()
1465 /// .unwrap_or_else(Utc::now)` chain the sibling
1466 /// [`PoolMemberSnapshot`] `created_at` seed two branches below
1467 /// already routes through — closing the last raw
1468 /// `.status.as_ref()` chain on `Process` at that reconciler site.
1469 ///
1470 /// Return-form axis: `Option<DateTime<Utc>>` matches the copy-form
1471 /// discipline of the sibling metadata-projection primitive
1472 /// [`Self::created_at`] (both return `Option<DateTime<Utc>>` and
1473 /// hide the wire-format wrapper — `ProcessStatus` on the status
1474 /// side, `k8s_openapi::…::v1::Time` on the metadata side) so the
1475 /// two timestamp-projection primitives compose byte-uniformly at
1476 /// the pool reconciler's `PoolMember` / `PoolMemberSnapshot`
1477 /// seeds. Returning owned `DateTime<Utc>` with a
1478 /// substrate-injected `Utc::now()` fallback would fold an impure
1479 /// wall-clock read into the primitive, breaking the pure-
1480 /// projection discipline every peer `observed_*` accessor
1481 /// follows; the sink stays at the callsite where it composes with
1482 /// [`Self::created_at`]'s identical `.unwrap_or_else(Utc::now)`
1483 /// tail.
1484 ///
1485 /// Peer to the copy-form status-projection primitive
1486 /// [`Self::observed_phase`] on the (return-shape × status-slot)
1487 /// axis pair — both walk the paired `.status.as_ref().<map|and_then>
1488 /// (|s| s.<slot>)` chain and both project a `Copy` inner from a
1489 /// wire slot whose "not yet observed" corner collapses to `None`.
1490 /// [`Self::observed_phase`] projects the `phase` slot (a bare
1491 /// [`ProcessPhase`] with a `Default` sentinel — collapses only on
1492 /// missing `status`); this method projects the `phase_since` slot
1493 /// (an `Option<DateTime<Utc>>` with no sentinel — collapses on
1494 /// missing `status` OR on empty slot). The paired
1495 /// `.map` vs `.and_then` choice tracks the difference: the raw
1496 /// slot is `Option<DateTime<Utc>>` here so the closure returns an
1497 /// `Option` and the outer combinator flattens through `.and_then`,
1498 /// where `observed_phase`'s raw slot is a bare `ProcessPhase` so
1499 /// the closure returns a bare value and the outer combinator maps
1500 /// through `.map`. Future status-timestamp projections (an
1501 /// `observed_last_boundary_check` on
1502 /// [`crate::status::BoundaryStatus.last_check`], an
1503 /// `observed_last_export_receipt` on a future receipt-observation
1504 /// slot) land as peer methods on this same axis.
1505 ///
1506 /// A future normalization step (a per-cluster clock-skew guard
1507 /// that offsets the returned timestamp by the observing controller's
1508 /// measured skew, a canonicalization pass that maps a suspiciously-
1509 /// zero `phase_since` to `None` so consumers' `.unwrap_or_else
1510 /// (Utc::now)` tails synthesize a fresh anchor, a staleness gate
1511 /// that drops a `phase_since` predating a reconcile deadline) lands
1512 /// at ONE substrate method here and every downstream consumer
1513 /// picks up the upgrade mechanically — no per-callsite hand-edit
1514 /// at `reconcile_inner`'s member-seed builder.
1515 ///
1516 /// Theory anchor: THEORY.md §VI.1 (generation over composition —
1517 /// the paired `.status.as_ref().and_then(|s| s.phase_since)` chain
1518 /// closes the last raw `.status.as_ref()` chain in
1519 /// `tatara-pool-reconciler`'s production reconciler code on
1520 /// `Process`, and is lifted to ONE substrate owner here alongside
1521 /// the sibling `observed_phase` / `observed_phase_or_pending` /
1522 /// `observed_identity` / `observed_pid` / `observed_attestation` /
1523 /// `observed_flux_resources` primitives that closed their axes
1524 /// previously). THEORY.md §II.1 invariant 5 (composition preserves
1525 /// proofs — the pins bind the missing-`status` corner + the empty-
1526 /// slot corner + the populated-slot pass-through + the pure-
1527 /// projection discipline + the byte-identical parity with the pre-
1528 /// lift `.status.as_ref().and_then(|s| s.phase_since)` chain + the
1529 /// composition-shape agreement with [`Self::created_at`]'s
1530 /// identical `.unwrap_or_else(Utc::now)` tail at the peer
1531 /// pool-reconciler seed, so a regression that drifted any surface
1532 /// at `tests::observed_phase_since_*` rather than as silent
1533 /// operator-facing skew between the `PoolMember` row's observed-
1534 /// transition anchor and the `PoolMemberSnapshot`'s creation-
1535 /// timestamp anchor on the SAME owned `Process` within one
1536 /// reconcile pass).
1537 #[must_use]
1538 pub fn observed_phase_since(&self) -> Option<DateTime<Utc>> {
1539 self.status.as_ref().and_then(|s| s.phase_since)
1540 }
1541
1542 /// Pure composer over [`Self::observed_phase_since`] that folds the
1543 /// paired `.unwrap_or(fallback)` sink into ONE substrate owner — the
1544 /// ONE-liner collapse of the paired
1545 /// `p.observed_phase_since().unwrap_or_else(Utc::now)` incantation
1546 /// the pool-reconciler consumer restated by hand pre-lift, and the
1547 /// status-slot peer of the sibling [`Self::created_at_or`] composer
1548 /// on the metadata-timestamp axis. The wall-clock read stays at the
1549 /// callsite (as `Utc::now()` passed in positionally) so the composer
1550 /// itself stays pure — matching the discipline every peer `observed_*`
1551 /// / `created_at` copy-form projection follows and the explicit
1552 /// warning against a substrate-injected `Utc::now()` fallback that
1553 /// [`Self::observed_phase_since`]'s doc already spelled out.
1554 ///
1555 /// Pre-lift the paired 2-step
1556 /// `.observed_phase_since().unwrap_or_else(Utc::now)` chain was hand-
1557 /// authored at THREE workspace-wide sites past the ★★ PRIME-DIRECTIVE
1558 /// ≥ 2 duplication threshold, all stamping the SAME wall-clock
1559 /// fallback on the same missing-`phase_since` corner:
1560 /// * `tatara-pool-reconciler::controller_pool::reconcile_inner` —
1561 /// the per-owned-Process `PoolMember { entered_state_at, .. }`
1562 /// seed feeding `pool_phase_from_members` +
1563 /// `apply_pool_reconcile_decision`. A freshly-forked pool member
1564 /// whose reconciler has not yet stamped a first phase-transition
1565 /// gets `Utc::now()` synthesized so the observed-transition
1566 /// anchor sorts as "just entered" rather than short-circuiting
1567 /// on the missing slot.
1568 /// * `tatara-process::crd::tests::
1569 /// observed_phase_since_composes_with_unwrap_or_else_utc_now_tail_at_pool_seed`
1570 /// — the call-site-shape pin that binds the composed tail's
1571 /// behavior on both the populated corner (fallback silent) and
1572 /// the empty corner (fallback fires). Two hand-authored
1573 /// restatements inside the single test pin the same 2-step chain
1574 /// at fail-before-pass-after granularity.
1575 ///
1576 /// All THREE sites walked the SAME `.unwrap_or_else(Utc::now)` tail
1577 /// on the SAME [`Self::observed_phase_since`] pure projection and
1578 /// all THREE wanted the resolved `DateTime<Utc>` the composer
1579 /// returns. Post-lift each production callsite reads
1580 /// `p.observed_phase_since_or(Utc::now())` and the produced value
1581 /// feeds the same downstream slot unchanged; the test pin retains
1582 /// the pre-lift `.unwrap_or_else(Utc::now)` composition to bind the
1583 /// pure-projection primitive's own byte-identical behavior while a
1584 /// peer test pin binds this composer's byte-identical parity.
1585 ///
1586 /// The `fallback: DateTime<Utc>` parameter (rather than a
1587 /// substrate-injected `Utc::now()`) keeps the composer pure — a
1588 /// test with a fixed-clock harness passes its own frozen anchor, a
1589 /// production consumer passes `Utc::now()`, both go through the
1590 /// same primitive without the composer itself reaching for the
1591 /// wall clock. This resolves exactly the tension
1592 /// [`Self::observed_phase_since`]'s doc spelled out (a buried
1593 /// `Utc::now()` fallback "would fold an impure wall-clock read
1594 /// into the primitive, breaking the pure-projection discipline
1595 /// every peer `observed_*` accessor follows") by lifting the
1596 /// composition shape, not the wall-clock read.
1597 ///
1598 /// Return-form axis: `DateTime<Utc>` matches the `unwrap_or`-style
1599 /// composer discipline of `Option::unwrap_or` in std — takes the
1600 /// pure projection, an owned fallback, returns the resolved owned
1601 /// value. Peer to the substrate composer [`Self::created_at_or`]
1602 /// on the metadata-timestamp axis — both lift a
1603 /// `.unwrap_or(<fallback>)` tail into ONE substrate site so the
1604 /// fallback-shape decision lives at ONE owner per axis; the two
1605 /// timestamp-projection composers on `Process` (metadata-timestamp
1606 /// [`Self::created_at_or`] + status-timestamp [`Self`]) now
1607 /// compose byte-uniformly at the pool reconciler's per-member row
1608 /// builder, closing the last hand-authored `.unwrap_or_else(Utc::
1609 /// now)` tail on `Process` in that reconciler's production code.
1610 ///
1611 /// A future normalization step (a per-cluster clock-skew guard
1612 /// that offsets the returned timestamp by the observing
1613 /// controller's measured skew before applying the fallback, a
1614 /// canonicalization pass that folds a suspiciously-zero
1615 /// `phase_since` to the fallback rather than accepting it, a
1616 /// staleness gate that treats a `phase_since` predating a
1617 /// reconcile deadline as unobserved and falls through to the
1618 /// fallback) lands at ONE substrate method here and every
1619 /// downstream consumer picks up the upgrade mechanically — no
1620 /// per-callsite hand-edit at
1621 /// `controller_pool::reconcile_inner`'s member-seed builder or at
1622 /// any future observed-transition consumer (a stable-name claim-
1623 /// arbiter age tie-break on the status-transition anchor, a
1624 /// per-pool dwell-time reap probe on the same slot).
1625 ///
1626 /// Theory anchor: THEORY.md §VI.1 (generation over composition —
1627 /// the paired `.observed_phase_since().unwrap_or_else(Utc::now)`
1628 /// chain recurred at three hand-authored sites past the ★★
1629 /// PRIME-DIRECTIVE ≥ 2 duplication trigger, and is lifted to ONE
1630 /// owner here alongside the sibling [`Self::created_at_or`] on
1631 /// the metadata-timestamp axis). THEORY.md §II.1 invariant 5
1632 /// (composition preserves proofs — the pins bind the missing-slot
1633 /// fallback corner + the populated-slot pass-through + the pure-
1634 /// composer discipline + the byte-identical parity with the
1635 /// pre-lift `.unwrap_or(fallback)` chain, so a regression that
1636 /// drifted any surface at `tests::observed_phase_since_or_*`
1637 /// rather than as silent operator-facing skew between the
1638 /// `PoolMember` row's observed-transition anchor and any future
1639 /// observed-transition consumer on the SAME `Process` within one
1640 /// reconcile pass).
1641 #[must_use]
1642 pub fn observed_phase_since_or(&self, fallback: DateTime<Utc>) -> DateTime<Utc> {
1643 self.observed_phase_since().unwrap_or(fallback)
1644 }
1645
1646 /// Copy-form metadata-projection primitive on the deletion-tombstone
1647 /// axis: returns `true` iff the K8s API server has stamped a
1648 /// `metadata.deletionTimestamp` on this Process (the moment the
1649 /// object entered the "being deleted" corner of its lifecycle,
1650 /// after which further mutating writes are refused and finalizers
1651 /// are drained before the object is actually removed) — the ONE-
1652 /// liner collapse of the paired `self.metadata.deletion_timestamp
1653 /// .is_some()` incantation every consumer restated by hand
1654 /// pre-lift.
1655 ///
1656 /// Pre-lift the `.metadata.deletion_timestamp.is_some()` chain
1657 /// was hand-authored at TWO sites past the ★★ PRIME-DIRECTIVE
1658 /// ≥ 2 duplication threshold in `tatara-reconciler`, both
1659 /// projecting the SAME tombstone-presence predicate on a
1660 /// `Process` value:
1661 /// * `controller::reconcile` — the top-level dispatcher's
1662 /// deletion-preempt gate that forces the SIGTERM cascade
1663 /// (`→ Exiting`) as soon as the API server stamps the
1664 /// tombstone, before the phase handler for the current
1665 /// [`ProcessPhase`] gets a chance to run. Composed with
1666 /// [`ProcessPhase::is_alive`] so the preempt only fires on a
1667 /// Process still in an alive phase — a Process already in
1668 /// `Zombie` / `Reaped` / `Failed` runs its normal handler.
1669 /// * `phase_machine::handle_exiting` — the SIGTERM cascade's
1670 /// child-fan-out loop that enumerates every child Process and
1671 /// skips ones the API server has already tombstoned (so the
1672 /// reconciler does not re-issue a `DELETE` against a child
1673 /// whose deletion the API server is already draining through
1674 /// its own finalizer). The skip composes with
1675 /// [`Self::coordinates_or_none`]'s name-required probe so a
1676 /// child missing either its tombstone-absent gate or its
1677 /// `metadata.name` slot is a clean `continue` rather than an
1678 /// attempted `child_api.delete("")` no-op.
1679 ///
1680 /// Both sites walked the SAME `.metadata.deletion_timestamp
1681 /// .is_some()` chain and both wanted the `bool` form the
1682 /// primitive returns — the `controller::reconcile` site to gate
1683 /// the SIGTERM preempt with `&& current_phase.is_alive()` and
1684 /// the `handle_exiting` site to gate the DELETE-skip with a
1685 /// bare `if child.is_being_deleted() { continue; }`. Post-lift
1686 /// each callsite reads `process.is_being_deleted()` and the
1687 /// produced `bool` feeds the same downstream gate unchanged.
1688 ///
1689 /// Return-form axis: `bool` matches the copy-form discipline of
1690 /// [`Self::observed_phase`] (an `Option<Copy>` scalar) — the
1691 /// underlying slot is a wire-format `Option<Time>` that carries
1692 /// only presence information at this axis (the RFC-3339 timestamp
1693 /// payload itself is not what the two consumers read; both only
1694 /// probe presence to detect the tombstone-stamped state).
1695 /// Returning the raw `Option<&Time>` would push the `.is_some()`
1696 /// probe back to every callsite, restating the pre-lift chain
1697 /// one link shorter without collapsing the primitive.
1698 ///
1699 /// Peer to the metadata-fallback primitives
1700 /// [`Self::namespace_or_default`], [`Self::name_or_placeholder`],
1701 /// [`Self::uid_or_empty`], [`Self::coordinates_or_defaults`],
1702 /// [`Self::coordinates_or_none`], [`Self::owned_coordinates_or_err`],
1703 /// [`Self::annotation`] on the metadata axis; this method opens
1704 /// the copy-form peer for the presence-probe corner. Future
1705 /// metadata-presence projections (an `is_being_finalized`
1706 /// projection on `metadata.finalizers.is_empty()`'s negation,
1707 /// a `has_owner` projection on `metadata.owner_references.is_empty()`'s
1708 /// negation) land as peer methods on this same axis.
1709 ///
1710 /// A future normalization step (a per-tombstone staleness gate
1711 /// that returns `false` for a tombstone older than the reconciler's
1712 /// grace-period budget, a canonicalization pass that treats a
1713 /// tombstone from a paused controller as absent, a cross-cluster
1714 /// tombstone-observation clock skew guard) lands at ONE substrate
1715 /// method here and both downstream consumers pick up the upgrade
1716 /// mechanically — no per-callsite hand-edit at
1717 /// `controller::reconcile` / `phase_machine::handle_exiting`.
1718 ///
1719 /// Theory anchor: THEORY.md §VI.1 (generation over composition —
1720 /// the `.metadata.deletion_timestamp.is_some()` chain recurred at
1721 /// two hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2
1722 /// duplication trigger, and is lifted to ONE owner here).
1723 /// THEORY.md §II.1 invariant 5 (composition preserves proofs —
1724 /// the pins bind the missing-tombstone corner + the present-
1725 /// tombstone corner + the copy-form `bool` return + the byte-
1726 /// identical parity with the pre-lift `.is_some()` chain, so a
1727 /// regression that drifted any surface at
1728 /// `tests::is_being_deleted_*` rather than as silent operator-
1729 /// facing skew between the top-level dispatcher's SIGTERM
1730 /// preempt and the SIGTERM cascade's child-fan-out DELETE-skip
1731 /// on the SAME `Process` within one reconcile pass).
1732 pub fn is_being_deleted(&self) -> bool {
1733 self.metadata.deletion_timestamp.is_some()
1734 }
1735
1736 /// Copy-form metadata-projection primitive on the
1737 /// `metadata.creationTimestamp` axis: returns the K8s-API-server-
1738 /// assigned creation moment as a `DateTime<Utc>`, hiding the wire-
1739 /// format `k8s_openapi::apimachinery::pkg::apis::meta::v1::Time`
1740 /// newtype behind an inherent projection — the ONE-liner collapse
1741 /// of the paired `self.metadata.creation_timestamp.as_ref().map(|t|
1742 /// t.0)` incantation every timestamp-driven consumer restated by
1743 /// hand pre-lift.
1744 ///
1745 /// Pre-lift the paired `.metadata.creation_timestamp.as_ref()` +
1746 /// `t.0` unwrap chain was hand-authored at THREE sites past the
1747 /// ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold across the
1748 /// workspace, all projecting the SAME creation-moment `DateTime<Utc>`
1749 /// on a `Process`:
1750 /// * `tatara-process::lifetime_clock::evaluate` — TTL-expiry gate
1751 /// in the ephemeral-lifetime decision (`elapsed = now
1752 /// .signed_duration_since(creation.0)`), inside the non-terminal-
1753 /// phase guard that fires the `AutoTerminate::Now { TtlExpired }`
1754 /// branch. Pre-lift the site read `if let Some(creation) = process
1755 /// .metadata.creation_timestamp.as_ref() { ... creation.0 ... }`.
1756 /// * `tatara-process::lifetime_clock::requeue_with_ttl` — sleep-
1757 /// budget picker for the reconciler's next requeue, choosing the
1758 /// smaller of HEARTBEAT and TTL-remaining so the reconciler
1759 /// doesn't oversleep past a TTL boundary. Pre-lift the site read
1760 /// `let Some(creation) = process.metadata.creation_timestamp
1761 /// .as_ref() else { return default; };` + `creation.0`.
1762 /// * `tatara-reconciler::table_controller::reconcile_process_table`
1763 /// — stable-name claim-arbiter row builder, seeding each
1764 /// candidate row's `created_at` for the tie-break ordering
1765 /// (oldest wins). Pre-lift the site read `p.metadata
1766 /// .creation_timestamp.as_ref().map(|t| t.0).unwrap_or_else(Utc
1767 /// ::now)`.
1768 ///
1769 /// All THREE sites walked the SAME two-link chain — read the
1770 /// `Option<Time>` slot as a borrow, then unwrap the `Time` newtype
1771 /// to its inner `DateTime<Utc>` — differing only in the tail
1772 /// (`if-let-Some` guard, `let-else` short-circuit, `Utc::now`
1773 /// fallback). Post-lift each callsite reads
1774 /// `process.created_at()` and applies its own tail at its own site
1775 /// (`if let Some(creation) = ...`, `let Some(creation) = ... else`,
1776 /// `.unwrap_or_else(Utc::now)`).
1777 ///
1778 /// Return-form axis: `Option<DateTime<Utc>>` matches the copy-form
1779 /// discipline of the sibling status-projection primitive
1780 /// [`Self::observed_phase`] — both return `Option<T>` where `T:
1781 /// Copy` and hide the wire-format wrapper (`ProcessStatus` on the
1782 /// status side; `Time` on the metadata side). Returning the raw
1783 /// `Option<&Time>` would push the `.0` unwrap back to every
1784 /// callsite, restating the pre-lift chain one link shorter without
1785 /// collapsing the primitive; returning owned `Option<Time>` would
1786 /// force a `Time` import at every consumer for a projection every
1787 /// consumer immediately discards past `.0`.
1788 ///
1789 /// Peer to the metadata-fallback + presence-probe primitives
1790 /// [`Self::namespace_or_default`], [`Self::name_or_placeholder`],
1791 /// [`Self::uid_or_empty`], [`Self::coordinates_or_defaults`],
1792 /// [`Self::coordinates_or_none`], [`Self::owned_coordinates_or_err`],
1793 /// [`Self::annotation`], [`Self::is_being_deleted`] on the metadata
1794 /// axis; this method opens the copy-form timestamp corner. Future
1795 /// metadata-timestamp projections (a
1796 /// `deletion_at() -> Option<DateTime<Utc>>` peer on the
1797 /// tombstone-payload axis for staleness gates that need the
1798 /// timestamp value alongside the presence bit) land as peer
1799 /// methods on this same axis.
1800 ///
1801 /// A future normalization step (a per-cluster clock-skew guard
1802 /// that offsets the returned timestamp by the observing controller's
1803 /// measured skew, a canonicalization pass that maps a suspiciously-
1804 /// zero creation moment to `None`, a per-namespace override that
1805 /// substitutes a `spec.identity`-declared creation anchor for the
1806 /// metadata slot on adopted resources) lands at ONE substrate
1807 /// method here and all three downstream consumers pick up the
1808 /// upgrade mechanically — no per-callsite hand-edit at `evaluate`
1809 /// / `requeue_with_ttl` / `reconcile_process_table`.
1810 ///
1811 /// Theory anchor: THEORY.md §VI.1 (generation over composition —
1812 /// the `.metadata.creation_timestamp.as_ref().map(|t| t.0)` chain
1813 /// recurred at three hand-authored sites past the ★★
1814 /// PRIME-DIRECTIVE ≥ 2 duplication trigger, and is lifted to ONE
1815 /// owner here). THEORY.md §II.1 invariant 5 (composition preserves
1816 /// proofs — the pins bind the missing-timestamp corner + the
1817 /// present-timestamp corner + the copy-form `DateTime<Utc>` return
1818 /// + the byte-identical parity with the pre-lift `.as_ref().map(|t|
1819 /// t.0)` chain, so a regression that drifted any surface at
1820 /// `tests::created_at_*` rather than as silent operator-facing
1821 /// skew between the TTL-expiry gate, the requeue-budget picker,
1822 /// and the stable-name claim-arbiter tie-break on the SAME
1823 /// `Process` within one reconcile pass).
1824 pub fn created_at(&self) -> Option<DateTime<Utc>> {
1825 self.metadata.creation_timestamp.as_ref().map(|t| t.0)
1826 }
1827
1828 /// Pure composer over [`Self::created_at`] that folds the paired
1829 /// `.unwrap_or(fallback)` sink into ONE substrate owner — the
1830 /// ONE-liner collapse of the paired
1831 /// `p.created_at().unwrap_or_else(Utc::now)` incantation the two
1832 /// production consumers restated by hand pre-lift, with the
1833 /// wall-clock read kept at the callsite (as `Utc::now()` passed in
1834 /// positionally) so the primitive itself stays pure — matching the
1835 /// discipline every peer `observed_*` / `created_at` copy-form
1836 /// projection follows and the explicit warning against a
1837 /// substrate-injected `Utc::now()` fallback that
1838 /// [`Self::observed_phase_since`]'s doc already spelled out.
1839 ///
1840 /// Pre-lift the paired 2-step
1841 /// `.created_at().unwrap_or_else(Utc::now)` chain was hand-authored
1842 /// at TWO production sites past the ★★ PRIME-DIRECTIVE ≥ 2
1843 /// duplication threshold, both stamping the SAME wall-clock
1844 /// fallback on the same missing-timestamp corner:
1845 /// * `tatara-reconciler::table_controller::reconcile_process_table` —
1846 /// the per-Process claim-row's `created_at` anchor that feeds
1847 /// the stable-name group's tie-break comparator; a freshly-forked
1848 /// Process whose API server has not yet stamped
1849 /// `metadata.creationTimestamp` gets `Utc::now()` synthesized so
1850 /// the tie-break sorts by "just-created" order rather than
1851 /// short-circuiting on the missing slot.
1852 /// * `tatara-pool-reconciler::controller_pool::reconcile_inner`'s
1853 /// desired-count `PoolMemberSnapshot { created_at, .. }` seed —
1854 /// the per-owned-Process snapshot fed to
1855 /// `decide_pool_convergence`, whose stability arithmetic
1856 /// subtracts the anchor from `now` to compute the observed dwell
1857 /// time; the same "just-created" fallback keeps a freshly-spawned
1858 /// pool member from being reaped as if it were a stale zombie.
1859 ///
1860 /// Both sites walked the SAME `.unwrap_or_else(Utc::now)` tail on
1861 /// the SAME [`Self::created_at`] pure projection and both wanted
1862 /// the resolved `DateTime<Utc>` the composer returns. Post-lift
1863 /// each callsite reads `p.created_at_or(Utc::now())` and the
1864 /// produced value feeds the same downstream slot unchanged.
1865 ///
1866 /// The `fallback: DateTime<Utc>` parameter (rather than a
1867 /// substrate-injected `Utc::now()`) keeps the composer pure — a
1868 /// test with a fixed-clock harness passes its own frozen anchor, a
1869 /// production consumer passes `Utc::now()`, both go through the
1870 /// same primitive without the composer itself reaching for the
1871 /// wall clock. This resolves the tension the sibling
1872 /// [`Self::observed_phase_since`]'s doc spelled out (a buried
1873 /// `Utc::now()` fallback "would fold an impure wall-clock read
1874 /// into the primitive, breaking the pure-projection discipline
1875 /// every peer `observed_*` accessor follows") by lifting the
1876 /// composition shape, not the wall-clock read.
1877 ///
1878 /// Return-form axis: `DateTime<Utc>` matches the `unwrap_or`-style
1879 /// composer discipline of `Option::unwrap_or` in std — takes the
1880 /// pure projection, an owned fallback, returns the resolved owned
1881 /// value. Peer to the substrate composers
1882 /// [`Self::observed_phase_or_pending`] on the status-phase axis and
1883 /// [`Self::coordinates_or_defaults`] on the metadata-coordinate
1884 /// axis; all three lift a `.unwrap_or(<fallback>)` tail into ONE
1885 /// substrate site so the fallback-shape decision lives at ONE
1886 /// owner per axis.
1887 ///
1888 /// A future normalization step (a per-cluster clock-skew guard
1889 /// that offsets the returned timestamp by the observing
1890 /// controller's measured skew before applying the fallback, a
1891 /// canonicalization pass that folds a suspiciously-zero
1892 /// `creationTimestamp` to the fallback rather than accepting it,
1893 /// a per-namespace override that substitutes a `spec.identity`-
1894 /// declared creation anchor for the metadata slot on adopted
1895 /// resources) lands at ONE substrate method here and both
1896 /// downstream consumers pick up the upgrade mechanically — no
1897 /// per-callsite hand-edit at `reconcile_process_table` /
1898 /// `reconcile_inner`.
1899 ///
1900 /// Theory anchor: THEORY.md §VI.1 (generation over composition —
1901 /// the paired `.created_at().unwrap_or_else(Utc::now)` chain
1902 /// recurred at two hand-authored sites past the ★★
1903 /// PRIME-DIRECTIVE ≥ 2 duplication trigger, and is lifted to ONE
1904 /// owner here). THEORY.md §II.1 invariant 5 (composition
1905 /// preserves proofs — the pins bind the missing-slot fallback
1906 /// corner + the populated-slot pass-through + the pure-composer
1907 /// discipline + the byte-identical parity with the pre-lift
1908 /// `.unwrap_or(fallback)` chain, so a regression that drifted
1909 /// any surface at `tests::created_at_or_*` rather than as
1910 /// silent operator-facing skew between the claim-arbiter's
1911 /// tie-break anchor and the pool convergence snapshot's dwell-time
1912 /// anchor on the SAME `Process` within one reconcile pass).
1913 #[must_use]
1914 pub fn created_at_or(&self, fallback: DateTime<Utc>) -> DateTime<Utc> {
1915 self.created_at().unwrap_or(fallback)
1916 }
1917
1918 /// Wall-clock-anchored peer of [`Self::created_at_or`] — reads
1919 /// `Utc::now()` at call time and forwards it into the pure composer's
1920 /// `fallback` slot so the wall-clock projection lives at ONE
1921 /// substrate site rather than at each production callsite.
1922 ///
1923 /// # Why it exists
1924 ///
1925 /// Pre-lift the 2-arg `p.created_at_or(Utc::now())` chain was
1926 /// hand-authored at TWO production sites past the ★★ PRIME-DIRECTIVE
1927 /// ≥ 2 duplication threshold, each pairing the pure
1928 /// [`Self::created_at_or`] composer with a `Utc::now()` fallback
1929 /// argument at a per-Process anchor seed:
1930 ///
1931 /// * `tatara-reconciler::table_controller::reconcile_process_table`
1932 /// — the per-Process claim-row `created_at` anchor feeding the
1933 /// stable-name group's tie-break comparator; a freshly-forked
1934 /// Process whose API server has not yet stamped
1935 /// `metadata.creationTimestamp` gets the wall-clock read
1936 /// synthesized so the tie-break sorts by "just-created" order
1937 /// rather than short-circuiting on the missing slot.
1938 /// * `tatara-pool-reconciler::controller_pool::reconcile_inner`'s
1939 /// desired-count `PoolMemberSnapshot { created_at, .. }` seed —
1940 /// the per-owned-Process snapshot fed to
1941 /// `decide_pool_convergence`, whose stability arithmetic
1942 /// subtracts the anchor from `now` to compute observed dwell
1943 /// time; the same "just-created" wall-clock fallback keeps a
1944 /// freshly-spawned pool member from being reaped as a stale
1945 /// zombie.
1946 ///
1947 /// Both sites walked the SAME 2-arg call with the SAME `Utc::now()`
1948 /// fallback — the wall-clock projection had no per-callsite
1949 /// variation. Post-lift both consumers share ONE substrate owner
1950 /// for the wall-clock-at-tick projection; a future clock swap (a
1951 /// monotonic clock cross-check, a per-reconciler injected time
1952 /// source, a test-only override at the production callsite via
1953 /// feature flag) lands at ONE substrate function and both anchor
1954 /// seeds inherit the upgrade mechanically.
1955 ///
1956 /// The 2-arg [`Self::created_at_or`] peer stays load-bearing for
1957 /// this crate's own test suite — the injected-`fallback` shape is
1958 /// what unit tests use to drive the fallback anchor deterministically
1959 /// (every `p.created_at_or(seeded_anchor)` in the pin family below
1960 /// reads that surface). This peer is production-only: pinning the
1961 /// wall-clock at the substrate site means no test can accidentally
1962 /// consume `created_at_or_now` without the deterministic-clock
1963 /// injection that makes the test meaningful.
1964 ///
1965 /// Sibling of the wall-clock-anchored peer family across the
1966 /// workspace's timed-decision axes:
1967 /// [`crate::pool::PoolStatus::observed_now`] on the
1968 /// `PoolStatus`-observation axis,
1969 /// [`crate::allocation::AllocationStatus::transition_now`] on the
1970 /// `AllocationStatus`-transition axis, and
1971 /// [`crate::lifetime_clock::evaluate_now`] on the
1972 /// `AutoTerminate` timed-decision axis. All four primitives own the
1973 /// "read the wall clock at tick-time" projection on a peer
1974 /// clock-injectable pure composer so the workspace's
1975 /// wall-clock-anchored peer family stays uniform across every
1976 /// production callsite.
1977 ///
1978 /// # Invariants
1979 ///
1980 /// - **Same shape:** returns the SAME `DateTime<Utc>` the 2-arg
1981 /// [`Self::created_at_or`] returns when passed `Utc::now()` as
1982 /// the fallback argument. This is a delegation, not a
1983 /// re-implementation.
1984 /// - **Wall-clock read once:** `Utc::now()` is called exactly ONCE
1985 /// per invocation, at the primitive's body, so a future consumer
1986 /// that chains two `created_at_or_now` calls back-to-back still
1987 /// sees monotonic `now` reads (each call reads a fresh instant,
1988 /// not a cached one) — matches the pre-lift shape where each of
1989 /// the two anchor sites computed its own `Utc::now()` at its own
1990 /// line.
1991 ///
1992 /// # `#[must_use]`
1993 ///
1994 /// Every consumer feeds the returned `DateTime<Utc>` into a
1995 /// downstream slot (`ClaimRecord.created_at`, `PoolMemberSnapshot
1996 /// .created_at`). Dropping the return means the anchor was
1997 /// computed for no observable reason — the attribute surfaces that
1998 /// as a warning at every call site.
1999 ///
2000 /// Theory anchor: THEORY.md §VI.1 (generation over composition —
2001 /// the 2-arg call with `Utc::now()` as the fallback argument
2002 /// recurred at 2 hand-authored sites past the ★★ PRIME-DIRECTIVE
2003 /// ≥ 2 duplication trigger, lifted onto the ONE workspace-wide
2004 /// substrate owner here). THEORY.md §II.1 invariant 5 (composition
2005 /// preserves proofs — the wall-clock projection lives at ONE site
2006 /// so a future clock swap reaches both consumers through one
2007 /// edit).
2008 #[must_use]
2009 pub fn created_at_or_now(&self) -> DateTime<Utc> {
2010 self.created_at_or(Utc::now())
2011 }
2012
2013 /// Compound spec-projection primitive on the `spec.lifetime` axis:
2014 /// returns `Some(&e)` iff the resolver unambiguously picks the
2015 /// `Ephemeral` slot, `None` otherwise — the ONE-liner collapse of
2016 /// the 4-step `self.spec.lifetime.resolved_ephemeral()` chain the
2017 /// two `lifetime_clock` consumers previously reached through and
2018 /// the coherence-tightening lift of the naked
2019 /// `self.spec.lifetime.ephemeral.as_ref()` raw-field access
2020 /// `tatara_reconciler::render::render_export_jobs` previously
2021 /// walked past.
2022 ///
2023 /// Pre-lift THREE consumer sites past the ★★ PRIME-DIRECTIVE ≥ 2
2024 /// duplication threshold reached the ephemeral inner through TWO
2025 /// different chains that disagreed on the ambiguous corner:
2026 /// * `tatara_process::lifetime_clock::evaluate` — ambiguity-aware:
2027 /// `process.spec.lifetime.resolved_ephemeral()` collapses BOTH-
2028 /// slots-set to `None`, matching the "no ephemeral action"
2029 /// outcome (`AutoTerminate::Skip`) the ambiguous case must yield.
2030 /// * `tatara_process::lifetime_clock::requeue_with_ttl` —
2031 /// ambiguity-aware peer of `evaluate`; both share the SAME
2032 /// `resolved_ephemeral()` gate and MUST agree on the ambiguous
2033 /// corner or the reconciler's teardown decision and requeue-
2034 /// budget picker drift apart on the SAME `Process` within one
2035 /// reconcile pass.
2036 /// * `tatara_reconciler::render::render_export_jobs` — RAW field
2037 /// access: `process.spec.lifetime.ephemeral.as_ref()` returned
2038 /// `Some(&e)` on the ambiguous corner, so an operator-authored
2039 /// `Process` with BOTH `permanent:` AND `ephemeral:` slots
2040 /// populated would emit export Jobs whose teardown-triggered
2041 /// fire semantics `lifetime_clock` refused to honor. The two
2042 /// consumers drifted at the mis-configuration corner.
2043 ///
2044 /// Post-lift ALL THREE consumers reach through ONE `Process` method
2045 /// that composes `self.spec.lifetime.resolved_ephemeral()` — the
2046 /// ambiguity-aware `variant().ok() + as_ephemeral` chain
2047 /// [`crate::lifetime::Lifetime::resolved_ephemeral`] owns — and
2048 /// the drift between the reconciler's export-render arm and the
2049 /// lifetime clock's teardown/TTL arm CLOSES at ONE substrate site.
2050 ///
2051 /// Return-form axis: `Option<&EphemeralLifetime>` matches the
2052 /// borrow-form discipline of the underlying
2053 /// [`crate::lifetime::Lifetime::resolved_ephemeral`] projection so
2054 /// the borrow carries the `'_self` lifetime through directly
2055 /// without a temporary `LifetimeVariant` binding. Peer to the
2056 /// borrow-form status-projection primitives
2057 /// [`Self::observed_attestation`], [`Self::observed_identity`] and
2058 /// the borrow-form metadata-projection primitive
2059 /// [`Self::uid_or_empty`] — all four hide a wrapping `Option`-
2060 /// carrying wire slot behind an inherent projection.
2061 ///
2062 /// A future normalization step (a canonicalization pass that maps
2063 /// a suspiciously-zero `ttl` to a per-cluster default, a per-
2064 /// namespace override that substitutes an operator-declared
2065 /// teardown policy on adopted resources, a wire-schema migration
2066 /// that renames `spec.lifetime.ephemeral` to `spec.lifetime.timed`
2067 /// with a bridging `From` shim) lands at ONE substrate method here
2068 /// and all three downstream consumers pick up the upgrade
2069 /// mechanically — no per-callsite hand-edit at `evaluate` /
2070 /// `requeue_with_ttl` / `render_export_jobs`.
2071 ///
2072 /// Theory anchor: THEORY.md §II.1 invariant 5 (composition
2073 /// preserves proofs — the pins bind the Permanent-only corner, the
2074 /// Ephemeral-only corner, the Both-set-ambiguous corner, the
2075 /// empty-default corner, and the byte-identity parity with the
2076 /// underlying `self.spec.lifetime.resolved_ephemeral()` delegate,
2077 /// so a regression that silently swapped the projection back to
2078 /// the raw `.ephemeral.as_ref()` field would surface here rather
2079 /// than as operator-facing drift between the export-render arm
2080 /// and the teardown/TTL arm on the SAME `Process`). THEORY.md
2081 /// §VI.1 (generation over composition — the ambiguity-aware
2082 /// projection recurred at three hand-authored sites past the ★★
2083 /// PRIME-DIRECTIVE ≥ 2 duplication trigger, and is lifted to ONE
2084 /// owner here).
2085 pub fn resolved_ephemeral(&self) -> Option<&EphemeralLifetime> {
2086 self.spec.lifetime.resolved_ephemeral()
2087 }
2088}
2089
2090impl ProcessSpec {
2091 /// Canonical minimum [`ProcessSpec`] — a [`Classification::gate_compute`]
2092 /// classification with every other field parked at its [`Default`] —
2093 /// the workspace-baseline spec every consumer that needed "a
2094 /// `ProcessSpec` that just exists, with no domain-specific claim on
2095 /// intent / boundary / lifetime / routing / encapsulates" hand-authored
2096 /// as a 12-line struct-literal at scattered sites across the workspace.
2097 ///
2098 /// The composition is the two-primitive product of
2099 /// [`Classification::gate_compute`] (the two axes with no `Default` — a
2100 /// `Gate` point on the `Compute` substrate) with the `Default` impl on
2101 /// every other slot: [`IdentitySpec`], [`Intent`], [`Boundary`],
2102 /// [`ComplianceSpec`], `Vec<DependsOn>`, [`SignalPolicy`], [`Lifetime`],
2103 /// `Option<RoutingSpec>`, `Option<EncapsulatesSpec>`, `bool`. The 11
2104 /// defaulted axes ride at the sibling closed-set + `#[serde(default)]`
2105 /// defaults the CRD already owns; the two `_or_default` /
2106 /// `_or_placeholder` corners on the metadata axis stay closed at the
2107 /// substrate ([`Process::coordinates_or_defaults`],
2108 /// [`Process::name_or_placeholder`]) since this primitive builds the
2109 /// `spec` half, not the `metadata` half.
2110 ///
2111 /// Pre-lift the 12-line `ProcessSpec { identity: <Default>,
2112 /// classification: Classification::gate_compute(), intent: <Default>,
2113 /// boundary: Default::default(), compliance: Default::default(),
2114 /// depends_on: vec![], signals: Default::default(), lifetime:
2115 /// Default::default(), routing: None, encapsulates: None, suspended:
2116 /// false }` struct-literal recurred at EIGHT hand-authored sites past
2117 /// the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold across four
2118 /// crates, each restating the SAME 12-slot verbatim:
2119 /// * `tatara-process::crd::tests::empty_spec` — the substrate test
2120 /// fixture that pins every `Process::*_or_*` metadata-projection
2121 /// primitive on the (return-form × fallback-shape) axis;
2122 /// * `tatara-process::lib::tests::empty_process_spec` (×2) — the
2123 /// sibling fixture inside the `qualified_process_ref` +
2124 /// `DeletionTombstoned` / `Annotated` trait pin modules;
2125 /// * `tatara-process::lib::tests` (one inline site in the
2126 /// `qualified_process_ref_composes_from_process_coordinates_or_defaults`
2127 /// pin) — restated the SAME 12-line block inside the test body;
2128 /// * `tatara-reconciler::claim::tests::empty_process` — the claim-
2129 /// arbiter row-builder pin fixture;
2130 /// * `tatara-pool-reconciler::controller_pool::tests` (×3) — the
2131 /// `empty_spec` fixture + two inline `process_to_member_state_*` pin
2132 /// sites that hand-composed the same 12-slot spec inline.
2133 ///
2134 /// Five more sites walked the SAME 12-slot shape but overrode ONE
2135 /// field (intent, lifetime, or routing) inline and are lifted onto
2136 /// the primitive via struct-update syntax
2137 /// (`..ProcessSpec::gate_compute_defaults()`): the three
2138 /// `tatara-reconciler::render` test-fixture helpers
2139 /// (`render_through_top_level_intent_dispatch`, `process_with`,
2140 /// `demo_process`) and the two `tatara-process::lifetime_clock`
2141 /// helpers (`ephemeral_process`, `permanent_process`).
2142 ///
2143 /// Post-lift each callsite reads `ProcessSpec::gate_compute_defaults()`
2144 /// (or `ProcessSpec { <slot>: <value>,
2145 /// ..ProcessSpec::gate_compute_defaults() }` for the override sites);
2146 /// a future workspace-wide baseline shift (a new `#[serde(default)]`
2147 /// on a promoted [`Intent`] variant, a rename of a defaulted slot, a
2148 /// per-baseline compliance overlay stamping through the spec) lands
2149 /// at ONE substrate function here and every downstream consumer
2150 /// inherits the upgrade mechanically. The current pin ties the
2151 /// classification axis to the sibling [`Classification::gate_compute`]
2152 /// primitive so a future change to that baseline surfaces at this
2153 /// primitive's tests rather than as silent drift across thirteen
2154 /// independent callsites.
2155 ///
2156 /// Sibling to [`Classification::gate_compute`] on the composition-
2157 /// depth axis — that primitive owns the ONE-axis-slice construction
2158 /// (the 5-slot [`Classification`] value); this primitive owns the
2159 /// FULL-spec construction (the 11-slot [`ProcessSpec`] value that
2160 /// wraps the classification-slice plus every other slot at
2161 /// `Default`). A future peer `ProcessSpec::observability_stack()` or
2162 /// similar named variant lands as a sibling method here when a
2163 /// second unremarkable-baseline shape opens.
2164 ///
2165 /// Theory anchor: THEORY.md §VI.1 (generation over composition — the
2166 /// 12-line struct-literal shape recurred at EIGHT hand-authored sites
2167 /// past the ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger and is lifted
2168 /// onto ONE workspace-wide owner here). THEORY.md §II.1 invariant 5
2169 /// (composition preserves proofs — a regression that drifted the
2170 /// baseline axis choice at only one consumer, or that broke the
2171 /// sibling-default correspondence with [`Classification::gate_compute`],
2172 /// surfaces at this primitive's tests rather than as silent operator-
2173 /// visible skew between the eight exact-match test-fixtures + the
2174 /// five override sites whose struct-update composition depends on the
2175 /// shape).
2176 #[must_use]
2177 pub fn gate_compute_defaults() -> Self {
2178 Self {
2179 identity: IdentitySpec::default(),
2180 classification: Classification::gate_compute(),
2181 intent: Intent::default(),
2182 boundary: Boundary::default(),
2183 compliance: ComplianceSpec::default(),
2184 depends_on: Vec::new(),
2185 signals: SignalPolicy::default(),
2186 lifetime: Lifetime::default(),
2187 routing: None,
2188 encapsulates: None,
2189 suspended: false,
2190 }
2191 }
2192}
2193
2194/// Process status — every field optional until the reconciler writes it.
2195#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema)]
2196#[serde(rename_all = "camelCase")]
2197pub struct ProcessStatus {
2198 /// Hierarchical PID path — e.g., `"seph.1.7"`.
2199 #[serde(default, skip_serializing_if = "Option::is_none")]
2200 pub pid: Option<String>,
2201
2202 /// Parent PID path (mirror of `spec.identity.parent`, resolved at fork).
2203 #[serde(default, skip_serializing_if = "Option::is_none")]
2204 pub parent: Option<String>,
2205
2206 /// Direct children's PID paths.
2207 #[serde(default)]
2208 pub children: Vec<String>,
2209
2210 /// Resolved identity (name + content hash).
2211 #[serde(default, skip_serializing_if = "Option::is_none")]
2212 pub identity: Option<Identity>,
2213
2214 /// Current phase.
2215 #[serde(default)]
2216 pub phase: ProcessPhase,
2217
2218 /// When the process entered the current phase.
2219 #[serde(default, skip_serializing_if = "Option::is_none")]
2220 pub phase_since: Option<DateTime<Utc>>,
2221
2222 /// Three-pillar attestation (written at end of every successful cycle).
2223 #[serde(default, skip_serializing_if = "Option::is_none")]
2224 pub attestation: Option<ProcessAttestation>,
2225
2226 /// FluxCD resources currently owned by this Process.
2227 #[serde(default)]
2228 pub flux_resources: Vec<FluxResourceRef>,
2229
2230 /// Boundary verification state.
2231 #[serde(default)]
2232 pub boundary: BoundaryStatus,
2233
2234 /// Compliance summary at the latest attestation.
2235 #[serde(default)]
2236 pub compliance: ComplianceStatus,
2237
2238 /// Pending signals (delivered, not yet handled).
2239 #[serde(default)]
2240 pub signal_queue: Vec<ProcessSignal>,
2241
2242 /// Standard K8s Conditions.
2243 #[serde(default)]
2244 pub conditions: Vec<ProcessCondition>,
2245
2246 /// Human-readable last status message.
2247 #[serde(default, skip_serializing_if = "Option::is_none")]
2248 pub message: Option<String>,
2249
2250 /// Exit code (only set on Failed / Reaped).
2251 #[serde(default, skip_serializing_if = "Option::is_none")]
2252 pub exit_code: Option<i32>,
2253}
2254
2255impl ProcessStatus {
2256 /// Canonical phase-slot-only [`ProcessStatus`] fixture — a
2257 /// [`ProcessPhase`] pinned at the caller-supplied variant with every
2258 /// other slot parked at its [`Default`] — the workspace-baseline
2259 /// status shape every pool-reconciler phase-decision fixture and
2260 /// every below-controller test that "just wants a Process whose
2261 /// status carries a specific `phase`, nothing else observed" hand-
2262 /// authored as a 3-line `Some(ProcessStatus { phase, ..Default })`
2263 /// struct-literal at scattered pin sites.
2264 ///
2265 /// Pre-lift the 3-line `ProcessStatus { phase: <ProcessPhase::…>,
2266 /// ..Default::default() }` shape recurred at TWO hand-authored
2267 /// sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold,
2268 /// both inside `tatara-pool-reconciler::controller_pool::tests`:
2269 /// * `process_to_member_state_attested_permanent_is_free` — the
2270 /// Free-arm pin that binds "a Process whose observed phase is
2271 /// Attested + whose declared `lifetime` is Permanent maps to
2272 /// `MemberState::Free`".
2273 /// * `process_to_member_state_attested_ephemeral_is_allocated` —
2274 /// the Allocated-arm pin that binds the peer transition on the
2275 /// `Lifetime::Ephemeral` corner.
2276 ///
2277 /// Both pin sites walked the SAME 3-line shape stamping
2278 /// `ProcessPhase::Attested`; the composer serves both directly and
2279 /// stays parameterized on `phase` so a future pin on a peer variant
2280 /// (`Running`, `Reconverging`, `Reaped`) rides the same primitive
2281 /// without a new shape opening.
2282 ///
2283 /// Post-lift each callsite reads
2284 /// `p.status = Some(ProcessStatus::at_phase(ProcessPhase::Attested));`
2285 /// and the phase-slot-only status fixture lives at ONE substrate
2286 /// owner. Sibling to [`ProcessSpec::gate_compute_defaults`] on the
2287 /// (spec × status) construction-shape pair: that primitive owns the
2288 /// FULL-spec baseline builder for every downstream `Process::new`
2289 /// consumer; this primitive owns the phase-slot-observation status
2290 /// builder for every downstream `p.status = Some(...)` fixture.
2291 ///
2292 /// A future normalization of the phase-only status shape (a
2293 /// call-time `phase_since` stamp mirroring the phase-transition
2294 /// writer's discipline, a `boundary` slot default overlay pinning
2295 /// the phase to a matching BoundaryStatus corner, a wired-in
2296 /// `identity` fixture for the phase-decision fixtures that today
2297 /// leave the slot at `None`) lands at THIS ONE function and every
2298 /// downstream phase-decision pin inherits the upgrade mechanically.
2299 /// Directly benefits the P5 shigoto Dag refactor (any RecordingJob
2300 /// test fixture stamping "a Process whose observed phase is X" rides
2301 /// the same composer rather than restating the 3-line shape a third
2302 /// time) and the P3 kenshi-runner library lift (any test-Job
2303 /// controller that binds a phase-observation fixture on its owning
2304 /// Process rides through the same composer as the pool-reconciler's
2305 /// two phase-decision pins).
2306 ///
2307 /// Theory anchor: THEORY.md §VI.1 (generation over composition —
2308 /// the 3-line `ProcessStatus { phase, ..Default::default() }`
2309 /// struct-literal recurred at 2 hand-authored sites past the ★★
2310 /// PRIME-DIRECTIVE ≥ 2 duplication trigger inside one workspace
2311 /// crate, and is lifted onto ONE substrate owner here). THEORY.md
2312 /// §II.1 invariant 5 (composition preserves proofs — the pin block
2313 /// binds the primitive at fail-before-pass-after granularity so a
2314 /// regression that drifted the phase slot pass-through, leaked a
2315 /// sibling slot away from `Default`, or hijacked the composer to
2316 /// stamp a static `phase_since` on the `phase` transition surfaces
2317 /// at THESE pins rather than as silent phase-decision skew across
2318 /// the two pool-reconciler callsites).
2319 #[must_use]
2320 pub fn at_phase(phase: ProcessPhase) -> Self {
2321 Self {
2322 phase,
2323 ..Self::default()
2324 }
2325 }
2326}
2327
2328#[cfg(test)]
2329mod tests {
2330 use super::*;
2331 use crate::classification::{ConvergencePointType, SubstrateType};
2332 use crate::intent::NixIntent;
2333
2334 #[test]
2335 fn minimal_spec_serializes() {
2336 let spec = ProcessSpec {
2337 identity: IdentitySpec::default(),
2338 classification: Classification {
2339 point_type: ConvergencePointType::Gate,
2340 substrate: SubstrateType::Observability,
2341 horizon: Default::default(),
2342 calm: Default::default(),
2343 data_classification: Default::default(),
2344 },
2345 intent: Intent {
2346 nix: Some(NixIntent {
2347 flake_ref: "github:pleme-io/k8s".into(),
2348 attribute: "obs".into(),
2349 system: None,
2350 attic_cache: None,
2351 extra_args: vec![],
2352 delegate_to_nix_build: false,
2353 }),
2354 ..Intent::default()
2355 },
2356 boundary: Default::default(),
2357 compliance: Default::default(),
2358 depends_on: vec![],
2359 signals: Default::default(),
2360 lifetime: Default::default(),
2361 routing: None,
2362 encapsulates: None,
2363 suspended: false,
2364 };
2365 let yaml = serde_yaml::to_string(&spec).unwrap();
2366 assert!(yaml.contains("pointType: Gate"));
2367 assert!(yaml.contains("substrate: Observability"));
2368 assert!(yaml.contains("flakeRef: github:pleme-io/k8s"));
2369 }
2370
2371 // ─── Process::coordinates_or_defaults substrate pins ────────────────
2372 //
2373 // Pins the (namespace, name) coordinate-primitive family on the
2374 // (metadata slot × fallback shape) axis. Fail-before-pass-after
2375 // granularity: a regression that flipped either fallback string,
2376 // swapped the return-tuple axis order, or dropped the
2377 // `Option::as_deref` unwrap surfaces here rather than as silent
2378 // drift at every downstream annotation writer / claim-arbiter row
2379 // builder / render owner-metadata seed.
2380
2381 fn empty_spec() -> ProcessSpec {
2382 // Routes through the ONE substrate composer
2383 // `ProcessSpec::gate_compute_defaults` — pre-lift this was the
2384 // 12-line struct-literal restated verbatim at every fixture in
2385 // this pin family, one of EIGHT hand-authored exact-match sites
2386 // past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold across
2387 // four crates.
2388 ProcessSpec::gate_compute_defaults()
2389 }
2390
2391 #[test]
2392 fn default_namespace_constant_is_k8s_canonical_default() {
2393 // Pins the load-bearing convention that this primitive's
2394 // namespace fallback matches K8s's own implicit-namespace
2395 // spelling. A regression that renamed this to "kube-system"
2396 // or any other K8s-reserved name would silently misroute
2397 // every downstream namespaced-Api call on a Process without
2398 // a metadata.namespace.
2399 assert_eq!(Process::DEFAULT_NAMESPACE, "default");
2400 }
2401
2402 #[test]
2403 fn unnamed_placeholder_constant_matches_prior_annotation_writer_fallback() {
2404 // Pins the load-bearing convention that this primitive's name
2405 // fallback matches the exact spelling every annotation writer
2406 // (tatara-reconciler::ssapply::inject_annotations,
2407 // tatara-reconciler::render::render, and
2408 // tatara-reconciler::table_controller's claim-row builder)
2409 // was hand-authoring pre-lift ("unnamed", NOT "<unnamed>" or
2410 // ""). A regression that renamed this would break the
2411 // annotation-writer / claim-arbiter grep contract silently.
2412 assert_eq!(Process::UNNAMED_PLACEHOLDER, "unnamed");
2413 }
2414
2415 #[test]
2416 fn namespace_or_default_falls_back_when_metadata_namespace_is_none() {
2417 let mut p = Process::new("some-proc", empty_spec());
2418 p.metadata.namespace = None;
2419 assert_eq!(p.namespace_or_default(), Process::DEFAULT_NAMESPACE);
2420 }
2421
2422 #[test]
2423 fn namespace_or_default_returns_metadata_slice_when_some() {
2424 let mut p = Process::new("some-proc", empty_spec());
2425 p.metadata.namespace = Some("prod-app".into());
2426 assert_eq!(p.namespace_or_default(), "prod-app");
2427 }
2428
2429 #[test]
2430 fn name_or_placeholder_falls_back_when_metadata_name_is_none() {
2431 let mut p = Process::new("real-name", empty_spec());
2432 p.metadata.name = None;
2433 assert_eq!(p.name_or_placeholder(), Process::UNNAMED_PLACEHOLDER);
2434 }
2435
2436 #[test]
2437 fn name_or_placeholder_returns_metadata_slice_when_some() {
2438 let p = Process::new("api-gateway", empty_spec());
2439 assert_eq!(p.name_or_placeholder(), "api-gateway");
2440 }
2441
2442 #[test]
2443 fn coordinates_or_defaults_composes_both_halves() {
2444 // Both slots present — returns metadata slices in
2445 // (namespace, name) axis order.
2446 let mut p = Process::new("api", empty_spec());
2447 p.metadata.namespace = Some("staging".into());
2448 assert_eq!(p.coordinates_or_defaults(), ("staging", "api"));
2449 }
2450
2451 #[test]
2452 fn coordinates_or_defaults_falls_back_on_both_slots() {
2453 // Both slots None — returns (DEFAULT_NAMESPACE,
2454 // UNNAMED_PLACEHOLDER) in axis order.
2455 let mut p = Process::new("scratch", empty_spec());
2456 p.metadata.name = None;
2457 p.metadata.namespace = None;
2458 assert_eq!(
2459 p.coordinates_or_defaults(),
2460 (Process::DEFAULT_NAMESPACE, Process::UNNAMED_PLACEHOLDER)
2461 );
2462 }
2463
2464 #[test]
2465 fn coordinates_or_defaults_mixes_slotted_and_fallback_halves() {
2466 // Namespace set, name missing — the (namespace, name) tuple
2467 // pins each half independently. A regression that returned
2468 // BOTH fallbacks when EITHER metadata slot was None would
2469 // surface here rather than at every downstream reader.
2470 let mut p = Process::new("kept-name", empty_spec());
2471 p.metadata.namespace = Some("prod".into());
2472 assert_eq!(p.coordinates_or_defaults(), ("prod", "kept-name"));
2473
2474 // Name set, namespace missing — the peer corner.
2475 let mut q = Process::new("api", empty_spec());
2476 q.metadata.namespace = None;
2477 assert_eq!(
2478 q.coordinates_or_defaults(),
2479 (Process::DEFAULT_NAMESPACE, "api")
2480 );
2481 }
2482
2483 // ─── Process::qualified_ref substrate pins ─────────────────────────
2484 //
2485 // Pins the paired-projection + shape-composer chain
2486 // `coordinates_or_defaults() → qualified_process_ref(ns, name)` on
2487 // the (return-form × composition-depth) axis pair. Fail-before-
2488 // pass-after granularity: a regression that swapped the `<ns>/<name>`
2489 // axis order, dropped either half, drifted the fallback strings
2490 // between the paired-projection primitive and the shape composer, or
2491 // inserted a normalization step at only the composed site and not
2492 // the pair-returning primitive (or vice versa) surfaces here rather
2493 // than as silent operator-visible skew across the three pre-lift
2494 // `tatara-reconciler` sites (`render::render_routing`,
2495 // `render::render_export_jobs`, `table_controller::reconcile`)
2496 // whose downstream greps the reference shape verbatim (the
2497 // `PROCESS=<ref>` annotation seed on every emitted Ingress /
2498 // DNSEndpoint / export Job, the `ClaimRecord.holder` slot on the
2499 // stable-name claim registry).
2500
2501 #[test]
2502 fn qualified_ref_composes_ns_and_name_with_slash_when_both_slots_present() {
2503 // Happy path — both metadata slots populated. The composed
2504 // reference is EXACTLY `<ns>/<name>`, in that order, joined by
2505 // a single `/`. A regression that swapped the two axes at
2506 // this primitive would silently break every downstream
2507 // `PROCESS=<ref>` annotation grep + claim-registry lookup.
2508 let mut p = Process::new("api-gateway", empty_spec());
2509 p.metadata.namespace = Some("prod-app".into());
2510 assert_eq!(p.qualified_ref(), "prod-app/api-gateway");
2511 }
2512
2513 #[test]
2514 fn qualified_ref_falls_back_to_default_namespace_when_metadata_namespace_is_none() {
2515 // Namespace-fallback pin: an absent `metadata.namespace` rides
2516 // through `namespace_or_default()` → `DEFAULT_NAMESPACE`, so
2517 // the composed reference lands as `default/<name>`. Matches
2518 // what a pre-lift `qualified_process_ref(process.
2519 // coordinates_or_defaults())` composition produced.
2520 let mut p = Process::new("api-gateway", empty_spec());
2521 p.metadata.namespace = None;
2522 assert_eq!(p.qualified_ref(), "default/api-gateway");
2523 }
2524
2525 #[test]
2526 fn qualified_ref_falls_back_to_unnamed_placeholder_when_metadata_name_is_none() {
2527 // Name-fallback pin: an absent `metadata.name` rides through
2528 // `name_or_placeholder()` → `UNNAMED_PLACEHOLDER`, so the
2529 // composed reference lands as `<ns>/unnamed`. A pre-lift
2530 // consumer whose paired projection returned the placeholder
2531 // (annotation writer, render owner-metadata seed) sees the
2532 // exact same `<ns>/unnamed` shape post-lift, so downstream
2533 // greps keyed on the pre-metadata Process's reference match
2534 // bytewise.
2535 let mut p = Process::new("ignored", empty_spec());
2536 p.metadata.namespace = Some("staging".into());
2537 p.metadata.name = None;
2538 assert_eq!(p.qualified_ref(), "staging/unnamed");
2539 }
2540
2541 #[test]
2542 fn qualified_ref_falls_back_on_both_slots_when_both_metadata_are_none() {
2543 // Both slots absent → both fallbacks land in the composed
2544 // reference. The `default/unnamed` shape is what every pre-
2545 // lift caller produced when a Process fixture (test or
2546 // dynamic API response) surfaced without populated metadata;
2547 // pinning it here holds the primitive's contract against a
2548 // regression that dropped either fallback at only the
2549 // composed site.
2550 let mut p = Process::new("ignored", empty_spec());
2551 p.metadata.namespace = None;
2552 p.metadata.name = None;
2553 assert_eq!(
2554 p.qualified_ref(),
2555 format!(
2556 "{}/{}",
2557 Process::DEFAULT_NAMESPACE,
2558 Process::UNNAMED_PLACEHOLDER
2559 )
2560 );
2561 }
2562
2563 #[test]
2564 fn qualified_ref_matches_pre_lift_paired_composition_bytewise() {
2565 // Byte-identical parity with the exact pre-lift 2-step
2566 // composition every `tatara-reconciler` site hand-authored:
2567 // `let (ns, name) = process.coordinates_or_defaults(); let r
2568 // = qualified_process_ref(ns, name);`. Sweeps every metadata-
2569 // slot combination the three pre-lift consumers plausibly
2570 // encountered — both slots populated (steady state), one
2571 // slot absent (Process mid-fork before API-server metadata
2572 // stamp), both slots absent (dynamic API response / test
2573 // fixture) — so a regression that reshaped the composition at
2574 // the substrate primitive would surface here rather than as
2575 // silent drift at the three consumer sites.
2576 let fixtures: [(Option<&str>, Option<&str>); 4] = [
2577 (Some("prod-app"), Some("api-gateway")),
2578 (None, Some("api-gateway")),
2579 (Some("staging"), None),
2580 (None, None),
2581 ];
2582 for (ns_slot, name_slot) in fixtures {
2583 let mut p = Process::new(name_slot.unwrap_or("seed"), empty_spec());
2584 p.metadata.namespace = ns_slot.map(str::to_string);
2585 p.metadata.name = name_slot.map(str::to_string);
2586 let via_primitive = p.qualified_ref();
2587 let (ns, name) = p.coordinates_or_defaults();
2588 let via_paired = crate::qualified_process_ref(ns, name);
2589 assert_eq!(
2590 via_primitive, via_paired,
2591 "qualified_ref must be byte-identical to the pre-lift \
2592 paired composition on (ns={ns_slot:?}, name={name_slot:?})"
2593 );
2594 }
2595 }
2596
2597 #[test]
2598 fn qualified_ref_composes_from_the_shared_coordinates_or_defaults_owner() {
2599 // Composition invariant: the composed reference decomposes at
2600 // the single `/` separator into EXACTLY the (ns, name) pair
2601 // `coordinates_or_defaults` returns. A regression that
2602 // introduced a per-callsite normalization at the shape
2603 // composer (URL-escape, case-fold, path-normalize) or that
2604 // pulled the pair from a different metadata source than the
2605 // paired-projection primitive would surface here rather than
2606 // at every downstream reference-shape grep.
2607 let mut p = Process::new("api-gateway", empty_spec());
2608 p.metadata.namespace = Some("prod-app".into());
2609 let composed = p.qualified_ref();
2610 let (ns, name) = p.coordinates_or_defaults();
2611 let (composed_ns, composed_name) = composed.split_once('/').unwrap();
2612 assert_eq!(composed_ns, ns);
2613 assert_eq!(composed_name, name);
2614 }
2615
2616 // ─── Process::owned_coordinates_or_err substrate pins ──────────────
2617 //
2618 // Pins the owned + name-required peer of the coordinate-primitive
2619 // family on the (return-form × name gate) axis pair. Fail-before-
2620 // pass-after granularity: a regression that flipped the namespace
2621 // fallback string, dropped the `Option::clone` unwrap, changed the
2622 // return-tuple axis order, or altered the "Process has no
2623 // metadata.name" error wording surfaces here rather than as silent
2624 // drift at every pre-lift caller (10 sites in
2625 // `tatara-reconciler::phase_machine` + 2 sites in
2626 // `tatara-reconciler::signals` pre-lift).
2627
2628 #[test]
2629 fn owned_coordinates_or_err_returns_owned_strings_when_both_slots_present() {
2630 // Happy path — both slots populated, method returns owned
2631 // Strings in (namespace, name) axis order.
2632 let mut p = Process::new("api-gateway", empty_spec());
2633 p.metadata.namespace = Some("prod-app".into());
2634 let (ns, name) = p.owned_coordinates_or_err().unwrap();
2635 assert_eq!(ns, "prod-app");
2636 assert_eq!(name, "api-gateway");
2637 // Ownership pin: type inference above binds ns/name as
2638 // owned Strings — a regression that returned &str would
2639 // fail to compile at the following .push() call. This
2640 // holds the "owned" half of the primitive's contract.
2641 let mut owned_ns = ns;
2642 owned_ns.push_str("-mutated");
2643 assert_eq!(owned_ns, "prod-app-mutated");
2644 }
2645
2646 #[test]
2647 fn owned_coordinates_or_err_falls_back_on_namespace_but_returns_owned_name() {
2648 // Namespace absent → DEFAULT_NAMESPACE. Name present → owned.
2649 let p = Process::new("api", empty_spec());
2650 // Process::new leaves metadata.namespace = None by default.
2651 let (ns, name) = p.owned_coordinates_or_err().unwrap();
2652 assert_eq!(ns, Process::DEFAULT_NAMESPACE);
2653 assert_eq!(name, "api");
2654 }
2655
2656 #[test]
2657 fn owned_coordinates_or_err_errors_when_metadata_name_absent_regardless_of_namespace() {
2658 // Name absent → Err, REGARDLESS of whether the namespace is
2659 // populated. The name gate is strictly on `metadata.name` and
2660 // does NOT fall back to `Self::UNNAMED_PLACEHOLDER` (that
2661 // fallback is on the peer `coordinates_or_defaults`, which
2662 // exists precisely for consumers that can tolerate a
2663 // display placeholder).
2664 for ns_slot in [None, Some("prod".to_string())] {
2665 let mut p = Process::new("scratch", empty_spec());
2666 p.metadata.name = None;
2667 p.metadata.namespace = ns_slot.clone();
2668 let err = p.owned_coordinates_or_err().unwrap_err();
2669 assert!(
2670 err.to_string().contains("metadata.name"),
2671 "err on missing name (ns={ns_slot:?}) should mention metadata.name; got {err}"
2672 );
2673 }
2674 }
2675
2676 #[test]
2677 fn owned_coordinates_or_err_error_message_matches_pre_lift_reconciler_wording() {
2678 // Load-bearing wording pin — every pre-lift `tatara-reconciler`
2679 // helper (`phase_machine::namespace_and_name`,
2680 // `signals::ingest`, `signals::consume_effect`) errored with
2681 // EXACTLY this wording. Post-lift the substrate owner produces
2682 // the same wording so log-line / test greps that anchored on
2683 // it keep matching, and no operator-visible message drift
2684 // lands as a side effect of the substrate move.
2685 let mut p = Process::new("scratch", empty_spec());
2686 p.metadata.name = None;
2687 let err = p.owned_coordinates_or_err().unwrap_err();
2688 assert_eq!(err.to_string(), "Process has no metadata.name");
2689 }
2690
2691 #[test]
2692 fn owned_coordinates_or_err_namespace_fallback_matches_default_namespace_const() {
2693 // Byte-identity pin between the owned form's namespace
2694 // fallback and the workspace-wide `DEFAULT_NAMESPACE` const.
2695 // A regression that spelled this fallback as any other
2696 // string ("kube-system", "", "default-ns") would silently
2697 // misroute every downstream namespaced-Api call on a
2698 // Process without a metadata.namespace — surfaces here
2699 // rather than at every kube-rs API caller.
2700 let mut p = Process::new("api", empty_spec());
2701 p.metadata.namespace = None;
2702 let (ns, _) = p.owned_coordinates_or_err().unwrap();
2703 assert_eq!(ns, Process::DEFAULT_NAMESPACE);
2704 }
2705
2706 #[test]
2707 fn owned_coordinates_or_err_matches_pre_lift_reconciler_helper_shape() {
2708 // Byte-identical parity pin between the owned + name-required
2709 // primitive here and the pre-lift `tatara-reconciler` helper
2710 // shape — the exact 2-slot unwrap chain each pre-lift caller
2711 // spelled by hand:
2712 //
2713 // let ns = p.metadata.namespace.clone().unwrap_or_else(|| "default".into());
2714 // let name = p.metadata.name.clone().ok_or_else(|| anyhow!(...))?;
2715 // Ok((ns, name))
2716 //
2717 // Sweeps every corner every callsite plausibly encounters
2718 // (both slots present, namespace absent, name absent, both
2719 // absent). A regression that inserted a normalization step
2720 // at the primitive that the pre-lift chain does NOT apply —
2721 // or vice versa — surfaces here rather than as silent drift
2722 // between the 12 pre-lift consumer callsites and the ONE
2723 // substrate owner they now route through.
2724 fn pre_lift(p: &Process) -> anyhow::Result<(String, String)> {
2725 let ns = p
2726 .metadata
2727 .namespace
2728 .clone()
2729 .unwrap_or_else(|| "default".into());
2730 let name = p
2731 .metadata
2732 .name
2733 .clone()
2734 .ok_or_else(|| anyhow::anyhow!("Process has no metadata.name"))?;
2735 Ok((ns, name))
2736 }
2737 // Both present.
2738 let mut p = Process::new("api", empty_spec());
2739 p.metadata.namespace = Some("prod".into());
2740 assert_eq!(p.owned_coordinates_or_err().unwrap(), pre_lift(&p).unwrap());
2741 // Namespace absent.
2742 let p = Process::new("api", empty_spec());
2743 assert_eq!(p.owned_coordinates_or_err().unwrap(), pre_lift(&p).unwrap());
2744 // Name absent → both variants error with the same wording.
2745 let mut p = Process::new("api", empty_spec());
2746 p.metadata.name = None;
2747 p.metadata.namespace = Some("prod".into());
2748 assert_eq!(
2749 p.owned_coordinates_or_err().unwrap_err().to_string(),
2750 pre_lift(&p).unwrap_err().to_string(),
2751 );
2752 // Both absent → still errors on the name gate.
2753 let mut p = Process::new("api", empty_spec());
2754 p.metadata.name = None;
2755 p.metadata.namespace = None;
2756 assert_eq!(
2757 p.owned_coordinates_or_err().unwrap_err().to_string(),
2758 pre_lift(&p).unwrap_err().to_string(),
2759 );
2760 }
2761
2762 #[test]
2763 fn owned_coordinates_or_err_axis_order_matches_coordinates_or_defaults() {
2764 // Cross-primitive coherence pin between the owned + name-
2765 // required form and the borrow + name-defaulted peer:
2766 // (namespace, name) axis order is IDENTICAL across both
2767 // return-forms. A regression that swapped the tuple slots on
2768 // only ONE of the two primitives would silently misroute
2769 // every consumer that picked between the two forms based on
2770 // its callsite's ownership needs. The pin re-reads both
2771 // primitives at test time so the equality holds iff both
2772 // live paths are the current implementation.
2773 let mut p = Process::new("app", empty_spec());
2774 p.metadata.namespace = Some("infra".into());
2775 let (borrow_ns, borrow_name) = p.coordinates_or_defaults();
2776 let (owned_ns, owned_name) = p.owned_coordinates_or_err().unwrap();
2777 assert_eq!(owned_ns, borrow_ns);
2778 assert_eq!(owned_name, borrow_name);
2779 // Explicit slot labels — pins the (namespace, name) axis
2780 // order as opposed to (name, namespace).
2781 assert_eq!(owned_ns, "infra"); // NOT "app"
2782 assert_eq!(owned_name, "app"); // NOT "infra"
2783 }
2784
2785 // ─── Process::coordinates_or_none substrate pins ──────────────────
2786 //
2787 // Pins the borrow + name-required peer of the coordinate-primitive
2788 // family on the (return-form × name-gate) axis pair. Closes the
2789 // corner previously left open (borrow + name-required) so the
2790 // three consumer shapes (child-Process delete-fan-out at
2791 // `phase_machine::handle_exiting`, claim-arbiter probe at
2792 // `phase_machine::process_holds_any_claim`, any future non-fatal
2793 // skip site) route through ONE primitive rather than three hand-
2794 // authored empty-string / `unwrap_or_default()` sentinel chains.
2795 // Fail-before-pass-after granularity: a regression that flipped
2796 // the namespace fallback, swapped the return-tuple axis order,
2797 // returned an owned form, or promoted a missing name to an error
2798 // rather than `None` surfaces here rather than as silent drift at
2799 // every borrow + name-required consumer.
2800
2801 #[test]
2802 fn coordinates_or_none_returns_slices_when_both_slots_present() {
2803 // Happy path — both slots populated, method returns borrowed
2804 // (&str, &str) in (namespace, name) axis order wrapped in
2805 // `Some`.
2806 let mut p = Process::new("api-gateway", empty_spec());
2807 p.metadata.namespace = Some("prod-app".into());
2808 let (ns, name) = p.coordinates_or_none().expect("Some when name set");
2809 assert_eq!(ns, "prod-app");
2810 assert_eq!(name, "api-gateway");
2811 }
2812
2813 #[test]
2814 fn coordinates_or_none_falls_back_on_namespace_but_returns_name_slice() {
2815 // Namespace absent → DEFAULT_NAMESPACE (shared with the peer
2816 // `coordinates_or_defaults` + `namespace_or_default`). Name
2817 // present → the metadata slice, wrapped in `Some`.
2818 let mut p = Process::new("api", empty_spec());
2819 p.metadata.namespace = None;
2820 let (ns, name) = p.coordinates_or_none().expect("Some when name set");
2821 assert_eq!(ns, Process::DEFAULT_NAMESPACE);
2822 assert_eq!(name, "api");
2823 }
2824
2825 #[test]
2826 fn coordinates_or_none_returns_none_when_metadata_name_absent_regardless_of_namespace() {
2827 // Name absent → `None`, REGARDLESS of whether the namespace
2828 // slot is populated. The name gate is strictly on
2829 // `metadata.name` and does NOT fall back to
2830 // `Self::UNNAMED_PLACEHOLDER` (that fallback is on the peer
2831 // `coordinates_or_defaults`, which exists precisely for
2832 // consumers that tolerate a display placeholder). Peer to
2833 // `owned_coordinates_or_err_errors_when_metadata_name_absent_regardless_of_namespace`
2834 // on the sibling primitive; a regression that widened THIS
2835 // form to substitute the placeholder while leaving the owned
2836 // form strict would silently drift the two borrow-form
2837 // primitives out of the coherence the family carries.
2838 for ns_slot in [None, Some("prod".to_string())] {
2839 let mut p = Process::new("scratch", empty_spec());
2840 p.metadata.name = None;
2841 p.metadata.namespace = ns_slot.clone();
2842 assert!(
2843 p.coordinates_or_none().is_none(),
2844 "coordinates_or_none must be None on missing name (ns={ns_slot:?})",
2845 );
2846 }
2847 }
2848
2849 #[test]
2850 fn coordinates_or_none_namespace_fallback_matches_default_namespace_const() {
2851 // Byte-identity pin between the borrow + name-required form's
2852 // namespace fallback and the workspace-wide `DEFAULT_NAMESPACE`
2853 // const. Sibling to
2854 // `owned_coordinates_or_err_namespace_fallback_matches_default_namespace_const`
2855 // on the peer primitive — the two forms MUST substitute the
2856 // same fallback string, else a consumer that switches between
2857 // them based on its ownership need silently observes a
2858 // different namespace-fallback shape as a side effect.
2859 let mut p = Process::new("api", empty_spec());
2860 p.metadata.namespace = None;
2861 let (ns, _) = p.coordinates_or_none().unwrap();
2862 assert_eq!(ns, Process::DEFAULT_NAMESPACE);
2863 }
2864
2865 #[test]
2866 fn coordinates_or_none_axis_order_matches_coordinates_or_defaults_when_name_present() {
2867 // Cross-primitive coherence pin between the two borrow-form
2868 // primitives: when the name is present, the (namespace, name)
2869 // return-tuple axis order is IDENTICAL across the two forms,
2870 // and the returned slices are the SAME `&str` view onto the
2871 // same metadata slots. A regression that swapped the tuple
2872 // slots on ONE form would silently misroute every consumer
2873 // that picked between the two forms based on its name-gate
2874 // need. The pin re-reads both primitives at test time so the
2875 // equality holds iff both live paths are the current
2876 // implementation.
2877 let mut p = Process::new("app", empty_spec());
2878 p.metadata.namespace = Some("infra".into());
2879 let (defaulted_ns, defaulted_name) = p.coordinates_or_defaults();
2880 let (required_ns, required_name) = p.coordinates_or_none().unwrap();
2881 assert_eq!(defaulted_ns, required_ns);
2882 assert_eq!(defaulted_name, required_name);
2883 // Explicit slot labels — pins the (namespace, name) axis order
2884 // as opposed to (name, namespace).
2885 assert_eq!(required_ns, "infra"); // NOT "app"
2886 assert_eq!(required_name, "app"); // NOT "infra"
2887 }
2888
2889 #[test]
2890 fn coordinates_or_none_axis_pair_diverges_from_coordinates_or_defaults_on_missing_name() {
2891 // Divergence pin between the two borrow-form primitives when
2892 // the name gate fires: `coordinates_or_defaults` substitutes
2893 // the display placeholder AND still returns a tuple;
2894 // `coordinates_or_none` returns `None`. A regression that
2895 // collapsed the two behaviors (either by dropping the gate
2896 // from the required form or by adding a `None` corner to the
2897 // defaulted form) would blur the axis pair's whole reason to
2898 // exist as two peer primitives.
2899 let mut p = Process::new("scratch", empty_spec());
2900 p.metadata.name = None;
2901 p.metadata.namespace = Some("prod".into());
2902 // Defaulted form: substitutes placeholder, no gate.
2903 assert_eq!(
2904 p.coordinates_or_defaults(),
2905 ("prod", Process::UNNAMED_PLACEHOLDER)
2906 );
2907 // Required form: gate fires, `None`.
2908 assert!(p.coordinates_or_none().is_none());
2909 }
2910
2911 #[test]
2912 fn coordinates_or_none_matches_pre_lift_reconciler_helper_shape() {
2913 // Byte-identical parity pin between the borrow + name-required
2914 // primitive here and the pre-lift `tatara-reconciler` helper
2915 // shapes — the exact 2-slot unwrap + gate chains each pre-lift
2916 // caller spelled by hand (`phase_machine::process_holds_any_claim`
2917 // spelled it as `unwrap_or("")` + `is_empty` early-return;
2918 // `phase_machine::handle_exiting`'s child-fan-out spelled it
2919 // as `unwrap_or_default()` + implicit no-op delete on the
2920 // empty API-path). Sweeps every corner every callsite plausibly
2921 // encounters (both slots present, namespace absent, name
2922 // absent + ns present, both absent). A regression that
2923 // inserted a normalization step at the primitive the pre-lift
2924 // chain does NOT apply — or vice versa — surfaces here rather
2925 // than as silent drift between the pre-lift consumer sites
2926 // and the ONE substrate owner they now route through.
2927 fn pre_lift_holds_any_claim(p: &Process) -> Option<(&str, &str)> {
2928 let ns = p.metadata.namespace.as_deref().unwrap_or("default");
2929 let name = p.metadata.name.as_deref().unwrap_or("");
2930 if name.is_empty() {
2931 return None;
2932 }
2933 Some((ns, name))
2934 }
2935 // Both present.
2936 let mut p = Process::new("api", empty_spec());
2937 p.metadata.namespace = Some("prod".into());
2938 assert_eq!(p.coordinates_or_none(), pre_lift_holds_any_claim(&p));
2939 // Namespace absent.
2940 let p = Process::new("api", empty_spec());
2941 assert_eq!(p.coordinates_or_none(), pre_lift_holds_any_claim(&p));
2942 // Name absent → both variants return `None` regardless of ns.
2943 let mut p = Process::new("api", empty_spec());
2944 p.metadata.name = None;
2945 p.metadata.namespace = Some("prod".into());
2946 assert_eq!(p.coordinates_or_none(), pre_lift_holds_any_claim(&p));
2947 // Both absent → still `None` on the name gate.
2948 let mut p = Process::new("api", empty_spec());
2949 p.metadata.name = None;
2950 p.metadata.namespace = None;
2951 assert_eq!(p.coordinates_or_none(), pre_lift_holds_any_claim(&p));
2952 }
2953
2954 #[test]
2955 fn coordinates_or_none_axis_order_matches_owned_coordinates_or_err_on_happy_path() {
2956 // Cross-primitive coherence pin at the sibling corner: when
2957 // BOTH slots are present, the borrow + name-required form
2958 // (this method) and the owned + name-required peer
2959 // (`owned_coordinates_or_err`) return the SAME `(ns, name)`
2960 // pair — the axis order is IDENTICAL and neither primitive
2961 // silently applies a normalization the other omits. A
2962 // regression that skewed one form's normalization would
2963 // surface here rather than as silent drift between the two
2964 // name-required corners of the primitive family.
2965 let mut p = Process::new("app", empty_spec());
2966 p.metadata.namespace = Some("infra".into());
2967 let (borrow_ns, borrow_name) = p.coordinates_or_none().unwrap();
2968 let (owned_ns, owned_name) = p.owned_coordinates_or_err().unwrap();
2969 assert_eq!(borrow_ns, owned_ns.as_str());
2970 assert_eq!(borrow_name, owned_name.as_str());
2971 }
2972
2973 #[test]
2974 fn coordinates_or_defaults_axis_order_matches_qualified_process_ref() {
2975 // Pins the load-bearing convention that the return-tuple
2976 // axis order is (namespace, name) — the exact positional
2977 // argument order the substrate's paired-composer primitive
2978 // `tatara_reconciler::ssapply::qualified_process_ref(ns,
2979 // name)` consumes. A regression that swapped the tuple
2980 // slots would silently misroute every annotation writer /
2981 // claim-arbiter row / owner-metadata seed built by feeding
2982 // this pair into the composer — every downstream `<ns>/
2983 // <name>` grep would suddenly see `<name>/<ns>`. The test
2984 // verifies the tuple's first slot is what a hand-authored
2985 // `.metadata.namespace.as_deref()...` produced pre-lift, and
2986 // the second slot is what `.metadata.name.as_deref()...`
2987 // produced.
2988 let mut p = Process::new("app", empty_spec());
2989 p.metadata.namespace = Some("infra".into());
2990 let (ns, name) = p.coordinates_or_defaults();
2991 assert_eq!(ns, "infra"); // NOT "app"
2992 assert_eq!(name, "app"); // NOT "infra"
2993 }
2994
2995 // ─── Process::annotation substrate pins ────────────────────────────
2996 //
2997 // Pins the borrow-form annotation-lookup primitive that owns the
2998 // 3-line `.metadata.annotations.as_ref().and_then(|m| m.get(KEY))`
2999 // chain three hand-authored sites restated by hand pre-lift:
3000 // `tatara-reconciler::signals::ingest` (SIGNAL),
3001 // `tatara-reconciler::phase_machine::released_from_annotation`
3002 // (RELEASED_FROM), and
3003 // `tatara-pool-reconciler::controller_pool::process_belongs_to_pool`
3004 // (POOL). Fail-before-pass-after granularity: a regression that
3005 // widened the missing-`annotations` corner (returning `Some("")`
3006 // instead of `None`), promoted a missing key to an error, dropped
3007 // the borrow-form return, or changed the two swallowed corners'
3008 // shared collapse to `None` surfaces here rather than as silent
3009 // drift at the three consumer sites.
3010 fn process_with_annotation(key: &str, value: &str) -> Process {
3011 let mut p = Process::new("some-proc", empty_spec());
3012 let mut anns = std::collections::BTreeMap::new();
3013 anns.insert(key.to_string(), value.to_string());
3014 p.metadata.annotations = Some(anns);
3015 p
3016 }
3017
3018 #[test]
3019 fn annotation_returns_none_when_metadata_annotations_is_none() {
3020 // Missing-`annotations` corner: a Process with no annotations
3021 // block at all returns `None` for every key. Peer to
3022 // `observed_flux_resources_returns_empty_slice_when_status_is_none`
3023 // on the status-projection axis; both primitives collapse the
3024 // outer `Option` corner rather than requiring each consumer
3025 // to spell the guard by hand.
3026 let mut p = Process::new("scratch", empty_spec());
3027 p.metadata.annotations = None;
3028 assert!(p.annotation("tatara.pleme.io/signal").is_none());
3029 assert!(p.annotation("tatara.pleme.io/pool").is_none());
3030 assert!(p.annotation("").is_none());
3031 }
3032
3033 #[test]
3034 fn annotation_returns_none_when_key_absent_from_populated_map() {
3035 // Missing-key corner: annotations block populated with OTHER
3036 // keys returns `None` for the queried key. Symmetric with the
3037 // missing-`annotations` corner — both corners collapse to the
3038 // same `None`, matching the pre-lift `.and_then(...)`
3039 // behavior every consumer relied on.
3040 let p = process_with_annotation("tatara.pleme.io/other", "value");
3041 assert!(p.annotation("tatara.pleme.io/signal").is_none());
3042 assert!(p.annotation("").is_none());
3043 }
3044
3045 #[test]
3046 fn annotation_returns_borrowed_slice_when_key_present() {
3047 // Happy path: annotations block populated + key present →
3048 // `Some(&str)` borrowed from the underlying `String` in the
3049 // map. A regression that returned an owned `String` (defeating
3050 // the primitive's role as a zero-copy projection) would
3051 // surface at the lifetime of the returned reference — the
3052 // `&str` outlives the borrow of `&p` here.
3053 let p = process_with_annotation("tatara.pleme.io/signal", "SIGHUP");
3054 assert_eq!(p.annotation("tatara.pleme.io/signal"), Some("SIGHUP"));
3055 }
3056
3057 #[test]
3058 fn annotation_returns_borrowed_empty_string_slice_when_value_is_empty() {
3059 // Edge corner between the missing-key `None` and the present-
3060 // key `Some("")` — a Process whose annotation is EXPLICITLY
3061 // set to an empty string returns `Some("")`, NOT `None`. A
3062 // regression that normalized the empty-string value to `None`
3063 // (a plausible "defensive" simplification) would silently
3064 // reshape the corner every callsite pre-lift kept distinct via
3065 // `.cloned().unwrap_or_default()` (which collapses BOTH to
3066 // `""`) or `.map(String::as_str)` (which keeps them distinct
3067 // as `None` vs `Some("")`).
3068 let p = process_with_annotation("tatara.pleme.io/signal", "");
3069 assert_eq!(p.annotation("tatara.pleme.io/signal"), Some(""));
3070 }
3071
3072 #[test]
3073 fn annotation_is_a_pure_projection() {
3074 // Purity pin — repeated calls return equal results and the
3075 // primitive does not mutate `self`. Peer to
3076 // `observed_flux_resources_is_a_pure_projection` on the
3077 // status-projection axis.
3078 let p = process_with_annotation("tatara.pleme.io/released-from", "Attested");
3079 let a = p.annotation("tatara.pleme.io/released-from");
3080 let b = p.annotation("tatara.pleme.io/released-from");
3081 assert_eq!(a, b);
3082 assert_eq!(a, Some("Attested"));
3083 }
3084
3085 #[test]
3086 fn annotation_matches_pre_lift_reconciler_chain_shape() {
3087 // Byte-identical parity pin between the borrow-form primitive
3088 // here and the pre-lift `tatara-reconciler` / `tatara-pool-
3089 // reconciler` chain shape — the exact 3-line
3090 // `.metadata.annotations.as_ref().and_then(|m| m.get(KEY))
3091 // .map(String::as_str)` incantation each pre-lift caller
3092 // spelled by hand (three variants of tail collapsed onto ONE
3093 // borrow-form primitive here; each caller reapplies its own
3094 // tail at its own site). Sweeps every corner (missing
3095 // annotations map, missing key, present key with value,
3096 // present key with empty value) so a regression that inserted
3097 // a normalization at the primitive the pre-lift chain does
3098 // NOT apply — or vice versa — surfaces here rather than as
3099 // silent drift between the ONE substrate owner and the three
3100 // consumer sites.
3101 fn pre_lift<'a>(p: &'a Process, key: &str) -> Option<&'a str> {
3102 p.metadata
3103 .annotations
3104 .as_ref()
3105 .and_then(|m| m.get(key))
3106 .map(String::as_str)
3107 }
3108 // Missing annotations map.
3109 let mut p = Process::new("x", empty_spec());
3110 p.metadata.annotations = None;
3111 assert_eq!(p.annotation("k"), pre_lift(&p, "k"));
3112 // Missing key in populated map.
3113 let p = process_with_annotation("other", "v");
3114 assert_eq!(p.annotation("k"), pre_lift(&p, "k"));
3115 // Present key with non-empty value.
3116 let p = process_with_annotation("k", "v");
3117 assert_eq!(p.annotation("k"), pre_lift(&p, "k"));
3118 // Present key with explicitly-empty value — the corner
3119 // `.cloned().unwrap_or_default()` collapses to `""` post-tail
3120 // but the primitive-level shape stays `Some("")`.
3121 let p = process_with_annotation("k", "");
3122 assert_eq!(p.annotation("k"), pre_lift(&p, "k"));
3123 }
3124
3125 #[test]
3126 fn annotation_composes_owned_tail_matching_pre_lift_signals_ingest() {
3127 // Pins the exact tail shape `tatara-reconciler::signals::
3128 // ingest` composed pre-lift: an `Option<String>` for the
3129 // downstream `let Some(raw) = raw else { ... }` guard.
3130 // Post-lift the callsite composes `.map(str::to_string)` at
3131 // its own site; this test pins the composition matches the
3132 // pre-lift `.cloned()` tail byte-for-byte on both corners the
3133 // consumer's downstream distinguishes (annotation present →
3134 // `Some(String)`; absent → `None`).
3135 let p = process_with_annotation("tatara.pleme.io/signal", "SIGUSR1");
3136 assert_eq!(
3137 p.annotation("tatara.pleme.io/signal").map(str::to_string),
3138 Some("SIGUSR1".to_string())
3139 );
3140 let mut q = Process::new("y", empty_spec());
3141 q.metadata.annotations = None;
3142 assert_eq!(
3143 q.annotation("tatara.pleme.io/signal").map(str::to_string),
3144 None
3145 );
3146 }
3147
3148 #[test]
3149 fn annotation_composes_default_tail_matching_pre_lift_released_from() {
3150 // Pins the exact tail shape
3151 // `tatara-reconciler::phase_machine::released_from_annotation`
3152 // composed pre-lift: a bare `String` via `.cloned()
3153 // .unwrap_or_default()` for the downstream
3154 // `match v.as_str()` dispatch. Post-lift the callsite matches
3155 // directly on `Option<&str>` (Some("Failed") vs _); this test
3156 // pins that the borrow-form primitive plus the `.unwrap_or("")`
3157 // fallback reproduces the pre-lift bare-string shape on both
3158 // corners.
3159 let p = process_with_annotation("tatara.pleme.io/released-from", "Failed");
3160 assert_eq!(
3161 p.annotation("tatara.pleme.io/released-from").unwrap_or(""),
3162 "Failed"
3163 );
3164 let mut q = Process::new("y", empty_spec());
3165 q.metadata.annotations = None;
3166 assert_eq!(
3167 q.annotation("tatara.pleme.io/released-from").unwrap_or(""),
3168 ""
3169 );
3170 }
3171
3172 #[test]
3173 fn annotation_composes_borrow_equality_tail_matching_pre_lift_pool() {
3174 // Pins the exact tail shape `tatara-pool-reconciler::
3175 // controller_pool::process_belongs_to_pool` composed pre-lift:
3176 // an `Option<&str>` compared with `== Some(pool_name)` for the
3177 // membership gate. Post-lift the callsite composes
3178 // `p.annotation(POOL) == Some(pool_name)` verbatim; this test
3179 // pins that the borrow-form primitive returns exactly the
3180 // shape the equality gate expects.
3181 let p = process_with_annotation("tatara.pleme.io/pool", "demo-pool");
3182 assert_eq!(
3183 p.annotation("tatara.pleme.io/pool") == Some("demo-pool"),
3184 true
3185 );
3186 assert_eq!(p.annotation("tatara.pleme.io/pool") == Some("other"), false);
3187 }
3188
3189 // ─── Process::uid_or_empty substrate pins ──────────────────────────
3190 //
3191 // Pins the borrow-form metadata-projection primitive on the
3192 // `metadata.uid` axis that owns the `.metadata.uid.as_deref()
3193 // .unwrap_or("")` chain the two hand-authored
3194 // `tatara-reconciler::render` sites (`render_routing` +
3195 // `render_export_jobs`) restated by hand pre-lift. Peer to the
3196 // sibling `namespace_or_default_*` + `name_or_placeholder_*` pin
3197 // families on the metadata-slot × fallback-shape axis; all three
3198 // primitives return borrows of an owned-metadata slot with a slot-
3199 // specific fallback baked in (`"default"` for namespace, `"unnamed"`
3200 // for name, `""` for uid — the load-bearing gate value for
3201 // `owner_references_json`'s `is_empty` check). Fail-before-pass-
3202 // after granularity: `uid_or_empty` did not exist pre-lift, so any
3203 // test invoking it fails to compile pre-lift and passes post-lift.
3204
3205 #[test]
3206 fn uid_or_empty_returns_empty_string_when_metadata_uid_is_none() {
3207 // Empty-slot corner pin: the primitive collapses the no-uid
3208 // case to `""`, matching the pre-lift `.as_deref().unwrap_or("")`
3209 // chain's `""` byte-identically at both render consumer sites.
3210 // Semantically corresponds to a Process pre-metadata (fixtured
3211 // in tests, or caught mid-Forking before the API server has
3212 // stamped a `uid`); the downstream `owner_references_json`
3213 // composer gates on this exact `""` sentinel to stamp
3214 // `metadata.ownerReferences: []` rather than emit an owner-ref
3215 // pointing at a placeholder uid.
3216 let mut p = Process::new("scratch", empty_spec());
3217 p.metadata.uid = None;
3218 assert_eq!(p.uid_or_empty(), "");
3219 }
3220
3221 #[test]
3222 fn uid_or_empty_returns_borrowed_str_when_slot_is_populated() {
3223 // Happy-path pin: with a populated `metadata.uid` slot, the
3224 // primitive returns a borrowed `&str` whose contents match the
3225 // persisted `String`. A regression that reshaped / normalized
3226 // / cross-cluster-stripped the uid without touching this pin
3227 // would surface here rather than as silent skew at the two
3228 // `owner_references_json(name, uid)` emitters on the SAME
3229 // Process.
3230 let mut p = Process::new("owned-proc", empty_spec());
3231 p.metadata.uid = Some("uid-abc-123".into());
3232 assert_eq!(p.uid_or_empty(), "uid-abc-123");
3233 }
3234
3235 #[test]
3236 fn uid_or_empty_returns_empty_string_when_slot_is_explicitly_empty_string() {
3237 // Corner between the missing-slot `None` and the explicitly-
3238 // empty-string `Some("")` — both collapse to `""` at the
3239 // primitive because the downstream gate at
3240 // `owner_references_json` treats `.is_empty()` uniformly (the
3241 // empty-slot posture is what the whole primitive family
3242 // encodes: "no admissible owner reference, stamp `[]`"). A
3243 // regression that discriminated the two corners (returning a
3244 // sentinel `"<none>"` for the missing slot but `""` for the
3245 // explicit slot) would break the composition with
3246 // `owner_references_json` at the exactly-two-corner gate.
3247 let mut p = Process::new("owned-proc", empty_spec());
3248 p.metadata.uid = Some(String::new());
3249 assert_eq!(p.uid_or_empty(), "");
3250 }
3251
3252 #[test]
3253 fn uid_or_empty_is_a_zero_copy_borrow_projection() {
3254 // Borrow-discipline pin: the returned `&str` borrows the
3255 // persisted `String`'s underlying byte buffer in place — NOT
3256 // a fresh allocation or a clone. A regression that switched
3257 // the projection to an owned `String` (via `.clone()` or a
3258 // `format!` wrap) would defeat the zero-copy contract the
3259 // lift's primary strict-widening delivers, and would surface
3260 // here via pointer-identity comparison.
3261 let mut p = Process::new("owned-proc", empty_spec());
3262 p.metadata.uid = Some("uid-borrow-pin".into());
3263 let slice = p.uid_or_empty();
3264 assert!(std::ptr::eq(
3265 slice.as_ptr(),
3266 p.metadata.uid.as_ref().unwrap().as_ptr()
3267 ));
3268 }
3269
3270 #[test]
3271 fn uid_or_empty_is_a_pure_projection() {
3272 // Purity pin — repeated calls return byte-identical slices
3273 // (same pointer, same length). A regression that introduced
3274 // state (a lazy-cached normalized slot, a first-call
3275 // canonicalization pass) would surface here rather than as
3276 // silent drift between the two render consumer sites on the
3277 // SAME Process within one render pass.
3278 let mut p = Process::new("owned-proc", empty_spec());
3279 p.metadata.uid = Some("uid-pure".into());
3280 let a = p.uid_or_empty();
3281 let b = p.uid_or_empty();
3282 assert!(std::ptr::eq(a.as_ptr(), b.as_ptr()));
3283 assert_eq!(a.len(), b.len());
3284 }
3285
3286 #[test]
3287 fn uid_or_empty_matches_pre_lift_render_chain_shape() {
3288 // Byte-identical parity pin between the borrow-form primitive
3289 // here and the pre-lift `tatara-reconciler::render` chain shape
3290 // — the exact `.metadata.uid.as_deref().unwrap_or("")`
3291 // incantation both `render_routing` (line 514) and
3292 // `render_export_jobs` (line 653) spelled by hand pre-lift.
3293 // Sweeps every corner (missing uid slot, populated uid slot,
3294 // explicitly-empty uid slot) so a regression that inserted a
3295 // normalization the pre-lift chain does NOT apply — or vice
3296 // versa — surfaces here rather than as silent drift between
3297 // the ONE substrate owner and the two consumer sites.
3298 fn pre_lift(p: &Process) -> &str {
3299 p.metadata.uid.as_deref().unwrap_or("")
3300 }
3301 // Missing slot.
3302 let mut p = Process::new("x", empty_spec());
3303 p.metadata.uid = None;
3304 assert_eq!(p.uid_or_empty(), pre_lift(&p));
3305 // Populated slot.
3306 let mut p = Process::new("x", empty_spec());
3307 p.metadata.uid = Some("uid-42".into());
3308 assert_eq!(p.uid_or_empty(), pre_lift(&p));
3309 // Explicitly-empty slot.
3310 let mut p = Process::new("x", empty_spec());
3311 p.metadata.uid = Some(String::new());
3312 assert_eq!(p.uid_or_empty(), pre_lift(&p));
3313 }
3314
3315 #[test]
3316 fn uid_or_empty_composes_with_owner_references_json_empty_gate() {
3317 // Cross-primitive composition pin — the empty-string sentinel
3318 // this primitive returns for the missing-uid corner is EXACTLY
3319 // the sentinel the sibling substrate composer
3320 // `owner_references_json(name, uid)` gates on to stamp
3321 // `metadata.ownerReferences: []`. A regression that changed
3322 // the sentinel at either end (this primitive returning
3323 // `"<none>"`, `owner_references_json` gating on `uid == "0"`
3324 // instead of `uid.is_empty()`) would break the composition
3325 // and surface here rather than as an operator-observed
3326 // orphan resource after apply.
3327 let mut p = Process::new("x", empty_spec());
3328 p.metadata.uid = None;
3329 let refs = crate::owner_references_json("some-name", p.uid_or_empty());
3330 assert!(
3331 refs.is_empty(),
3332 "empty-uid corner must produce empty owner-refs array"
3333 );
3334
3335 p.metadata.uid = Some("real-uid".into());
3336 let refs = crate::owner_references_json("some-name", p.uid_or_empty());
3337 assert_eq!(
3338 refs.len(),
3339 1,
3340 "populated-uid corner must produce one owner-ref entry"
3341 );
3342 }
3343
3344 // ─── Process::owned_name_or_empty substrate pins ─────────────────
3345 //
3346 // Pins the owned-form metadata-projection primitive on the
3347 // `metadata.name` axis that owns the
3348 // `.metadata.name.clone().unwrap_or_default()` chain the two hand-
3349 // authored `tatara-pool-reconciler::controller_pool` sites (the
3350 // `PoolMember` seed at line 68 + the `PoolMemberSnapshot` desired-
3351 // count seed at line 108) restated by hand pre-lift. Peer to the
3352 // sibling `uid_or_empty` pin family on the (return-form × fallback-
3353 // value) axis pair — `uid_or_empty` owns the BORROW + empty-sentinel
3354 // corner (`&str` for owner-ref emitters gating on `.is_empty()`);
3355 // this method owns the OWNED + empty-sentinel corner (`String` for
3356 // struct-literal / HashMap-key row-builder consumers whose
3357 // downstream fills a `String` field with the load-bearing `""`
3358 // sentinel). Fail-before-pass-after granularity: `owned_name_or_empty`
3359 // did not exist pre-lift, so any test invoking it fails to compile
3360 // pre-lift and passes post-lift.
3361
3362 #[test]
3363 fn owned_name_or_empty_returns_empty_string_when_metadata_name_is_none() {
3364 // Empty-slot corner pin: the primitive collapses the no-name
3365 // case to `String::new()`, matching the pre-lift
3366 // `.clone().unwrap_or_default()` chain's empty `String` byte-
3367 // identically at both pool-reconciler consumer sites.
3368 // Semantically corresponds to a Process pre-metadata-name (test
3369 // fixture, dynamic API response pre-name-resolution); the
3370 // downstream `PoolMember { process_name, .. }` slot then holds
3371 // `""` as a stable "no name to key by" signal rather than a
3372 // display placeholder that would silently alias distinct rows.
3373 let mut p = Process::new("scratch", empty_spec());
3374 p.metadata.name = None;
3375 assert_eq!(p.owned_name_or_empty(), String::new());
3376 }
3377
3378 #[test]
3379 fn owned_name_or_empty_returns_owned_string_when_slot_is_populated() {
3380 // Happy-path pin: with a populated `metadata.name` slot, the
3381 // primitive returns an owned `String` whose contents match the
3382 // persisted `String`. A regression that reshaped / normalized
3383 // / case-folded the name without touching this pin would surface
3384 // here rather than as silent skew between the two pool-member
3385 // seeds keying on the SAME Process's name.
3386 let p = Process::new("api", empty_spec());
3387 assert_eq!(p.owned_name_or_empty(), "api");
3388 }
3389
3390 #[test]
3391 fn owned_name_or_empty_returns_empty_string_when_slot_is_explicitly_empty_string() {
3392 // Corner between the missing-slot `None` and the explicitly-
3393 // empty-string `Some(String::new())` — both collapse to `""` at
3394 // the primitive because the downstream pool-member consumers
3395 // treat both corners uniformly (no name, no key). A regression
3396 // that discriminated the two corners (returning a sentinel
3397 // `"<none>"` for the missing slot but `""` for the explicit
3398 // slot) would break `String::is_empty` gating at the row-builder
3399 // callsites without moving this pin.
3400 let mut p = Process::new("scratch", empty_spec());
3401 p.metadata.name = Some(String::new());
3402 assert_eq!(p.owned_name_or_empty(), String::new());
3403 assert!(p.owned_name_or_empty().is_empty());
3404 }
3405
3406 #[test]
3407 fn owned_name_or_empty_is_a_pure_projection() {
3408 // Purity pin — repeated calls return byte-identical `String`
3409 // values. A regression that introduced state (a lazy-cached
3410 // normalized slot, a first-call canonicalization pass) would
3411 // surface here rather than as silent drift between the pool-
3412 // member seed and the desired-count snapshot seed on the SAME
3413 // Process within one reconcile pass.
3414 let p = Process::new("stable-name", empty_spec());
3415 assert_eq!(p.owned_name_or_empty(), p.owned_name_or_empty());
3416 }
3417
3418 #[test]
3419 fn owned_name_or_empty_returns_independent_owned_string() {
3420 // Owned-discipline pin: the returned `String` is an independent
3421 // allocation the caller may consume, `.push_str` into, or move
3422 // into a struct-literal `process_name: String` slot — NOT a
3423 // shared reference into `metadata.name`. A regression that
3424 // switched the projection to a `Cow`-shaped variant or a slice-
3425 // form projection would defeat the owned-form contract the two
3426 // pool-reconciler struct-literal consumers depend on (a slice
3427 // cannot land in a `process_name: String` slot without a re-
3428 // clone), and would surface here at compile time via the mutate-
3429 // in-place test below.
3430 let p = Process::new("owned-proc", empty_spec());
3431 let mut owned = p.owned_name_or_empty();
3432 owned.push_str("-mutated");
3433 assert_eq!(owned, "owned-proc-mutated");
3434 // The Process's own slot is unchanged — the returned String
3435 // owns its own byte buffer, disjoint from `metadata.name`.
3436 assert_eq!(p.metadata.name.as_deref(), Some("owned-proc"));
3437 }
3438
3439 #[test]
3440 fn owned_name_or_empty_matches_pre_lift_controller_pool_chain_shape() {
3441 // Byte-identical parity pin between the owned-form primitive
3442 // here and the pre-lift `tatara-pool-reconciler::controller_pool`
3443 // chain shape — the exact `.metadata.name.clone().unwrap_or_default()`
3444 // incantation both `PoolMember` seed (line 68) and
3445 // `PoolMemberSnapshot` seed (line 108) spelled by hand pre-lift.
3446 // Sweeps every corner (missing name slot, populated name slot,
3447 // explicitly-empty name slot) so a regression that inserted a
3448 // normalization the pre-lift chain does NOT apply — or vice
3449 // versa — surfaces here rather than as silent drift between
3450 // the ONE substrate owner and the two consumer sites.
3451 fn pre_lift(p: &Process) -> String {
3452 p.metadata.name.clone().unwrap_or_default()
3453 }
3454 // Missing slot.
3455 let mut p = Process::new("x", empty_spec());
3456 p.metadata.name = None;
3457 assert_eq!(p.owned_name_or_empty(), pre_lift(&p));
3458 // Populated slot.
3459 let p = Process::new("real-name", empty_spec());
3460 assert_eq!(p.owned_name_or_empty(), pre_lift(&p));
3461 // Explicitly-empty slot.
3462 let mut p = Process::new("x", empty_spec());
3463 p.metadata.name = Some(String::new());
3464 assert_eq!(p.owned_name_or_empty(), pre_lift(&p));
3465 }
3466
3467 #[test]
3468 fn owned_name_or_empty_shares_empty_sentinel_with_uid_or_empty() {
3469 // Cross-primitive coherence pin — the empty-string fallback this
3470 // primitive returns for the missing-name corner is the SAME
3471 // sentinel the sibling borrow-form primitive `uid_or_empty`
3472 // returns for the missing-uid corner. Both partition the OWNED
3473 // × BORROW corner of the metadata-slot family on identical
3474 // fallback semantics ("the slot is unset"), so a consumer that
3475 // switches between them based on downstream ownership
3476 // requirements never sees a different missing-slot spelling as
3477 // a side effect. A regression that drifted either sentinel
3478 // (this primitive returning `"<unnamed>"`, `uid_or_empty`
3479 // returning `"<none>"`) would break the partition and surface
3480 // here rather than as silent shape drift across the family.
3481 let mut p = Process::new("scratch", empty_spec());
3482 p.metadata.name = None;
3483 p.metadata.uid = None;
3484 assert_eq!(p.owned_name_or_empty(), p.uid_or_empty());
3485 assert!(p.owned_name_or_empty().is_empty());
3486 assert!(p.uid_or_empty().is_empty());
3487 }
3488
3489 #[test]
3490 fn owned_name_or_empty_returns_distinct_fallback_from_name_or_placeholder() {
3491 // Axis-partition pin — the owned + empty-sentinel primitive here
3492 // and the borrow + display-placeholder primitive
3493 // [`Self::name_or_placeholder`] MUST return distinct fallback
3494 // values on the missing-name corner. The distinction is load-
3495 // bearing: `owned_name_or_empty` is for HashMap-key / row-builder
3496 // consumers that need distinct keys for missing-name Processes
3497 // (empty string collides only with other missing-name rows,
3498 // never with a real "unnamed" Process); `name_or_placeholder`
3499 // is for log-line / display consumers that render the
3500 // `"unnamed"` word to operators. A regression that unified the
3501 // two fallbacks (either primitive returning the other's
3502 // sentinel) would silently collapse missing-name pool members
3503 // into a display-string key or expose the empty sentinel to
3504 // operator log lines. This pin catches either drift.
3505 let mut p = Process::new("scratch", empty_spec());
3506 p.metadata.name = None;
3507 assert_eq!(p.owned_name_or_empty(), "");
3508 assert_eq!(p.name_or_placeholder(), Process::UNNAMED_PLACEHOLDER);
3509 assert_ne!(p.owned_name_or_empty(), p.name_or_placeholder());
3510 }
3511
3512 // ─── Process::declared_parent_pid substrate pins ─────────────────
3513 //
3514 // Pins the borrow-form spec-projection primitive on the declared
3515 // parent-PID axis that owns the `.spec.identity.parent.as_deref()`
3516 // chain the two hand-authored `tatara-reconciler::phase_machine`
3517 // sites (`handle_forking` ALLOCATE-PID composer + `handle_exiting`
3518 // SIGTERM-cascade child-fan-out filter) restated by hand pre-lift.
3519 // Peer to the sibling `observed_pid_*` pin family on the (spec-
3520 // declared × status-observed) axis pair; both compose the same
3521 // borrow-form `Option<&str>` return-shape skeleton on distinct
3522 // slots (`spec.identity.parent` vs. `status.pid`). Fail-before-
3523 // pass-after granularity: `declared_parent_pid` did not exist
3524 // pre-lift, so any test invoking it fails to compile pre-lift and
3525 // passes post-lift.
3526 fn process_with_declared_parent(parent: Option<&str>) -> Process {
3527 let mut spec = empty_spec();
3528 spec.identity.parent = parent.map(str::to_string);
3529 Process::new("child-proc", spec)
3530 }
3531
3532 #[test]
3533 fn declared_parent_pid_returns_none_when_slot_is_none() {
3534 // Empty-slot corner pin: the primitive collapses the no-
3535 // parent case to `None`, matching the pre-lift `.as_deref()`
3536 // chain's `None` byte-identically at both reconciler consumer
3537 // sites. Semantically corresponds to a Process authored at
3538 // cluster init (PID 1) with no upstream parent — the
3539 // ALLOCATE-PID composer feeds `None` into `pid::allocate_pid`
3540 // to signal "no prefix", and the SIGTERM cascade's filter
3541 // never matches such a Process because a child's declared
3542 // parent can never equal `Some(pid)` when the slot is `None`.
3543 let p = process_with_declared_parent(None);
3544 assert!(p.declared_parent_pid().is_none());
3545 }
3546
3547 #[test]
3548 fn declared_parent_pid_returns_borrowed_str_when_slot_is_populated() {
3549 // Happy-path pin: with a populated `spec.identity.parent`
3550 // slot, the primitive returns a borrowed `&str` whose
3551 // contents match the persisted `String`. A regression that
3552 // filtered / reshaped / canonicalized the string would
3553 // surface here rather than as silent skew at the child-fan-
3554 // out filter's `.declared_parent_pid() == Some(pid)`
3555 // equality check on the SAME parent-child pair.
3556 let p = process_with_declared_parent(Some("seph.1"));
3557 assert_eq!(p.declared_parent_pid(), Some("seph.1"));
3558 }
3559
3560 #[test]
3561 fn declared_parent_pid_is_a_zero_copy_borrow_projection() {
3562 // Borrow-discipline pin: the returned `&str` borrows the
3563 // persisted `String`'s underlying byte buffer in place —
3564 // NOT a fresh allocation or a clone. A regression that
3565 // switched the projection to an owned `String` (via
3566 // `.clone()` or `.to_owned()`) would defeat the zero-copy
3567 // contract the lift's primary strict-widening delivers.
3568 // The `handle_exiting` cascade filter runs per candidate
3569 // child across the cluster-wide Process list; a per-row
3570 // `String::clone` would allocate one heap block per non-
3571 // matching row, so the borrow-form primitive is load-
3572 // bearing for large clusters. Peer to the sibling
3573 // `observed_pid_is_a_zero_copy_borrow_projection` pin on
3574 // the status-observed side of the axis pair.
3575 let p = process_with_declared_parent(Some("seph.1"));
3576 let borrowed = p.declared_parent_pid().expect("populated slot");
3577 let persisted = p.spec.identity.parent.as_ref().unwrap();
3578 assert!(std::ptr::eq(borrowed.as_ptr(), persisted.as_ptr()));
3579 }
3580
3581 #[test]
3582 fn declared_parent_pid_is_a_pure_projection() {
3583 // Purity pin: calling the projection twice on the same
3584 // `Process` returns byte-identical `&str`s (same pointer,
3585 // same length). A regression that introduced state — a
3586 // lazy-cached slice materialized on first call, a
3587 // normalization step that ran once and cached — would
3588 // surface here rather than as silent drift between the
3589 // ALLOCATE-PID composer and the SIGTERM cascade's child-
3590 // fan-out filter within one reconcile pass.
3591 let p = process_with_declared_parent(Some("seph.1.3"));
3592 let a = p.declared_parent_pid().expect("populated slot");
3593 let b = p.declared_parent_pid().expect("populated slot");
3594 assert!(std::ptr::eq(a.as_ptr(), b.as_ptr()));
3595 assert_eq!(a.len(), b.len());
3596 }
3597
3598 #[test]
3599 fn declared_parent_pid_matches_pre_lift_reconciler_chain_shape() {
3600 // Byte-identical parity pin between the borrow-form primitive
3601 // here and the pre-lift `tatara-reconciler::phase_machine`
3602 // `.spec.identity.parent.as_deref()` chain shape. Sweeps
3603 // every corner every callsite plausibly encounters (empty
3604 // slot, populated with a hierarchical PID). A regression
3605 // that inserted a normalization step at the primitive the
3606 // pre-lift chain does NOT apply — or vice versa — surfaces
3607 // here rather than as silent drift between the pre-lift
3608 // consumer sites and the ONE substrate owner they now route
3609 // through. Peer to
3610 // `observed_pid_matches_pre_lift_reconciler_chain_shape` on
3611 // the sibling axis's borrow-form primitive.
3612 fn pre_lift(p: &Process) -> Option<&str> {
3613 p.spec.identity.parent.as_deref()
3614 }
3615 // Empty slot.
3616 let p = process_with_declared_parent(None);
3617 assert_eq!(p.declared_parent_pid(), pre_lift(&p));
3618 // Populated with a hierarchical PID.
3619 let p = process_with_declared_parent(Some("seph.1"));
3620 assert_eq!(p.declared_parent_pid(), pre_lift(&p));
3621 // Populated with a deeper hierarchical PID.
3622 let p = process_with_declared_parent(Some("seph.1.7.42"));
3623 assert_eq!(p.declared_parent_pid(), pre_lift(&p));
3624 }
3625
3626 #[test]
3627 fn declared_parent_pid_preserves_hierarchical_pid_format() {
3628 // Format-preservation pin: the hierarchical PID path
3629 // (dotted-segment form `seph.1.7`, matching the ported
3630 // `convergence-controller/src/identity.rs` scheme) reaches
3631 // the caller with segments and separators byte-identical
3632 // to the persisted `String`. A regression that inserted a
3633 // canonicalization pass (a segment-count validator, a
3634 // separator swap `.` → `/`, a leading/trailing whitespace
3635 // trim) would silently misroute the SIGTERM cascade's
3636 // `declared_parent_pid() == Some(pid)` comparator against
3637 // children whose `parent` field was authored in the ported
3638 // scheme's exact form — the SAME children the observed_pid
3639 // primitive is pinned to match on the other side of the
3640 // axis pair.
3641 for parent in ["seph", "seph.1", "seph.1.7", "seph.1.7.42"] {
3642 let p = process_with_declared_parent(Some(parent));
3643 assert_eq!(p.declared_parent_pid(), Some(parent));
3644 }
3645 }
3646
3647 #[test]
3648 fn declared_parent_pid_composes_with_observed_pid_for_child_fanout_filter() {
3649 // Cross-axis coherence pin against the sibling
3650 // [`Self::observed_pid`] on the (spec-declared × status-
3651 // observed) axis pair: a child's `.declared_parent_pid()`
3652 // and its parent's `.observed_pid()` compose through the
3653 // SAME borrow-form `Option<&str>` skeleton so the
3654 // `handle_exiting` cascade filter's equality gate holds
3655 // structurally. A regression that skewed EITHER primitive's
3656 // return-form (return-shape, borrow discipline, empty-slot
3657 // collapse) would silently misroute every SIGTERM cascade
3658 // on the parent-child pair. This pin re-reads both primitives
3659 // at test time so the composition holds iff both live paths
3660 // are the current implementation.
3661 // Parent Process: has an observed PID.
3662 let mut parent = Process::new("parent-proc", empty_spec());
3663 parent.status = Some(ProcessStatus {
3664 pid: Some("seph.1".to_string()),
3665 ..Default::default()
3666 });
3667 // Child Process: declared parent matches parent's observed PID.
3668 let child = process_with_declared_parent(Some("seph.1"));
3669 // The `handle_exiting` filter's equality gate:
3670 // `child.declared_parent_pid() == Some(parent.observed_pid()?)`.
3671 let parent_pid = parent.observed_pid().expect("parent has PID");
3672 assert_eq!(child.declared_parent_pid(), Some(parent_pid));
3673 // Sibling Process with an unrelated declared parent must NOT
3674 // match the same parent — pins that the filter's SKIP branch
3675 // holds on the other side of the axis pair.
3676 let sibling = process_with_declared_parent(Some("seph.2"));
3677 assert_ne!(sibling.declared_parent_pid(), Some(parent_pid));
3678 }
3679
3680 // ─── Process::declared_name_override substrate pins ──────────────
3681 //
3682 // Pins the borrow-form spec-projection primitive on the declared
3683 // name-override sub-axis of the declared-identity axis that owns
3684 // the `.spec.identity.name_override.as_deref()` chain the two
3685 // hand-authored `tatara-reconciler::phase_machine` sites
3686 // (`handle_pending` DECLARE composer + `handle_forking` ALLOCATE-
3687 // PID rehydration branch) restated by hand pre-lift. Peer to the
3688 // sibling `declared_parent_pid_*` pin family on the (parent ×
3689 // name-override) sub-axis pair; both compose the same borrow-form
3690 // `Option<&str>` return-shape skeleton on distinct slots
3691 // (`spec.identity.name_override` vs `spec.identity.parent`).
3692 // Fail-before-pass-after granularity: `declared_name_override`
3693 // did not exist pre-lift, so any test invoking it fails to
3694 // compile pre-lift and passes post-lift.
3695 fn process_with_declared_name_override(name_override: Option<&str>) -> Process {
3696 let mut spec = empty_spec();
3697 spec.identity.name_override = name_override.map(str::to_string);
3698 Process::new("some-proc", spec)
3699 }
3700
3701 #[test]
3702 fn declared_name_override_returns_none_when_slot_is_none() {
3703 // Empty-slot corner pin: the primitive collapses the no-
3704 // override case to `None`, matching the pre-lift `.as_deref()`
3705 // chain's `None` byte-identically at both reconciler consumer
3706 // sites. Semantically corresponds to a Process authored
3707 // WITHOUT the human-name-override escape hatch — the default;
3708 // `derive_identity` then computes the name from the content
3709 // hash and stamps `name_override: false` on the resulting
3710 // [`Identity`].
3711 let p = process_with_declared_name_override(None);
3712 assert!(p.declared_name_override().is_none());
3713 }
3714
3715 #[test]
3716 fn declared_name_override_returns_borrowed_str_when_slot_is_populated() {
3717 // Happy-path pin: with a populated `spec.identity
3718 // .name_override` slot, the primitive returns a borrowed
3719 // `&str` whose contents match the persisted `String`. A
3720 // regression that filtered / reshaped / canonicalized the
3721 // string at the primitive (as opposed to inside
3722 // `derive_identity`, where the trim/empty-filter lives today)
3723 // would surface here rather than as silent skew between the
3724 // DECLARE composer and the ALLOCATE-PID rehydration branch on
3725 // the SAME Process spec.
3726 let p = process_with_declared_name_override(Some("observability-stack"));
3727 assert_eq!(p.declared_name_override(), Some("observability-stack"));
3728 }
3729
3730 #[test]
3731 fn declared_name_override_is_a_zero_copy_borrow_projection() {
3732 // Borrow-discipline pin: the returned `&str` borrows the
3733 // persisted `String`'s underlying byte buffer in place —
3734 // NOT a fresh allocation or a clone. Peer to the sibling
3735 // `declared_parent_pid_is_a_zero_copy_borrow_projection` pin
3736 // on the other side of the (parent × name-override) sub-axis
3737 // pair; the borrow discipline holds structurally on BOTH
3738 // sub-axes so a future `declared_identity` composite that
3739 // returns both halves together can compose them without
3740 // dropping into an owning form.
3741 let p = process_with_declared_name_override(Some("observability-stack"));
3742 let borrowed = p.declared_name_override().expect("populated slot");
3743 let persisted = p.spec.identity.name_override.as_ref().unwrap();
3744 assert!(std::ptr::eq(borrowed.as_ptr(), persisted.as_ptr()));
3745 }
3746
3747 #[test]
3748 fn declared_name_override_is_a_pure_projection() {
3749 // Purity pin: calling the projection twice on the same
3750 // `Process` returns byte-identical `&str`s (same pointer,
3751 // same length). A regression that introduced state — a
3752 // lazy-cached slice materialized on first call, a
3753 // normalization step that ran once and cached — would
3754 // surface here rather than as silent drift between the
3755 // DECLARE composer and the ALLOCATE-PID rehydration branch
3756 // within one reconcile pass.
3757 let p = process_with_declared_name_override(Some("gateway-primary"));
3758 let a = p.declared_name_override().expect("populated slot");
3759 let b = p.declared_name_override().expect("populated slot");
3760 assert!(std::ptr::eq(a.as_ptr(), b.as_ptr()));
3761 assert_eq!(a.len(), b.len());
3762 }
3763
3764 #[test]
3765 fn declared_name_override_matches_pre_lift_reconciler_chain_shape() {
3766 // Byte-identical parity pin between the borrow-form primitive
3767 // here and the pre-lift `tatara-reconciler::phase_machine`
3768 // `.spec.identity.name_override.as_deref()` chain shape.
3769 // Sweeps every corner every callsite plausibly encounters
3770 // (empty slot, populated with a bare name, populated with a
3771 // whitespace-containing name that `derive_identity`'s
3772 // internal trim would collapse, populated with an explicitly
3773 // empty string that `derive_identity`'s internal
3774 // `!s.is_empty()` filter would reject). A regression that
3775 // inserted a normalization step at the primitive the pre-
3776 // lift chain does NOT apply — or vice versa — surfaces here
3777 // rather than as silent drift between the pre-lift consumer
3778 // sites and the ONE substrate owner they now route through.
3779 // Peer to
3780 // `declared_parent_pid_matches_pre_lift_reconciler_chain_shape`
3781 // on the sibling sub-axis's borrow-form primitive.
3782 fn pre_lift(p: &Process) -> Option<&str> {
3783 p.spec.identity.name_override.as_deref()
3784 }
3785 // Empty slot.
3786 let p = process_with_declared_name_override(None);
3787 assert_eq!(p.declared_name_override(), pre_lift(&p));
3788 // Populated with a bare name.
3789 let p = process_with_declared_name_override(Some("observability-stack"));
3790 assert_eq!(p.declared_name_override(), pre_lift(&p));
3791 // Populated with a whitespace-containing name.
3792 let p = process_with_declared_name_override(Some(" observability-stack "));
3793 assert_eq!(p.declared_name_override(), pre_lift(&p));
3794 // Populated with an explicitly empty string. Distinct from
3795 // the missing-slot `None` corner both at the primitive here
3796 // and at the pre-lift chain (the trim/filter that collapses
3797 // these two into the same `false`-branched
3798 // `Identity { name_override: false, .. }` lives INSIDE
3799 // `derive_identity`, NOT at the borrow site) — the primitive
3800 // MUST preserve the distinction so a future lift of the trim/
3801 // filter OUT of `derive_identity` INTO the primitive is a
3802 // conscious substrate change, not a silent one.
3803 let p = process_with_declared_name_override(Some(""));
3804 assert_eq!(p.declared_name_override(), pre_lift(&p));
3805 }
3806
3807 #[test]
3808 fn declared_name_override_preserves_raw_slot_verbatim() {
3809 // Invariance-under-`derive_identity`-normalization pin: the
3810 // primitive returns the slot's raw byte contents verbatim —
3811 // no trim, no empty-string filter, no case fold, no
3812 // normalization of any kind. `derive_identity` internally
3813 // applies `.map(str::trim).filter(|s| !s.is_empty())` before
3814 // dispatching on `Some(non_empty)` vs `None | Some(empty |
3815 // whitespace)`, but that transform lives IN `derive_identity`,
3816 // NOT at the borrow site. A regression that pulled the trim/
3817 // filter forward INTO the primitive would silently collapse
3818 // three currently-distinct corners at the borrow site (bare
3819 // populated → `Some(name)`; whitespace-only → `Some(" ")`;
3820 // empty → `Some("")`) into two (bare → `Some(name)`; the
3821 // other two → `None`). That collapse might be an intentional
3822 // substrate change some future run wants to make; if so, it
3823 // lands as a conscious edit here (with this pin updated in
3824 // the same commit) rather than as silent behavior drift.
3825 for value in ["bare", " padded ", "\ttabs\t", " ", ""] {
3826 let p = process_with_declared_name_override(Some(value));
3827 assert_eq!(
3828 p.declared_name_override(),
3829 Some(value),
3830 "declared_name_override must preserve raw slot verbatim for value {value:?}"
3831 );
3832 }
3833 }
3834
3835 #[test]
3836 fn declared_name_override_composes_with_derive_identity_call_shape() {
3837 // Cross-primitive coherence pin against the [`derive_identity`]
3838 // consumer: the two live `tatara-reconciler::phase_machine`
3839 // callsites feed `p.declared_name_override()` as the second
3840 // positional argument to `derive_identity(&p.spec, …)`. This
3841 // pin exercises that exact call shape at test time so a
3842 // regression that skewed the primitive's return-form (return-
3843 // shape, borrow discipline, empty-slot collapse) surfaces
3844 // here as a shape mismatch at the [`derive_identity`] call
3845 // site rather than as silent operator-facing skew between the
3846 // DECLARE composer and the ALLOCATE-PID rehydration branch.
3847 // Populated with a bare non-empty name: `derive_identity`
3848 // dispatches on `Some(non_empty)` and stamps
3849 // `name_override: true` on the resulting [`Identity`], with
3850 // the resulting `.name` equal to the raw slot value.
3851 let p = process_with_declared_name_override(Some("gateway-primary"));
3852 let id = crate::identity::derive_identity(&p.spec, p.declared_name_override());
3853 assert!(id.name_override);
3854 assert_eq!(id.name, "gateway-primary");
3855 // Empty slot: `derive_identity` dispatches on `None` and
3856 // stamps `name_override: false` on the resulting [`Identity`],
3857 // with the resulting `.name` derived from the content hash
3858 // (NOT equal to any operator-authored slot value).
3859 let p = process_with_declared_name_override(None);
3860 let id = crate::identity::derive_identity(&p.spec, p.declared_name_override());
3861 assert!(!id.name_override);
3862 }
3863
3864 // ─── Process::observed_flux_resources substrate pins ───────────────
3865 //
3866 // Pins the borrow-form status-projection primitive that owns the
3867 // 5-line `.status.as_ref().map(|s| s.flux_resources.clone())
3868 // .unwrap_or_default()` chain the two hand-authored
3869 // `tatara-reconciler::phase_machine` sites (`handle_running` +
3870 // `handle_attested`) restated by hand pre-lift. Fail-before-pass-
3871 // after granularity: a regression that widened the missing-`status`
3872 // corner, dropped the slot, or drifted the borrow discipline
3873 // surfaces here rather than as silent operator-facing skew between
3874 // the VERIFY-phase readiness probe and the ATTEST-heartbeat drift
3875 // detector.
3876
3877 fn sample_flux_ref(name: &str) -> FluxResourceRef {
3878 // Distinct slot values so a swap between adjacent tuple
3879 // positions surfaces as an equality failure at the assertion
3880 // site — a slot-inversion regression cannot masquerade as
3881 // identity by accident. Peer to the sibling
3882 // `tatara_process::status::tests::sample_flux_ref` discipline
3883 // on the fetch-coords axis. Routes through the ONE substrate
3884 // composer [`FluxResourceRef::pending`] — the 4-slot pre-
3885 // observation-shape composer that owns the workspace-wide
3886 // `FluxResourceRef { …, ready: false, message: None,
3887 // last_check: None }` fixture literal.
3888 FluxResourceRef::pending(
3889 "kustomize.toolkit.fluxcd.io/v1",
3890 "Kustomization",
3891 name,
3892 "flux-system",
3893 )
3894 }
3895
3896 fn process_with_flux_resources(refs: Vec<FluxResourceRef>) -> Process {
3897 let mut p = Process::new("api-gateway", empty_spec());
3898 p.metadata.namespace = Some("prod".into());
3899 let mut status = ProcessStatus::default();
3900 status.flux_resources = refs;
3901 p.status = Some(status);
3902 p
3903 }
3904
3905 #[test]
3906 fn observed_flux_resources_returns_empty_slice_when_status_is_none() {
3907 // Missing-`status` corner pin: the primitive collapses the
3908 // no-status case to `&[]` so downstream `.is_empty()` /
3909 // `.len()` / iteration behave identically on a `Process`
3910 // whose status field is `None` and on one whose status
3911 // carries an empty `flux_resources` slot. Matches the
3912 // pre-lift `.unwrap_or_default()`'s empty-`Vec` corner
3913 // byte-identically at every reconciler consumer's downstream
3914 // shape.
3915 let mut p = Process::new("api", empty_spec());
3916 p.status = None;
3917 assert!(p.observed_flux_resources().is_empty());
3918 assert_eq!(p.observed_flux_resources().len(), 0);
3919 }
3920
3921 #[test]
3922 fn observed_flux_resources_returns_empty_slice_when_flux_resources_is_empty() {
3923 // Zero-refs-under-populated-status corner pin: the primitive
3924 // returns an empty slice, matching the missing-`status`
3925 // corner byte-identically. A regression that treated the two
3926 // corners differently (a `None`-vs-empty signal that
3927 // downstream consumers could grep on) would silently promote
3928 // an internal representation detail (whether the reconciler
3929 // has ever written a status subresource) into observable
3930 // behavior.
3931 let p = process_with_flux_resources(vec![]);
3932 assert!(p.observed_flux_resources().is_empty());
3933 assert_eq!(p.observed_flux_resources().len(), 0);
3934 }
3935
3936 #[test]
3937 fn observed_flux_resources_returns_slice_of_persisted_vec() {
3938 // Happy-path pin: with a populated `status.flux_resources`
3939 // slot, the primitive returns a borrowed slice whose length
3940 // and per-element identity match the persisted vector. A
3941 // regression that filtered / reshaped / deduplicated the
3942 // slice would surface here rather than as silent skew at the
3943 // downstream fetch consumers.
3944 let refs = vec![
3945 sample_flux_ref("observability-stack"),
3946 sample_flux_ref("gateway"),
3947 ];
3948 let p = process_with_flux_resources(refs.clone());
3949 let observed = p.observed_flux_resources();
3950 assert_eq!(observed.len(), 2);
3951 assert_eq!(observed[0].name, "observability-stack");
3952 assert_eq!(observed[1].name, "gateway");
3953 }
3954
3955 #[test]
3956 fn observed_flux_resources_is_a_zero_copy_borrow_projection() {
3957 // Borrow-discipline pin: the returned slice borrows the
3958 // persisted `Vec<FluxResourceRef>` in place — NOT a fresh
3959 // allocation or a clone. A regression that switched the
3960 // projection to owned refs (via `.clone()` or `.to_vec()`)
3961 // would defeat the zero-copy contract the lift's primary
3962 // strict-widening delivers (the pre-lift 5-line chain
3963 // eagerly cloned the whole vector per reconcile pass; the
3964 // post-lift primitive borrows). Peer to the sibling
3965 // `flux_resource_ref_fetch_coords_returns_borrows_of_owned_slots`
3966 // pin on the per-ref borrow-projection axis.
3967 let refs = vec![sample_flux_ref("observability-stack")];
3968 let p = process_with_flux_resources(refs);
3969 let observed = p.observed_flux_resources();
3970 let persisted = &p.status.as_ref().unwrap().flux_resources;
3971 assert!(std::ptr::eq(observed.as_ptr(), persisted.as_ptr()));
3972 }
3973
3974 #[test]
3975 fn observed_flux_resources_is_a_pure_projection() {
3976 // Purity pin: calling the projection twice on the same
3977 // `Process` returns byte-identical slices (same pointer,
3978 // same length). A regression that introduced state — a
3979 // lazy-cached slice materialized on first call, a
3980 // normalization step that ran once and cached — would
3981 // surface here rather than as silent drift between the
3982 // VERIFY-phase and ATTEST-heartbeat consumers on the SAME
3983 // `Process` within one reconcile pass.
3984 let refs = vec![sample_flux_ref("observability-stack")];
3985 let p = process_with_flux_resources(refs);
3986 let a = p.observed_flux_resources();
3987 let b = p.observed_flux_resources();
3988 assert!(std::ptr::eq(a.as_ptr(), b.as_ptr()));
3989 assert_eq!(a.len(), b.len());
3990 }
3991
3992 #[test]
3993 fn observed_flux_resources_matches_pre_lift_reconciler_chain_shape() {
3994 // Byte-identical parity pin between the borrow-form primitive
3995 // here and the pre-lift `tatara-reconciler::phase_machine`
3996 // 5-line chain shape. Sweeps every corner every callsite
3997 // plausibly encounters (missing status, empty flux_resources,
3998 // populated flux_resources with one ref, populated with
3999 // multiple refs). A regression that inserted a normalization
4000 // step at the primitive the pre-lift chain does NOT apply —
4001 // or vice versa — surfaces here rather than as silent drift
4002 // between the pre-lift consumer sites and the ONE substrate
4003 // owner they now route through. Peer to
4004 // `coordinates_or_none_matches_pre_lift_reconciler_helper_shape`
4005 // on the metadata axis's borrow-form primitive.
4006 // `FluxResourceRef` does not derive `PartialEq` — the parity
4007 // check walks the per-ref fetch-coords tuple (the same 4-slot
4008 // borrow projection every downstream fetch consumer routes
4009 // through) so a regression that reshaped ANY slot at ANY
4010 // index surfaces here through the sibling
4011 // `FluxResourceRef::fetch_coords` typed projection.
4012 fn pre_lift(p: &Process) -> Vec<FluxResourceRef> {
4013 p.status
4014 .as_ref()
4015 .map(|s| s.flux_resources.clone())
4016 .unwrap_or_default()
4017 }
4018 fn coord_shape(refs: &[FluxResourceRef]) -> Vec<(String, String, String, String)> {
4019 refs.iter()
4020 .map(|r| {
4021 let (ns, av, kind, name) = r.fetch_coords();
4022 (
4023 ns.to_string(),
4024 av.to_string(),
4025 kind.to_string(),
4026 name.to_string(),
4027 )
4028 })
4029 .collect()
4030 }
4031 // Missing status.
4032 let mut p = Process::new("api", empty_spec());
4033 p.status = None;
4034 assert_eq!(
4035 coord_shape(p.observed_flux_resources()),
4036 coord_shape(&pre_lift(&p))
4037 );
4038 // Populated status, empty slot.
4039 let p = process_with_flux_resources(vec![]);
4040 assert_eq!(
4041 coord_shape(p.observed_flux_resources()),
4042 coord_shape(&pre_lift(&p))
4043 );
4044 // Populated status, one ref.
4045 let p = process_with_flux_resources(vec![sample_flux_ref("obs")]);
4046 assert_eq!(
4047 coord_shape(p.observed_flux_resources()),
4048 coord_shape(&pre_lift(&p))
4049 );
4050 // Populated status, multiple refs.
4051 let p = process_with_flux_resources(vec![
4052 sample_flux_ref("obs"),
4053 sample_flux_ref("gw"),
4054 sample_flux_ref("api"),
4055 ]);
4056 assert_eq!(
4057 coord_shape(p.observed_flux_resources()),
4058 coord_shape(&pre_lift(&p))
4059 );
4060 }
4061
4062 #[test]
4063 fn observed_flux_resources_missing_status_and_empty_slot_collapse_to_the_same_slice_shape() {
4064 // Cross-corner coherence pin: the missing-`status` corner and
4065 // the populated-empty-slot corner return slices whose
4066 // `.is_empty()` / `.len()` observations are IDENTICAL. A
4067 // regression that promoted the missing-`status` corner to
4068 // returning `None` (via a signature change) — or that widened
4069 // the empty-slot corner to a synthetic single-element slice
4070 // — would surface here rather than as silent operator-facing
4071 // divergence between a never-status-written Process and a
4072 // status-emptied Process.
4073 let mut p_no_status = Process::new("api", empty_spec());
4074 p_no_status.status = None;
4075 let p_empty_status = process_with_flux_resources(vec![]);
4076 assert_eq!(
4077 p_no_status.observed_flux_resources().len(),
4078 p_empty_status.observed_flux_resources().len()
4079 );
4080 assert_eq!(
4081 p_no_status.observed_flux_resources().is_empty(),
4082 p_empty_status.observed_flux_resources().is_empty()
4083 );
4084 }
4085
4086 #[test]
4087 fn observed_flux_resources_slice_preserves_persisted_ordering() {
4088 // Ordering-preservation pin: the borrowed slice preserves
4089 // the exact insertion order of the persisted vector — no
4090 // sort, no dedup, no reshape. A regression that inserted a
4091 // sort or reordering would silently misroute per-ref
4092 // observations at the downstream VERIFY-phase / ATTEST-
4093 // heartbeat consumers, both of which walk the slice
4094 // positionally and correlate the position to the observed
4095 // readiness.
4096 let refs = vec![
4097 sample_flux_ref("z-last"),
4098 sample_flux_ref("a-first"),
4099 sample_flux_ref("m-middle"),
4100 ];
4101 let p = process_with_flux_resources(refs);
4102 let observed = p.observed_flux_resources();
4103 assert_eq!(observed[0].name, "z-last");
4104 assert_eq!(observed[1].name, "a-first");
4105 assert_eq!(observed[2].name, "m-middle");
4106 }
4107
4108 // ─── Process::observed_pid substrate pins ─────────────────────────
4109 //
4110 // Pins the borrow-form status-projection primitive on the PID axis
4111 // that owns the 3-line `.status.as_ref().and_then(|s| s.pid.clone())`
4112 // chain the two hand-authored `tatara-reconciler::phase_machine`
4113 // sites (`handle_forking` ALLOCATE-PID gate + `handle_exiting`
4114 // SIGTERM cascade) restated by hand pre-lift. Peer to the sibling
4115 // `observed_flux_resources_*` pin family on the flux-resources
4116 // axis; both compose the missing-`status` fallback + borrow-form
4117 // return-shape skeleton on distinct `ProcessStatus` slots. Fail-
4118 // before-pass-after granularity: `observed_pid` did not exist
4119 // pre-lift, so any test invoking it fails to compile pre-lift and
4120 // passes post-lift.
4121
4122 fn process_with_pid(pid: Option<&str>) -> Process {
4123 let mut p = Process::new("api-gateway", empty_spec());
4124 p.metadata.namespace = Some("prod".into());
4125 let mut status = ProcessStatus::default();
4126 status.pid = pid.map(str::to_string);
4127 p.status = Some(status);
4128 p
4129 }
4130
4131 #[test]
4132 fn observed_pid_returns_none_when_status_is_none() {
4133 // Missing-`status` corner pin: the primitive collapses the
4134 // no-status case to `None` so downstream `.is_some()` /
4135 // `if let Some(_)` / `.map(...)` behave identically on a
4136 // `Process` whose status field is `None` and on one whose
4137 // status carries an unpopulated `pid` slot. Matches the
4138 // pre-lift `.and_then(...)` chain's `None` byte-identically
4139 // at every reconciler consumer's downstream shape.
4140 let mut p = Process::new("api", empty_spec());
4141 p.status = None;
4142 assert!(p.observed_pid().is_none());
4143 }
4144
4145 #[test]
4146 fn observed_pid_returns_none_when_pid_slot_is_none() {
4147 // Empty-slot-under-populated-status corner pin: the
4148 // primitive returns `None`, matching the missing-`status`
4149 // corner byte-identically. A regression that treated the
4150 // two corners differently (a `None`-vs-`Some("")` signal
4151 // that downstream consumers could grep on) would silently
4152 // promote an internal representation detail (whether the
4153 // reconciler has ever written a status subresource) into
4154 // observable behavior at the ALLOCATE-PID gate.
4155 let p = process_with_pid(None);
4156 assert!(p.observed_pid().is_none());
4157 }
4158
4159 #[test]
4160 fn observed_pid_returns_borrowed_str_when_pid_slot_is_populated() {
4161 // Happy-path pin: with a populated `status.pid` slot, the
4162 // primitive returns a borrowed `&str` whose contents match
4163 // the persisted `String`. A regression that filtered /
4164 // reshaped / canonicalized the string would surface here
4165 // rather than as silent skew at the downstream cascade
4166 // comparator's `.as_deref() == Some(...)` equality check.
4167 let p = process_with_pid(Some("seph.1.7"));
4168 assert_eq!(p.observed_pid(), Some("seph.1.7"));
4169 }
4170
4171 #[test]
4172 fn observed_pid_is_a_zero_copy_borrow_projection() {
4173 // Borrow-discipline pin: the returned `&str` borrows the
4174 // persisted `String`'s underlying byte buffer in place —
4175 // NOT a fresh allocation or a clone. A regression that
4176 // switched the projection to an owned `String` (via
4177 // `.clone()` or `.to_owned()`) would defeat the zero-copy
4178 // contract the lift's primary strict-widening delivers
4179 // (the pre-lift 3-line chain eagerly cloned the `String`
4180 // per reconcile pass at BOTH call sites even though the
4181 // ALLOCATE-PID gate immediately dropped the clone and the
4182 // SIGTERM cascade only re-borrowed it via `.as_str()`; the
4183 // post-lift primitive borrows). Peer to the sibling
4184 // `observed_flux_resources_is_a_zero_copy_borrow_projection`
4185 // pin on the flux-resources borrow-projection axis.
4186 let p = process_with_pid(Some("seph.1.7"));
4187 let observed = p.observed_pid().expect("populated slot");
4188 let persisted = p.status.as_ref().unwrap().pid.as_ref().unwrap();
4189 assert!(std::ptr::eq(observed.as_ptr(), persisted.as_ptr()));
4190 }
4191
4192 #[test]
4193 fn observed_pid_is_a_pure_projection() {
4194 // Purity pin: calling the projection twice on the same
4195 // `Process` returns byte-identical `&str`s (same pointer,
4196 // same length). A regression that introduced state — a
4197 // lazy-cached slice materialized on first call, a
4198 // normalization step that ran once and cached — would
4199 // surface here rather than as silent drift between the
4200 // ALLOCATE-PID gate and the SIGTERM cascade on the SAME
4201 // `Process` within one reconcile pass.
4202 let p = process_with_pid(Some("seph.1.7"));
4203 let a = p.observed_pid().expect("populated slot");
4204 let b = p.observed_pid().expect("populated slot");
4205 assert!(std::ptr::eq(a.as_ptr(), b.as_ptr()));
4206 assert_eq!(a.len(), b.len());
4207 }
4208
4209 #[test]
4210 fn observed_pid_matches_pre_lift_reconciler_chain_shape() {
4211 // Byte-identical parity pin between the borrow-form
4212 // primitive here and the pre-lift `tatara-reconciler
4213 // ::phase_machine` 3-line chain shape. Sweeps every corner
4214 // every callsite plausibly encounters (missing status,
4215 // empty pid slot, populated pid slot). A regression that
4216 // inserted a normalization step at the primitive the pre-
4217 // lift chain does NOT apply — or vice versa — surfaces
4218 // here rather than as silent drift between the pre-lift
4219 // consumer sites and the ONE substrate owner they now
4220 // route through. Peer to
4221 // `observed_flux_resources_matches_pre_lift_reconciler_chain_shape`
4222 // on the flux-resources axis's borrow-form primitive.
4223 fn pre_lift(p: &Process) -> Option<String> {
4224 p.status.as_ref().and_then(|s| s.pid.clone())
4225 }
4226 // Missing status.
4227 let mut p = Process::new("api", empty_spec());
4228 p.status = None;
4229 assert_eq!(p.observed_pid().map(str::to_string), pre_lift(&p));
4230 // Populated status, empty pid slot.
4231 let p = process_with_pid(None);
4232 assert_eq!(p.observed_pid().map(str::to_string), pre_lift(&p));
4233 // Populated status, populated pid slot.
4234 let p = process_with_pid(Some("seph.1.7"));
4235 assert_eq!(p.observed_pid().map(str::to_string), pre_lift(&p));
4236 }
4237
4238 #[test]
4239 fn observed_pid_missing_status_and_empty_slot_collapse_to_the_same_option_shape() {
4240 // Cross-corner coherence pin: the missing-`status` corner
4241 // and the populated-empty-slot corner return `Option`s whose
4242 // `.is_none()` observations are IDENTICAL. A regression
4243 // that promoted the missing-`status` corner to returning a
4244 // typed error (via a signature change to `Result<_, _>`) —
4245 // or that widened the empty-slot corner to a synthetic
4246 // `Some("")` — would surface here rather than as silent
4247 // operator-facing divergence between a never-status-
4248 // written Process and a status-emptied Process on the
4249 // ALLOCATE-PID gate.
4250 let mut p_no_status = Process::new("api", empty_spec());
4251 p_no_status.status = None;
4252 let p_empty_slot = process_with_pid(None);
4253 assert_eq!(
4254 p_no_status.observed_pid().is_none(),
4255 p_empty_slot.observed_pid().is_none()
4256 );
4257 assert_eq!(
4258 p_no_status.observed_pid().is_some(),
4259 p_empty_slot.observed_pid().is_some()
4260 );
4261 }
4262
4263 #[test]
4264 fn observed_pid_preserves_hierarchical_pid_format() {
4265 // Format-preservation pin: the hierarchical PID path
4266 // (dotted-segment form `seph.1.7`, matching the ported
4267 // `convergence-controller/src/identity.rs` scheme) reaches
4268 // the caller with segments and separators byte-identical
4269 // to the persisted `String`. A regression that inserted a
4270 // canonicalization pass (a segment-count validator, a
4271 // separator swap `.` → `/`, a leading/trailing whitespace
4272 // trim) would silently misroute the SIGTERM cascade's
4273 // `spec.identity.parent == Some(pid)` comparator against
4274 // children whose `parent` field was authored in the ported
4275 // scheme's exact form.
4276 for pid in ["seph", "seph.1", "seph.1.7", "seph.1.7.42"] {
4277 let p = process_with_pid(Some(pid));
4278 assert_eq!(p.observed_pid(), Some(pid));
4279 }
4280 }
4281
4282 // ─── Process::observed_attestation substrate pins ─────────────────
4283 //
4284 // Pins the borrow-form status-projection primitive on the
4285 // attestation-chain axis that owns the 3-line
4286 // `.status.as_ref().and_then(|s| s.attestation.as_ref())` chain
4287 // the two hand-authored `tatara-reconciler` sites
4288 // (`phase_machine::advance_to_attested` ATTEST composer +
4289 // `render::render_export_jobs` export-Job builder) restated by
4290 // hand pre-lift. Peer to the sibling `observed_pid_*` +
4291 // `observed_flux_resources_*` pin families; all three compose
4292 // the missing-`status` fallback + borrow-form return-shape
4293 // skeleton on distinct `ProcessStatus` slots. Fail-before-pass-
4294 // after granularity: `observed_attestation` did not exist
4295 // pre-lift, so any test invoking it fails to compile pre-lift
4296 // and passes post-lift.
4297
4298 fn sample_attestation(artifact: &str, intent: &str) -> ProcessAttestation {
4299 // Distinct pillar strings so a regression that swapped the
4300 // artifact / intent pillars silently surfaces as an
4301 // equality failure at the composed-root parity pin.
4302 ProcessAttestation::initial(artifact.to_string(), None, intent.to_string())
4303 }
4304
4305 fn process_with_attestation(attestation: Option<ProcessAttestation>) -> Process {
4306 let mut p = Process::new("api-gateway", empty_spec());
4307 p.metadata.namespace = Some("prod".into());
4308 let mut status = ProcessStatus::default();
4309 status.attestation = attestation;
4310 p.status = Some(status);
4311 p
4312 }
4313
4314 #[test]
4315 fn observed_attestation_returns_none_when_status_is_none() {
4316 // Missing-`status` corner pin: the primitive collapses the
4317 // no-status case to `None` so downstream `.is_some()` /
4318 // `if let Some(_)` / `.map(...)` behave identically on a
4319 // `Process` whose status field is `None` and on one whose
4320 // status carries an unpopulated `attestation` slot.
4321 // Matches the pre-lift `.and_then(...)` chain's `None`
4322 // byte-identically at every reconciler consumer's
4323 // downstream shape.
4324 let mut p = Process::new("api", empty_spec());
4325 p.status = None;
4326 assert!(p.observed_attestation().is_none());
4327 }
4328
4329 #[test]
4330 fn observed_attestation_returns_none_when_attestation_slot_is_none() {
4331 // Empty-slot-under-populated-status corner pin: the
4332 // primitive returns `None`, matching the missing-`status`
4333 // corner byte-identically. A regression that treated the
4334 // two corners differently (a `None`-vs-`Some(_)` signal
4335 // that downstream consumers could grep on) would silently
4336 // promote an internal representation detail (whether the
4337 // reconciler has ever written a status subresource) into
4338 // observable behavior at the ATTEST composer's
4339 // seed-vs-chain branch.
4340 let p = process_with_attestation(None);
4341 assert!(p.observed_attestation().is_none());
4342 }
4343
4344 #[test]
4345 fn observed_attestation_returns_borrow_when_slot_is_populated() {
4346 // Happy-path pin: with a populated `status.attestation`
4347 // slot, the primitive returns a borrowed
4348 // `&ProcessAttestation` whose fields match the persisted
4349 // record. A regression that filtered / reshaped /
4350 // canonicalized the record would surface here rather than
4351 // as silent skew at the downstream `prior.next(pillars)`
4352 // chain composer + the ephemeral-export receipt's
4353 // `previous_root` linker.
4354 let att = sample_attestation("art-1", "int-1");
4355 let composed_root = att.composed_root.clone();
4356 let p = process_with_attestation(Some(att));
4357 let observed = p.observed_attestation().expect("populated slot");
4358 assert_eq!(observed.artifact_hash, "art-1");
4359 assert_eq!(observed.intent_hash, "int-1");
4360 assert_eq!(observed.composed_root, composed_root);
4361 assert_eq!(observed.generation, 0);
4362 assert!(observed.previous_root.is_none());
4363 }
4364
4365 #[test]
4366 fn observed_attestation_is_a_zero_copy_borrow_projection() {
4367 // Borrow-discipline pin: the returned reference points at
4368 // the persisted `ProcessAttestation` in place — NOT a fresh
4369 // allocation or a clone. A regression that switched the
4370 // projection to an owned `ProcessAttestation` (via
4371 // `.clone()`) would defeat the zero-copy contract the
4372 // lift's primary strict-widening delivers (the pre-lift
4373 // 3-line chain returned a borrow, but the export-Job
4374 // builder then cloned `composed_root` off it; the post-
4375 // lift primitive preserves the borrow all the way to the
4376 // consumer's own cloning choice). Peer to the sibling
4377 // `observed_pid_is_a_zero_copy_borrow_projection` +
4378 // `observed_flux_resources_is_a_zero_copy_borrow_projection`
4379 // pins on the PID + flux-resources borrow-projection axes.
4380 let att = sample_attestation("art-1", "int-1");
4381 let p = process_with_attestation(Some(att));
4382 let observed = p.observed_attestation().expect("populated slot") as *const _;
4383 let persisted = p.status.as_ref().unwrap().attestation.as_ref().unwrap() as *const _;
4384 assert!(std::ptr::eq(observed, persisted));
4385 }
4386
4387 #[test]
4388 fn observed_attestation_is_a_pure_projection() {
4389 // Purity pin: calling the projection twice on the same
4390 // `Process` returns byte-identical borrows (same pointer).
4391 // A regression that introduced state — a lazy-cached
4392 // reference materialized on first call, a normalization
4393 // step that ran once and cached — would surface here
4394 // rather than as silent drift between the ATTEST composer
4395 // and the ephemeral-export receipt chain on the SAME
4396 // `Process` within one reconcile pass.
4397 let att = sample_attestation("art-1", "int-1");
4398 let p = process_with_attestation(Some(att));
4399 let a = p.observed_attestation().expect("populated slot") as *const _;
4400 let b = p.observed_attestation().expect("populated slot") as *const _;
4401 assert!(std::ptr::eq(a, b));
4402 }
4403
4404 #[test]
4405 fn observed_attestation_matches_pre_lift_reconciler_chain_shape() {
4406 // Byte-identical parity pin between the borrow-form
4407 // primitive here and the pre-lift `tatara-reconciler`
4408 // 3-line chain shape. Sweeps every corner every callsite
4409 // plausibly encounters (missing status, empty attestation
4410 // slot, populated attestation slot). A regression that
4411 // inserted a normalization step at the primitive the pre-
4412 // lift chain does NOT apply — or vice versa — surfaces
4413 // here rather than as silent drift between the pre-lift
4414 // consumer sites and the ONE substrate owner they now
4415 // route through. Peer to
4416 // `observed_pid_matches_pre_lift_reconciler_chain_shape` +
4417 // `observed_flux_resources_matches_pre_lift_reconciler_chain_shape`
4418 // on the PID + flux-resources axes.
4419 // `ProcessAttestation` does not derive `PartialEq` — the
4420 // parity check walks the `composed_root` field (the
4421 // byte-string every downstream consumer keys off) so a
4422 // regression that reshaped the record without touching
4423 // the composed-root observation surfaces here through
4424 // the receipt-chain projection.
4425 fn pre_lift(p: &Process) -> Option<String> {
4426 p.status
4427 .as_ref()
4428 .and_then(|s| s.attestation.as_ref())
4429 .map(|a| a.composed_root.clone())
4430 }
4431 // Missing status.
4432 let mut p = Process::new("api", empty_spec());
4433 p.status = None;
4434 assert_eq!(
4435 p.observed_attestation().map(|a| a.composed_root.clone()),
4436 pre_lift(&p)
4437 );
4438 // Populated status, empty attestation slot.
4439 let p = process_with_attestation(None);
4440 assert_eq!(
4441 p.observed_attestation().map(|a| a.composed_root.clone()),
4442 pre_lift(&p)
4443 );
4444 // Populated status, populated attestation slot.
4445 let p = process_with_attestation(Some(sample_attestation("art-1", "int-1")));
4446 assert_eq!(
4447 p.observed_attestation().map(|a| a.composed_root.clone()),
4448 pre_lift(&p)
4449 );
4450 }
4451
4452 #[test]
4453 fn observed_attestation_missing_status_and_empty_slot_collapse_to_the_same_option_shape() {
4454 // Cross-corner coherence pin: the missing-`status` corner
4455 // and the populated-empty-slot corner return `Option`s
4456 // whose `.is_none()` observations are IDENTICAL. A
4457 // regression that promoted the missing-`status` corner to
4458 // returning a typed error (via a signature change to
4459 // `Result<_, _>`) — or that widened the empty-slot corner
4460 // to a synthetic `Some(default_attestation)` — would
4461 // surface here rather than as silent operator-facing
4462 // divergence between a never-status-written Process and
4463 // an attestation-emptied Process on the ATTEST composer's
4464 // seed-vs-chain branch.
4465 let mut p_no_status = Process::new("api", empty_spec());
4466 p_no_status.status = None;
4467 let p_empty_slot = process_with_attestation(None);
4468 assert_eq!(
4469 p_no_status.observed_attestation().is_none(),
4470 p_empty_slot.observed_attestation().is_none()
4471 );
4472 assert_eq!(
4473 p_no_status.observed_attestation().is_some(),
4474 p_empty_slot.observed_attestation().is_some()
4475 );
4476 }
4477
4478 #[test]
4479 fn observed_attestation_preserves_chain_generation_field() {
4480 // Generation-preservation pin: a chained attestation
4481 // (`prior.next(...)` at generation N ≥ 1 with a
4482 // `previous_root` linked to `prior.composed_root`) reaches
4483 // the caller with its `generation` counter + `previous_root`
4484 // link byte-identical to the persisted record. The pre-lift
4485 // ATTEST composer discriminated exactly on this borrow's
4486 // `Some(prior)` vs `None` arm; a regression that dropped
4487 // the chain's `generation` counter (say, by folding
4488 // `next(...)` into a fresh `initial(...)` on every
4489 // reconcile pass) would silently reset every chain and
4490 // orphan every downstream `previous_root` link, but that
4491 // drift is invisible to a Process CRD reader who only
4492 // observes the LATEST composed_root.
4493 let prior = sample_attestation("art-0", "int-0");
4494 let chained = prior.next("art-1".to_string(), None, "int-1".to_string());
4495 let expected_generation = chained.generation;
4496 let expected_previous = chained.previous_root.clone();
4497 let p = process_with_attestation(Some(chained));
4498 let observed = p.observed_attestation().expect("populated slot");
4499 assert_eq!(observed.generation, expected_generation);
4500 assert_eq!(observed.generation, 1);
4501 assert_eq!(observed.previous_root, expected_previous);
4502 assert_eq!(
4503 observed.previous_root.as_deref(),
4504 Some(prior.composed_root.as_str())
4505 );
4506 }
4507
4508 // ─── Process::observed_identity substrate pins ────────────────────
4509 //
4510 // The borrow-form status-projection primitive on the resolved-
4511 // identity axis. Collapses the paired 3-line `.status.as_ref()
4512 // .and_then(|s| s.identity.<clone|as_ref>())` chain every
4513 // consumer in `tatara-reconciler` restated by hand pre-lift at
4514 // TWO sites (`phase_machine::handle_forking` seed +
4515 // `ssapply::inject_annotations` content-hash annotation
4516 // composer). Peer to the sibling `observed_pid_*` +
4517 // `observed_attestation_*` + `observed_flux_resources_*` pin
4518 // families; all four compose the same missing-`status` fallback
4519 // + borrow-form return-shape skeleton on distinct
4520 // `ProcessStatus` slots. Each pin fails-before-pass-after
4521 // granularity: `observed_identity` did not exist pre-lift, so
4522 // any test invoking it fails to compile pre-lift and passes
4523 // post-lift.
4524
4525 fn sample_identity(name: &str) -> Identity {
4526 // Distinct name + content_hash + override flag so a
4527 // regression that reshaped one slot surfaces at the
4528 // populated-slot pin's field-equality check without
4529 // aliasing the sibling slots.
4530 Identity {
4531 name: name.to_string(),
4532 content_hash: "a".repeat(26),
4533 name_override: true,
4534 }
4535 }
4536
4537 fn process_with_identity(identity: Option<Identity>) -> Process {
4538 let mut p = Process::new("api-gateway", empty_spec());
4539 p.metadata.namespace = Some("prod".into());
4540 let mut status = ProcessStatus::default();
4541 status.identity = identity;
4542 p.status = Some(status);
4543 p
4544 }
4545
4546 #[test]
4547 fn observed_identity_returns_none_when_status_is_none() {
4548 // Missing-`status` corner pin: the primitive collapses the
4549 // no-status case to `None` so downstream `.is_some()` /
4550 // `if let Some(_)` / `.cloned().unwrap_or_else(...)` behave
4551 // identically on a `Process` whose status field is `None`
4552 // and on one whose status carries an unpopulated `identity`
4553 // slot. Matches the pre-lift `.and_then(...)` chain's `None`
4554 // byte-identically at every reconciler consumer's
4555 // downstream shape.
4556 let mut p = Process::new("api", empty_spec());
4557 p.status = None;
4558 assert!(p.observed_identity().is_none());
4559 }
4560
4561 #[test]
4562 fn observed_identity_returns_none_when_identity_slot_is_none() {
4563 // Empty-slot-under-populated-status corner pin: the
4564 // primitive returns `None`, matching the missing-`status`
4565 // corner byte-identically. A regression that treated the
4566 // two corners differently (a `None`-vs-`Some(_)` signal
4567 // that downstream consumers could grep on) would silently
4568 // promote an internal representation detail (whether the
4569 // reconciler has ever written a status subresource) into
4570 // observable behavior at the FORK-time `derive_identity`
4571 // fallback branch.
4572 let p = process_with_identity(None);
4573 assert!(p.observed_identity().is_none());
4574 }
4575
4576 #[test]
4577 fn observed_identity_returns_borrow_when_slot_is_populated() {
4578 // Happy-path pin: with a populated `status.identity` slot,
4579 // the primitive returns a borrowed `&Identity` whose fields
4580 // match the persisted record. A regression that filtered /
4581 // reshaped / canonicalized the record would surface here
4582 // rather than as silent skew at the FORK-time seed's
4583 // `.cloned().unwrap_or_else(derive_identity)` composition
4584 // + the SSA-time content-hash annotation stamp on the SAME
4585 // Process.
4586 let id = sample_identity("seph");
4587 let expected = id.clone();
4588 let p = process_with_identity(Some(id));
4589 let observed = p.observed_identity().expect("populated slot");
4590 assert_eq!(observed, &expected);
4591 assert_eq!(observed.name, "seph");
4592 assert_eq!(observed.content_hash, "a".repeat(26));
4593 assert!(observed.name_override);
4594 }
4595
4596 #[test]
4597 fn observed_identity_is_a_zero_copy_borrow_projection() {
4598 // Borrow-discipline pin: the returned reference points at
4599 // the persisted `Identity` in place — NOT a fresh
4600 // allocation or a clone. A regression that switched the
4601 // projection to an owned `Identity` (via `.clone()`) would
4602 // defeat the zero-copy contract the lift's primary strict-
4603 // widening delivers (the SSA-time consumer never clones the
4604 // whole `Identity`, only the `content_hash` field it stamps
4605 // onto the annotation map, so the borrow-form return
4606 // shape's happy-path allocation count is exactly ZERO).
4607 // Peer to the sibling
4608 // `observed_attestation_is_a_zero_copy_borrow_projection`
4609 // + `observed_pid_is_a_zero_copy_borrow_projection` +
4610 // `observed_flux_resources_is_a_zero_copy_borrow_projection`
4611 // pins on the attestation-chain + PID + flux-resources
4612 // borrow-projection axes.
4613 let id = sample_identity("seph");
4614 let p = process_with_identity(Some(id));
4615 let observed = p.observed_identity().expect("populated slot") as *const _;
4616 let persisted = p.status.as_ref().unwrap().identity.as_ref().unwrap() as *const _;
4617 assert!(std::ptr::eq(observed, persisted));
4618 }
4619
4620 #[test]
4621 fn observed_identity_is_a_pure_projection() {
4622 // Purity pin: calling the projection twice on the same
4623 // `Process` returns byte-identical borrows (same pointer).
4624 // A regression that introduced state — a lazy-cached
4625 // reference materialized on first call, a normalization
4626 // step that ran once and cached — would surface here
4627 // rather than as silent drift between the FORK-time
4628 // identity seed and the SSA-time content-hash annotation
4629 // stamp on the SAME `Process` within one reconcile pass.
4630 let p = process_with_identity(Some(sample_identity("seph")));
4631 let a = p.observed_identity().expect("populated slot") as *const _;
4632 let b = p.observed_identity().expect("populated slot") as *const _;
4633 assert!(std::ptr::eq(a, b));
4634 }
4635
4636 #[test]
4637 fn observed_identity_matches_pre_lift_reconciler_chain_shape() {
4638 // Byte-identical parity pin between the borrow-form
4639 // primitive here and the pre-lift `tatara-reconciler`
4640 // 3-line chain shape. Sweeps every corner every callsite
4641 // plausibly encounters (missing status, empty identity
4642 // slot, populated identity slot). A regression that
4643 // inserted a normalization step at the primitive the pre-
4644 // lift chain does NOT apply — or vice versa — surfaces
4645 // here rather than as silent drift between the pre-lift
4646 // consumer sites and the ONE substrate owner they now
4647 // route through. Peer to
4648 // `observed_attestation_matches_pre_lift_reconciler_chain_shape`
4649 // + `observed_pid_matches_pre_lift_reconciler_chain_shape`
4650 // + `observed_flux_resources_matches_pre_lift_reconciler_chain_shape`
4651 // on the attestation-chain + PID + flux-resources axes.
4652 fn pre_lift(p: &Process) -> Option<Identity> {
4653 p.status.as_ref().and_then(|s| s.identity.clone())
4654 }
4655 // Missing status.
4656 let mut p = Process::new("api", empty_spec());
4657 p.status = None;
4658 assert_eq!(p.observed_identity().cloned(), pre_lift(&p));
4659 // Populated status, empty identity slot.
4660 let p = process_with_identity(None);
4661 assert_eq!(p.observed_identity().cloned(), pre_lift(&p));
4662 // Populated status, populated identity slot.
4663 let p = process_with_identity(Some(sample_identity("seph")));
4664 assert_eq!(p.observed_identity().cloned(), pre_lift(&p));
4665 }
4666
4667 #[test]
4668 fn observed_identity_missing_status_and_empty_slot_collapse_to_the_same_option_shape() {
4669 // Cross-corner coherence pin: the missing-`status` corner
4670 // and the populated-empty-slot corner return `Option`s
4671 // whose `.is_none()` observations are IDENTICAL. A
4672 // regression that promoted the missing-`status` corner to
4673 // returning a typed error (via a signature change to
4674 // `Result<_, _>`) — or that widened the empty-slot corner
4675 // to a synthetic `Some(derive_identity(default_spec))` —
4676 // would surface here rather than as silent operator-facing
4677 // divergence between a never-status-written Process and an
4678 // identity-cleared Process on the FORK-time seed branch.
4679 let mut p_no_status = Process::new("api", empty_spec());
4680 p_no_status.status = None;
4681 let p_empty_slot = process_with_identity(None);
4682 assert_eq!(
4683 p_no_status.observed_identity().is_none(),
4684 p_empty_slot.observed_identity().is_none()
4685 );
4686 assert_eq!(
4687 p_no_status.observed_identity().is_some(),
4688 p_empty_slot.observed_identity().is_some()
4689 );
4690 }
4691
4692 #[test]
4693 fn observed_identity_cloned_composes_with_derive_identity_fallback() {
4694 // Cross-primitive composition pin: the borrow-form
4695 // primitive threaded through `.cloned().unwrap_or_else(||
4696 // derive_identity(...))` reproduces the pre-lift FORK-time
4697 // seed's owned-`Identity` shape at every corner. Binds the
4698 // exact composition the `phase_machine::handle_forking`
4699 // consumer performs: on the populated-slot corner the
4700 // reconciler-persisted `Identity` is returned verbatim (the
4701 // fallback never fires), and on both empty corners
4702 // (missing-status + empty-slot) the fallback fires
4703 // producing a fresh `derive_identity(&spec,
4704 // name_override)`. A regression that (a) swapped the
4705 // fallback direction, (b) made `.cloned()` re-derive
4706 // instead of clone, or (c) made the empty-slot corner
4707 // return a synthetic `Some(default_identity)` collides
4708 // with the fallback surfaces here rather than as silent
4709 // FORK-time PID allocator skew.
4710 let spec = empty_spec();
4711 let fallback_expected = crate::identity::derive_identity(&spec, None);
4712 // Populated-slot corner: the seed returns the persisted
4713 // identity, NOT the derive fallback.
4714 let persisted = sample_identity("seph");
4715 let p = process_with_identity(Some(persisted.clone()));
4716 let seed = p.observed_identity().cloned().unwrap_or_else(|| {
4717 crate::identity::derive_identity(&p.spec, p.declared_name_override())
4718 });
4719 assert_eq!(seed, persisted);
4720 assert_ne!(seed, fallback_expected);
4721 // Empty-slot corner: the seed fires the derive fallback.
4722 let p = process_with_identity(None);
4723 let seed = p.observed_identity().cloned().unwrap_or_else(|| {
4724 crate::identity::derive_identity(&p.spec, p.declared_name_override())
4725 });
4726 assert_eq!(seed, fallback_expected);
4727 // Missing-status corner: the seed fires the derive
4728 // fallback, byte-identical to the empty-slot corner.
4729 let mut p = Process::new("api-gateway", empty_spec());
4730 p.metadata.namespace = Some("prod".into());
4731 p.status = None;
4732 let seed = p.observed_identity().cloned().unwrap_or_else(|| {
4733 crate::identity::derive_identity(&p.spec, p.declared_name_override())
4734 });
4735 assert_eq!(seed, fallback_expected);
4736 }
4737
4738 // ─── Process::observed_phase substrate pins ───────────────────────
4739 //
4740 // The copy-form status-projection primitive on the phase axis.
4741 // Collapses the paired 3-line `.status.as_ref().map(|s| s.phase)`
4742 // chain every consumer in `tatara-reconciler` restated by hand
4743 // pre-lift at FIVE sites. Peer to the borrow-form
4744 // `observed_pid_*` + `observed_flux_resources_*` +
4745 // `observed_attestation_*` pin families; all four compose the
4746 // same missing-`status` fallback skeleton on distinct
4747 // `ProcessStatus` slots, with the phase-axis form returning
4748 // `Option<ProcessPhase>` (copy of a `Copy` scalar) rather than
4749 // `Option<&T>` (borrow) because the underlying slot is a bare
4750 // `ProcessPhase` — no allocation to borrow past, and the enum
4751 // is one byte on the wire. Each pin fails-before-pass-after
4752 // granularity: `observed_phase` did not exist pre-lift, so any
4753 // test invoking it fails to compile pre-lift and passes
4754 // post-lift.
4755
4756 fn process_with_phase(phase: Option<ProcessPhase>) -> Process {
4757 let mut p = Process::new("api-gateway", empty_spec());
4758 p.metadata.namespace = Some("prod".into());
4759 if let Some(ph) = phase {
4760 let mut status = ProcessStatus::default();
4761 status.phase = ph;
4762 p.status = Some(status);
4763 }
4764 p
4765 }
4766
4767 #[test]
4768 fn observed_phase_returns_none_when_status_is_none() {
4769 // Missing-`status` corner pin: the primitive collapses the
4770 // no-status case to `None` so downstream `.unwrap_or(...)`
4771 // at every reconciler consumer chooses the default
4772 // deliberately (`Pending` for the top-level dispatch seed
4773 // + boundary evaluator + routing groupby; `Attested` for
4774 // the released-from annotation composer). Matches the
4775 // pre-lift `.map(|s| s.phase)` chain's `None`
4776 // byte-identically at every consumer's downstream shape.
4777 let mut p = Process::new("api", empty_spec());
4778 p.status = None;
4779 assert!(p.observed_phase().is_none());
4780 }
4781
4782 #[test]
4783 fn observed_phase_returns_some_default_when_status_is_populated_with_default_phase() {
4784 // Populated-status corner pin: the primitive returns
4785 // `Some(ProcessPhase::default())` — a `ProcessStatus`
4786 // constructed via `default()` carries `phase: Pending`
4787 // because the phase field is a bare `ProcessPhase` (not
4788 // `Option<ProcessPhase>`), so there is NO "empty slot"
4789 // corner peer to the borrow-form projections' empty-slot
4790 // pins. A regression that reshaped the return type to
4791 // filter out `Pending` (treating it as "unset") would
4792 // surface here and silently break the top-level
4793 // dispatcher's Pending → Forking transition on a Process
4794 // freshly written by the reconciler.
4795 let p = process_with_phase(Some(ProcessPhase::default()));
4796 assert_eq!(p.observed_phase(), Some(ProcessPhase::Pending));
4797 assert_eq!(p.observed_phase(), Some(ProcessPhase::default()));
4798 }
4799
4800 #[test]
4801 fn observed_phase_returns_persisted_phase_when_status_is_populated() {
4802 // Happy-path pin: with a populated `status.phase` slot,
4803 // the primitive returns the persisted `ProcessPhase`.
4804 // A regression that filtered / reshaped / canonicalized
4805 // the phase would surface here rather than as silent
4806 // skew at the top-level dispatcher's phase handler
4807 // dispatch on the SAME Process.
4808 let p = process_with_phase(Some(ProcessPhase::Running));
4809 assert_eq!(p.observed_phase(), Some(ProcessPhase::Running));
4810 }
4811
4812 #[test]
4813 fn observed_phase_is_a_pure_projection() {
4814 // Purity pin: two consecutive calls return byte-identical
4815 // `Option<ProcessPhase>` values (no lazy materialization,
4816 // no interior mutation of `self`). Peer to the sibling
4817 // `observed_pid_is_a_pure_projection` +
4818 // `observed_flux_resources_is_a_pure_projection` +
4819 // `observed_attestation_is_a_pure_projection` pins; all
4820 // four bind the pure-projection discipline on the ONE
4821 // substrate accessor per status slot.
4822 let p = process_with_phase(Some(ProcessPhase::Attested));
4823 let a = p.observed_phase();
4824 let b = p.observed_phase();
4825 assert_eq!(a, b);
4826 assert_eq!(a, Some(ProcessPhase::Attested));
4827 }
4828
4829 #[test]
4830 fn observed_phase_matches_pre_lift_reconciler_chain_shape() {
4831 // Parity pin: sweeps the two corners every pre-lift
4832 // consumer plausibly encountered (missing status,
4833 // populated status with a particular phase) and compares
4834 // the substrate call against a hand-authored pre-lift
4835 // chain byte-identically. A regression that reshaped ANY
4836 // of the two corners would surface here rather than as
4837 // silent operator-facing skew between the top-level
4838 // dispatcher and any of the four other reconciler
4839 // consumers on the SAME `Process`.
4840 fn pre_lift(p: &Process) -> Option<ProcessPhase> {
4841 p.status.as_ref().map(|s| s.phase)
4842 }
4843 let mut p = Process::new("api", empty_spec());
4844 p.status = None;
4845 assert_eq!(p.observed_phase(), pre_lift(&p));
4846 let p = process_with_phase(Some(ProcessPhase::Running));
4847 assert_eq!(p.observed_phase(), pre_lift(&p));
4848 let p = process_with_phase(Some(ProcessPhase::Attested));
4849 assert_eq!(p.observed_phase(), pre_lift(&p));
4850 let p = process_with_phase(Some(ProcessPhase::Failed));
4851 assert_eq!(p.observed_phase(), pre_lift(&p));
4852 }
4853
4854 #[test]
4855 fn observed_phase_default_unwrap_matches_pre_lift_pending_default() {
4856 // Callsite-shape pin: three of the FIVE pre-lift consumers
4857 // (`controller::reconcile`, `boundary::evaluate_process_phase`,
4858 // `table_controller::stable_name_group_key`) closed the
4859 // 3-line chain with `.unwrap_or(ProcessPhase::Pending)`
4860 // (identical to `.unwrap_or_default()`). This pin binds
4861 // that call-site shape: `observed_phase().unwrap_or
4862 // (Pending)` returns `Pending` on missing status and the
4863 // persisted phase otherwise. A regression that swapped
4864 // the `None` sentinel's downstream default would surface
4865 // here rather than as silent skew at three of the five
4866 // consumer sites.
4867 let mut p = Process::new("api", empty_spec());
4868 p.status = None;
4869 assert_eq!(
4870 p.observed_phase().unwrap_or(ProcessPhase::Pending),
4871 ProcessPhase::Pending
4872 );
4873 let p = process_with_phase(Some(ProcessPhase::Running));
4874 assert_eq!(
4875 p.observed_phase().unwrap_or(ProcessPhase::Pending),
4876 ProcessPhase::Running
4877 );
4878 }
4879
4880 #[test]
4881 fn observed_phase_attested_unwrap_matches_pre_lift_released_from_default() {
4882 // Callsite-shape pin: the ONE pre-lift consumer
4883 // (`phase_machine::p_current_phase_str` — the
4884 // released-from annotation composer) closed the 3-line
4885 // chain with `.unwrap_or(ProcessPhase::Attested)` rather
4886 // than the `Default` (`Pending`). This pin binds that
4887 // call-site shape: `observed_phase().unwrap_or(Attested)`
4888 // returns `Attested` on missing status and the persisted
4889 // phase otherwise. A regression that folded the
4890 // `Attested`-default consumer into the `Pending`-default
4891 // majority would break the SIGSTOP/SIGCONT release gate's
4892 // "which annotation label to emit" branch — the pin binds
4893 // the primitive at the raw `Option<ProcessPhase>` form so
4894 // this default choice stays local at the callsite.
4895 let mut p = Process::new("api", empty_spec());
4896 p.status = None;
4897 assert_eq!(
4898 p.observed_phase().unwrap_or(ProcessPhase::Attested),
4899 ProcessPhase::Attested
4900 );
4901 let p = process_with_phase(Some(ProcessPhase::Failed));
4902 assert_eq!(
4903 p.observed_phase().unwrap_or(ProcessPhase::Attested),
4904 ProcessPhase::Failed
4905 );
4906 }
4907
4908 #[test]
4909 fn observed_phase_preserves_every_process_phase_variant() {
4910 // Round-trip pin: every `ProcessPhase` variant round-
4911 // trips through the primitive unchanged. Peer to the
4912 // sibling `observed_pid_preserves_hierarchical_pid_format`
4913 // pin's dotted-segment sweep; this pin sweeps the closed
4914 // set of `ProcessPhase` variants directly so a
4915 // canonicalization pass that dropped or reshaped one
4916 // (e.g. folded `Reconverging` back into `Execing`, or
4917 // remapped `Zombie` to `Reaped`) surfaces here rather
4918 // than as silent skew at the SIGSTOP/SIGCONT release
4919 // gate's phase-name annotation branch. Covers every
4920 // variant the `ProcessPhase::DeriveClosedSet` enumerates
4921 // so a future variant addition surfaces via the closed-
4922 // set macro rather than at a silent partial sweep.
4923 for phase in [
4924 ProcessPhase::Pending,
4925 ProcessPhase::Forking,
4926 ProcessPhase::Execing,
4927 ProcessPhase::Running,
4928 ProcessPhase::Attested,
4929 ProcessPhase::Reconverging,
4930 ProcessPhase::Releasing,
4931 ProcessPhase::Exiting,
4932 ProcessPhase::Failed,
4933 ProcessPhase::Zombie,
4934 ProcessPhase::Reaped,
4935 ] {
4936 let p = process_with_phase(Some(phase));
4937 assert_eq!(
4938 p.observed_phase(),
4939 Some(phase),
4940 "phase variant {phase:?} did not round-trip"
4941 );
4942 }
4943 }
4944
4945 // ─── Process::observed_phase_or_pending substrate pins ─────────────
4946 //
4947 // Pins the copy-form status-projection primitive on the phase
4948 // axis with the `Pending` sink applied. Sibling to the raw
4949 // `observed_phase_*` pin family on the (return-form × fallback
4950 // shape) axis pair — the raw-`Option` corner stays with the
4951 // sibling family; this pin family opens the `Pending`-defaulted
4952 // corner that four of the five pre-lift `observed_phase`
4953 // consumers wrote by hand. Fail-before-pass-after granularity:
4954 // `observed_phase_or_pending` did not exist pre-lift, so any
4955 // test invoking it fails to compile pre-lift and passes
4956 // post-lift.
4957
4958 #[test]
4959 fn observed_phase_or_pending_returns_pending_when_status_is_none() {
4960 // Missing-`status` corner pin: the primitive collapses the
4961 // no-status case to `Pending` — the sink four of the five
4962 // pre-lift `observed_phase` consumers wrote by hand
4963 // (`controller::reconcile` / `boundary::
4964 // evaluate_process_phase` / `table_controller::
4965 // stable_name_group_key` / `controller_pool::reconcile_pool`)
4966 // and the sentinel `ProcessPhase::default()` returns. A
4967 // regression that folded the `None` sink to any other phase
4968 // (e.g. `Forking` — treating "not yet observed" as "already
4969 // dispatched") would silently mis-seed the top-level
4970 // dispatcher's `Pending → Forking` transition and surface as
4971 // operator-visible reconcile-cycle skew on a freshly-forked
4972 // Process rather than at this pin.
4973 let mut p = Process::new("api", empty_spec());
4974 p.status = None;
4975 assert_eq!(p.observed_phase_or_pending(), ProcessPhase::Pending);
4976 }
4977
4978 #[test]
4979 fn observed_phase_or_pending_returns_persisted_phase_when_status_is_populated() {
4980 // Populated-status corner pin: the primitive passes through
4981 // the persisted `ProcessPhase` unchanged — the sink only
4982 // fires on missing `status`, not on a populated one carrying
4983 // a `Pending`-adjacent variant. Two variants pinned to
4984 // separate the "pass through the persisted phase" arm from
4985 // the "sink fires" arm: `Running` (mid-lifecycle) and
4986 // `Attested` (post-verify) both round-trip unchanged where
4987 // a regression that always returned `Pending` (dropped the
4988 // pass-through arm entirely) would surface here rather than
4989 // as silent skew at every reconciler's per-phase branch.
4990 let p = process_with_phase(Some(ProcessPhase::Running));
4991 assert_eq!(p.observed_phase_or_pending(), ProcessPhase::Running);
4992 let p = process_with_phase(Some(ProcessPhase::Attested));
4993 assert_eq!(p.observed_phase_or_pending(), ProcessPhase::Attested);
4994 }
4995
4996 #[test]
4997 fn observed_phase_or_pending_matches_pre_lift_unwrap_or_pending_chain_shape() {
4998 // Byte-identical parity pin: the primitive's return equals
4999 // the pre-lift two-link `.observed_phase().unwrap_or
5000 // (ProcessPhase::Pending)` chain at every one of the four
5001 // corner values (missing `status` → `Pending`, populated
5002 // with `Pending` → `Pending`, populated with a mid-lifecycle
5003 // variant → pass-through, populated with a terminal variant
5004 // → pass-through). A regression that swapped the sink to
5005 // `ProcessPhase::default()` (currently equivalent to
5006 // `Pending`) would keep this pin green until the enum's
5007 // `Default` impl drifted — the explicit `Pending` spelling
5008 // in the pin binds the operator-visible label rather than
5009 // the derived `Default`, so a future rename or reordering
5010 // of `ProcessPhase` variants that shifted `Default` off
5011 // `Pending` would surface here rather than as silent skew
5012 // at the four downstream consumer sites.
5013 let pre_lift = |p: &Process| p.observed_phase().unwrap_or(ProcessPhase::Pending);
5014 let mut p = Process::new("api", empty_spec());
5015 p.status = None;
5016 assert_eq!(p.observed_phase_or_pending(), pre_lift(&p));
5017 let p = process_with_phase(Some(ProcessPhase::Pending));
5018 assert_eq!(p.observed_phase_or_pending(), pre_lift(&p));
5019 let p = process_with_phase(Some(ProcessPhase::Running));
5020 assert_eq!(p.observed_phase_or_pending(), pre_lift(&p));
5021 let p = process_with_phase(Some(ProcessPhase::Reaped));
5022 assert_eq!(p.observed_phase_or_pending(), pre_lift(&p));
5023 }
5024
5025 #[test]
5026 fn observed_phase_or_pending_is_a_pure_projection() {
5027 // Purity pin: two back-to-back calls on the same `Process`
5028 // return the same `ProcessPhase` — the primitive stamps no
5029 // side effect (no clock read, no metadata write, no
5030 // `status` mutation) despite the sibling `observed_phase`
5031 // taking `&self` too. Peer to the sibling `observed_phase`
5032 // purity pin; a regression that folded a clock read (e.g.
5033 // "if the sink fired, stamp `phase_since = Utc::now()`")
5034 // into the primitive would surface here rather than at the
5035 // consumer sites' downstream reconcile-cycle behavior.
5036 let p = process_with_phase(Some(ProcessPhase::Running));
5037 let a = p.observed_phase_or_pending();
5038 let b = p.observed_phase_or_pending();
5039 assert_eq!(a, b);
5040 }
5041
5042 #[test]
5043 fn observed_phase_or_pending_preserves_every_process_phase_variant() {
5044 // Round-trip pin: every `ProcessPhase` variant round-trips
5045 // through the primitive unchanged when the `status` slot is
5046 // populated. Peer to the sibling `observed_phase_preserves
5047 // _every_process_phase_variant` sweep; this pin sweeps the
5048 // closed set through the `Pending`-sinked accessor rather
5049 // than the raw-`Option` accessor so a canonicalization pass
5050 // that dropped or reshaped one variant (e.g. folded
5051 // `Reconverging` back into `Execing`, remapped `Zombie` to
5052 // `Reaped`) surfaces at BOTH primitives' pin sets rather
5053 // than as silent skew at a subset of the reconciler
5054 // consumers. Covers every variant the
5055 // `ProcessPhase::DeriveClosedSet` enumerates so a future
5056 // variant addition surfaces via the closed-set macro rather
5057 // than at a silent partial sweep.
5058 for phase in [
5059 ProcessPhase::Pending,
5060 ProcessPhase::Forking,
5061 ProcessPhase::Execing,
5062 ProcessPhase::Running,
5063 ProcessPhase::Attested,
5064 ProcessPhase::Reconverging,
5065 ProcessPhase::Releasing,
5066 ProcessPhase::Exiting,
5067 ProcessPhase::Failed,
5068 ProcessPhase::Zombie,
5069 ProcessPhase::Reaped,
5070 ] {
5071 let p = process_with_phase(Some(phase));
5072 assert_eq!(
5073 p.observed_phase_or_pending(),
5074 phase,
5075 "phase variant {phase:?} did not round-trip through observed_phase_or_pending"
5076 );
5077 }
5078 }
5079
5080 // ─── Process::observed_phase_since substrate pins ──────────────────
5081 //
5082 // Pins the copy-form status-projection primitive on the
5083 // `status.phase_since` axis that owns the paired 5-line
5084 // `.status.as_ref().and_then(|s| s.phase_since).unwrap_or_else
5085 // (Utc::now)` chain the pool reconciler's per-owned-Process
5086 // `PoolMember { entered_state_at: … }` seed restated by hand pre-
5087 // lift. Peer to the sibling `observed_phase_*` +
5088 // `observed_identity_*` + `observed_attestation_*` +
5089 // `observed_flux_resources_*` + `observed_pid_*` + `created_at_*`
5090 // pin families — all six / seven primitives project a wire-format
5091 // `Option<T>` slot into a `Copy`-or-borrow inner value at ONE
5092 // owner. Fail-before-pass-after granularity: `observed_phase_since`
5093 // did not exist pre-lift, so any test invoking it fails to
5094 // compile pre-lift and passes post-lift.
5095
5096 fn process_with_phase_since(phase_since: Option<DateTime<Utc>>) -> Process {
5097 let mut p = Process::new("api-gateway", empty_spec());
5098 p.metadata.namespace = Some("prod".into());
5099 let mut status = ProcessStatus::default();
5100 status.phase_since = phase_since;
5101 p.status = Some(status);
5102 p
5103 }
5104
5105 #[test]
5106 fn observed_phase_since_returns_none_when_status_is_none() {
5107 // Missing-`status` corner pin: the primitive collapses the
5108 // no-status case to `None` so the pool reconciler's `PoolMember
5109 // { entered_state_at: p.observed_phase_since().unwrap_or_else
5110 // (Utc::now), .. }` seed synthesizes a "just entered" anchor
5111 // at its own tail rather than materializing a stale timestamp
5112 // at the substrate. Matches the pre-lift `.and_then(|s| s
5113 // .phase_since)` chain's `None` byte-identically at the
5114 // consumer's downstream tail.
5115 let mut p = Process::new("api", empty_spec());
5116 p.status = None;
5117 assert!(p.observed_phase_since().is_none());
5118 }
5119
5120 #[test]
5121 fn observed_phase_since_returns_none_when_slot_is_empty() {
5122 // Populated-status + empty-slot corner pin: a `ProcessStatus`
5123 // whose `phase_since` slot is `None` (a freshly-forked
5124 // Process whose reconciler has not yet stamped a first
5125 // transition) collapses to `None` at the primitive. The
5126 // paired-corner collapse with the missing-`status` corner
5127 // (both → `None`) matches what `.and_then` produces
5128 // structurally — one `None` cannot recover into a `Some` at
5129 // the flat outer wrapper. A regression that swapped the outer
5130 // combinator to `.map(|s| s.phase_since)` would flatten to
5131 // `Option<Option<_>>` and the compiler would reject the
5132 // signature, but a regression that "synthesized" a default
5133 // anchor at the substrate (e.g. `Utc::now()` on the empty
5134 // slot) would silently break the callsite's own
5135 // `.unwrap_or_else(Utc::now)` tail's semantics — the sink
5136 // fires ONCE at the callsite, not twice.
5137 let p = process_with_phase_since(None);
5138 assert!(p.observed_phase_since().is_none());
5139 }
5140
5141 #[test]
5142 fn observed_phase_since_returns_populated_timestamp_verbatim() {
5143 // Populated-slot corner pin: with a populated `status
5144 // .phase_since` slot, the primitive returns the persisted
5145 // `DateTime<Utc>` verbatim — no rounding, no timezone
5146 // stripping, no `Time` wrapper leaked. A regression that
5147 // canonicalized the timestamp (e.g. truncated to the second,
5148 // stripped the timezone marker) would surface here rather
5149 // than as silent skew at the pool reconciler's per-member
5150 // entered-state-at seed comparison against `Utc::now()`
5151 // downstream at `pool_phase_from_members`.
5152 let anchor = crate::time::seconds_ago(720);
5153 let p = process_with_phase_since(Some(anchor));
5154 assert_eq!(p.observed_phase_since(), Some(anchor));
5155 }
5156
5157 #[test]
5158 fn observed_phase_since_is_a_pure_projection() {
5159 // Purity pin: two consecutive calls return byte-identical
5160 // `Option<DateTime<Utc>>` values (no lazy materialization,
5161 // no interior mutation of `self`, no wall-clock read on the
5162 // empty corner). Peer to the sibling
5163 // `is_being_deleted_is_a_pure_projection` +
5164 // `created_at_is_a_pure_projection` +
5165 // `observed_phase_is_a_pure_projection` +
5166 // `observed_phase_or_pending_is_a_pure_projection` pins; all
5167 // five bind the pure-projection discipline on the ONE
5168 // substrate accessor per metadata / status slot. A
5169 // regression that folded the impure `Utc::now()` sink into
5170 // this primitive (rather than keeping it at the callsite's
5171 // `.unwrap_or_else(Utc::now)` tail alongside the sibling
5172 // `created_at` seed) would surface here as two consecutive
5173 // calls that returned distinct `Some(now_1)` /
5174 // `Some(now_2)` values.
5175 let anchor = crate::time::seconds_ago(5);
5176 let p = process_with_phase_since(Some(anchor));
5177 let a = p.observed_phase_since();
5178 let b = p.observed_phase_since();
5179 assert_eq!(a, b);
5180 assert_eq!(a, Some(anchor));
5181 // Empty-slot corner: pure `None`, not a fresh `Utc::now()`.
5182 let p_empty = process_with_phase_since(None);
5183 let a = p_empty.observed_phase_since();
5184 let b = p_empty.observed_phase_since();
5185 assert_eq!(a, b);
5186 assert!(a.is_none());
5187 }
5188
5189 #[test]
5190 fn observed_phase_since_matches_pre_lift_pool_reconciler_chain_shape() {
5191 // Byte-identical parity pin between the copy-form primitive
5192 // here and the pre-lift `tatara-pool-reconciler::
5193 // controller_pool::reconcile_inner` 5-line chain shape
5194 // (without the callsite's `.unwrap_or_else(Utc::now)` tail —
5195 // that tail stays at the callsite). Sweeps every corner
5196 // every pre-lift callsite plausibly encountered: missing
5197 // `status`, populated `status` + empty `phase_since` slot,
5198 // populated `status` + populated `phase_since` slot. A
5199 // regression that inserted a normalization step at the
5200 // primitive the pre-lift chain does NOT apply — or vice
5201 // versa — surfaces here rather than as silent drift between
5202 // the pre-lift consumer site and the ONE substrate owner it
5203 // now routes through.
5204 fn pre_lift(p: &Process) -> Option<DateTime<Utc>> {
5205 p.status.as_ref().and_then(|s| s.phase_since)
5206 }
5207 // Missing status.
5208 let mut p = Process::new("x", empty_spec());
5209 p.status = None;
5210 assert_eq!(p.observed_phase_since(), pre_lift(&p));
5211 // Populated status, empty slot.
5212 let p = process_with_phase_since(None);
5213 assert_eq!(p.observed_phase_since(), pre_lift(&p));
5214 // Populated status, populated slot.
5215 let anchor = crate::time::seconds_ago(90);
5216 let p = process_with_phase_since(Some(anchor));
5217 assert_eq!(p.observed_phase_since(), pre_lift(&p));
5218 }
5219
5220 #[test]
5221 fn observed_phase_since_missing_status_and_empty_slot_collapse_to_the_same_option_shape() {
5222 // Cross-corner coherence pin: the missing-`status` corner
5223 // AND the populated-empty-slot corner return `Option`s
5224 // whose `.is_none()` observations are IDENTICAL — a
5225 // shape peer to `observed_identity_missing_status_and_empty
5226 // _slot_collapse_to_the_same_option_shape`. A regression
5227 // that promoted the missing-`status` corner to returning a
5228 // typed error (via a signature change to `Result<_, _>`) —
5229 // or that widened the empty-slot corner to a synthetic
5230 // `Some(Utc::now())` at the substrate — would surface here
5231 // rather than as silent operator-facing divergence between
5232 // a never-status-written Process and a phase-since-cleared
5233 // Process at the pool reconciler's per-member row builder.
5234 let mut p_no_status = Process::new("api", empty_spec());
5235 p_no_status.status = None;
5236 let p_empty_slot = process_with_phase_since(None);
5237 assert_eq!(
5238 p_no_status.observed_phase_since().is_none(),
5239 p_empty_slot.observed_phase_since().is_none()
5240 );
5241 assert_eq!(
5242 p_no_status.observed_phase_since().is_some(),
5243 p_empty_slot.observed_phase_since().is_some()
5244 );
5245 }
5246
5247 #[test]
5248 fn observed_phase_since_composes_with_unwrap_or_else_utc_now_tail_at_pool_seed() {
5249 // Call-site-shape pin: the `tatara-pool-reconciler::
5250 // controller_pool::reconcile_inner` per-owned-Process
5251 // `PoolMember { entered_state_at: … }` seed composes
5252 // `p.observed_phase_since().unwrap_or_else(Utc::now)`. A
5253 // regression that returned `Some(Utc::now())` on the empty
5254 // corner (folding the sink into the primitive) would break
5255 // the observable contract that a caller with a distinct
5256 // now-source (e.g. an injected `time_source: impl Fn() ->
5257 // DateTime<Utc>`, or a test-time frozen clock) could
5258 // substitute at the tail — this pin binds the empty-corner
5259 // shape by observing that the substrate returns `None` (so
5260 // the `.unwrap_or_else` runs at the callsite) and that the
5261 // populated-corner shape is byte-identical between the
5262 // substrate `Some(anchor)` and the composed
5263 // `Some(anchor).unwrap_or_else(...)` (the fallback never
5264 // fires when the corner is populated). Peer to
5265 // `created_at_composes_with_signed_duration_since_at_ttl_gate`
5266 // on the metadata-timestamp side — both bind the
5267 // composition shape at the callsite so a substrate-side
5268 // refactor cannot silently break the tail semantics.
5269 let anchor = crate::time::seconds_ago(30);
5270 // Populated corner: substrate returns `Some(anchor)` and
5271 // the composed tail returns `anchor` (fallback silent).
5272 let p = process_with_phase_since(Some(anchor));
5273 let composed = p.observed_phase_since().unwrap_or_else(Utc::now);
5274 assert_eq!(composed, anchor);
5275 // Empty corner: substrate returns `None` and the composed
5276 // tail fires `Utc::now()` at the callsite (observed as a
5277 // timestamp >= a `before` sample AND close to now).
5278 let before = Utc::now();
5279 let p = process_with_phase_since(None);
5280 assert!(p.observed_phase_since().is_none());
5281 let composed = p.observed_phase_since().unwrap_or_else(Utc::now);
5282 assert!(composed >= before);
5283 assert!(composed <= Utc::now() + chrono::Duration::seconds(1));
5284 }
5285
5286 // ─── Process::observed_phase_since_or substrate pins ────────────────
5287 //
5288 // Pins the pure composer over `Process::observed_phase_since` that
5289 // owns the paired `.observed_phase_since().unwrap_or_else(Utc::now)`
5290 // chain the pool-reconciler consumer restated by hand pre-lift
5291 // (`tatara-pool-reconciler::controller_pool::reconcile_inner`) and
5292 // that the sibling `observed_phase_since_composes_with_unwrap_or_
5293 // else_utc_now_tail_at_pool_seed` call-site-shape pin binds at
5294 // fail-before-pass-after granularity above. Peer to the sibling
5295 // `created_at_or_*` pin family — both bind the pure-composer
5296 // discipline (fallback owned by the caller, wall-clock read stays
5297 // at the callsite) at one substrate accessor per axis. Fail-before-
5298 // pass-after granularity: `observed_phase_since_or` did not exist
5299 // pre-lift, so any test invoking it fails to compile pre-lift and
5300 // passes post-lift.
5301
5302 #[test]
5303 fn observed_phase_since_or_returns_fallback_when_status_is_none() {
5304 // Missing-`status` corner pin: the composer collapses the
5305 // no-status case to the caller's fallback anchor byte-
5306 // identically to the pre-lift `.unwrap_or(fallback)` tail on
5307 // the `.and_then(|s| s.phase_since)` pure projection. A
5308 // freshly-forked Process whose reconciler has not yet stamped
5309 // a first phase-transition gets the caller's wall-clock read
5310 // (or a test's frozen anchor) synthesized so downstream
5311 // dwell-time / tie-break arithmetic proceeds without a
5312 // special-case branch at each consumer.
5313 let mut p = Process::new("api", empty_spec());
5314 p.status = None;
5315 let fallback = crate::time::seconds_ago(42);
5316 assert_eq!(p.observed_phase_since_or(fallback), fallback);
5317 }
5318
5319 #[test]
5320 fn observed_phase_since_or_returns_fallback_when_slot_is_empty() {
5321 // Populated-`status` + empty-slot corner pin: a
5322 // `ProcessStatus` whose `phase_since` slot is `None`
5323 // collapses to the caller's fallback at the composer. Peer
5324 // to the missing-`status` corner above — both compose the
5325 // `None` output of the pure projection through the same
5326 // `.unwrap_or(fallback)` tail. A regression that returned
5327 // the fallback ONLY on the missing-`status` corner (and
5328 // panicked / returned a stale sentinel on the empty-slot
5329 // corner) would silently break the pool-reconciler's
5330 // per-member row builder on freshly-forked members whose
5331 // reconciler HAD stamped an empty status but not yet a
5332 // first transition.
5333 let p = process_with_phase_since(None);
5334 let fallback = crate::time::seconds_ago(7);
5335 assert_eq!(p.observed_phase_since_or(fallback), fallback);
5336 }
5337
5338 #[test]
5339 fn observed_phase_since_or_returns_anchor_when_slot_is_populated() {
5340 // Populated-slot corner pin: with a populated
5341 // `phase_since` slot, the composer ignores the caller's
5342 // fallback and returns the observed anchor byte-identically
5343 // to the pre-lift `.unwrap_or(fallback)` pass-through.
5344 // Sibling to `observed_phase_since_returns_anchor_when_slot_
5345 // is_populated` — that pin binds the pure projection, this
5346 // pin binds the composer's pass-through on the same
5347 // populated corner.
5348 let anchor = crate::time::seconds_ago(300);
5349 let p = process_with_phase_since(Some(anchor));
5350 let unrelated_fallback = Utc::now() + chrono::Duration::seconds(9_999);
5351 assert_eq!(p.observed_phase_since_or(unrelated_fallback), anchor);
5352 }
5353
5354 #[test]
5355 fn observed_phase_since_or_is_pure_over_the_fallback_argument() {
5356 // Purity pin: the composer itself never reads the wall clock
5357 // — two consecutive calls with the SAME fallback return
5358 // byte-identical `DateTime<Utc>` values on both the missing-
5359 // slot corner (both calls return the caller's fallback) and
5360 // the populated-slot corner (both calls return the observed
5361 // anchor). Peer to the sibling `created_at_or_is_pure_over_
5362 // the_fallback_argument` pin; both bind the pure-composer
5363 // discipline on the ONE substrate accessor per timestamp
5364 // axis.
5365 let fallback = crate::time::seconds_ago(7);
5366 // Missing status.
5367 let mut p = Process::new("x", empty_spec());
5368 p.status = None;
5369 assert_eq!(
5370 p.observed_phase_since_or(fallback),
5371 p.observed_phase_since_or(fallback),
5372 );
5373 // Populated slot.
5374 let anchor = crate::time::seconds_ago(120);
5375 let p = process_with_phase_since(Some(anchor));
5376 assert_eq!(
5377 p.observed_phase_since_or(fallback),
5378 p.observed_phase_since_or(fallback),
5379 );
5380 }
5381
5382 #[test]
5383 fn observed_phase_since_or_matches_pre_lift_unwrap_or_chain_shape() {
5384 // Parity pin: sweeps the three corners every pre-lift
5385 // consumer encountered (missing status, empty slot, populated
5386 // slot) and compares the substrate call against the hand-
5387 // authored pre-lift `.observed_phase_since().unwrap_or(
5388 // fallback)` chain byte-identically. A regression that
5389 // reshaped either corner (returning the fallback on a
5390 // populated slot, returning a sentinel like `DateTime::MIN`
5391 // on the missing corner regardless of the caller's fallback)
5392 // would surface here rather than as silent operator-facing
5393 // skew between the pool convergence snapshot's observed-
5394 // transition anchor and any future observed-transition
5395 // consumer on the SAME `Process` within one reconcile pass.
5396 fn pre_lift(p: &Process, fallback: DateTime<Utc>) -> DateTime<Utc> {
5397 p.observed_phase_since().unwrap_or(fallback)
5398 }
5399 let fallback = crate::time::seconds_ago(13);
5400 // Missing status.
5401 let mut p = Process::new("x", empty_spec());
5402 p.status = None;
5403 assert_eq!(p.observed_phase_since_or(fallback), pre_lift(&p, fallback),);
5404 // Empty slot.
5405 let p = process_with_phase_since(None);
5406 assert_eq!(p.observed_phase_since_or(fallback), pre_lift(&p, fallback),);
5407 // Populated slot.
5408 let anchor = crate::time::seconds_ago(42);
5409 let p = process_with_phase_since(Some(anchor));
5410 assert_eq!(p.observed_phase_since_or(fallback), pre_lift(&p, fallback),);
5411 }
5412
5413 #[test]
5414 fn observed_phase_since_or_composes_with_utc_now_at_reconciler_callsite() {
5415 // Call-site-shape pin: the production consumer
5416 // (`controller_pool::reconcile_inner`'s per-owned-Process
5417 // `PoolMember { entered_state_at, .. }` seed) calls
5418 // `p.observed_phase_since_or(Utc::now())`. On the populated
5419 // corner the wall-clock fallback is irrelevant (the observed
5420 // anchor wins); on the missing/empty corner the fallback
5421 // becomes the resolved value within the sub-second window
5422 // between the caller's `Utc::now()` read and the assertion
5423 // below. This pin binds that the callsite composition
5424 // returns the observed anchor exactly on the populated
5425 // corner (the stable, drift-free assertion) and a "recent"
5426 // wall-clock read on the empty corner (bounded within a
5427 // two-second window to absorb scheduler jitter). A
5428 // regression that silently substituted a different fallback
5429 // (`DateTime::MIN`, a per-cluster prefix offset, a
5430 // hardcoded epoch) would surface at the second half of this
5431 // pin.
5432 // Populated corner: byte-identical to the observed anchor.
5433 let anchor = crate::time::seconds_ago(600);
5434 let p = process_with_phase_since(Some(anchor));
5435 assert_eq!(p.observed_phase_since_or(Utc::now()), anchor);
5436 // Missing status corner: within a two-second wall-clock window.
5437 let mut p = Process::new("x", empty_spec());
5438 p.status = None;
5439 let before = Utc::now();
5440 let resolved = p.observed_phase_since_or(Utc::now());
5441 let after = Utc::now();
5442 assert!(
5443 resolved >= before - chrono::Duration::seconds(2),
5444 "resolved {resolved} is before window start {before}"
5445 );
5446 assert!(
5447 resolved <= after + chrono::Duration::seconds(2),
5448 "resolved {resolved} is after window end {after}"
5449 );
5450 }
5451
5452 // ─── Process::is_being_deleted substrate pins ───────────────────────
5453 //
5454 // Pins the copy-form metadata-projection primitive on the
5455 // deletion-tombstone axis. Peer to the borrow-form + copy-form
5456 // metadata-fallback family (`namespace_or_default`,
5457 // `name_or_placeholder`, `uid_or_empty`, `coordinates_or_defaults`,
5458 // `coordinates_or_none`, `owned_coordinates_or_err`, `annotation`);
5459 // this one opens the presence-probe corner for the tombstone slot.
5460 // Fail-before-pass-after granularity: `is_being_deleted` did not
5461 // exist pre-lift, so any test invoking it fails to compile pre-
5462 // lift and passes post-lift.
5463
5464 fn tombstoned_process() -> Process {
5465 let mut p = Process::new("api-gateway", empty_spec());
5466 p.metadata.namespace = Some("prod".into());
5467 // Routes through the ONE substrate composer
5468 // `tatara_process::time::tombstone_now` — one of 12 pre-lift
5469 // exact-match sites past the ★★ PRIME-DIRECTIVE ≥ 2 threshold
5470 // for the `Some(Time(Utc::now()))` wire shape.
5471 p.metadata.deletion_timestamp = crate::time::tombstone_now();
5472 p
5473 }
5474
5475 #[test]
5476 fn is_being_deleted_returns_false_when_deletion_timestamp_is_absent() {
5477 // Missing-tombstone corner pin: the primitive collapses the
5478 // no-tombstone case to `false` so the SIGTERM preempt at
5479 // `controller::reconcile` skips the `→ Exiting` forcing
5480 // branch and the DELETE-skip at `handle_exiting`'s child
5481 // fan-out does NOT `continue` past a child that is still
5482 // healthy. Matches the pre-lift `.is_some()` chain's `false`
5483 // byte-identically at every consumer's downstream gate.
5484 let mut p = Process::new("api", empty_spec());
5485 p.metadata.deletion_timestamp = None;
5486 assert!(!p.is_being_deleted());
5487 }
5488
5489 #[test]
5490 fn is_being_deleted_returns_true_when_deletion_timestamp_is_present() {
5491 // Present-tombstone corner pin: the primitive returns
5492 // `true` on any populated `metadata.deletionTimestamp`
5493 // slot regardless of the timestamp payload — the two
5494 // consumers only read the tombstone's PRESENCE, never
5495 // its RFC-3339 timestamp value. A regression that gated
5496 // the `true` return on the timestamp being non-epoch, or
5497 // parsed the timestamp before returning, would surface
5498 // here rather than as silent skew at the SIGTERM preempt
5499 // or child-fan-out DELETE-skip on the SAME `Process`.
5500 let p = tombstoned_process();
5501 assert!(p.is_being_deleted());
5502 }
5503
5504 #[test]
5505 fn is_being_deleted_is_a_pure_projection() {
5506 // Purity pin: two consecutive calls return byte-identical
5507 // `bool` values (no lazy materialization, no interior
5508 // mutation of `self`). Peer to the sibling
5509 // `observed_phase_is_a_pure_projection` +
5510 // `observed_pid_is_a_pure_projection` +
5511 // `observed_flux_resources_is_a_pure_projection` +
5512 // `observed_attestation_is_a_pure_projection` pins; all
5513 // five bind the pure-projection discipline on the ONE
5514 // substrate accessor per metadata / status slot.
5515 let p = tombstoned_process();
5516 let a = p.is_being_deleted();
5517 let b = p.is_being_deleted();
5518 assert_eq!(a, b);
5519 assert!(a);
5520 }
5521
5522 #[test]
5523 fn is_being_deleted_matches_pre_lift_reconciler_chain_shape() {
5524 // Parity pin: sweeps the two corners every pre-lift
5525 // consumer plausibly encountered (missing tombstone,
5526 // present tombstone) and compares the substrate call
5527 // against a hand-authored pre-lift chain byte-identically.
5528 // A regression that reshaped either corner would surface
5529 // here rather than as silent operator-facing skew between
5530 // the top-level dispatcher's SIGTERM preempt and the
5531 // SIGTERM cascade's child-fan-out DELETE-skip on the
5532 // SAME `Process` within one reconcile pass.
5533 fn pre_lift(p: &Process) -> bool {
5534 p.metadata.deletion_timestamp.is_some()
5535 }
5536 let mut p = Process::new("api", empty_spec());
5537 p.metadata.deletion_timestamp = None;
5538 assert_eq!(p.is_being_deleted(), pre_lift(&p));
5539 let p = tombstoned_process();
5540 assert_eq!(p.is_being_deleted(), pre_lift(&p));
5541 }
5542
5543 #[test]
5544 fn is_being_deleted_composes_with_process_phase_is_alive_at_reconcile_preempt() {
5545 // Call-site-shape pin: the `controller::reconcile` SIGTERM
5546 // preempt composes `is_being_deleted() && current_phase
5547 // .is_alive()` — the tombstone-presence probe AND the
5548 // alive-phase gate must BOTH hold to force `→ Exiting`.
5549 // A dead-phase (`Zombie` / `Reaped` / `Failed`) Process
5550 // that carries a tombstone still runs its normal handler,
5551 // not the preempt. This pin binds that composition shape
5552 // at the primitive so a regression that flipped either
5553 // half of the `&&` (or that broadened the tombstone probe
5554 // to include the `is_alive` half implicitly) surfaces
5555 // here rather than as silent skew at the top-level
5556 // dispatch on the SAME `Process`.
5557 let mut p = tombstoned_process();
5558 // Alive + tombstoned → preempt fires.
5559 let mut alive = ProcessStatus::default();
5560 alive.phase = ProcessPhase::Running;
5561 p.status = Some(alive);
5562 assert!(p.is_being_deleted());
5563 assert!(p.observed_phase().unwrap_or_default().is_alive());
5564 // Dead + tombstoned → preempt does NOT fire (composition
5565 // with `is_alive` returns false).
5566 let mut dead = ProcessStatus::default();
5567 dead.phase = ProcessPhase::Reaped;
5568 p.status = Some(dead);
5569 assert!(p.is_being_deleted());
5570 assert!(!p.observed_phase().unwrap_or_default().is_alive());
5571 }
5572
5573 // ─── Process::created_at substrate pins ─────────────────────────
5574 //
5575 // Pins the copy-form metadata-projection primitive on the
5576 // `metadata.creationTimestamp` axis that owns the
5577 // `.metadata.creation_timestamp.as_ref().map(|t| t.0)` chain the
5578 // three hand-authored sites (`lifetime_clock::evaluate`,
5579 // `lifetime_clock::requeue_with_ttl`,
5580 // `tatara-reconciler::table_controller`) restated by hand pre-lift.
5581 // Peer to the sibling `is_being_deleted_*` +
5582 // `observed_phase_*` pin families — all three primitives project a
5583 // wire-format `Option<T>` slot into a `Copy` inner value at ONE
5584 // owner. Fail-before-pass-after granularity: `created_at` did not
5585 // exist pre-lift, so any test invoking it fails to compile pre-lift
5586 // and passes post-lift.
5587
5588 fn creation_stamped_process(t: DateTime<Utc>) -> Process {
5589 let mut p = Process::new("age-anchor", empty_spec());
5590 p.metadata.namespace = Some("prod".into());
5591 p.metadata.creation_timestamp =
5592 Some(k8s_openapi::apimachinery::pkg::apis::meta::v1::Time(t));
5593 p
5594 }
5595
5596 #[test]
5597 fn created_at_returns_none_when_creation_timestamp_is_absent() {
5598 // Missing-slot corner pin: the primitive collapses the
5599 // no-creation-timestamp case to `None` so the TTL-expiry gate
5600 // at `lifetime_clock::evaluate` short-circuits its inner
5601 // `if let Some(...)` branch (no elapsed computation), the
5602 // requeue-budget picker returns its default sleep, and the
5603 // stable-name arbiter's `.unwrap_or_else(Utc::now)` tail
5604 // synthesizes a "just created" anchor at its own site. Matches
5605 // the pre-lift `.as_ref().map(|t| t.0)` chain's `None`
5606 // byte-identically at every consumer's downstream tail.
5607 let mut p = Process::new("api", empty_spec());
5608 p.metadata.creation_timestamp = None;
5609 assert!(p.created_at().is_none());
5610 }
5611
5612 #[test]
5613 fn created_at_returns_some_datetime_when_slot_is_populated() {
5614 // Populated-slot corner pin: with a populated
5615 // `metadata.creationTimestamp` slot, the primitive unwraps the
5616 // wire-format `Time` newtype to its inner `DateTime<Utc>` and
5617 // returns it as `Some(datetime)` — hiding the `.0` field-access
5618 // every pre-lift consumer restated to reach the underlying
5619 // instant.
5620 let anchor = crate::time::seconds_ago(300);
5621 let p = creation_stamped_process(anchor);
5622 assert_eq!(p.created_at(), Some(anchor));
5623 }
5624
5625 #[test]
5626 fn created_at_is_a_pure_projection() {
5627 // Purity pin: two consecutive calls return byte-identical
5628 // `Option<DateTime<Utc>>` values (no lazy materialization, no
5629 // interior mutation of `self`). Peer to the sibling
5630 // `is_being_deleted_is_a_pure_projection` +
5631 // `observed_phase_is_a_pure_projection` pins; all three bind
5632 // the pure-projection discipline on the ONE substrate accessor
5633 // per metadata / status slot.
5634 let anchor = Utc::now();
5635 let p = creation_stamped_process(anchor);
5636 let a = p.created_at();
5637 let b = p.created_at();
5638 assert_eq!(a, b);
5639 assert_eq!(a, Some(anchor));
5640 }
5641
5642 #[test]
5643 fn created_at_matches_pre_lift_creation_timestamp_chain_shape() {
5644 // Parity pin: sweeps the two corners every pre-lift consumer
5645 // plausibly encountered (missing slot, populated slot) and
5646 // compares the substrate call against a hand-authored pre-lift
5647 // chain byte-identically. A regression that reshaped either
5648 // corner (returning `Some(Utc::now())` on the missing slot,
5649 // returning a rounded / truncated timestamp on the populated
5650 // slot) would surface here rather than as silent operator-
5651 // facing skew between the TTL-expiry gate, the requeue-budget
5652 // picker, and the stable-name claim-arbiter tie-break on the
5653 // SAME `Process` within one reconcile pass.
5654 fn pre_lift(p: &Process) -> Option<DateTime<Utc>> {
5655 p.metadata.creation_timestamp.as_ref().map(|t| t.0)
5656 }
5657 // Missing slot.
5658 let mut p = Process::new("x", empty_spec());
5659 p.metadata.creation_timestamp = None;
5660 assert_eq!(p.created_at(), pre_lift(&p));
5661 // Populated slot.
5662 let anchor = crate::time::seconds_ago(42);
5663 let p = creation_stamped_process(anchor);
5664 assert_eq!(p.created_at(), pre_lift(&p));
5665 }
5666
5667 #[test]
5668 fn created_at_composes_with_signed_duration_since_at_ttl_gate() {
5669 // Call-site-shape pin: the `lifetime_clock::evaluate` TTL-
5670 // expiry gate composes `now.signed_duration_since(creation)`
5671 // where `creation` is the `DateTime<Utc>` returned by this
5672 // primitive's `Some` corner. A regression that returned a
5673 // per-callsite `Local` timezone (or that stripped the timezone
5674 // marker) would break the arithmetic silently. This pin
5675 // computes the elapsed duration byte-identically against the
5676 // pre-lift `.map(|t| t.0)` chain so a timezone drift surfaces
5677 // here rather than as silent skew at the TTL-expiry decision
5678 // on the SAME `Process` within one reconcile pass.
5679 let now = Utc::now();
5680 let anchor = now - chrono::Duration::seconds(120);
5681 let p = creation_stamped_process(anchor);
5682 let via_primitive = p.created_at().expect("populated slot");
5683 let via_pre_lift = p
5684 .metadata
5685 .creation_timestamp
5686 .as_ref()
5687 .map(|t| t.0)
5688 .expect("populated slot");
5689 assert_eq!(
5690 now.signed_duration_since(via_primitive),
5691 now.signed_duration_since(via_pre_lift)
5692 );
5693 }
5694
5695 // ─── Process::created_at_or substrate pins ──────────────────────
5696 //
5697 // Pins the pure composer over `Process::created_at` that owns the
5698 // paired `.created_at().unwrap_or_else(Utc::now)` chain the two
5699 // production consumers restated by hand pre-lift
5700 // (`tatara-reconciler::table_controller::reconcile_process_table`
5701 // + `tatara-pool-reconciler::controller_pool::reconcile_inner`).
5702 // Fail-before-pass-after granularity: `created_at_or` did not
5703 // exist pre-lift, so any test invoking it fails to compile
5704 // pre-lift and passes post-lift.
5705
5706 #[test]
5707 fn created_at_or_returns_fallback_when_creation_timestamp_is_absent() {
5708 // Missing-slot corner pin: the composer collapses the
5709 // no-creation-timestamp case to the caller's fallback anchor
5710 // byte-identically to the pre-lift `.unwrap_or(fallback)`
5711 // tail. A freshly-forked Process whose API server has not yet
5712 // stamped `metadata.creationTimestamp` gets the caller's
5713 // wall-clock read (or a test's frozen anchor) synthesized so
5714 // downstream dwell-time / tie-break arithmetic proceeds
5715 // without a special-case branch at each consumer.
5716 let mut p = Process::new("api", empty_spec());
5717 p.metadata.creation_timestamp = None;
5718 let fallback = crate::time::seconds_ago(42);
5719 assert_eq!(p.created_at_or(fallback), fallback);
5720 }
5721
5722 #[test]
5723 fn created_at_or_returns_anchor_when_slot_is_populated() {
5724 // Populated-slot corner pin: with a populated
5725 // `metadata.creationTimestamp` slot, the composer ignores the
5726 // caller's fallback and returns the observed anchor
5727 // byte-identically to the pre-lift `.unwrap_or(fallback)`
5728 // pass-through. Sibling to `created_at_returns_some_datetime_
5729 // when_slot_is_populated` — that pin binds the pure projection,
5730 // this pin binds the composer's pass-through on the same
5731 // populated corner.
5732 let anchor = crate::time::seconds_ago(300);
5733 let p = creation_stamped_process(anchor);
5734 let unrelated_fallback = Utc::now() + chrono::Duration::seconds(9_999);
5735 assert_eq!(p.created_at_or(unrelated_fallback), anchor);
5736 }
5737
5738 #[test]
5739 fn created_at_or_is_pure_over_the_fallback_argument() {
5740 // Purity pin: the composer itself never reads the wall clock —
5741 // two consecutive calls with the SAME fallback return
5742 // byte-identical `DateTime<Utc>` values on both the missing-
5743 // slot corner (both calls return the caller's fallback) and
5744 // the populated-slot corner (both calls return the observed
5745 // anchor). Peer to the sibling
5746 // `created_at_is_a_pure_projection` pin; both bind the pure-
5747 // projection / pure-composer discipline on the ONE substrate
5748 // accessor per axis.
5749 let fallback = crate::time::seconds_ago(7);
5750 // Missing slot.
5751 let mut p = Process::new("x", empty_spec());
5752 p.metadata.creation_timestamp = None;
5753 assert_eq!(p.created_at_or(fallback), p.created_at_or(fallback));
5754 // Populated slot.
5755 let anchor = crate::time::seconds_ago(120);
5756 let p = creation_stamped_process(anchor);
5757 assert_eq!(p.created_at_or(fallback), p.created_at_or(fallback));
5758 }
5759
5760 #[test]
5761 fn created_at_or_matches_pre_lift_unwrap_or_chain_shape() {
5762 // Parity pin: sweeps the two corners every pre-lift consumer
5763 // encountered (missing slot, populated slot) and compares the
5764 // substrate call against the hand-authored pre-lift
5765 // `.created_at().unwrap_or(fallback)` chain byte-identically.
5766 // A regression that reshaped either corner (returning the
5767 // fallback on a populated slot, returning `Utc::now()` on the
5768 // missing slot regardless of the caller's fallback) would
5769 // surface here rather than as silent operator-facing skew
5770 // between the claim-arbiter's tie-break anchor and the pool
5771 // convergence snapshot's dwell-time anchor on the SAME
5772 // `Process` within one reconcile pass.
5773 fn pre_lift(p: &Process, fallback: DateTime<Utc>) -> DateTime<Utc> {
5774 p.created_at().unwrap_or(fallback)
5775 }
5776 let fallback = crate::time::seconds_ago(13);
5777 // Missing slot.
5778 let mut p = Process::new("x", empty_spec());
5779 p.metadata.creation_timestamp = None;
5780 assert_eq!(p.created_at_or(fallback), pre_lift(&p, fallback));
5781 // Populated slot.
5782 let anchor = crate::time::seconds_ago(42);
5783 let p = creation_stamped_process(anchor);
5784 assert_eq!(p.created_at_or(fallback), pre_lift(&p, fallback));
5785 }
5786
5787 #[test]
5788 fn created_at_or_composes_with_utc_now_at_reconciler_callsites() {
5789 // Call-site-shape pin: the two production consumers
5790 // (`table_controller::reconcile_process_table` +
5791 // `controller_pool::reconcile_inner`) both call
5792 // `p.created_at_or(Utc::now())`. On the populated corner the
5793 // wall-clock fallback is irrelevant (the observed anchor
5794 // wins); on the missing corner the fallback becomes the
5795 // resolved value within the sub-second window between the
5796 // caller's `Utc::now()` read and the assertion below. This
5797 // pin binds that the callsite composition returns the
5798 // observed anchor exactly on the populated corner (the
5799 // stable, drift-free assertion) and a "recent" wall-clock
5800 // read on the missing corner (bounded within a two-second
5801 // window to absorb scheduler jitter). A regression that
5802 // silently substituted a different fallback (`DateTime::MIN`,
5803 // a per-cluster prefix offset, a hardcoded epoch) would
5804 // surface at the second half of this pin.
5805 // Populated corner: byte-identical to the observed anchor.
5806 let anchor = crate::time::seconds_ago(600);
5807 let p = creation_stamped_process(anchor);
5808 assert_eq!(p.created_at_or(Utc::now()), anchor);
5809 // Missing corner: within a two-second wall-clock window.
5810 let mut p = Process::new("x", empty_spec());
5811 p.metadata.creation_timestamp = None;
5812 let before = Utc::now();
5813 let resolved = p.created_at_or(Utc::now());
5814 let after = Utc::now();
5815 assert!(
5816 resolved >= before - chrono::Duration::seconds(2),
5817 "resolved {resolved} is before window start {before}"
5818 );
5819 assert!(
5820 resolved <= after + chrono::Duration::seconds(2),
5821 "resolved {resolved} is after window end {after}"
5822 );
5823 }
5824
5825 // ─── Process::created_at_or_now substrate pins ──────────────────
5826 //
5827 // Pins the wall-clock-anchored peer of `Process::created_at_or` —
5828 // the ONE substrate owner of the 2-arg `p.created_at_or(Utc::now())`
5829 // chain the two production consumers hand-authored pre-lift
5830 // (`tatara-reconciler::table_controller::reconcile_process_table`
5831 // + `tatara-pool-reconciler::controller_pool::reconcile_inner`).
5832 // Fail-before-pass-after granularity: `created_at_or_now` did not
5833 // exist pre-lift, so any test invoking it fails to compile pre-lift
5834 // and passes post-lift.
5835
5836 #[test]
5837 fn created_at_or_now_returns_wall_clock_when_creation_timestamp_is_absent() {
5838 // Missing-slot corner pin: the peer stamps the wall-clock read
5839 // as the resolved anchor byte-identically to
5840 // `p.created_at_or(Utc::now())` — bounded within a two-second
5841 // window to absorb scheduler jitter between the pin's own
5842 // `Utc::now()` reads and the peer's internal read. A regression
5843 // that silently substituted a different fallback source
5844 // (`DateTime::MIN`, a cached-at-module-load constant, a
5845 // per-namespace override) would surface at this window rather
5846 // than as silent tie-break skew at the claim-arbiter row seed
5847 // or dwell-time skew at the pool convergence snapshot.
5848 let mut p = Process::new("x", empty_spec());
5849 p.metadata.creation_timestamp = None;
5850 let before = Utc::now();
5851 let resolved = p.created_at_or_now();
5852 let after = Utc::now();
5853 assert!(
5854 resolved >= before - chrono::Duration::seconds(2),
5855 "resolved {resolved} is before window start {before}"
5856 );
5857 assert!(
5858 resolved <= after + chrono::Duration::seconds(2),
5859 "resolved {resolved} is after window end {after}"
5860 );
5861 }
5862
5863 #[test]
5864 fn created_at_or_now_returns_anchor_when_slot_is_populated() {
5865 // Populated-slot corner pin: with a populated
5866 // `metadata.creationTimestamp` slot, the peer's internal
5867 // `Utc::now()` fallback is irrelevant and the observed anchor
5868 // wins byte-identically to the 2-arg
5869 // `p.created_at_or(<any-fallback>)` pass-through. Sibling to
5870 // the peer `created_at_or_returns_anchor_when_slot_is_populated`
5871 // pin — both bind the pass-through discipline on the same
5872 // populated corner, one on the pure composer and one on the
5873 // wall-clock-anchored peer.
5874 let anchor = crate::time::seconds_ago(300);
5875 let p = creation_stamped_process(anchor);
5876 assert_eq!(p.created_at_or_now(), anchor);
5877 }
5878
5879 #[test]
5880 fn created_at_or_now_reads_wall_clock_at_call_time_not_module_load() {
5881 // Per-invocation wall-clock-read pin: two consecutive calls on
5882 // a missing-slot Process must return DISTINCT (or at least
5883 // monotonically-non-decreasing) `DateTime<Utc>` values, since
5884 // each call reads a fresh `Utc::now()`. A regression that
5885 // hoisted the wall-clock read to a stale module-load constant
5886 // (or cached the first-invocation value inside `Self`) would
5887 // return the SAME value on the second call — this pin surfaces
5888 // that regression directly, matching the peer-family discipline
5889 // on `PoolStatus::observed_now` / `AllocationStatus::transition_now`
5890 // / `lifetime_clock::evaluate_now` where each invocation reads
5891 // its own `Utc::now()` at the primitive's body.
5892 let mut p = Process::new("x", empty_spec());
5893 p.metadata.creation_timestamp = None;
5894 let first = p.created_at_or_now();
5895 // A `std::thread::sleep(...)` here would be flaky under CI clock
5896 // jitter; the monotonicity check (each call is >= previous)
5897 // suffices to catch the module-load-constant regression class
5898 // because two module-load-constant reads would return identical
5899 // values on a `chrono::DateTime<Utc>` field (equality, not
5900 // ordering, is what the regression breaks).
5901 let second = p.created_at_or_now();
5902 assert!(
5903 second >= first,
5904 "second `created_at_or_now` read {second} must be >= first {first}; \
5905 a regression that cached the wall-clock read at module load \
5906 would return byte-identical values"
5907 );
5908 }
5909
5910 #[test]
5911 fn created_at_or_now_matches_created_at_or_with_utc_now_bytewise() {
5912 // Delegation pin: the peer's body is `self.created_at_or(Utc::now())`
5913 // — a pure delegation, not a re-implementation. On the
5914 // populated corner both surfaces return the observed anchor
5915 // byte-identically (wall-clock fallback is irrelevant). A
5916 // regression that re-implemented the peer with different
5917 // semantics (a different fallback source, a per-slot override
5918 // that only applied to one surface) would surface at the
5919 // populated-corner half of this pin.
5920 let anchor = crate::time::seconds_ago(600);
5921 let p = creation_stamped_process(anchor);
5922 assert_eq!(p.created_at_or_now(), p.created_at_or(Utc::now()));
5923 assert_eq!(p.created_at_or_now(), anchor);
5924 }
5925
5926 #[test]
5927 fn created_at_or_now_composes_at_reconciler_callsites_verbatim() {
5928 // Cross-callsite parity pin: both production consumers
5929 // (`table_controller::reconcile_process_table` +
5930 // `controller_pool::reconcile_inner`) pre-lift called
5931 // `p.created_at_or(Utc::now())` inline; post-lift both call
5932 // `p.created_at_or_now()`. This pin sweeps both the populated
5933 // and missing corners on the SAME `Process` fixture and asserts
5934 // that both surfaces (pre-lift chain, post-lift peer) resolve
5935 // to the same anchor on the populated corner. The missing
5936 // corner is elided from this specific pin because the pre-lift
5937 // and post-lift `Utc::now()` reads happen at different call
5938 // sites (across the `p.created_at_or(Utc::now())` argument
5939 // evaluation vs. the peer's body), so an exact-equality
5940 // assertion between the two reads would race the wall clock —
5941 // the `_reads_wall_clock_at_call_time_not_module_load` pin
5942 // above already binds the per-invocation freshness invariant
5943 // on the missing corner without needing the cross-shape
5944 // equality here.
5945 let anchor = crate::time::seconds_ago(120);
5946 let p = creation_stamped_process(anchor);
5947 let pre_lift_shape = p.created_at_or(Utc::now());
5948 let post_lift_shape = p.created_at_or_now();
5949 assert_eq!(pre_lift_shape, anchor);
5950 assert_eq!(post_lift_shape, anchor);
5951 assert_eq!(pre_lift_shape, post_lift_shape);
5952 }
5953
5954 // ─── Process::resolved_ephemeral substrate pins ─────────────────
5955 //
5956 // Pins the compound spec-projection primitive on the
5957 // `spec.lifetime` axis that owns the ambiguity-aware
5958 // `resolved_ephemeral` chain the three hand-authored sites
5959 // (`lifetime_clock::evaluate`, `lifetime_clock::requeue_with_ttl`,
5960 // `tatara-reconciler::render::render_export_jobs`) restated by
5961 // hand pre-lift through TWO different chains that disagreed on
5962 // the ambiguous corner. Fail-before-pass-after granularity:
5963 // `resolved_ephemeral` did not exist pre-lift on `impl Process`,
5964 // so any test invoking it fails to compile pre-lift and passes
5965 // post-lift.
5966
5967 fn permanent_only_process() -> Process {
5968 let mut spec = empty_spec();
5969 // Routes through the ONE substrate composer
5970 // [`crate::lifetime::Lifetime::permanent`] — one of FOUR
5971 // pre-lift exact-match sites past the ★★ PRIME-DIRECTIVE ≥ 2
5972 // threshold; see the composer's doc-comment for the full
5973 // migration rationale.
5974 spec.lifetime = crate::lifetime::Lifetime::permanent();
5975 Process::new("perm", spec)
5976 }
5977
5978 fn ephemeral_only_process(ttl: &str) -> Process {
5979 let mut spec = empty_spec();
5980 // Routes through the ONE substrate composer
5981 // [`crate::lifetime::Lifetime::ephemeral`] — one of ELEVEN+
5982 // pre-lift exact-match sites past the ★★ PRIME-DIRECTIVE ≥ 2
5983 // threshold; see the composer's doc-comment for the full
5984 // migration rationale.
5985 spec.lifetime = crate::lifetime::Lifetime::ephemeral(EphemeralLifetime {
5986 ttl: ttl.into(),
5987 teardown_policy: crate::lifetime::TeardownPolicy::OnAttested,
5988 max_concurrent: 3,
5989 exports: vec![],
5990 });
5991 Process::new("eph", spec)
5992 }
5993
5994 fn ambiguous_lifetime_process() -> Process {
5995 let mut spec = empty_spec();
5996 spec.lifetime = crate::lifetime::Lifetime {
5997 permanent: Some(crate::lifetime::PermanentLifetime {}),
5998 ephemeral: Some(EphemeralLifetime::default()),
5999 };
6000 Process::new("both", spec)
6001 }
6002
6003 #[test]
6004 fn resolved_ephemeral_returns_none_when_lifetime_is_default_empty() {
6005 // Empty-default corner pin: neither slot populated. The
6006 // resolver collapses to `Permanent(&DEFAULT_PERMANENT)` and
6007 // the compound projection sees no ephemeral inner. Matches
6008 // the pre-lift `lifetime_clock::evaluate` early-return to
6009 // `AutoTerminate::Skip` byte-identically.
6010 let p = Process::new("empty-lifetime", empty_spec());
6011 assert!(p.resolved_ephemeral().is_none());
6012 }
6013
6014 #[test]
6015 fn resolved_ephemeral_returns_none_for_permanent_only_process() {
6016 // Permanent-only corner pin: the `permanent:` slot is
6017 // populated, `ephemeral:` is not. Matches the pre-lift
6018 // `lifetime_clock::evaluate` outcome — the teardown/TTL
6019 // branch is never reached on a Permanent Process, and the
6020 // export-render arm now agrees at this call site (was
6021 // previously reached through the raw `.ephemeral.as_ref()`
6022 // that also returned `None` on this same corner — no drift
6023 // here; the drift is at the ambiguous corner below).
6024 let p = permanent_only_process();
6025 assert!(p.resolved_ephemeral().is_none());
6026 }
6027
6028 #[test]
6029 fn resolved_ephemeral_returns_some_for_ephemeral_only_process() {
6030 // Ephemeral-only corner pin: the ONE arm that projects. The
6031 // returned borrow carries the operator-authored `ttl` /
6032 // `teardown_policy` / `max_concurrent` verbatim. A
6033 // regression that swapped the projection to the sibling
6034 // `permanent:` slot would surface here as a type mismatch on
6035 // the `EphemeralLifetime` fields rather than as silent
6036 // operator-facing no-op teardown at the reconciler.
6037 let p = ephemeral_only_process("42m");
6038 let e = p
6039 .resolved_ephemeral()
6040 .expect("ephemeral-only Process must project");
6041 assert_eq!(e.ttl, "42m");
6042 assert_eq!(
6043 e.teardown_policy,
6044 crate::lifetime::TeardownPolicy::OnAttested
6045 );
6046 assert_eq!(e.max_concurrent, 3);
6047 }
6048
6049 #[test]
6050 fn resolved_ephemeral_returns_none_for_ambiguous_lifetime() {
6051 // DRIFT-CLOSING CONTRACT: BOTH `permanent:` AND `ephemeral:`
6052 // slots populated is an operator-authored mis-configuration.
6053 // Pre-lift, `lifetime_clock::evaluate` (via
6054 // `resolved_ephemeral()` on `Lifetime`) collapsed this
6055 // corner to `None` and yielded `AutoTerminate::Skip`, while
6056 // `tatara-reconciler::render::render_export_jobs` walked
6057 // the naked `.spec.lifetime.ephemeral.as_ref()` chain and
6058 // returned `Some(&e)` — so the reconciler would emit export
6059 // Jobs on a Process whose teardown-triggered fire semantics
6060 // the lifetime clock refused to honor. Post-lift this
6061 // primitive collapses ambiguity to `None` at ONE site so
6062 // BOTH consumers agree. A regression that broadened the
6063 // projection back to the raw field (or that silently
6064 // "preferred ephemeral" in the ambiguous case) surfaces
6065 // here rather than as export-Job noise on a mis-configured
6066 // ephemeral.
6067 let p = ambiguous_lifetime_process();
6068 assert!(p.resolved_ephemeral().is_none());
6069 // The raw field IS populated at this corner — pins the
6070 // pre-lift `.spec.lifetime.ephemeral.as_ref()` shape that
6071 // returned `Some` here.
6072 assert!(p.spec.lifetime.ephemeral.is_some());
6073 }
6074
6075 #[test]
6076 fn resolved_ephemeral_matches_spec_lifetime_forwarder() {
6077 // Byte-identity pin: the `Process` projection delegates
6078 // through the underlying `Lifetime::resolved_ephemeral`
6079 // primitive at every corner (empty, permanent-only,
6080 // ephemeral-only, ambiguous). A regression that silently
6081 // reintroduced the raw `.ephemeral.as_ref()` shortcut, or
6082 // that decided the ambiguous case by "prefer ephemeral"
6083 // at the Process layer instead of delegating, surfaces
6084 // here.
6085 for p in [
6086 Process::new("empty", empty_spec()),
6087 permanent_only_process(),
6088 ephemeral_only_process("1h"),
6089 ambiguous_lifetime_process(),
6090 ] {
6091 let via_process = p.resolved_ephemeral();
6092 let via_lifetime = p.spec.lifetime.resolved_ephemeral();
6093 // Both borrows point into the SAME `EphemeralLifetime`
6094 // slot when present — a regression that materialized a
6095 // per-call clone at the Process layer would fail the
6096 // pointer-equality gate.
6097 match (via_process, via_lifetime) {
6098 (Some(a), Some(b)) => assert!(
6099 std::ptr::eq(a, b),
6100 "Process::resolved_ephemeral must borrow the same slot as Lifetime::resolved_ephemeral"
6101 ),
6102 (None, None) => {}
6103 (a, b) => panic!(
6104 "resolved_ephemeral shape drift: process={:?}, lifetime={:?}",
6105 a.is_some(),
6106 b.is_some()
6107 ),
6108 }
6109 }
6110 }
6111
6112 #[test]
6113 fn resolved_ephemeral_is_a_pure_projection() {
6114 // Purity pin: two consecutive calls return borrows into the
6115 // same underlying slot (no lazy materialization, no interior
6116 // mutation of `self`). Peer to the sibling
6117 // `is_being_deleted_is_a_pure_projection` +
6118 // `observed_attestation_is_a_pure_projection` pins; all
6119 // three bind the pure-projection discipline on the ONE
6120 // substrate accessor per spec / metadata / status slot.
6121 let p = ephemeral_only_process("5m");
6122 let a = p.resolved_ephemeral();
6123 let b = p.resolved_ephemeral();
6124 match (a, b) {
6125 (Some(x), Some(y)) => assert!(std::ptr::eq(x, y)),
6126 other => panic!("expected two Some borrows into the same slot, got {other:?}"),
6127 }
6128 }
6129
6130 // ── ProcessSpec::gate_compute_defaults substrate pins ───────────────
6131 //
6132 // The 12-line `ProcessSpec { identity: <Default>, classification:
6133 // Classification::gate_compute(), intent: <Default>, boundary:
6134 // Default::default(), compliance: Default::default(), depends_on:
6135 // vec![], signals: Default::default(), lifetime: Default::default(),
6136 // routing: None, encapsulates: None, suspended: false }` struct-
6137 // literal was open-coded verbatim at eight hand-authored callsites
6138 // before this primitive closed it. These pins bind the composed
6139 // shape at fail-before-pass-after granularity so a regression that
6140 // drifted the classification baseline, promoted a defaulted slot to
6141 // a non-default, or leaked a non-baseline slot into the substrate
6142 // composer surfaces HERE rather than as silent operator-visible
6143 // drift across every test fixture that keys assertions on the
6144 // shape.
6145 fn hand_authored_pre_lift() -> ProcessSpec {
6146 ProcessSpec {
6147 identity: IdentitySpec::default(),
6148 classification: Classification::gate_compute(),
6149 intent: Intent::default(),
6150 boundary: Default::default(),
6151 compliance: Default::default(),
6152 depends_on: vec![],
6153 signals: Default::default(),
6154 lifetime: Default::default(),
6155 routing: None,
6156 encapsulates: None,
6157 suspended: false,
6158 }
6159 }
6160
6161 #[test]
6162 fn gate_compute_defaults_composes_the_classification_baseline() {
6163 // Primary shape: the classification axis rides the sibling
6164 // `Classification::gate_compute` primitive verbatim. A
6165 // regression that flipped the classification baseline (a new
6166 // `#[default]` on the sibling closed-set, a re-import through a
6167 // different composer) surfaces HERE rather than at every
6168 // downstream fixture whose assertions key on
6169 // `spec.classification`.
6170 let s = ProcessSpec::gate_compute_defaults();
6171 assert_eq!(s.classification, Classification::gate_compute());
6172 }
6173
6174 #[test]
6175 fn gate_compute_defaults_defaulted_slots_ride_sibling_defaults() {
6176 // Pins the sibling-default correspondence the doc comment
6177 // names — a regression that promoted any defaulted slot to a
6178 // non-default (a new `#[default]` on `Intent`, a `Lifetime`
6179 // baseline shift, a per-field overlay stamping through the
6180 // primitive) would move the baseline HERE rather than at every
6181 // downstream consumer.
6182 let s = ProcessSpec::gate_compute_defaults();
6183 assert_eq!(
6184 serde_json::to_value(&s.identity).unwrap(),
6185 serde_json::to_value(IdentitySpec::default()).unwrap()
6186 );
6187 assert_eq!(
6188 serde_json::to_value(&s.intent).unwrap(),
6189 serde_json::to_value(Intent::default()).unwrap()
6190 );
6191 assert_eq!(
6192 serde_json::to_value(&s.boundary).unwrap(),
6193 serde_json::to_value(Boundary::default()).unwrap()
6194 );
6195 assert_eq!(
6196 serde_json::to_value(&s.compliance).unwrap(),
6197 serde_json::to_value(ComplianceSpec::default()).unwrap()
6198 );
6199 assert!(s.depends_on.is_empty());
6200 assert_eq!(
6201 serde_json::to_value(&s.signals).unwrap(),
6202 serde_json::to_value(SignalPolicy::default()).unwrap()
6203 );
6204 assert_eq!(
6205 serde_json::to_value(&s.lifetime).unwrap(),
6206 serde_json::to_value(Lifetime::default()).unwrap()
6207 );
6208 assert!(s.routing.is_none());
6209 assert!(s.encapsulates.is_none());
6210 assert!(!s.suspended);
6211 }
6212
6213 #[test]
6214 fn gate_compute_defaults_matches_hand_authored_pre_lift_bytewise() {
6215 // Byte-identical parity pin between the substrate primitive
6216 // and the pre-lift 12-line struct-literal that recurred at
6217 // eight hand-authored sites. Compares via `serde_json` value
6218 // equality — `ProcessSpec` does not derive `PartialEq` (the
6219 // typed fields it composes over do not uniformly derive it),
6220 // so a serialize round-trip is the shape-equality currency the
6221 // pin family already uses for `ProcessSpec`-shaped assertions
6222 // elsewhere in this test module. A regression that reshaped
6223 // the primitive would diverge from the pre-lift block HERE
6224 // rather than at every downstream fixture that keys on the
6225 // shape.
6226 let composed = ProcessSpec::gate_compute_defaults();
6227 let hand_authored = hand_authored_pre_lift();
6228 assert_eq!(
6229 serde_json::to_value(&composed).unwrap(),
6230 serde_json::to_value(&hand_authored).unwrap(),
6231 );
6232 }
6233
6234 #[test]
6235 fn gate_compute_defaults_supports_struct_update_override() {
6236 // The five override sites (three `render.rs` fixtures + two
6237 // `lifetime_clock.rs` fixtures) rely on struct-update syntax
6238 // to override a single slot while the primitive supplies the
6239 // other eleven. Pin the composition here so a regression that
6240 // broke the struct-update path (e.g. a `#[non_exhaustive]`
6241 // attribute added to `ProcessSpec` that would refuse struct-
6242 // update syntax across crate boundaries) surfaces at compile
6243 // time HERE rather than as a five-site downstream break.
6244 let base = ProcessSpec::gate_compute_defaults();
6245 let overridden = ProcessSpec {
6246 suspended: true,
6247 ..ProcessSpec::gate_compute_defaults()
6248 };
6249 assert!(!base.suspended);
6250 assert!(overridden.suspended);
6251 // Every other slot rides the same default as the base.
6252 assert_eq!(
6253 serde_json::to_value(&overridden.classification).unwrap(),
6254 serde_json::to_value(&base.classification).unwrap(),
6255 );
6256 assert_eq!(
6257 serde_json::to_value(&overridden.lifetime).unwrap(),
6258 serde_json::to_value(&base.lifetime).unwrap(),
6259 );
6260 }
6261
6262 #[test]
6263 fn gate_compute_defaults_is_call_time_construction_not_a_shared_singleton() {
6264 // Two independent calls produce structurally-equal but
6265 // distinct values — pins that the primitive is a plain
6266 // constructor rather than a `lazy_static` clone whose in-
6267 // place mutation at one consumer would silently mutate the
6268 // shape at every other consumer. Mirrors the sibling
6269 // `gate_compute_is_call_time_construction_not_a_shared_singleton`
6270 // pin on `Classification::gate_compute`.
6271 let a = ProcessSpec::gate_compute_defaults();
6272 let b = ProcessSpec::gate_compute_defaults();
6273 assert_eq!(
6274 serde_json::to_value(&a).unwrap(),
6275 serde_json::to_value(&b).unwrap(),
6276 );
6277 assert!(!std::ptr::eq(&a, &b));
6278 }
6279
6280 // ─── ProcessStatus::at_phase substrate pins ─────────────────────
6281 //
6282 // The 3-line `ProcessStatus { phase: <ProcessPhase::…>, ..Default::
6283 // default() }` shape now rides through the ONE substrate composer
6284 // [`ProcessStatus::at_phase`] across the two pool-reconciler
6285 // phase-decision pin sites (`process_to_member_state_attested_
6286 // permanent_is_free`, `process_to_member_state_attested_ephemeral_
6287 // is_allocated`). These pins bind the primitive at fail-before-pass-
6288 // after granularity so a regression that drifted the phase slot
6289 // pass-through, leaked a sibling slot away from `Default`, or
6290 // hijacked the composer to stamp a static `phase_since` /
6291 // `attestation` on the `phase` transition surfaces HERE rather
6292 // than as silent phase-decision skew across the two pool-reconciler
6293 // callsites (or across any future consumer fixture that binds a
6294 // phase-observation shape).
6295
6296 #[test]
6297 fn at_phase_binds_caller_supplied_phase_verbatim_at_the_phase_slot() {
6298 // The composer's `phase` slot is the caller-supplied
6299 // `ProcessPhase` verbatim — no case-fold, no substitution, no
6300 // remapping to a peer variant. Sweep every variant so a
6301 // regression that hijacked one arm to stamp a different variant
6302 // silently would surface here (per-variant coverage matters
6303 // because the pool-reconciler's `process_to_member_state`
6304 // matcher already keys on `ProcessPhase::Attested` specifically,
6305 // and a peer variant lift would need the pass-through to
6306 // faithfully carry any of the eight variants without translation).
6307 for phase in [
6308 ProcessPhase::Pending,
6309 ProcessPhase::Forking,
6310 ProcessPhase::Execing,
6311 ProcessPhase::Running,
6312 ProcessPhase::Reconverging,
6313 ProcessPhase::Attested,
6314 ProcessPhase::Failed,
6315 ProcessPhase::Exiting,
6316 ] {
6317 let s = ProcessStatus::at_phase(phase);
6318 assert_eq!(
6319 s.phase, phase,
6320 "at_phase({phase:?}) must stamp the caller-supplied phase verbatim",
6321 );
6322 }
6323 }
6324
6325 #[test]
6326 fn at_phase_leaves_every_other_slot_at_default_no_sibling_leak() {
6327 // The composer stamps ONLY the `phase` slot — every other slot
6328 // (`pid`, `parent`, `children`, `identity`, `phase_since`,
6329 // `attestation`, `flux_resources`, `boundary`, `compliance`,
6330 // `signal_queue`, `conditions`, `message`, `exit_code`) parks at
6331 // `Default`. A regression that widened the composer's stamped
6332 // slot set (an auto-stamped `phase_since = Utc::now()` overlay
6333 // that would break byte-identical parity with the pre-lift
6334 // 3-line struct-literal, a defaulted-non-empty `flux_resources`
6335 // fixture that would silently reshape every pool-reconciler
6336 // phase-decision test's downstream `.flux_resources` observation)
6337 // surfaces HERE at the pin block rather than as silent skew
6338 // at every fixture consumer.
6339 let s = ProcessStatus::at_phase(ProcessPhase::Attested);
6340 assert!(s.pid.is_none(), "pid parks at Default (None)");
6341 assert!(s.parent.is_none(), "parent parks at Default (None)");
6342 assert!(
6343 s.children.is_empty(),
6344 "children parks at Default (Vec::new())"
6345 );
6346 assert!(s.identity.is_none(), "identity parks at Default (None)");
6347 assert!(
6348 s.phase_since.is_none(),
6349 "phase_since parks at Default (None) — a call-time Utc::now() stamp would break \
6350 byte-identical parity with the pre-lift `..Default::default()` struct-update shape",
6351 );
6352 assert!(
6353 s.attestation.is_none(),
6354 "attestation parks at Default (None)"
6355 );
6356 assert!(
6357 s.flux_resources.is_empty(),
6358 "flux_resources parks at Default (Vec::new())",
6359 );
6360 assert_eq!(
6361 serde_json::to_value(&s.boundary).unwrap(),
6362 serde_json::to_value(BoundaryStatus::default()).unwrap(),
6363 "boundary parks at Default",
6364 );
6365 assert_eq!(
6366 serde_json::to_value(&s.compliance).unwrap(),
6367 serde_json::to_value(ComplianceStatus::default()).unwrap(),
6368 "compliance parks at Default",
6369 );
6370 assert!(
6371 s.signal_queue.is_empty(),
6372 "signal_queue parks at Default (Vec::new())",
6373 );
6374 assert!(
6375 s.conditions.is_empty(),
6376 "conditions parks at Default (Vec::new())"
6377 );
6378 assert!(s.message.is_none(), "message parks at Default (None)");
6379 assert!(s.exit_code.is_none(), "exit_code parks at Default (None)");
6380 }
6381
6382 #[test]
6383 fn at_phase_matches_hand_authored_pre_lift_bytewise() {
6384 // Byte-identical parity pin between the substrate composer and
6385 // the pre-lift 3-line `ProcessStatus { phase: <p>, ..Default::
6386 // default() }` struct-literal that recurred at both pool-
6387 // reconciler pin sites. Compares via `serde_json` value
6388 // equality — `ProcessStatus` does not derive `PartialEq` (the
6389 // typed fields it composes over do not uniformly derive it),
6390 // so a serialize round-trip is the shape-equality currency the
6391 // pin family already uses for status-shaped assertions in this
6392 // module (see the sibling `gate_compute_defaults_matches_hand_
6393 // authored_pre_lift_bytewise` pin on the spec side). A
6394 // regression that reshaped the primitive would diverge from
6395 // the pre-lift struct-literal HERE rather than at every
6396 // downstream fixture that keys on the shape.
6397 for phase in [
6398 ProcessPhase::Attested,
6399 ProcessPhase::Running,
6400 ProcessPhase::Pending,
6401 ] {
6402 let composed = ProcessStatus::at_phase(phase);
6403 let hand_authored = ProcessStatus {
6404 phase,
6405 ..Default::default()
6406 };
6407 assert_eq!(
6408 serde_json::to_value(&composed).unwrap(),
6409 serde_json::to_value(&hand_authored).unwrap(),
6410 "primitive must be byte-identical to the pre-lift struct-literal for phase {phase:?}",
6411 );
6412 }
6413 }
6414
6415 #[test]
6416 fn at_phase_is_call_time_construction_not_a_shared_singleton() {
6417 // Two independent calls produce structurally-equal but distinct
6418 // values — pins that the primitive is a plain constructor
6419 // rather than a `lazy_static` clone whose in-place mutation at
6420 // one consumer would silently mutate the shape at every other
6421 // consumer. Mirrors the sibling
6422 // `gate_compute_defaults_is_call_time_construction_not_a_shared_singleton`
6423 // pin on `ProcessSpec::gate_compute_defaults`.
6424 let a = ProcessStatus::at_phase(ProcessPhase::Attested);
6425 let b = ProcessStatus::at_phase(ProcessPhase::Attested);
6426 assert_eq!(
6427 serde_json::to_value(&a).unwrap(),
6428 serde_json::to_value(&b).unwrap(),
6429 );
6430 assert!(!std::ptr::eq(&a, &b));
6431 }
6432
6433 #[test]
6434 fn at_phase_default_variant_equals_process_status_default() {
6435 // Handing the composer the `ProcessPhase::default()` variant
6436 // yields a value byte-identical to `ProcessStatus::default()`
6437 // itself — pins that the composer's ONLY divergence from
6438 // `Default` is the caller-supplied `phase` slot, and that when
6439 // the caller passes the same variant `phase` already defaults
6440 // to, the composer collapses cleanly to the plain default.
6441 // A regression that stamped a non-default value on any sibling
6442 // slot (a runtime timestamp on `phase_since`, a synthetic
6443 // `identity` seed) would break this collapse and surface HERE.
6444 let default_phase = ProcessPhase::default();
6445 let via_at_phase = ProcessStatus::at_phase(default_phase);
6446 let via_default = ProcessStatus::default();
6447 assert_eq!(
6448 serde_json::to_value(&via_at_phase).unwrap(),
6449 serde_json::to_value(&via_default).unwrap(),
6450 );
6451 }
6452
6453 // ─── Process::owned_name_and_uid_or_err substrate pins ─────────────
6454 //
6455 // Fail-before-pass-after granularity: the
6456 // `Process::owned_name_and_uid_or_err` method did not exist before
6457 // this commit, so each test below fails to compile pre-lift.
6458 // Post-lift they collectively pin the paired 2-slot required-
6459 // extract shape at ONE substrate owner — a regression that swapped
6460 // the two gates (uid before name), drifted the wire-form back to
6461 // the pre-lift `tatara-reconciler::ssapply::build_owner_reference`
6462 // lowercase-verb spelling (`"process missing metadata.name"`),
6463 // relaxed either gate to a silent-string fallback (an `unwrap_or_default`
6464 // that would silently propagate as an orphan owner reference at
6465 // `owner_references_json`'s empty-uid `is_empty` gate), or reshaped
6466 // the axis order of the return tuple (`(uid, name)` — bytewise
6467 // wrong at the `owner_reference_json(name, uid)` positional-arg
6468 // consumer) surfaces HERE rather than as silent operator-facing
6469 // drift at the pre-lift ssapply consumer whose downstream fed
6470 // `owner_reference_json` positionally.
6471
6472 #[test]
6473 fn owned_name_and_uid_or_err_returns_owned_pair_when_both_slots_present() {
6474 // Happy-path pin: both slots populated — method returns owned
6475 // `String`s in `(name, uid)` axis order (matches
6476 // `owner_reference_json(name, uid)` positional-arg order).
6477 let mut p = Process::new("api-gateway", empty_spec());
6478 p.metadata.uid = Some("uid-abc-123".into());
6479 let (name, uid) = p.owned_name_and_uid_or_err().unwrap();
6480 assert_eq!(name, "api-gateway");
6481 assert_eq!(uid, "uid-abc-123");
6482 // Ownership pin: type inference above binds name/uid as owned
6483 // Strings — a regression that returned `&str` would fail to
6484 // compile at the following .push_str() call. Holds the "owned"
6485 // half of the primitive's contract.
6486 let mut owned_uid = uid;
6487 owned_uid.push_str("-mutated");
6488 assert_eq!(owned_uid, "uid-abc-123-mutated");
6489 }
6490
6491 #[test]
6492 fn owned_name_and_uid_or_err_errors_when_metadata_uid_absent() {
6493 // Uid-gate pin: name populated (via `Process::new`), uid
6494 // absent → Err mentioning `metadata.uid`. Load-bearing at
6495 // the pre-lift `ssapply::build_owner_reference` caller whose
6496 // downstream `owner_reference_json` cannot compose without
6497 // both slots.
6498 let p = Process::new("some-proc", empty_spec());
6499 // Process::new leaves metadata.uid = None by default.
6500 let err = p.owned_name_and_uid_or_err().unwrap_err();
6501 assert_eq!(err.to_string(), "Process has no metadata.uid");
6502 }
6503
6504 #[test]
6505 fn owned_name_and_uid_or_err_errors_when_metadata_name_absent() {
6506 // Name-gate pin: name absent → Err mentioning `metadata.name`.
6507 // The name gate fires FIRST — see the paired ordering pin
6508 // below for the missing-both corner.
6509 let mut p = Process::new("scratch", empty_spec());
6510 p.metadata.name = None;
6511 p.metadata.uid = Some("uid-42".into());
6512 let err = p.owned_name_and_uid_or_err().unwrap_err();
6513 assert_eq!(err.to_string(), "Process has no metadata.name");
6514 }
6515
6516 #[test]
6517 fn owned_name_and_uid_or_err_reports_name_first_when_both_slots_absent() {
6518 // Ordering pin: on a `Process` fixture missing BOTH slots the
6519 // returned error names `metadata.name` (matches how
6520 // `owned_coordinates_or_err` orders its two gates on the
6521 // sibling `(namespace, name)` primitive — the "first-missing-
6522 // slot" slug the family surfaces at the paired missing-both
6523 // corner is consistently the FIRST gate). A regression that
6524 // swapped the two gates would flip the reported slug and
6525 // surface HERE rather than as a subtle wire-form drift in
6526 // operator alerts bisecting a "which slot is missing" fault.
6527 let mut p = Process::new("scratch", empty_spec());
6528 p.metadata.name = None;
6529 p.metadata.uid = None;
6530 let err = p.owned_name_and_uid_or_err().unwrap_err();
6531 assert_eq!(err.to_string(), "Process has no metadata.name");
6532 }
6533
6534 #[test]
6535 fn owned_name_and_uid_or_err_wire_form_matches_owned_coordinates_or_err_family_spelling() {
6536 // Cross-primitive wire-form coherence pin — BOTH gates of this
6537 // method's error output use the workspace-canonical
6538 // `"Process has no metadata.<slot>"` spelling
6539 // `Self::owned_coordinates_or_err` pins in the family. A
6540 // regression that reverted either gate to the pre-lift
6541 // `ssapply::build_owner_reference` lowercase-verb spelling
6542 // (`"process missing metadata.<slot>"`) would reopen a
6543 // workspace-wide operator-facing wire-form drift the lift
6544 // closed, and surface HERE rather than as silent
6545 // divergence between the two owned-required-extract primitives
6546 // in the family (operators bisecting a "which slot faulted"
6547 // alert see a mixed-case grep footprint when the two are out
6548 // of sync).
6549 let mut p_name_absent = Process::new("s", empty_spec());
6550 p_name_absent.metadata.name = None;
6551 assert_eq!(
6552 p_name_absent
6553 .owned_name_and_uid_or_err()
6554 .unwrap_err()
6555 .to_string(),
6556 "Process has no metadata.name",
6557 );
6558 let p_uid_absent = Process::new("s", empty_spec());
6559 // `Process::new` leaves metadata.uid = None.
6560 assert_eq!(
6561 p_uid_absent
6562 .owned_name_and_uid_or_err()
6563 .unwrap_err()
6564 .to_string(),
6565 "Process has no metadata.uid",
6566 );
6567 }
6568
6569 #[test]
6570 fn owned_name_and_uid_or_err_matches_pre_lift_reconciler_helper_shape() {
6571 // Byte-identical parity pin between the paired required-extract
6572 // primitive here and the pre-lift
6573 // `tatara-reconciler::ssapply::build_owner_reference` helper
6574 // shape — the exact 2-slot unwrap chain the pre-lift caller
6575 // spelled by hand (with the wire-form updated from the pre-lift
6576 // lowercase-verb spelling to the workspace-canonical
6577 // `owned_coordinates_or_err`-family spelling — the intentional
6578 // wire-form drift-close per the primitive's docs).
6579 //
6580 // Sweeps every corner every callsite plausibly encounters
6581 // (both slots present, uid absent, name absent, both absent).
6582 // A regression that inserted a normalization step at the
6583 // primitive that the pre-lift chain does NOT apply — or vice
6584 // versa — surfaces here rather than as silent drift between
6585 // the pre-lift consumer callsite and the ONE substrate owner
6586 // it now routes through.
6587 fn pre_lift(p: &Process) -> anyhow::Result<(String, String)> {
6588 let name = p
6589 .metadata
6590 .name
6591 .clone()
6592 .ok_or_else(|| anyhow::anyhow!("Process has no metadata.name"))?;
6593 let uid = p
6594 .metadata
6595 .uid
6596 .clone()
6597 .ok_or_else(|| anyhow::anyhow!("Process has no metadata.uid"))?;
6598 Ok((name, uid))
6599 }
6600 // Both present.
6601 let mut p = Process::new("api", empty_spec());
6602 p.metadata.uid = Some("uid-1".into());
6603 assert_eq!(
6604 p.owned_name_and_uid_or_err().unwrap(),
6605 pre_lift(&p).unwrap()
6606 );
6607 // Uid absent (name populated by Process::new).
6608 let p = Process::new("api", empty_spec());
6609 assert_eq!(
6610 p.owned_name_and_uid_or_err().unwrap_err().to_string(),
6611 pre_lift(&p).unwrap_err().to_string(),
6612 );
6613 // Name absent, uid present.
6614 let mut p = Process::new("api", empty_spec());
6615 p.metadata.name = None;
6616 p.metadata.uid = Some("uid-1".into());
6617 assert_eq!(
6618 p.owned_name_and_uid_or_err().unwrap_err().to_string(),
6619 pre_lift(&p).unwrap_err().to_string(),
6620 );
6621 // Both absent — the name gate fires first at both routes.
6622 let mut p = Process::new("api", empty_spec());
6623 p.metadata.name = None;
6624 p.metadata.uid = None;
6625 assert_eq!(
6626 p.owned_name_and_uid_or_err().unwrap_err().to_string(),
6627 pre_lift(&p).unwrap_err().to_string(),
6628 );
6629 }
6630
6631 #[test]
6632 fn owned_name_and_uid_or_err_axis_order_matches_owner_reference_json_positional_args() {
6633 // Cross-substrate composition pin — the `(name, uid)` tuple
6634 // this primitive returns MUST feed
6635 // `crate::owner_reference_json(name, uid)` positionally without
6636 // an axis-swap step. A regression that reshaped the return
6637 // tuple to `(uid, name)` would type-check silently (both
6638 // arguments are `&str`) but produce a bytewise wrong owner
6639 // reference whose `name` slot carried the uid string. Load-
6640 // bearing at every downstream K8s apiserver reader of the
6641 // stamped OwnerReference (garbage-collector cascade-delete
6642 // fan-out, the `KUBECTL get -o wide` output an operator
6643 // inspects, every controller reconciling a downstream-owned
6644 // resource).
6645 let mut p = Process::new("owner-name", empty_spec());
6646 p.metadata.uid = Some("owner-uid".into());
6647 let (name, uid) = p.owned_name_and_uid_or_err().unwrap();
6648 let owner_ref = crate::owner_reference_json(&name, &uid);
6649 assert_eq!(owner_ref["name"], "owner-name");
6650 assert_eq!(owner_ref["uid"], "owner-uid");
6651 }
6652}