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