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::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 /// Borrowed lookup of ONE key in `metadata.annotations`, with
290 /// BOTH the missing-`annotations` corner AND the missing-key
291 /// corner collapsed to `None` — the ONE-liner collapse of the
292 /// paired `self.metadata.annotations.as_ref().and_then(|m|
293 /// m.get(key)).map(String::as_str)` incantation every consumer
294 /// restated by hand pre-lift.
295 ///
296 /// Pre-lift the 3-line `.metadata.annotations.as_ref().and_then
297 /// (|m| m.get(KEY))` chain (in three tail variants — `.cloned()`,
298 /// `.cloned().unwrap_or_default()`, `.map(String::as_str)`) was
299 /// hand-authored at THREE sites past the ★★ PRIME-DIRECTIVE ≥ 2
300 /// duplication threshold across the workspace:
301 /// * `tatara-reconciler::signals::ingest` — SIGNAL annotation
302 /// lookup (pre-lift `.cloned()` for owned parsing).
303 /// * `tatara-reconciler::phase_machine::released_from_annotation`
304 /// — RELEASED_FROM annotation lookup (pre-lift `.cloned()
305 /// .unwrap_or_default()` for `match v.as_str()`).
306 /// * `tatara-pool-reconciler::controller_pool::process_belongs_to_pool`
307 /// — POOL annotation lookup (pre-lift `.map(String::as_str)`
308 /// for `== Some(pool_name)`).
309 ///
310 /// All THREE sites walked the SAME 3-line chain — read the
311 /// annotations map, gate on presence, index by key — differing
312 /// only in the tail that shaped the result. Post-lift each
313 /// caller routes through the ONE substrate primitive here and
314 /// applies its own tail at its own site (`.map(str::to_string)`
315 /// / bare match / `==`).
316 ///
317 /// Return-form axis: `Option<&str>` mirrors the existing borrow-
318 /// first discipline of the peer metadata primitives
319 /// [`Self::namespace_or_default`], [`Self::name_or_placeholder`],
320 /// [`Self::coordinates_or_none`]. The two corners the chain
321 /// swallowed pre-lift (missing `metadata.annotations` map,
322 /// missing key inside the map) BOTH collapse to `None` so
323 /// `.is_some()` / `if let Some(_)` / `Option::map` behave
324 /// identically on a `Process` whose annotations block is `None`
325 /// and on one whose annotations block is populated but omits the
326 /// key — matching what the pre-lift `.and_then(...)` chain
327 /// produced.
328 ///
329 /// A future normalization step (a key-canonicalization pass,
330 /// a case-fold lookup, a per-key alias table for renamed
331 /// annotations across API versions, a per-namespace override
332 /// substrate) lands at ONE substrate method here and all three
333 /// downstream consumers pick up the upgrade mechanically — no
334 /// per-callsite hand-edit at `ingest` / `released_from_annotation`
335 /// / `process_belongs_to_pool`.
336 ///
337 /// Sibling to the peer metadata primitives
338 /// ([`Self::namespace_or_default`], [`Self::name_or_placeholder`],
339 /// [`Self::coordinates_or_defaults`], [`Self::coordinates_or_none`],
340 /// [`Self::owned_coordinates_or_err`]) on the metadata axis;
341 /// this method opens the borrow-form peer on the ANNOTATION
342 /// axis. Future annotation projections (a paired
343 /// `label(&str) -> Option<&str>` on `metadata.labels`, a
344 /// `has_annotation(&str) -> bool` boolean gate for presence-
345 /// only consumers) land as peer methods on this same axis.
346 ///
347 /// Theory anchor: THEORY.md §VI.1 (generation over composition
348 /// — the 3-line annotation-lookup chain recurred at three
349 /// hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2
350 /// duplication trigger, and is lifted to ONE owner here).
351 /// THEORY.md §II.1 invariant 5 (composition preserves proofs —
352 /// the pins bind the missing-`annotations` corner + the
353 /// missing-key corner + the borrow-form `&str` lifetime + the
354 /// byte-identical parity with the pre-lift 3-line chain, so a
355 /// regression that drifted any surface at
356 /// `tests::annotation_*` rather than as silent operator-facing
357 /// skew between the SIGNAL / RELEASED_FROM / POOL annotation
358 /// readers).
359 pub fn annotation(&self, key: &str) -> Option<&str> {
360 self.metadata
361 .annotations
362 .as_ref()
363 .and_then(|m| m.get(key))
364 .map(String::as_str)
365 }
366
367 /// Borrow-form metadata-projection primitive on the `metadata.uid`
368 /// axis: returns the K8s-API-server-assigned uid as a `&str`, with
369 /// the missing-uid corner collapsed to the load-bearing empty-string
370 /// sentinel — the ONE-liner collapse of the paired
371 /// `self.metadata.uid.as_deref().unwrap_or("")` incantation every
372 /// owner-reference-emitting consumer restated by hand pre-lift.
373 ///
374 /// The empty-string fallback is NOT arbitrary — it is the exact
375 /// sentinel value the sibling substrate composer
376 /// [`crate::owner_references_json`] gates on (`if uid.is_empty()
377 /// { vec![] } else { vec![owner_reference_json(name, uid)] }`) to
378 /// stamp `metadata.ownerReferences: []` on a resource whose owning
379 /// Process pre-dates the API server's `metadata.uid` assignment
380 /// (test fixture, mid-Forking snapshot before the first `patch`
381 /// round-trip, dynamic API response pre-uid-resolution). Pre-lift
382 /// each consumer spelled the fallback as `.unwrap_or("")` at its
383 /// callsite; the two literals in two files could drift silently to
384 /// `.unwrap_or_default()`, `.unwrap_or("<unknown>")`, or an
385 /// `if let Some(u) = &process.metadata.uid` gate that returned a
386 /// different owner-refs shape for the missing-uid corner. Post-lift
387 /// the sentinel value is composed at ONE substrate site so the
388 /// empty-uid gate at `owner_references_json` and its per-callsite
389 /// producers share the SAME `""` byte-string, and a rename of the
390 /// sentinel would land at ONE substrate site rather than at every
391 /// downstream `owner_references_json(name, uid)` call.
392 ///
393 /// Peer to [`Self::namespace_or_default`] +
394 /// [`Self::name_or_placeholder`] on the metadata-slot × fallback-
395 /// shape axis: `namespace_or_default` returns the K8s-canonical
396 /// `"default"` fallback (matching what the API server substitutes
397 /// on namespaced writes with no explicit namespace);
398 /// `name_or_placeholder` returns the workspace-wide `"unnamed"`
399 /// sentinel (a display placeholder for downstream grepping /
400 /// label-selecting); this method returns the empty-string sentinel
401 /// (a load-bearing gate value that composes with
402 /// [`crate::owner_references_json`]'s `is_empty` check). The three
403 /// primitives partition the metadata-slot family by whether the
404 /// consumer wants a K8s-canonical fallback (namespace), a display
405 /// placeholder (name), or a gate sentinel (uid).
406 ///
407 /// Pre-lift the `.metadata.uid.as_deref().unwrap_or("")` chain was
408 /// hand-authored at TWO sites past the ★★ PRIME-DIRECTIVE ≥ 2
409 /// duplication threshold in `tatara-reconciler::render`, both
410 /// feeding a downstream owner-reference emitter:
411 /// * `render_routing` — the routing-edge seed that binds
412 /// `process_uid` into every routing-form `EdgeContext` (Ingress +
413 /// DNSEndpoint) built inside the fanout loop over
414 /// `RoutingSpec::hostnames`; each `Edge::render` impl then walks
415 /// its `EdgeContext` through `build_owner_refs` →
416 /// [`crate::owner_references_json`] to stamp
417 /// `metadata.ownerReferences` on the emitted resource.
418 /// * `render_export_jobs` — the ephemeral-export Job builder that
419 /// passes the same uid slice to `tatara_process::
420 /// owner_references_json(name, uid)` per rendered Job, stamping
421 /// the export-Job's `metadata.ownerReferences` back at the
422 /// owning Process.
423 ///
424 /// Both sites walked the SAME `.as_deref().unwrap_or("")` chain and
425 /// both wanted the `&str` form the primitive returns — as the
426 /// second positional argument to `owner_references_json(name, uid)`
427 /// on the ownership-tag axis. Post-lift each callsite reads
428 /// `let uid = process.uid_or_empty();` and the produced slice feeds
429 /// the same downstream composer unchanged.
430 ///
431 /// Return-form axis: `&str` mirrors the existing borrow-first
432 /// discipline of the peer metadata-fallback primitives
433 /// ([`Self::namespace_or_default`], [`Self::name_or_placeholder`]);
434 /// all three return owned-metadata borrows with a slot-specific
435 /// fallback baked in so downstream consumers compose the slice
436 /// directly into their next call without re-spelling the fallback.
437 ///
438 /// A future normalization step (a canonicalization pass that
439 /// rejects a malformed uid before the owner-ref stamp, a cross-
440 /// cluster uid rewrite for multi-tenant control planes, a stale-
441 /// uid warning annotation for a Process whose uid changed under
442 /// the reconciler mid-generation) lands at ONE substrate method
443 /// here and both downstream `owner_references_json` consumers
444 /// pick up the upgrade mechanically — no per-callsite hand-edit
445 /// at `render_routing` / `render_export_jobs`.
446 ///
447 /// Theory anchor: THEORY.md §VI.1 (generation over composition —
448 /// the `.metadata.uid.as_deref().unwrap_or("")` chain recurred at
449 /// two hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2
450 /// duplication trigger, and is lifted to ONE owner here).
451 /// THEORY.md §II.1 invariant 5 (composition preserves proofs —
452 /// the pins bind the missing-uid corner + the empty-string
453 /// sentinel byte-shape + the borrow-form `&str` lifetime + the
454 /// byte-identical parity with the pre-lift chain + the composition
455 /// coherence with [`crate::owner_references_json`]'s `is_empty`
456 /// gate, so a regression that drifted any surface at
457 /// `tests::uid_or_empty_*` rather than as silent operator-facing
458 /// skew between the two owner-reference emitters on the SAME
459 /// Process).
460 pub fn uid_or_empty(&self) -> &str {
461 self.metadata.uid.as_deref().unwrap_or("")
462 }
463
464 /// Borrow-form spec-projection primitive on the declared parent-PID
465 /// axis: returns the hierarchical PID path (e.g. `"seph.1"`) the
466 /// author declared at `spec.identity.parent`, with the empty-slot
467 /// corner collapsed to `None` — the ONE-liner collapse of the
468 /// paired `self.spec.identity.parent.as_deref()` incantation every
469 /// consumer restated by hand pre-lift.
470 ///
471 /// Pre-lift the `.spec.identity.parent.as_deref()` chain was hand-
472 /// authored at TWO sites past the ★★ PRIME-DIRECTIVE ≥ 2
473 /// duplication threshold in `tatara-reconciler::phase_machine`:
474 /// * `handle_forking` — the ALLOCATE-PID composer that threads the
475 /// declared parent PID into [`pid::allocate_pid`] and also into
476 /// the status patch payload (`{ "pid": new_pid, "parent":
477 /// parent_pid }`), so the reconciler-observed
478 /// [`ProcessStatus::parent`] slot mirrors the author-declared
479 /// [`IdentitySpec::parent`] at fork time. The `info!` tracing
480 /// span also reads the same slice as the `parent` field on the
481 /// PID-assigned log line.
482 /// * `handle_exiting` — the SIGTERM cascade's child-fan-out filter
483 /// that enumerates every Process cluster-wide and picks children
484 /// whose `spec.identity.parent` equals this Process's currently-
485 /// observed PID (`.filter(|c| c.spec.identity.parent.as_deref()
486 /// == Some(pid))`). The filter runs per candidate child, so the
487 /// borrow-form projection avoids allocating one `String` clone
488 /// per non-matching row in the cluster-wide list.
489 ///
490 /// Both sites walked the SAME `.as_deref()` chain and both wanted
491 /// the `Option<&str>` form the primitive returns — the
492 /// `handle_forking` site to feed positionally into
493 /// `pid::allocate_pid(&identity, parent_pid, next_seq)` and the
494 /// tracing span's `parent = ?parent_pid` debug print + the JSON
495 /// payload's `"parent": parent_pid` slot; the `handle_exiting`
496 /// filter to compare directly against `Some(pid)` where `pid:
497 /// &str` came off the borrow-form peer [`Self::observed_pid`].
498 ///
499 /// Return-form axis: `Option<&str>` mirrors the borrow-first
500 /// discipline of every peer primitive on the metadata / status
501 /// slot family ([`Self::namespace_or_default`],
502 /// [`Self::name_or_placeholder`], [`Self::observed_pid`],
503 /// [`Self::annotation`]). The empty-slot corner
504 /// (`spec.identity.parent = None`, matching `init` / PID 1 with
505 /// no parent) collapses to `None` so `.is_some()` / `if let
506 /// Some(_)` / `.map(...)` behave identically on a `Process`
507 /// authored at cluster init (PID 1, parent absent) and on any
508 /// PID-N child (parent present) — matching the pre-lift
509 /// `.as_deref()` chain's `None` byte-identically.
510 ///
511 /// Peer to [`Self::observed_pid`] on the (spec-declared ×
512 /// status-observed) axis pair: `observed_pid` returns the PID
513 /// path this Process currently OWNS (the reconciler-persisted
514 /// child position in the hierarchy), while `declared_parent_pid`
515 /// returns the PID path this Process's parent OWNS (the author-
516 /// declared upstream position). The SIGTERM cascade at
517 /// `handle_exiting` composes both: it reads its own
518 /// [`Self::observed_pid`] and matches each candidate child's
519 /// [`Self::declared_parent_pid`] against that value — the child-
520 /// fan-out relation IS the spec-declared × status-observed axis
521 /// pair collapsed to a single comparator, both sides routed
522 /// through the same borrow-form skeleton.
523 ///
524 /// A future normalization step (a per-slot canonicalization pass
525 /// that rejects malformed hierarchical PIDs, a case-fold lookup
526 /// against a table of renamed identities, a cross-cluster prefix
527 /// stripper, an alias-table lookup that maps a legacy PID to its
528 /// current spelling) lands at ONE substrate method here and both
529 /// downstream consumers pick up the upgrade mechanically — no
530 /// per-callsite hand-edit at `handle_forking` / `handle_exiting`.
531 ///
532 /// Sibling to the peer metadata-projection primitives
533 /// ([`Self::namespace_or_default`], [`Self::name_or_placeholder`],
534 /// [`Self::coordinates_or_defaults`], [`Self::coordinates_or_none`],
535 /// [`Self::owned_coordinates_or_err`], [`Self::annotation`]) on the
536 /// metadata axis; this method opens the borrow-form peer on the
537 /// declared-identity axis. Future identity projections
538 /// (`declared_name_override` on the `spec.identity.name_override`
539 /// axis, a paired `declared_identity` composite that returns both
540 /// halves) land as peer methods on this same axis.
541 ///
542 /// Theory anchor: THEORY.md §VI.1 (generation over composition —
543 /// the `.spec.identity.parent.as_deref()` chain recurred at two
544 /// hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
545 /// trigger, and is lifted to ONE owner here). THEORY.md §II.1
546 /// invariant 5 (composition preserves proofs — the pins bind the
547 /// empty-slot corner + the borrow-form `&str` lifetime + the
548 /// byte-identical parity with the pre-lift `.as_deref()` chain,
549 /// so a regression that drifted any surface at
550 /// `tests::declared_parent_pid_*` rather than as silent operator-
551 /// facing skew between the ALLOCATE-PID composer and the SIGTERM
552 /// cascade's child-fan-out filter on the SAME parent-child pair).
553 pub fn declared_parent_pid(&self) -> Option<&str> {
554 self.spec.identity.parent.as_deref()
555 }
556
557 /// Borrow-form spec-projection primitive on the declared
558 /// name-override axis: returns the human name the author declared
559 /// at `spec.identity.name_override` (used verbatim instead of the
560 /// content-hash-derived name in [`derive_identity`]), with the
561 /// empty-slot corner collapsed to `None` — the ONE-liner collapse
562 /// of the paired `self.spec.identity.name_override.as_deref()`
563 /// incantation every consumer restated by hand pre-lift.
564 ///
565 /// Pre-lift the `.spec.identity.name_override.as_deref()` chain
566 /// was hand-authored at TWO sites past the ★★ PRIME-DIRECTIVE ≥ 2
567 /// duplication threshold in `tatara-reconciler::phase_machine`,
568 /// both feeding the second positional argument of
569 /// [`derive_identity`]:
570 /// * `handle_pending` — the DECLARE composer that computes the
571 /// Process's [`Identity`] on entry to the state machine (before
572 /// `patch::phase_status` writes it into `status.identity`).
573 /// * `handle_forking` — the ALLOCATE-PID composer that recomputes
574 /// the same [`Identity`] on a rehydration path (status may
575 /// already carry an identity from a prior reconcile, in which
576 /// case the `.and_then(|s| s.identity.clone())` short-circuit
577 /// takes it; otherwise this `.unwrap_or_else` branch fires and
578 /// recomputes the identity fresh from the spec) so `pid::
579 /// allocate_pid` sees the SAME [`Identity`] the DECLARE phase
580 /// produced.
581 ///
582 /// Both sites walked the SAME `.as_deref()` chain and both wanted
583 /// the `Option<&str>` form the primitive returns — as the second
584 /// positional argument to `derive_identity(&self.spec, …)`, which
585 /// internally trims + filters empty strings + dispatches on
586 /// `Some(non_empty)` (verbatim name, `name_override: true`) vs
587 /// `None | Some(empty | whitespace)` (content-hash-derived name,
588 /// `name_override: false`). The primitive itself preserves the
589 /// raw slot byte-identically (the trim happens IN
590 /// `derive_identity`, not at the borrow site), so the two live
591 /// paths compose through the SAME borrow-form skeleton.
592 ///
593 /// Return-form axis: `Option<&str>` mirrors the borrow-first
594 /// discipline of every peer primitive on the metadata / status /
595 /// spec-identity slot family ([`Self::namespace_or_default`],
596 /// [`Self::name_or_placeholder`], [`Self::observed_pid`],
597 /// [`Self::annotation`], [`Self::declared_parent_pid`]). The
598 /// empty-slot corner (`spec.identity.name_override = None`,
599 /// matching a Process authored WITHOUT the human-name-override
600 /// escape hatch — the default; `derive_identity` then computes
601 /// the name from the content hash) collapses to `None` so
602 /// `.is_some()` / `if let Some(_)` / `.map(...)` behave
603 /// identically on the two Process shapes an operator can author.
604 ///
605 /// Peer to [`Self::declared_parent_pid`] on the (parent × name-
606 /// override) sub-axis of the declared-identity axis: both
607 /// primitives project a `Option<String>` slot on `IdentitySpec`
608 /// through the SAME borrow-form skeleton, so a future
609 /// `declared_identity` composite that returns both halves
610 /// together (e.g. as a `(Option<&str>, Option<&str>)` tuple or a
611 /// borrow-form `DeclaredIdentityView<'_>` newtype) lands as ONE
612 /// method that COMPOSES the two peer primitives, not as three
613 /// hand-authored `.as_deref()` chains restated at each callsite.
614 ///
615 /// A future normalization step (a per-slot canonicalization pass
616 /// that rejects malformed names, a case-fold lookup against a
617 /// table of renamed identities, an alias-table lookup that maps
618 /// a legacy name-override to its current spelling, a whitespace-
619 /// trim lift OUT of `derive_identity` INTO the primitive so both
620 /// consumers see the trimmed form) lands at ONE substrate method
621 /// here and both downstream consumers pick up the upgrade
622 /// mechanically — no per-callsite hand-edit at `handle_pending` /
623 /// `handle_forking`.
624 ///
625 /// Sibling to the peer spec-identity projection
626 /// [`Self::declared_parent_pid`] on the declared-identity axis;
627 /// this method opens the borrow-form peer on the name-override
628 /// sub-axis of the same closed set (`IdentitySpec { parent,
629 /// name_override }`). Future identity projections (a paired
630 /// `declared_identity` composite that returns both halves
631 /// together) land as peer methods on this same axis.
632 ///
633 /// Theory anchor: THEORY.md §VI.1 (generation over composition —
634 /// the `.spec.identity.name_override.as_deref()` chain recurred
635 /// at two hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2
636 /// duplication trigger, and is lifted to ONE owner here).
637 /// THEORY.md §II.1 invariant 5 (composition preserves proofs —
638 /// the pins bind the empty-slot corner + the borrow-form `&str`
639 /// lifetime + the byte-identical parity with the pre-lift
640 /// `.as_deref()` chain + the invariance under
641 /// [`derive_identity`]'s internal trim/filter step, so a
642 /// regression that drifted any surface at
643 /// `tests::declared_name_override_*` rather than as silent
644 /// operator-facing skew between the DECLARE composer and the
645 /// ALLOCATE-PID rehydration branch on the SAME Process spec).
646 pub fn declared_name_override(&self) -> Option<&str> {
647 self.spec.identity.name_override.as_deref()
648 }
649
650 /// Borrowed slice of the FluxCD resources this Process's status
651 /// currently persists at `status.flux_resources`, with the
652 /// missing-`status` corner collapsed to an empty slice — the ONE-
653 /// line collapse of the paired `self.status.as_ref().map(|s|
654 /// s.flux_resources.clone()).unwrap_or_default()` incantation
655 /// every VERIFY-phase / ATTEST-heartbeat consumer restated by hand
656 /// pre-lift.
657 ///
658 /// Pre-lift the 5-line `.status.as_ref().map(|s| s.flux_resources
659 /// .clone()).unwrap_or_default()` chain was hand-authored at TWO
660 /// sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold in
661 /// `tatara-reconciler::phase_machine`:
662 /// * `handle_running` — the VERIFY-phase per-ref readiness probe
663 /// seed that walks every ref through
664 /// [`crate::status::FluxResourceRef::fetch_coords`] via
665 /// `ssapply::fetch_flux_ref` and rebuilds an updated
666 /// `Vec<FluxResourceRef>` with `ready` + `message` + `last_check`
667 /// observed at reconcile time.
668 /// * `handle_attested` — the ATTEST-heartbeat drift detector that
669 /// short-circuits on the first non-Ready ref via
670 /// `ssapply::fetch_flux_ref` + `ssapply::ready_condition`.
671 ///
672 /// Both sites walked the SAME 5-line chain — clone the vector
673 /// eagerly for the length of the reconcile pass, then iterate it
674 /// by reference — even though neither site ever mutates the vector
675 /// nor keeps it alive past the enclosing async fn. Post-lift both
676 /// callers borrow the slice directly from `self.status`; the two
677 /// pre-lift `.clone()` calls disappear because the slice lives for
678 /// the borrow of `&self`, and both call sites' subsequent
679 /// downstream calls (`ssapply::fetch_flux_ref` / the
680 /// `patch::patch_process_status` write) do not touch the borrowed
681 /// `p: &Process`, so the borrow lifetime holds.
682 ///
683 /// Return-form axis: `&[FluxResourceRef]` mirrors the existing
684 /// borrow-first discipline every pre-lift consumer already
685 /// iterated by reference (`for r in &refs`), and the shape of
686 /// [`crate::status::FluxResourceRef::fetch_coords`]'s per-ref
687 /// borrow projection extends mechanically to the slice-level
688 /// projection here. The missing-`status` corner collapses to the
689 /// empty slice `&[]` so `.is_empty()` / `.len()` / iteration all
690 /// behave identically on a `Process` whose status is `None` and
691 /// on one whose status carries an empty `flux_resources` slot —
692 /// matching what the pre-lift `.unwrap_or_default()` produced
693 /// (an empty `Vec`).
694 ///
695 /// A future normalization step (a per-ref canonicalization pass
696 /// that skips duplicated refs, an owner-filter that returns only
697 /// refs stamped with the CURRENT `metadata.generation`, a
698 /// staleness gate that drops refs whose `last_check` predates a
699 /// reconcile deadline) lands at ONE substrate method here and
700 /// both downstream consumers pick up the upgrade mechanically —
701 /// no per-callsite hand-edit at `handle_running` /
702 /// `handle_attested`.
703 ///
704 /// Sibling to the [`Self::coordinates_or_none`] borrow-first
705 /// primitive on the metadata axis; this method opens the
706 /// analogous borrow-first primitive on the status-projection
707 /// axis. Future status projections (`observed_attestation` on
708 /// the attestation-chain axis, `observed_pid` on the PID axis,
709 /// `observed_children` on the child-fan-out axis) land as peer
710 /// methods on this same axis.
711 ///
712 /// Theory anchor: THEORY.md §VI.1 (generation over composition —
713 /// the 5-line status-projection chain recurred at two hand-
714 /// 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 /// missing-`status` corner + the slice-lifetime borrow discipline
718 /// + the byte-identical parity with the pre-lift 5-line chain, so
719 /// a regression that drifted any of the three surfaces at
720 /// `tests::observed_flux_resources_*` rather than as silent
721 /// operator-facing skew between the VERIFY-phase and ATTEST-
722 /// heartbeat consumers).
723 pub fn observed_flux_resources(&self) -> &[FluxResourceRef] {
724 self.status
725 .as_ref()
726 .map(|s| s.flux_resources.as_slice())
727 .unwrap_or(&[])
728 }
729
730 /// The borrow-form status-projection primitive on the PID axis:
731 /// returns the hierarchical PID path (e.g. `"seph.1.7"`) the
732 /// reconciler currently persists at `status.pid`, with BOTH the
733 /// missing-`status` corner AND the empty-slot corner collapsed
734 /// to `None` — the ONE-liner collapse of the paired
735 /// `self.status.as_ref().and_then(|s| s.pid.clone())` incantation
736 /// every consumer restated by hand pre-lift.
737 ///
738 /// Pre-lift the 3-line `.status.as_ref().and_then(|s| s.pid
739 /// .clone())` chain was hand-authored at TWO sites past the ★★
740 /// PRIME-DIRECTIVE ≥ 2 duplication threshold in
741 /// `tatara-reconciler::phase_machine`:
742 /// * `handle_forking` — the ALLOCATE-PID gate that short-
743 /// circuits the PID allocator when the reconciler already
744 /// assigned a PID on a prior reconcile pass (pre-lift the
745 /// chain composed with `.is_some()` and threw the clone away
746 /// without ever reading the string).
747 /// * `handle_exiting` — the SIGTERM cascade that enumerates
748 /// child Processes and terminates them by matching each
749 /// child's `spec.identity.parent` against the PID this Process
750 /// currently owns (pre-lift the chain bound an owned
751 /// `Option<String>` and threaded `pid.as_str()` into the
752 /// downstream `.as_deref() == Some(...)` comparator).
753 ///
754 /// Both sites walked the SAME 3-line chain — clone the `String`
755 /// eagerly, then either drop it (the `handle_forking` gate) or
756 /// re-borrow it through `.as_str()` (the `handle_exiting`
757 /// comparator) — even though neither site ever mutates the PID
758 /// nor keeps it alive past the enclosing async fn. Post-lift
759 /// both callers borrow the PID directly from `self.status`; the
760 /// pre-lift `.clone()` at both sites disappears because the
761 /// `&str` lives for the borrow of `&self`, and both call sites'
762 /// subsequent downstream calls (the K8s API list/patch, the
763 /// child-Process comparator) do not touch the borrowed
764 /// `p: &Process`, so the borrow lifetime holds.
765 ///
766 /// Return-form axis: `Option<&str>` mirrors the existing
767 /// borrow-first discipline every pre-lift consumer already
768 /// re-borrowed through `.as_str()` before use, and the shape of
769 /// [`Self::coordinates_or_none`]'s `Option<(&str, &str)>`
770 /// projection extends mechanically to the single-slot
771 /// projection here. The missing-`status` corner AND the
772 /// populated-status-with-`pid=None` corner BOTH collapse to
773 /// `None` so `.is_some()` / `if let Some(_)` / `.map(...)`
774 /// behave identically on a `Process` whose status is `None`
775 /// and on one whose status carries an unpopulated `pid` slot —
776 /// matching what the pre-lift `.and_then(...)` chain produced.
777 ///
778 /// A future normalization step (a per-slot canonicalization
779 /// pass that rejects malformed hierarchical PIDs, a
780 /// generation-filter that returns `None` for a PID stamped
781 /// with a stale `metadata.generation`, a staleness gate that
782 /// drops a PID whose observing `phase_since` predates a
783 /// reconcile deadline) lands at ONE substrate method here and
784 /// both downstream consumers pick up the upgrade mechanically
785 /// — no per-callsite hand-edit at `handle_forking` /
786 /// `handle_exiting`.
787 ///
788 /// Sibling to the peer [`Self::observed_flux_resources`]
789 /// borrow-first primitive on the flux-resources axis; both
790 /// methods compose the same missing-`status` fallback +
791 /// borrow-form return-shape skeleton on distinct
792 /// `ProcessStatus` slots. Future status projections
793 /// (`observed_parent` on the parent-pointer axis,
794 /// `observed_message` on the human-readable-status axis,
795 /// `observed_attestation` on the attestation-chain axis) land
796 /// as peer methods on this same axis.
797 ///
798 /// Theory anchor: THEORY.md §VI.1 (generation over
799 /// composition — the 3-line status-projection chain recurred
800 /// at two hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2
801 /// duplication trigger, and is lifted to ONE owner here).
802 /// THEORY.md §II.1 invariant 5 (composition preserves proofs —
803 /// the pins bind the missing-`status` corner + the empty-slot
804 /// corner + the borrow-form `&str` lifetime + the
805 /// byte-identical parity with the pre-lift 3-line chain, so a
806 /// regression that drifted any surface at
807 /// `tests::observed_pid_*` rather than as silent operator-
808 /// facing skew between the ALLOCATE-PID gate and the SIGTERM
809 /// cascade on the SAME `Process`).
810 pub fn observed_pid(&self) -> Option<&str> {
811 self.status.as_ref().and_then(|s| s.pid.as_deref())
812 }
813
814 /// The borrow-form status-projection primitive on the
815 /// attestation-chain axis: returns the last
816 /// [`ProcessAttestation`] the reconciler persisted at
817 /// `status.attestation`, with the missing-`status` corner AND the
818 /// empty-slot corner BOTH collapsed to `None` — the ONE-liner
819 /// collapse of the paired `self.status.as_ref().and_then(|s|
820 /// s.attestation.as_ref())` incantation every consumer restated
821 /// by hand pre-lift.
822 ///
823 /// Pre-lift the 3-line `.status.as_ref().and_then(|s| s
824 /// .attestation.as_ref())` chain was hand-authored at TWO sites
825 /// past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold in
826 /// `tatara-reconciler`:
827 /// * `phase_machine::advance_to_attested` — the ATTEST composer
828 /// that chains `prior.next(pillars)` when a prior attestation
829 /// is persisted and seeds with `ProcessAttestation::initial`
830 /// otherwise.
831 /// * `render::render_export_jobs` — the ephemeral-export Job
832 /// builder that pulls the prior `composed_root` off the last
833 /// persisted attestation and threads it into every rendered
834 /// Job's `previousRoot` env var, so the export receipt chains
835 /// into the Process's BLAKE3 attestation tree at the correct
836 /// generation boundary.
837 ///
838 /// Both sites walked the SAME 3-line chain — the borrow-form
839 /// `Option<&ProcessAttestation>` shape both consumers wanted
840 /// already — even though neither site ever mutated the
841 /// attestation nor kept it alive past the enclosing async fn.
842 /// Post-lift both callers borrow the attestation directly from
843 /// `self.status`; the pre-lift 3-line chain shrinks to a single
844 /// method call at both sites, and both consumers' subsequent
845 /// downstream calls (`ProcessAttestation::next` for the ATTEST
846 /// composer, `.composed_root.clone()` for the export Job builder)
847 /// do not touch the borrowed `p: &Process`, so the borrow
848 /// lifetime holds.
849 ///
850 /// Return-form axis: `Option<&ProcessAttestation>` mirrors the
851 /// existing borrow-first discipline every pre-lift consumer
852 /// already re-borrowed through `.as_ref()`, and the shape of the
853 /// peer [`Self::observed_pid`] projection extends mechanically
854 /// to the whole-attestation-record projection here. The missing-
855 /// `status` corner AND the populated-status-with-`attestation
856 /// =None` corner BOTH collapse to `None` so `.is_some()` / `if
857 /// let Some(_)` / `.map(...)` behave identically on a `Process`
858 /// whose status is `None` and on one whose status carries an
859 /// unpopulated `attestation` slot — matching what the pre-lift
860 /// `.and_then(...)` chain produced.
861 ///
862 /// A future normalization step (a per-slot canonicalization pass
863 /// that rejects a persisted attestation whose `composed_root`
864 /// fails `verify`, a generation-filter that returns `None` for
865 /// an attestation stamped with a stale `metadata.generation`, a
866 /// staleness gate that drops an attestation whose `attested_at`
867 /// predates a reconcile deadline) lands at ONE substrate method
868 /// here and both downstream consumers pick up the upgrade
869 /// mechanically — no per-callsite hand-edit at
870 /// `advance_to_attested` / `render_export_jobs`.
871 ///
872 /// Sibling to the peer [`Self::observed_pid`] +
873 /// [`Self::observed_flux_resources`] borrow-first primitives on
874 /// the PID + flux-resources axes; all three methods compose the
875 /// same missing-`status` fallback + borrow-form return-shape
876 /// skeleton on distinct `ProcessStatus` slots. Future status
877 /// projections (`observed_parent` on the parent-pointer axis,
878 /// `observed_message` on the human-readable-status axis) land
879 /// as peer methods on this same axis.
880 ///
881 /// Theory anchor: THEORY.md §VI.1 (generation over composition
882 /// — the 3-line status-projection chain recurred at two hand-
883 /// authored sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
884 /// trigger, and is lifted to ONE owner here). THEORY.md §II.1
885 /// invariant 5 (composition preserves proofs — the pins bind
886 /// the missing-`status` corner + the empty-slot corner + the
887 /// borrow-form `&ProcessAttestation` lifetime + the byte-
888 /// identical parity with the pre-lift 3-line chain, so a
889 /// regression that drifted any surface at
890 /// `tests::observed_attestation_*` rather than as silent
891 /// operator-facing skew between the ATTEST composer and the
892 /// ephemeral-export receipt chain on the SAME `Process`).
893 pub fn observed_attestation(&self) -> Option<&ProcessAttestation> {
894 self.status.as_ref().and_then(|s| s.attestation.as_ref())
895 }
896
897 /// The borrow-form status-projection primitive on the resolved-
898 /// identity axis: returns the [`Identity`] the reconciler
899 /// currently persists at `status.identity` (name + content hash +
900 /// override flag), with the missing-`status` corner AND the
901 /// empty-slot corner BOTH collapsed to `None` — the ONE-liner
902 /// collapse of the paired `self.status.as_ref().and_then(|s|
903 /// s.identity.as_ref())` incantation every consumer restated by
904 /// hand pre-lift.
905 ///
906 /// Pre-lift the paired `.status.as_ref().and_then(|s|
907 /// s.identity.<clone|as_ref>())` chain was hand-authored at TWO
908 /// sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold
909 /// in `tatara-reconciler`:
910 /// * `phase_machine::handle_forking` — the FORK-time identity
911 /// seed that reuses the reconciler-persisted `Identity` if
912 /// present and falls back to a fresh `derive_identity(&spec,
913 /// name_override)` otherwise. Pre-lift the site cloned the
914 /// whole `Identity` off the borrow before threading it through
915 /// `.unwrap_or_else(...)` even though the fallback path
916 /// allocates its own owned `Identity` — the pre-lift clone
917 /// allocated a fresh `Identity` on the happy path just so the
918 /// `Option`'s shape matched the fallback's `Identity` return
919 /// type.
920 /// * `ssapply::inject_annotations` — the SSA-time annotation
921 /// composer that stamps the content-hash annotation onto every
922 /// owned resource. Pre-lift the site nested the identity
923 /// borrow-form check inside a manual `if let Some(status) =
924 /// &process.status { … }` guard alongside sibling `status.pid`
925 /// and `status.attestation` accesses — three siblings the peer
926 /// primitives [`Self::observed_pid`] and
927 /// [`Self::observed_attestation`] already own, so the outer
928 /// status guard was the last hand-authored `.status.as_ref()`
929 /// destructure at this composer.
930 ///
931 /// Both sites walked the SAME 3-line chain (one via `.clone()`,
932 /// one via `.as_ref()`) — the borrow-form
933 /// `Option<&Identity>` shape both consumers wanted already, even
934 /// though the FORK-time seed then had to `.clone()` off the
935 /// borrow to compose with the owned-`Identity` fallback. Post-
936 /// lift the seed calls `.observed_identity().cloned()` at the
937 /// exact composition point where the owned value is required
938 /// (the empty-borrow corner clones nothing, since
939 /// `Option::cloned` on `None` is `None`), and the SSA-time
940 /// consumer drops the outer status guard entirely — the
941 /// three-sibling primitive family (pid + identity + attestation)
942 /// now peers through `observed_pid` +
943 /// `observed_identity` + `observed_attestation` at ONE call each
944 /// with no shared status destructure between them.
945 ///
946 /// Return-form axis: `Option<&Identity>` mirrors the
947 /// existing borrow-first discipline every pre-lift consumer
948 /// already re-borrowed through `.as_ref()` / re-cloned through
949 /// `.clone()`, and the shape of the peer
950 /// [`Self::observed_attestation`] projection extends
951 /// mechanically to the whole-`Identity`-record projection here.
952 /// The missing-`status` corner AND the populated-status-with-
953 /// `identity=None` corner BOTH collapse to `None` so
954 /// `.is_some()` / `if let Some(_)` / `.map(...)` behave
955 /// identically on a `Process` whose status is `None` and on one
956 /// whose status carries an unpopulated `identity` slot —
957 /// matching what the pre-lift `.and_then(...)` chain produced.
958 ///
959 /// A future normalization step (a per-slot canonicalization
960 /// pass that rejects an `Identity` whose `content_hash` fails
961 /// re-derivation against the current spec, a generation-filter
962 /// that returns `None` for an identity stamped with a stale
963 /// `metadata.generation`, a staleness gate that drops an
964 /// identity whose observing `phase_since` predates a reconcile
965 /// deadline) lands at ONE substrate method here and both
966 /// downstream consumers pick up the upgrade mechanically — no
967 /// per-callsite hand-edit at `handle_forking` /
968 /// `inject_annotations`.
969 ///
970 /// Sibling to the peer [`Self::observed_pid`] +
971 /// [`Self::observed_attestation`] +
972 /// [`Self::observed_flux_resources`] borrow-first primitives on
973 /// the PID + attestation-chain + flux-resources axes; all four
974 /// methods compose the same missing-`status` fallback +
975 /// borrow-form return-shape skeleton on distinct `ProcessStatus`
976 /// slots. Future status projections (`observed_parent` on the
977 /// parent-pointer axis, `observed_message` on the human-
978 /// readable-status axis) land as peer methods on this same
979 /// axis.
980 ///
981 /// Theory anchor: THEORY.md §VI.1 (generation over composition
982 /// — the 3-line status-projection chain recurred at two hand-
983 /// authored sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
984 /// trigger, and is lifted to ONE owner here). THEORY.md §II.1
985 /// invariant 5 (composition preserves proofs — the pins bind
986 /// the missing-`status` corner + the empty-slot corner + the
987 /// borrow-form `&Identity` lifetime + the byte-identical parity
988 /// with the pre-lift 3-line chain, so a regression that drifted
989 /// any surface at `tests::observed_identity_*` rather than as
990 /// silent operator-facing skew between the FORK-time identity
991 /// seed and the SSA-time content-hash annotation stamp on the
992 /// SAME `Process`).
993 pub fn observed_identity(&self) -> Option<&Identity> {
994 self.status.as_ref().and_then(|s| s.identity.as_ref())
995 }
996
997 /// The copy-form status-projection primitive on the phase axis:
998 /// returns the [`ProcessPhase`] the reconciler currently persists
999 /// at `status.phase`, wrapped in an `Option` so the missing-
1000 /// `status` corner collapses to `None` — the ONE-liner collapse
1001 /// of the paired `self.status.as_ref().map(|s| s.phase)`
1002 /// incantation every consumer restated by hand pre-lift.
1003 ///
1004 /// Peer to the borrow-form projections
1005 /// [`Self::observed_pid`] (PID axis, `Option<&str>`),
1006 /// [`Self::observed_flux_resources`] (flux-resources axis,
1007 /// `&[FluxResourceRef]`), and [`Self::observed_attestation`]
1008 /// (attestation-chain axis, `Option<&ProcessAttestation>`); this
1009 /// method opens the copy-form peer for `ProcessPhase` — a
1010 /// `Copy` scalar with a `Default` impl (`Pending`), so the
1011 /// return is `Option<ProcessPhase>` rather than
1012 /// `Option<&ProcessPhase>` (borrow would give the caller
1013 /// nothing over the copy for a 1-byte enum) and neither the
1014 /// missing-`status` corner nor a "empty slot" corner is
1015 /// meaningful — the underlying slot is a bare `ProcessPhase`,
1016 /// not `Option<ProcessPhase>`, so the primitive returns `None`
1017 /// iff `status: None`.
1018 ///
1019 /// Pre-lift the 3-line `.status.as_ref().map(|s| s.phase)`
1020 /// chain was hand-authored at FIVE sites past the ★★
1021 /// PRIME-DIRECTIVE ≥ 2 duplication threshold in
1022 /// `tatara-reconciler`:
1023 /// * `controller::reconcile` — the top-level dispatcher's
1024 /// `current_phase` seed that feeds the deletion-preempt +
1025 /// signal-ingestion gates + the per-phase handler dispatch.
1026 /// Pre-lift `.unwrap_or(ProcessPhase::Pending)`.
1027 /// * `boundary::evaluate_process_phase` — the boundary
1028 /// evaluator's `ProcessPhase` condition (a peer-Process
1029 /// `phase`-reached postcondition). Pre-lift
1030 /// `.unwrap_or(ProcessPhase::Pending)`.
1031 /// * `boundary::check_depends_on` — the `depends_on`
1032 /// pre-condition audit that stashes the observed phase into
1033 /// the `UnmetDependency::actual: Option<ProcessPhase>` slot
1034 /// (keeps the `Option` form). Pre-lift the raw
1035 /// `.map(|s| s.phase)` shape.
1036 /// * `phase_machine::p_current_phase_str` — the released-from
1037 /// annotation composer that emits `"Attested"` for every
1038 /// non-`Failed` phase (SIGSTOP/SIGCONT release gate).
1039 /// Pre-lift `.unwrap_or(ProcessPhase::Attested)` — the ONE
1040 /// site whose default is not `Pending`; the primitive
1041 /// returns the raw `Option` so the caller's `.unwrap_or`
1042 /// default choice stays local rather than baked in.
1043 /// * `table_controller::stable_name_group_key` — the routing-
1044 /// groupby seed that pairs the phase with the PID + creation
1045 /// timestamp when partitioning Processes claiming the same
1046 /// stable name. Pre-lift `.unwrap_or(ProcessPhase::Pending)`.
1047 ///
1048 /// All FIVE sites walked the SAME 3-line `.status.as_ref()
1049 /// .map(|s| s.phase)` chain — three closed with `unwrap_or
1050 /// (ProcessPhase::Pending)` (the `Default`), one closed with
1051 /// `unwrap_or(ProcessPhase::Attested)`, one kept the raw
1052 /// `Option<ProcessPhase>` — so the ONE substrate accessor
1053 /// returns the raw `Option<ProcessPhase>` and each consumer
1054 /// keeps its `.unwrap_or(...)` default choice at its own site.
1055 ///
1056 /// A future normalization step (a generation-filter that
1057 /// returns `None` for a phase stamped with a stale
1058 /// `metadata.generation`, a staleness gate that drops a phase
1059 /// whose observing `phase_since` predates a reconcile
1060 /// deadline, a canonicalization pass that maps a phase that
1061 /// no longer belongs to the CRD's closed set to `None`) lands
1062 /// at ONE substrate method here and all five consumers pick
1063 /// up the upgrade mechanically — no per-callsite hand-edit at
1064 /// `reconcile` / `evaluate_process_phase` / `check_depends_on`
1065 /// / `p_current_phase_str` / `stable_name_group_key`.
1066 ///
1067 /// Future status projections (`observed_parent` on the
1068 /// parent-pointer axis, `observed_message` on the human-
1069 /// readable-status axis, `observed_children` on the child
1070 /// fan-out axis, `observed_exit_code` on the terminal-exit
1071 /// axis) land as peer methods on this same axis.
1072 ///
1073 /// Theory anchor: THEORY.md §VI.1 (generation over
1074 /// composition — the 3-line status-projection chain recurred
1075 /// at FIVE hand-authored sites past the ★★ PRIME-DIRECTIVE
1076 /// ≥ 2 duplication trigger, and is lifted to ONE owner here).
1077 /// THEORY.md §II.1 invariant 5 (composition preserves proofs
1078 /// — the pins bind the missing-`status` corner + the
1079 /// per-variant enum round-trip + the byte-identical parity
1080 /// with the pre-lift 3-line chain, so a regression that
1081 /// drifted any surface at `tests::observed_phase_*` rather
1082 /// than as silent operator-facing skew between the
1083 /// controller's dispatch seed and the boundary evaluator's
1084 /// depends-on audit on the SAME `Process` within one
1085 /// reconcile pass).
1086 pub fn observed_phase(&self) -> Option<ProcessPhase> {
1087 self.status.as_ref().map(|s| s.phase)
1088 }
1089
1090 /// Copy-form metadata-projection primitive on the deletion-tombstone
1091 /// axis: returns `true` iff the K8s API server has stamped a
1092 /// `metadata.deletionTimestamp` on this Process (the moment the
1093 /// object entered the "being deleted" corner of its lifecycle,
1094 /// after which further mutating writes are refused and finalizers
1095 /// are drained before the object is actually removed) — the ONE-
1096 /// liner collapse of the paired `self.metadata.deletion_timestamp
1097 /// .is_some()` incantation every consumer restated by hand
1098 /// pre-lift.
1099 ///
1100 /// Pre-lift the `.metadata.deletion_timestamp.is_some()` chain
1101 /// was hand-authored at TWO sites past the ★★ PRIME-DIRECTIVE
1102 /// ≥ 2 duplication threshold in `tatara-reconciler`, both
1103 /// projecting the SAME tombstone-presence predicate on a
1104 /// `Process` value:
1105 /// * `controller::reconcile` — the top-level dispatcher's
1106 /// deletion-preempt gate that forces the SIGTERM cascade
1107 /// (`→ Exiting`) as soon as the API server stamps the
1108 /// tombstone, before the phase handler for the current
1109 /// [`ProcessPhase`] gets a chance to run. Composed with
1110 /// [`ProcessPhase::is_alive`] so the preempt only fires on a
1111 /// Process still in an alive phase — a Process already in
1112 /// `Zombie` / `Reaped` / `Failed` runs its normal handler.
1113 /// * `phase_machine::handle_exiting` — the SIGTERM cascade's
1114 /// child-fan-out loop that enumerates every child Process and
1115 /// skips ones the API server has already tombstoned (so the
1116 /// reconciler does not re-issue a `DELETE` against a child
1117 /// whose deletion the API server is already draining through
1118 /// its own finalizer). The skip composes with
1119 /// [`Self::coordinates_or_none`]'s name-required probe so a
1120 /// child missing either its tombstone-absent gate or its
1121 /// `metadata.name` slot is a clean `continue` rather than an
1122 /// attempted `child_api.delete("")` no-op.
1123 ///
1124 /// Both sites walked the SAME `.metadata.deletion_timestamp
1125 /// .is_some()` chain and both wanted the `bool` form the
1126 /// primitive returns — the `controller::reconcile` site to gate
1127 /// the SIGTERM preempt with `&& current_phase.is_alive()` and
1128 /// the `handle_exiting` site to gate the DELETE-skip with a
1129 /// bare `if child.is_being_deleted() { continue; }`. Post-lift
1130 /// each callsite reads `process.is_being_deleted()` and the
1131 /// produced `bool` feeds the same downstream gate unchanged.
1132 ///
1133 /// Return-form axis: `bool` matches the copy-form discipline of
1134 /// [`Self::observed_phase`] (an `Option<Copy>` scalar) — the
1135 /// underlying slot is a wire-format `Option<Time>` that carries
1136 /// only presence information at this axis (the RFC-3339 timestamp
1137 /// payload itself is not what the two consumers read; both only
1138 /// probe presence to detect the tombstone-stamped state).
1139 /// Returning the raw `Option<&Time>` would push the `.is_some()`
1140 /// probe back to every callsite, restating the pre-lift chain
1141 /// one link shorter without collapsing the primitive.
1142 ///
1143 /// Peer to the metadata-fallback primitives
1144 /// [`Self::namespace_or_default`], [`Self::name_or_placeholder`],
1145 /// [`Self::uid_or_empty`], [`Self::coordinates_or_defaults`],
1146 /// [`Self::coordinates_or_none`], [`Self::owned_coordinates_or_err`],
1147 /// [`Self::annotation`] on the metadata axis; this method opens
1148 /// the copy-form peer for the presence-probe corner. Future
1149 /// metadata-presence projections (an `is_being_finalized`
1150 /// projection on `metadata.finalizers.is_empty()`'s negation,
1151 /// a `has_owner` projection on `metadata.owner_references.is_empty()`'s
1152 /// negation) land as peer methods on this same axis.
1153 ///
1154 /// A future normalization step (a per-tombstone staleness gate
1155 /// that returns `false` for a tombstone older than the reconciler's
1156 /// grace-period budget, a canonicalization pass that treats a
1157 /// tombstone from a paused controller as absent, a cross-cluster
1158 /// tombstone-observation clock skew guard) lands at ONE substrate
1159 /// method here and both downstream consumers pick up the upgrade
1160 /// mechanically — no per-callsite hand-edit at
1161 /// `controller::reconcile` / `phase_machine::handle_exiting`.
1162 ///
1163 /// Theory anchor: THEORY.md §VI.1 (generation over composition —
1164 /// the `.metadata.deletion_timestamp.is_some()` chain recurred at
1165 /// two hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2
1166 /// duplication trigger, and is lifted to ONE owner here).
1167 /// THEORY.md §II.1 invariant 5 (composition preserves proofs —
1168 /// the pins bind the missing-tombstone corner + the present-
1169 /// tombstone corner + the copy-form `bool` return + the byte-
1170 /// identical parity with the pre-lift `.is_some()` chain, so a
1171 /// regression that drifted any surface at
1172 /// `tests::is_being_deleted_*` rather than as silent operator-
1173 /// facing skew between the top-level dispatcher's SIGTERM
1174 /// preempt and the SIGTERM cascade's child-fan-out DELETE-skip
1175 /// on the SAME `Process` within one reconcile pass).
1176 pub fn is_being_deleted(&self) -> bool {
1177 self.metadata.deletion_timestamp.is_some()
1178 }
1179}
1180
1181/// Process status — every field optional until the reconciler writes it.
1182#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema)]
1183#[serde(rename_all = "camelCase")]
1184pub struct ProcessStatus {
1185 /// Hierarchical PID path — e.g., `"seph.1.7"`.
1186 #[serde(default, skip_serializing_if = "Option::is_none")]
1187 pub pid: Option<String>,
1188
1189 /// Parent PID path (mirror of `spec.identity.parent`, resolved at fork).
1190 #[serde(default, skip_serializing_if = "Option::is_none")]
1191 pub parent: Option<String>,
1192
1193 /// Direct children's PID paths.
1194 #[serde(default)]
1195 pub children: Vec<String>,
1196
1197 /// Resolved identity (name + content hash).
1198 #[serde(default, skip_serializing_if = "Option::is_none")]
1199 pub identity: Option<Identity>,
1200
1201 /// Current phase.
1202 #[serde(default)]
1203 pub phase: ProcessPhase,
1204
1205 /// When the process entered the current phase.
1206 #[serde(default, skip_serializing_if = "Option::is_none")]
1207 pub phase_since: Option<DateTime<Utc>>,
1208
1209 /// Three-pillar attestation (written at end of every successful cycle).
1210 #[serde(default, skip_serializing_if = "Option::is_none")]
1211 pub attestation: Option<ProcessAttestation>,
1212
1213 /// FluxCD resources currently owned by this Process.
1214 #[serde(default)]
1215 pub flux_resources: Vec<FluxResourceRef>,
1216
1217 /// Boundary verification state.
1218 #[serde(default)]
1219 pub boundary: BoundaryStatus,
1220
1221 /// Compliance summary at the latest attestation.
1222 #[serde(default)]
1223 pub compliance: ComplianceStatus,
1224
1225 /// Pending signals (delivered, not yet handled).
1226 #[serde(default)]
1227 pub signal_queue: Vec<ProcessSignal>,
1228
1229 /// Standard K8s Conditions.
1230 #[serde(default)]
1231 pub conditions: Vec<ProcessCondition>,
1232
1233 /// Human-readable last status message.
1234 #[serde(default, skip_serializing_if = "Option::is_none")]
1235 pub message: Option<String>,
1236
1237 /// Exit code (only set on Failed / Reaped).
1238 #[serde(default, skip_serializing_if = "Option::is_none")]
1239 pub exit_code: Option<i32>,
1240}
1241
1242#[cfg(test)]
1243mod tests {
1244 use super::*;
1245 use crate::classification::{ConvergencePointType, SubstrateType};
1246 use crate::intent::NixIntent;
1247
1248 #[test]
1249 fn minimal_spec_serializes() {
1250 let spec = ProcessSpec {
1251 identity: IdentitySpec::default(),
1252 classification: Classification {
1253 point_type: ConvergencePointType::Gate,
1254 substrate: SubstrateType::Observability,
1255 horizon: Default::default(),
1256 calm: Default::default(),
1257 data_classification: Default::default(),
1258 },
1259 intent: Intent {
1260 nix: Some(NixIntent {
1261 flake_ref: "github:pleme-io/k8s".into(),
1262 attribute: "obs".into(),
1263 system: None,
1264 attic_cache: None,
1265 extra_args: vec![],
1266 delegate_to_nix_build: false,
1267 }),
1268 ..Intent::default()
1269 },
1270 boundary: Default::default(),
1271 compliance: Default::default(),
1272 depends_on: vec![],
1273 signals: Default::default(),
1274 lifetime: Default::default(),
1275 routing: None,
1276 encapsulates: None,
1277 suspended: false,
1278 };
1279 let yaml = serde_yaml::to_string(&spec).unwrap();
1280 assert!(yaml.contains("pointType: Gate"));
1281 assert!(yaml.contains("substrate: Observability"));
1282 assert!(yaml.contains("flakeRef: github:pleme-io/k8s"));
1283 }
1284
1285 // ─── Process::coordinates_or_defaults substrate pins ────────────────
1286 //
1287 // Pins the (namespace, name) coordinate-primitive family on the
1288 // (metadata slot × fallback shape) axis. Fail-before-pass-after
1289 // granularity: a regression that flipped either fallback string,
1290 // swapped the return-tuple axis order, or dropped the
1291 // `Option::as_deref` unwrap surfaces here rather than as silent
1292 // drift at every downstream annotation writer / claim-arbiter row
1293 // builder / render owner-metadata seed.
1294
1295 fn empty_spec() -> ProcessSpec {
1296 ProcessSpec {
1297 identity: IdentitySpec::default(),
1298 classification: Classification {
1299 point_type: ConvergencePointType::Gate,
1300 substrate: SubstrateType::Compute,
1301 horizon: Default::default(),
1302 calm: Default::default(),
1303 data_classification: Default::default(),
1304 },
1305 intent: Intent::default(),
1306 boundary: Default::default(),
1307 compliance: Default::default(),
1308 depends_on: vec![],
1309 signals: Default::default(),
1310 lifetime: Default::default(),
1311 routing: None,
1312 encapsulates: None,
1313 suspended: false,
1314 }
1315 }
1316
1317 #[test]
1318 fn default_namespace_constant_is_k8s_canonical_default() {
1319 // Pins the load-bearing convention that this primitive's
1320 // namespace fallback matches K8s's own implicit-namespace
1321 // spelling. A regression that renamed this to "kube-system"
1322 // or any other K8s-reserved name would silently misroute
1323 // every downstream namespaced-Api call on a Process without
1324 // a metadata.namespace.
1325 assert_eq!(Process::DEFAULT_NAMESPACE, "default");
1326 }
1327
1328 #[test]
1329 fn unnamed_placeholder_constant_matches_prior_annotation_writer_fallback() {
1330 // Pins the load-bearing convention that this primitive's name
1331 // fallback matches the exact spelling every annotation writer
1332 // (tatara-reconciler::ssapply::inject_annotations,
1333 // tatara-reconciler::render::render, and
1334 // tatara-reconciler::table_controller's claim-row builder)
1335 // was hand-authoring pre-lift ("unnamed", NOT "<unnamed>" or
1336 // ""). A regression that renamed this would break the
1337 // annotation-writer / claim-arbiter grep contract silently.
1338 assert_eq!(Process::UNNAMED_PLACEHOLDER, "unnamed");
1339 }
1340
1341 #[test]
1342 fn namespace_or_default_falls_back_when_metadata_namespace_is_none() {
1343 let mut p = Process::new("some-proc", empty_spec());
1344 p.metadata.namespace = None;
1345 assert_eq!(p.namespace_or_default(), Process::DEFAULT_NAMESPACE);
1346 }
1347
1348 #[test]
1349 fn namespace_or_default_returns_metadata_slice_when_some() {
1350 let mut p = Process::new("some-proc", empty_spec());
1351 p.metadata.namespace = Some("prod-app".into());
1352 assert_eq!(p.namespace_or_default(), "prod-app");
1353 }
1354
1355 #[test]
1356 fn name_or_placeholder_falls_back_when_metadata_name_is_none() {
1357 let mut p = Process::new("real-name", empty_spec());
1358 p.metadata.name = None;
1359 assert_eq!(p.name_or_placeholder(), Process::UNNAMED_PLACEHOLDER);
1360 }
1361
1362 #[test]
1363 fn name_or_placeholder_returns_metadata_slice_when_some() {
1364 let p = Process::new("api-gateway", empty_spec());
1365 assert_eq!(p.name_or_placeholder(), "api-gateway");
1366 }
1367
1368 #[test]
1369 fn coordinates_or_defaults_composes_both_halves() {
1370 // Both slots present — returns metadata slices in
1371 // (namespace, name) axis order.
1372 let mut p = Process::new("api", empty_spec());
1373 p.metadata.namespace = Some("staging".into());
1374 assert_eq!(p.coordinates_or_defaults(), ("staging", "api"));
1375 }
1376
1377 #[test]
1378 fn coordinates_or_defaults_falls_back_on_both_slots() {
1379 // Both slots None — returns (DEFAULT_NAMESPACE,
1380 // UNNAMED_PLACEHOLDER) in axis order.
1381 let mut p = Process::new("scratch", empty_spec());
1382 p.metadata.name = None;
1383 p.metadata.namespace = None;
1384 assert_eq!(
1385 p.coordinates_or_defaults(),
1386 (Process::DEFAULT_NAMESPACE, Process::UNNAMED_PLACEHOLDER)
1387 );
1388 }
1389
1390 #[test]
1391 fn coordinates_or_defaults_mixes_slotted_and_fallback_halves() {
1392 // Namespace set, name missing — the (namespace, name) tuple
1393 // pins each half independently. A regression that returned
1394 // BOTH fallbacks when EITHER metadata slot was None would
1395 // surface here rather than at every downstream reader.
1396 let mut p = Process::new("kept-name", empty_spec());
1397 p.metadata.namespace = Some("prod".into());
1398 assert_eq!(p.coordinates_or_defaults(), ("prod", "kept-name"));
1399
1400 // Name set, namespace missing — the peer corner.
1401 let mut q = Process::new("api", empty_spec());
1402 q.metadata.namespace = None;
1403 assert_eq!(
1404 q.coordinates_or_defaults(),
1405 (Process::DEFAULT_NAMESPACE, "api")
1406 );
1407 }
1408
1409 // ─── Process::owned_coordinates_or_err substrate pins ──────────────
1410 //
1411 // Pins the owned + name-required peer of the coordinate-primitive
1412 // family on the (return-form × name gate) axis pair. Fail-before-
1413 // pass-after granularity: a regression that flipped the namespace
1414 // fallback string, dropped the `Option::clone` unwrap, changed the
1415 // return-tuple axis order, or altered the "Process has no
1416 // metadata.name" error wording surfaces here rather than as silent
1417 // drift at every pre-lift caller (10 sites in
1418 // `tatara-reconciler::phase_machine` + 2 sites in
1419 // `tatara-reconciler::signals` pre-lift).
1420
1421 #[test]
1422 fn owned_coordinates_or_err_returns_owned_strings_when_both_slots_present() {
1423 // Happy path — both slots populated, method returns owned
1424 // Strings in (namespace, name) axis order.
1425 let mut p = Process::new("api-gateway", empty_spec());
1426 p.metadata.namespace = Some("prod-app".into());
1427 let (ns, name) = p.owned_coordinates_or_err().unwrap();
1428 assert_eq!(ns, "prod-app");
1429 assert_eq!(name, "api-gateway");
1430 // Ownership pin: type inference above binds ns/name as
1431 // owned Strings — a regression that returned &str would
1432 // fail to compile at the following .push() call. This
1433 // holds the "owned" half of the primitive's contract.
1434 let mut owned_ns = ns;
1435 owned_ns.push_str("-mutated");
1436 assert_eq!(owned_ns, "prod-app-mutated");
1437 }
1438
1439 #[test]
1440 fn owned_coordinates_or_err_falls_back_on_namespace_but_returns_owned_name() {
1441 // Namespace absent → DEFAULT_NAMESPACE. Name present → owned.
1442 let p = Process::new("api", empty_spec());
1443 // Process::new leaves metadata.namespace = None by default.
1444 let (ns, name) = p.owned_coordinates_or_err().unwrap();
1445 assert_eq!(ns, Process::DEFAULT_NAMESPACE);
1446 assert_eq!(name, "api");
1447 }
1448
1449 #[test]
1450 fn owned_coordinates_or_err_errors_when_metadata_name_absent_regardless_of_namespace() {
1451 // Name absent → Err, REGARDLESS of whether the namespace is
1452 // populated. The name gate is strictly on `metadata.name` and
1453 // does NOT fall back to `Self::UNNAMED_PLACEHOLDER` (that
1454 // fallback is on the peer `coordinates_or_defaults`, which
1455 // exists precisely for consumers that can tolerate a
1456 // display placeholder).
1457 for ns_slot in [None, Some("prod".to_string())] {
1458 let mut p = Process::new("scratch", empty_spec());
1459 p.metadata.name = None;
1460 p.metadata.namespace = ns_slot.clone();
1461 let err = p.owned_coordinates_or_err().unwrap_err();
1462 assert!(
1463 err.to_string().contains("metadata.name"),
1464 "err on missing name (ns={ns_slot:?}) should mention metadata.name; got {err}"
1465 );
1466 }
1467 }
1468
1469 #[test]
1470 fn owned_coordinates_or_err_error_message_matches_pre_lift_reconciler_wording() {
1471 // Load-bearing wording pin — every pre-lift `tatara-reconciler`
1472 // helper (`phase_machine::namespace_and_name`,
1473 // `signals::ingest`, `signals::consume_effect`) errored with
1474 // EXACTLY this wording. Post-lift the substrate owner produces
1475 // the same wording so log-line / test greps that anchored on
1476 // it keep matching, and no operator-visible message drift
1477 // lands as a side effect of the substrate move.
1478 let mut p = Process::new("scratch", empty_spec());
1479 p.metadata.name = None;
1480 let err = p.owned_coordinates_or_err().unwrap_err();
1481 assert_eq!(err.to_string(), "Process has no metadata.name");
1482 }
1483
1484 #[test]
1485 fn owned_coordinates_or_err_namespace_fallback_matches_default_namespace_const() {
1486 // Byte-identity pin between the owned form's namespace
1487 // fallback and the workspace-wide `DEFAULT_NAMESPACE` const.
1488 // A regression that spelled this fallback as any other
1489 // string ("kube-system", "", "default-ns") would silently
1490 // misroute every downstream namespaced-Api call on a
1491 // Process without a metadata.namespace — surfaces here
1492 // rather than at every kube-rs API caller.
1493 let mut p = Process::new("api", empty_spec());
1494 p.metadata.namespace = None;
1495 let (ns, _) = p.owned_coordinates_or_err().unwrap();
1496 assert_eq!(ns, Process::DEFAULT_NAMESPACE);
1497 }
1498
1499 #[test]
1500 fn owned_coordinates_or_err_matches_pre_lift_reconciler_helper_shape() {
1501 // Byte-identical parity pin between the owned + name-required
1502 // primitive here and the pre-lift `tatara-reconciler` helper
1503 // shape — the exact 2-slot unwrap chain each pre-lift caller
1504 // spelled by hand:
1505 //
1506 // let ns = p.metadata.namespace.clone().unwrap_or_else(|| "default".into());
1507 // let name = p.metadata.name.clone().ok_or_else(|| anyhow!(...))?;
1508 // Ok((ns, name))
1509 //
1510 // Sweeps every corner every callsite plausibly encounters
1511 // (both slots present, namespace absent, name absent, both
1512 // absent). A regression that inserted a normalization step
1513 // at the primitive that the pre-lift chain does NOT apply —
1514 // or vice versa — surfaces here rather than as silent drift
1515 // between the 12 pre-lift consumer callsites and the ONE
1516 // substrate owner they now route through.
1517 fn pre_lift(p: &Process) -> anyhow::Result<(String, String)> {
1518 let ns = p
1519 .metadata
1520 .namespace
1521 .clone()
1522 .unwrap_or_else(|| "default".into());
1523 let name = p
1524 .metadata
1525 .name
1526 .clone()
1527 .ok_or_else(|| anyhow::anyhow!("Process has no metadata.name"))?;
1528 Ok((ns, name))
1529 }
1530 // Both present.
1531 let mut p = Process::new("api", empty_spec());
1532 p.metadata.namespace = Some("prod".into());
1533 assert_eq!(p.owned_coordinates_or_err().unwrap(), pre_lift(&p).unwrap());
1534 // Namespace absent.
1535 let p = Process::new("api", empty_spec());
1536 assert_eq!(p.owned_coordinates_or_err().unwrap(), pre_lift(&p).unwrap());
1537 // Name absent → both variants error with the same wording.
1538 let mut p = Process::new("api", empty_spec());
1539 p.metadata.name = None;
1540 p.metadata.namespace = Some("prod".into());
1541 assert_eq!(
1542 p.owned_coordinates_or_err().unwrap_err().to_string(),
1543 pre_lift(&p).unwrap_err().to_string(),
1544 );
1545 // Both absent → still errors on the name gate.
1546 let mut p = Process::new("api", empty_spec());
1547 p.metadata.name = None;
1548 p.metadata.namespace = None;
1549 assert_eq!(
1550 p.owned_coordinates_or_err().unwrap_err().to_string(),
1551 pre_lift(&p).unwrap_err().to_string(),
1552 );
1553 }
1554
1555 #[test]
1556 fn owned_coordinates_or_err_axis_order_matches_coordinates_or_defaults() {
1557 // Cross-primitive coherence pin between the owned + name-
1558 // required form and the borrow + name-defaulted peer:
1559 // (namespace, name) axis order is IDENTICAL across both
1560 // return-forms. A regression that swapped the tuple slots on
1561 // only ONE of the two primitives would silently misroute
1562 // every consumer that picked between the two forms based on
1563 // its callsite's ownership needs. The pin re-reads both
1564 // primitives at test time so the equality holds iff both
1565 // live paths are the current implementation.
1566 let mut p = Process::new("app", empty_spec());
1567 p.metadata.namespace = Some("infra".into());
1568 let (borrow_ns, borrow_name) = p.coordinates_or_defaults();
1569 let (owned_ns, owned_name) = p.owned_coordinates_or_err().unwrap();
1570 assert_eq!(owned_ns, borrow_ns);
1571 assert_eq!(owned_name, borrow_name);
1572 // Explicit slot labels — pins the (namespace, name) axis
1573 // order as opposed to (name, namespace).
1574 assert_eq!(owned_ns, "infra"); // NOT "app"
1575 assert_eq!(owned_name, "app"); // NOT "infra"
1576 }
1577
1578 // ─── Process::coordinates_or_none substrate pins ──────────────────
1579 //
1580 // Pins the borrow + name-required peer of the coordinate-primitive
1581 // family on the (return-form × name-gate) axis pair. Closes the
1582 // corner previously left open (borrow + name-required) so the
1583 // three consumer shapes (child-Process delete-fan-out at
1584 // `phase_machine::handle_exiting`, claim-arbiter probe at
1585 // `phase_machine::process_holds_any_claim`, any future non-fatal
1586 // skip site) route through ONE primitive rather than three hand-
1587 // authored empty-string / `unwrap_or_default()` sentinel chains.
1588 // Fail-before-pass-after granularity: a regression that flipped
1589 // the namespace fallback, swapped the return-tuple axis order,
1590 // returned an owned form, or promoted a missing name to an error
1591 // rather than `None` surfaces here rather than as silent drift at
1592 // every borrow + name-required consumer.
1593
1594 #[test]
1595 fn coordinates_or_none_returns_slices_when_both_slots_present() {
1596 // Happy path — both slots populated, method returns borrowed
1597 // (&str, &str) in (namespace, name) axis order wrapped in
1598 // `Some`.
1599 let mut p = Process::new("api-gateway", empty_spec());
1600 p.metadata.namespace = Some("prod-app".into());
1601 let (ns, name) = p.coordinates_or_none().expect("Some when name set");
1602 assert_eq!(ns, "prod-app");
1603 assert_eq!(name, "api-gateway");
1604 }
1605
1606 #[test]
1607 fn coordinates_or_none_falls_back_on_namespace_but_returns_name_slice() {
1608 // Namespace absent → DEFAULT_NAMESPACE (shared with the peer
1609 // `coordinates_or_defaults` + `namespace_or_default`). Name
1610 // present → the metadata slice, wrapped in `Some`.
1611 let mut p = Process::new("api", empty_spec());
1612 p.metadata.namespace = None;
1613 let (ns, name) = p.coordinates_or_none().expect("Some when name set");
1614 assert_eq!(ns, Process::DEFAULT_NAMESPACE);
1615 assert_eq!(name, "api");
1616 }
1617
1618 #[test]
1619 fn coordinates_or_none_returns_none_when_metadata_name_absent_regardless_of_namespace() {
1620 // Name absent → `None`, REGARDLESS of whether the namespace
1621 // slot is populated. The name gate is strictly on
1622 // `metadata.name` and does NOT fall back to
1623 // `Self::UNNAMED_PLACEHOLDER` (that fallback is on the peer
1624 // `coordinates_or_defaults`, which exists precisely for
1625 // consumers that tolerate a display placeholder). Peer to
1626 // `owned_coordinates_or_err_errors_when_metadata_name_absent_regardless_of_namespace`
1627 // on the sibling primitive; a regression that widened THIS
1628 // form to substitute the placeholder while leaving the owned
1629 // form strict would silently drift the two borrow-form
1630 // primitives out of the coherence the family carries.
1631 for ns_slot in [None, Some("prod".to_string())] {
1632 let mut p = Process::new("scratch", empty_spec());
1633 p.metadata.name = None;
1634 p.metadata.namespace = ns_slot.clone();
1635 assert!(
1636 p.coordinates_or_none().is_none(),
1637 "coordinates_or_none must be None on missing name (ns={ns_slot:?})",
1638 );
1639 }
1640 }
1641
1642 #[test]
1643 fn coordinates_or_none_namespace_fallback_matches_default_namespace_const() {
1644 // Byte-identity pin between the borrow + name-required form's
1645 // namespace fallback and the workspace-wide `DEFAULT_NAMESPACE`
1646 // const. Sibling to
1647 // `owned_coordinates_or_err_namespace_fallback_matches_default_namespace_const`
1648 // on the peer primitive — the two forms MUST substitute the
1649 // same fallback string, else a consumer that switches between
1650 // them based on its ownership need silently observes a
1651 // different namespace-fallback shape as a side effect.
1652 let mut p = Process::new("api", empty_spec());
1653 p.metadata.namespace = None;
1654 let (ns, _) = p.coordinates_or_none().unwrap();
1655 assert_eq!(ns, Process::DEFAULT_NAMESPACE);
1656 }
1657
1658 #[test]
1659 fn coordinates_or_none_axis_order_matches_coordinates_or_defaults_when_name_present() {
1660 // Cross-primitive coherence pin between the two borrow-form
1661 // primitives: when the name is present, the (namespace, name)
1662 // return-tuple axis order is IDENTICAL across the two forms,
1663 // and the returned slices are the SAME `&str` view onto the
1664 // same metadata slots. A regression that swapped the tuple
1665 // slots on ONE form would silently misroute every consumer
1666 // that picked between the two forms based on its name-gate
1667 // need. The pin re-reads both primitives at test time so the
1668 // equality holds iff both live paths are the current
1669 // implementation.
1670 let mut p = Process::new("app", empty_spec());
1671 p.metadata.namespace = Some("infra".into());
1672 let (defaulted_ns, defaulted_name) = p.coordinates_or_defaults();
1673 let (required_ns, required_name) = p.coordinates_or_none().unwrap();
1674 assert_eq!(defaulted_ns, required_ns);
1675 assert_eq!(defaulted_name, required_name);
1676 // Explicit slot labels — pins the (namespace, name) axis order
1677 // as opposed to (name, namespace).
1678 assert_eq!(required_ns, "infra"); // NOT "app"
1679 assert_eq!(required_name, "app"); // NOT "infra"
1680 }
1681
1682 #[test]
1683 fn coordinates_or_none_axis_pair_diverges_from_coordinates_or_defaults_on_missing_name() {
1684 // Divergence pin between the two borrow-form primitives when
1685 // the name gate fires: `coordinates_or_defaults` substitutes
1686 // the display placeholder AND still returns a tuple;
1687 // `coordinates_or_none` returns `None`. A regression that
1688 // collapsed the two behaviors (either by dropping the gate
1689 // from the required form or by adding a `None` corner to the
1690 // defaulted form) would blur the axis pair's whole reason to
1691 // exist as two peer primitives.
1692 let mut p = Process::new("scratch", empty_spec());
1693 p.metadata.name = None;
1694 p.metadata.namespace = Some("prod".into());
1695 // Defaulted form: substitutes placeholder, no gate.
1696 assert_eq!(
1697 p.coordinates_or_defaults(),
1698 ("prod", Process::UNNAMED_PLACEHOLDER)
1699 );
1700 // Required form: gate fires, `None`.
1701 assert!(p.coordinates_or_none().is_none());
1702 }
1703
1704 #[test]
1705 fn coordinates_or_none_matches_pre_lift_reconciler_helper_shape() {
1706 // Byte-identical parity pin between the borrow + name-required
1707 // primitive here and the pre-lift `tatara-reconciler` helper
1708 // shapes — the exact 2-slot unwrap + gate chains each pre-lift
1709 // caller spelled by hand (`phase_machine::process_holds_any_claim`
1710 // spelled it as `unwrap_or("")` + `is_empty` early-return;
1711 // `phase_machine::handle_exiting`'s child-fan-out spelled it
1712 // as `unwrap_or_default()` + implicit no-op delete on the
1713 // empty API-path). Sweeps every corner every callsite plausibly
1714 // encounters (both slots present, namespace absent, name
1715 // absent + ns present, both absent). A regression that
1716 // inserted a normalization step at the primitive the pre-lift
1717 // chain does NOT apply — or vice versa — surfaces here rather
1718 // than as silent drift between the pre-lift consumer sites
1719 // and the ONE substrate owner they now route through.
1720 fn pre_lift_holds_any_claim(p: &Process) -> Option<(&str, &str)> {
1721 let ns = p.metadata.namespace.as_deref().unwrap_or("default");
1722 let name = p.metadata.name.as_deref().unwrap_or("");
1723 if name.is_empty() {
1724 return None;
1725 }
1726 Some((ns, name))
1727 }
1728 // Both present.
1729 let mut p = Process::new("api", empty_spec());
1730 p.metadata.namespace = Some("prod".into());
1731 assert_eq!(p.coordinates_or_none(), pre_lift_holds_any_claim(&p));
1732 // Namespace absent.
1733 let p = Process::new("api", empty_spec());
1734 assert_eq!(p.coordinates_or_none(), pre_lift_holds_any_claim(&p));
1735 // Name absent → both variants return `None` regardless of ns.
1736 let mut p = Process::new("api", empty_spec());
1737 p.metadata.name = None;
1738 p.metadata.namespace = Some("prod".into());
1739 assert_eq!(p.coordinates_or_none(), pre_lift_holds_any_claim(&p));
1740 // Both absent → still `None` on the name gate.
1741 let mut p = Process::new("api", empty_spec());
1742 p.metadata.name = None;
1743 p.metadata.namespace = None;
1744 assert_eq!(p.coordinates_or_none(), pre_lift_holds_any_claim(&p));
1745 }
1746
1747 #[test]
1748 fn coordinates_or_none_axis_order_matches_owned_coordinates_or_err_on_happy_path() {
1749 // Cross-primitive coherence pin at the sibling corner: when
1750 // BOTH slots are present, the borrow + name-required form
1751 // (this method) and the owned + name-required peer
1752 // (`owned_coordinates_or_err`) return the SAME `(ns, name)`
1753 // pair — the axis order is IDENTICAL and neither primitive
1754 // silently applies a normalization the other omits. A
1755 // regression that skewed one form's normalization would
1756 // surface here rather than as silent drift between the two
1757 // name-required corners of the primitive family.
1758 let mut p = Process::new("app", empty_spec());
1759 p.metadata.namespace = Some("infra".into());
1760 let (borrow_ns, borrow_name) = p.coordinates_or_none().unwrap();
1761 let (owned_ns, owned_name) = p.owned_coordinates_or_err().unwrap();
1762 assert_eq!(borrow_ns, owned_ns.as_str());
1763 assert_eq!(borrow_name, owned_name.as_str());
1764 }
1765
1766 #[test]
1767 fn coordinates_or_defaults_axis_order_matches_qualified_process_ref() {
1768 // Pins the load-bearing convention that the return-tuple
1769 // axis order is (namespace, name) — the exact positional
1770 // argument order the substrate's paired-composer primitive
1771 // `tatara_reconciler::ssapply::qualified_process_ref(ns,
1772 // name)` consumes. A regression that swapped the tuple
1773 // slots would silently misroute every annotation writer /
1774 // claim-arbiter row / owner-metadata seed built by feeding
1775 // this pair into the composer — every downstream `<ns>/
1776 // <name>` grep would suddenly see `<name>/<ns>`. The test
1777 // verifies the tuple's first slot is what a hand-authored
1778 // `.metadata.namespace.as_deref()...` produced pre-lift, and
1779 // the second slot is what `.metadata.name.as_deref()...`
1780 // produced.
1781 let mut p = Process::new("app", empty_spec());
1782 p.metadata.namespace = Some("infra".into());
1783 let (ns, name) = p.coordinates_or_defaults();
1784 assert_eq!(ns, "infra"); // NOT "app"
1785 assert_eq!(name, "app"); // NOT "infra"
1786 }
1787
1788 // ─── Process::annotation substrate pins ────────────────────────────
1789 //
1790 // Pins the borrow-form annotation-lookup primitive that owns the
1791 // 3-line `.metadata.annotations.as_ref().and_then(|m| m.get(KEY))`
1792 // chain three hand-authored sites restated by hand pre-lift:
1793 // `tatara-reconciler::signals::ingest` (SIGNAL),
1794 // `tatara-reconciler::phase_machine::released_from_annotation`
1795 // (RELEASED_FROM), and
1796 // `tatara-pool-reconciler::controller_pool::process_belongs_to_pool`
1797 // (POOL). Fail-before-pass-after granularity: a regression that
1798 // widened the missing-`annotations` corner (returning `Some("")`
1799 // instead of `None`), promoted a missing key to an error, dropped
1800 // the borrow-form return, or changed the two swallowed corners'
1801 // shared collapse to `None` surfaces here rather than as silent
1802 // drift at the three consumer sites.
1803 fn process_with_annotation(key: &str, value: &str) -> Process {
1804 let mut p = Process::new("some-proc", empty_spec());
1805 let mut anns = std::collections::BTreeMap::new();
1806 anns.insert(key.to_string(), value.to_string());
1807 p.metadata.annotations = Some(anns);
1808 p
1809 }
1810
1811 #[test]
1812 fn annotation_returns_none_when_metadata_annotations_is_none() {
1813 // Missing-`annotations` corner: a Process with no annotations
1814 // block at all returns `None` for every key. Peer to
1815 // `observed_flux_resources_returns_empty_slice_when_status_is_none`
1816 // on the status-projection axis; both primitives collapse the
1817 // outer `Option` corner rather than requiring each consumer
1818 // to spell the guard by hand.
1819 let mut p = Process::new("scratch", empty_spec());
1820 p.metadata.annotations = None;
1821 assert!(p.annotation("tatara.pleme.io/signal").is_none());
1822 assert!(p.annotation("tatara.pleme.io/pool").is_none());
1823 assert!(p.annotation("").is_none());
1824 }
1825
1826 #[test]
1827 fn annotation_returns_none_when_key_absent_from_populated_map() {
1828 // Missing-key corner: annotations block populated with OTHER
1829 // keys returns `None` for the queried key. Symmetric with the
1830 // missing-`annotations` corner — both corners collapse to the
1831 // same `None`, matching the pre-lift `.and_then(...)`
1832 // behavior every consumer relied on.
1833 let p = process_with_annotation("tatara.pleme.io/other", "value");
1834 assert!(p.annotation("tatara.pleme.io/signal").is_none());
1835 assert!(p.annotation("").is_none());
1836 }
1837
1838 #[test]
1839 fn annotation_returns_borrowed_slice_when_key_present() {
1840 // Happy path: annotations block populated + key present →
1841 // `Some(&str)` borrowed from the underlying `String` in the
1842 // map. A regression that returned an owned `String` (defeating
1843 // the primitive's role as a zero-copy projection) would
1844 // surface at the lifetime of the returned reference — the
1845 // `&str` outlives the borrow of `&p` here.
1846 let p = process_with_annotation("tatara.pleme.io/signal", "SIGHUP");
1847 assert_eq!(p.annotation("tatara.pleme.io/signal"), Some("SIGHUP"));
1848 }
1849
1850 #[test]
1851 fn annotation_returns_borrowed_empty_string_slice_when_value_is_empty() {
1852 // Edge corner between the missing-key `None` and the present-
1853 // key `Some("")` — a Process whose annotation is EXPLICITLY
1854 // set to an empty string returns `Some("")`, NOT `None`. A
1855 // regression that normalized the empty-string value to `None`
1856 // (a plausible "defensive" simplification) would silently
1857 // reshape the corner every callsite pre-lift kept distinct via
1858 // `.cloned().unwrap_or_default()` (which collapses BOTH to
1859 // `""`) or `.map(String::as_str)` (which keeps them distinct
1860 // as `None` vs `Some("")`).
1861 let p = process_with_annotation("tatara.pleme.io/signal", "");
1862 assert_eq!(p.annotation("tatara.pleme.io/signal"), Some(""));
1863 }
1864
1865 #[test]
1866 fn annotation_is_a_pure_projection() {
1867 // Purity pin — repeated calls return equal results and the
1868 // primitive does not mutate `self`. Peer to
1869 // `observed_flux_resources_is_a_pure_projection` on the
1870 // status-projection axis.
1871 let p = process_with_annotation("tatara.pleme.io/released-from", "Attested");
1872 let a = p.annotation("tatara.pleme.io/released-from");
1873 let b = p.annotation("tatara.pleme.io/released-from");
1874 assert_eq!(a, b);
1875 assert_eq!(a, Some("Attested"));
1876 }
1877
1878 #[test]
1879 fn annotation_matches_pre_lift_reconciler_chain_shape() {
1880 // Byte-identical parity pin between the borrow-form primitive
1881 // here and the pre-lift `tatara-reconciler` / `tatara-pool-
1882 // reconciler` chain shape — the exact 3-line
1883 // `.metadata.annotations.as_ref().and_then(|m| m.get(KEY))
1884 // .map(String::as_str)` incantation each pre-lift caller
1885 // spelled by hand (three variants of tail collapsed onto ONE
1886 // borrow-form primitive here; each caller reapplies its own
1887 // tail at its own site). Sweeps every corner (missing
1888 // annotations map, missing key, present key with value,
1889 // present key with empty value) so a regression that inserted
1890 // a normalization at the primitive the pre-lift chain does
1891 // NOT apply — or vice versa — surfaces here rather than as
1892 // silent drift between the ONE substrate owner and the three
1893 // consumer sites.
1894 fn pre_lift<'a>(p: &'a Process, key: &str) -> Option<&'a str> {
1895 p.metadata
1896 .annotations
1897 .as_ref()
1898 .and_then(|m| m.get(key))
1899 .map(String::as_str)
1900 }
1901 // Missing annotations map.
1902 let mut p = Process::new("x", empty_spec());
1903 p.metadata.annotations = None;
1904 assert_eq!(p.annotation("k"), pre_lift(&p, "k"));
1905 // Missing key in populated map.
1906 let p = process_with_annotation("other", "v");
1907 assert_eq!(p.annotation("k"), pre_lift(&p, "k"));
1908 // Present key with non-empty value.
1909 let p = process_with_annotation("k", "v");
1910 assert_eq!(p.annotation("k"), pre_lift(&p, "k"));
1911 // Present key with explicitly-empty value — the corner
1912 // `.cloned().unwrap_or_default()` collapses to `""` post-tail
1913 // but the primitive-level shape stays `Some("")`.
1914 let p = process_with_annotation("k", "");
1915 assert_eq!(p.annotation("k"), pre_lift(&p, "k"));
1916 }
1917
1918 #[test]
1919 fn annotation_composes_owned_tail_matching_pre_lift_signals_ingest() {
1920 // Pins the exact tail shape `tatara-reconciler::signals::
1921 // ingest` composed pre-lift: an `Option<String>` for the
1922 // downstream `let Some(raw) = raw else { ... }` guard.
1923 // Post-lift the callsite composes `.map(str::to_string)` at
1924 // its own site; this test pins the composition matches the
1925 // pre-lift `.cloned()` tail byte-for-byte on both corners the
1926 // consumer's downstream distinguishes (annotation present →
1927 // `Some(String)`; absent → `None`).
1928 let p = process_with_annotation("tatara.pleme.io/signal", "SIGUSR1");
1929 assert_eq!(
1930 p.annotation("tatara.pleme.io/signal").map(str::to_string),
1931 Some("SIGUSR1".to_string())
1932 );
1933 let mut q = Process::new("y", empty_spec());
1934 q.metadata.annotations = None;
1935 assert_eq!(
1936 q.annotation("tatara.pleme.io/signal").map(str::to_string),
1937 None
1938 );
1939 }
1940
1941 #[test]
1942 fn annotation_composes_default_tail_matching_pre_lift_released_from() {
1943 // Pins the exact tail shape
1944 // `tatara-reconciler::phase_machine::released_from_annotation`
1945 // composed pre-lift: a bare `String` via `.cloned()
1946 // .unwrap_or_default()` for the downstream
1947 // `match v.as_str()` dispatch. Post-lift the callsite matches
1948 // directly on `Option<&str>` (Some("Failed") vs _); this test
1949 // pins that the borrow-form primitive plus the `.unwrap_or("")`
1950 // fallback reproduces the pre-lift bare-string shape on both
1951 // corners.
1952 let p = process_with_annotation("tatara.pleme.io/released-from", "Failed");
1953 assert_eq!(
1954 p.annotation("tatara.pleme.io/released-from").unwrap_or(""),
1955 "Failed"
1956 );
1957 let mut q = Process::new("y", empty_spec());
1958 q.metadata.annotations = None;
1959 assert_eq!(
1960 q.annotation("tatara.pleme.io/released-from").unwrap_or(""),
1961 ""
1962 );
1963 }
1964
1965 #[test]
1966 fn annotation_composes_borrow_equality_tail_matching_pre_lift_pool() {
1967 // Pins the exact tail shape `tatara-pool-reconciler::
1968 // controller_pool::process_belongs_to_pool` composed pre-lift:
1969 // an `Option<&str>` compared with `== Some(pool_name)` for the
1970 // membership gate. Post-lift the callsite composes
1971 // `p.annotation(POOL) == Some(pool_name)` verbatim; this test
1972 // pins that the borrow-form primitive returns exactly the
1973 // shape the equality gate expects.
1974 let p = process_with_annotation("tatara.pleme.io/pool", "demo-pool");
1975 assert_eq!(
1976 p.annotation("tatara.pleme.io/pool") == Some("demo-pool"),
1977 true
1978 );
1979 assert_eq!(p.annotation("tatara.pleme.io/pool") == Some("other"), false);
1980 }
1981
1982 // ─── Process::uid_or_empty substrate pins ──────────────────────────
1983 //
1984 // Pins the borrow-form metadata-projection primitive on the
1985 // `metadata.uid` axis that owns the `.metadata.uid.as_deref()
1986 // .unwrap_or("")` chain the two hand-authored
1987 // `tatara-reconciler::render` sites (`render_routing` +
1988 // `render_export_jobs`) restated by hand pre-lift. Peer to the
1989 // sibling `namespace_or_default_*` + `name_or_placeholder_*` pin
1990 // families on the metadata-slot × fallback-shape axis; all three
1991 // primitives return borrows of an owned-metadata slot with a slot-
1992 // specific fallback baked in (`"default"` for namespace, `"unnamed"`
1993 // for name, `""` for uid — the load-bearing gate value for
1994 // `owner_references_json`'s `is_empty` check). Fail-before-pass-
1995 // after granularity: `uid_or_empty` did not exist pre-lift, so any
1996 // test invoking it fails to compile pre-lift and passes post-lift.
1997
1998 #[test]
1999 fn uid_or_empty_returns_empty_string_when_metadata_uid_is_none() {
2000 // Empty-slot corner pin: the primitive collapses the no-uid
2001 // case to `""`, matching the pre-lift `.as_deref().unwrap_or("")`
2002 // chain's `""` byte-identically at both render consumer sites.
2003 // Semantically corresponds to a Process pre-metadata (fixtured
2004 // in tests, or caught mid-Forking before the API server has
2005 // stamped a `uid`); the downstream `owner_references_json`
2006 // composer gates on this exact `""` sentinel to stamp
2007 // `metadata.ownerReferences: []` rather than emit an owner-ref
2008 // pointing at a placeholder uid.
2009 let mut p = Process::new("scratch", empty_spec());
2010 p.metadata.uid = None;
2011 assert_eq!(p.uid_or_empty(), "");
2012 }
2013
2014 #[test]
2015 fn uid_or_empty_returns_borrowed_str_when_slot_is_populated() {
2016 // Happy-path pin: with a populated `metadata.uid` slot, the
2017 // primitive returns a borrowed `&str` whose contents match the
2018 // persisted `String`. A regression that reshaped / normalized
2019 // / cross-cluster-stripped the uid without touching this pin
2020 // would surface here rather than as silent skew at the two
2021 // `owner_references_json(name, uid)` emitters on the SAME
2022 // Process.
2023 let mut p = Process::new("owned-proc", empty_spec());
2024 p.metadata.uid = Some("uid-abc-123".into());
2025 assert_eq!(p.uid_or_empty(), "uid-abc-123");
2026 }
2027
2028 #[test]
2029 fn uid_or_empty_returns_empty_string_when_slot_is_explicitly_empty_string() {
2030 // Corner between the missing-slot `None` and the explicitly-
2031 // empty-string `Some("")` — both collapse to `""` at the
2032 // primitive because the downstream gate at
2033 // `owner_references_json` treats `.is_empty()` uniformly (the
2034 // empty-slot posture is what the whole primitive family
2035 // encodes: "no admissible owner reference, stamp `[]`"). A
2036 // regression that discriminated the two corners (returning a
2037 // sentinel `"<none>"` for the missing slot but `""` for the
2038 // explicit slot) would break the composition with
2039 // `owner_references_json` at the exactly-two-corner gate.
2040 let mut p = Process::new("owned-proc", empty_spec());
2041 p.metadata.uid = Some(String::new());
2042 assert_eq!(p.uid_or_empty(), "");
2043 }
2044
2045 #[test]
2046 fn uid_or_empty_is_a_zero_copy_borrow_projection() {
2047 // Borrow-discipline pin: the returned `&str` borrows the
2048 // persisted `String`'s underlying byte buffer in place — NOT
2049 // a fresh allocation or a clone. A regression that switched
2050 // the projection to an owned `String` (via `.clone()` or a
2051 // `format!` wrap) would defeat the zero-copy contract the
2052 // lift's primary strict-widening delivers, and would surface
2053 // here via pointer-identity comparison.
2054 let mut p = Process::new("owned-proc", empty_spec());
2055 p.metadata.uid = Some("uid-borrow-pin".into());
2056 let slice = p.uid_or_empty();
2057 assert!(std::ptr::eq(
2058 slice.as_ptr(),
2059 p.metadata.uid.as_ref().unwrap().as_ptr()
2060 ));
2061 }
2062
2063 #[test]
2064 fn uid_or_empty_is_a_pure_projection() {
2065 // Purity pin — repeated calls return byte-identical slices
2066 // (same pointer, same length). A regression that introduced
2067 // state (a lazy-cached normalized slot, a first-call
2068 // canonicalization pass) would surface here rather than as
2069 // silent drift between the two render consumer sites on the
2070 // SAME Process within one render pass.
2071 let mut p = Process::new("owned-proc", empty_spec());
2072 p.metadata.uid = Some("uid-pure".into());
2073 let a = p.uid_or_empty();
2074 let b = p.uid_or_empty();
2075 assert!(std::ptr::eq(a.as_ptr(), b.as_ptr()));
2076 assert_eq!(a.len(), b.len());
2077 }
2078
2079 #[test]
2080 fn uid_or_empty_matches_pre_lift_render_chain_shape() {
2081 // Byte-identical parity pin between the borrow-form primitive
2082 // here and the pre-lift `tatara-reconciler::render` chain shape
2083 // — the exact `.metadata.uid.as_deref().unwrap_or("")`
2084 // incantation both `render_routing` (line 514) and
2085 // `render_export_jobs` (line 653) spelled by hand pre-lift.
2086 // Sweeps every corner (missing uid slot, populated uid slot,
2087 // explicitly-empty uid slot) so a regression that inserted a
2088 // normalization the pre-lift chain does NOT apply — or vice
2089 // versa — surfaces here rather than as silent drift between
2090 // the ONE substrate owner and the two consumer sites.
2091 fn pre_lift(p: &Process) -> &str {
2092 p.metadata.uid.as_deref().unwrap_or("")
2093 }
2094 // Missing slot.
2095 let mut p = Process::new("x", empty_spec());
2096 p.metadata.uid = None;
2097 assert_eq!(p.uid_or_empty(), pre_lift(&p));
2098 // Populated slot.
2099 let mut p = Process::new("x", empty_spec());
2100 p.metadata.uid = Some("uid-42".into());
2101 assert_eq!(p.uid_or_empty(), pre_lift(&p));
2102 // Explicitly-empty slot.
2103 let mut p = Process::new("x", empty_spec());
2104 p.metadata.uid = Some(String::new());
2105 assert_eq!(p.uid_or_empty(), pre_lift(&p));
2106 }
2107
2108 #[test]
2109 fn uid_or_empty_composes_with_owner_references_json_empty_gate() {
2110 // Cross-primitive composition pin — the empty-string sentinel
2111 // this primitive returns for the missing-uid corner is EXACTLY
2112 // the sentinel the sibling substrate composer
2113 // `owner_references_json(name, uid)` gates on to stamp
2114 // `metadata.ownerReferences: []`. A regression that changed
2115 // the sentinel at either end (this primitive returning
2116 // `"<none>"`, `owner_references_json` gating on `uid == "0"`
2117 // instead of `uid.is_empty()`) would break the composition
2118 // and surface here rather than as an operator-observed
2119 // orphan resource after apply.
2120 let mut p = Process::new("x", empty_spec());
2121 p.metadata.uid = None;
2122 let refs = crate::owner_references_json("some-name", p.uid_or_empty());
2123 assert!(
2124 refs.is_empty(),
2125 "empty-uid corner must produce empty owner-refs array"
2126 );
2127
2128 p.metadata.uid = Some("real-uid".into());
2129 let refs = crate::owner_references_json("some-name", p.uid_or_empty());
2130 assert_eq!(
2131 refs.len(),
2132 1,
2133 "populated-uid corner must produce one owner-ref entry"
2134 );
2135 }
2136
2137 // ─── Process::declared_parent_pid substrate pins ─────────────────
2138 //
2139 // Pins the borrow-form spec-projection primitive on the declared
2140 // parent-PID axis that owns the `.spec.identity.parent.as_deref()`
2141 // chain the two hand-authored `tatara-reconciler::phase_machine`
2142 // sites (`handle_forking` ALLOCATE-PID composer + `handle_exiting`
2143 // SIGTERM-cascade child-fan-out filter) restated by hand pre-lift.
2144 // Peer to the sibling `observed_pid_*` pin family on the (spec-
2145 // declared × status-observed) axis pair; both compose the same
2146 // borrow-form `Option<&str>` return-shape skeleton on distinct
2147 // slots (`spec.identity.parent` vs. `status.pid`). Fail-before-
2148 // pass-after granularity: `declared_parent_pid` did not exist
2149 // pre-lift, so any test invoking it fails to compile pre-lift and
2150 // passes post-lift.
2151 fn process_with_declared_parent(parent: Option<&str>) -> Process {
2152 let mut spec = empty_spec();
2153 spec.identity.parent = parent.map(str::to_string);
2154 Process::new("child-proc", spec)
2155 }
2156
2157 #[test]
2158 fn declared_parent_pid_returns_none_when_slot_is_none() {
2159 // Empty-slot corner pin: the primitive collapses the no-
2160 // parent case to `None`, matching the pre-lift `.as_deref()`
2161 // chain's `None` byte-identically at both reconciler consumer
2162 // sites. Semantically corresponds to a Process authored at
2163 // cluster init (PID 1) with no upstream parent — the
2164 // ALLOCATE-PID composer feeds `None` into `pid::allocate_pid`
2165 // to signal "no prefix", and the SIGTERM cascade's filter
2166 // never matches such a Process because a child's declared
2167 // parent can never equal `Some(pid)` when the slot is `None`.
2168 let p = process_with_declared_parent(None);
2169 assert!(p.declared_parent_pid().is_none());
2170 }
2171
2172 #[test]
2173 fn declared_parent_pid_returns_borrowed_str_when_slot_is_populated() {
2174 // Happy-path pin: with a populated `spec.identity.parent`
2175 // slot, the primitive returns a borrowed `&str` whose
2176 // contents match the persisted `String`. A regression that
2177 // filtered / reshaped / canonicalized the string would
2178 // surface here rather than as silent skew at the child-fan-
2179 // out filter's `.declared_parent_pid() == Some(pid)`
2180 // equality check on the SAME parent-child pair.
2181 let p = process_with_declared_parent(Some("seph.1"));
2182 assert_eq!(p.declared_parent_pid(), Some("seph.1"));
2183 }
2184
2185 #[test]
2186 fn declared_parent_pid_is_a_zero_copy_borrow_projection() {
2187 // Borrow-discipline pin: the returned `&str` borrows the
2188 // persisted `String`'s underlying byte buffer in place —
2189 // NOT a fresh allocation or a clone. A regression that
2190 // switched the projection to an owned `String` (via
2191 // `.clone()` or `.to_owned()`) would defeat the zero-copy
2192 // contract the lift's primary strict-widening delivers.
2193 // The `handle_exiting` cascade filter runs per candidate
2194 // child across the cluster-wide Process list; a per-row
2195 // `String::clone` would allocate one heap block per non-
2196 // matching row, so the borrow-form primitive is load-
2197 // bearing for large clusters. Peer to the sibling
2198 // `observed_pid_is_a_zero_copy_borrow_projection` pin on
2199 // the status-observed side of the axis pair.
2200 let p = process_with_declared_parent(Some("seph.1"));
2201 let borrowed = p.declared_parent_pid().expect("populated slot");
2202 let persisted = p.spec.identity.parent.as_ref().unwrap();
2203 assert!(std::ptr::eq(borrowed.as_ptr(), persisted.as_ptr()));
2204 }
2205
2206 #[test]
2207 fn declared_parent_pid_is_a_pure_projection() {
2208 // Purity pin: calling the projection twice on the same
2209 // `Process` returns byte-identical `&str`s (same pointer,
2210 // same length). A regression that introduced state — a
2211 // lazy-cached slice materialized on first call, a
2212 // normalization step that ran once and cached — would
2213 // surface here rather than as silent drift between the
2214 // ALLOCATE-PID composer and the SIGTERM cascade's child-
2215 // fan-out filter within one reconcile pass.
2216 let p = process_with_declared_parent(Some("seph.1.3"));
2217 let a = p.declared_parent_pid().expect("populated slot");
2218 let b = p.declared_parent_pid().expect("populated slot");
2219 assert!(std::ptr::eq(a.as_ptr(), b.as_ptr()));
2220 assert_eq!(a.len(), b.len());
2221 }
2222
2223 #[test]
2224 fn declared_parent_pid_matches_pre_lift_reconciler_chain_shape() {
2225 // Byte-identical parity pin between the borrow-form primitive
2226 // here and the pre-lift `tatara-reconciler::phase_machine`
2227 // `.spec.identity.parent.as_deref()` chain shape. Sweeps
2228 // every corner every callsite plausibly encounters (empty
2229 // slot, populated with a hierarchical PID). A regression
2230 // that inserted a normalization step at the primitive the
2231 // pre-lift chain does NOT apply — or vice versa — surfaces
2232 // here rather than as silent drift between the pre-lift
2233 // consumer sites and the ONE substrate owner they now route
2234 // through. Peer to
2235 // `observed_pid_matches_pre_lift_reconciler_chain_shape` on
2236 // the sibling axis's borrow-form primitive.
2237 fn pre_lift(p: &Process) -> Option<&str> {
2238 p.spec.identity.parent.as_deref()
2239 }
2240 // Empty slot.
2241 let p = process_with_declared_parent(None);
2242 assert_eq!(p.declared_parent_pid(), pre_lift(&p));
2243 // Populated with a hierarchical PID.
2244 let p = process_with_declared_parent(Some("seph.1"));
2245 assert_eq!(p.declared_parent_pid(), pre_lift(&p));
2246 // Populated with a deeper hierarchical PID.
2247 let p = process_with_declared_parent(Some("seph.1.7.42"));
2248 assert_eq!(p.declared_parent_pid(), pre_lift(&p));
2249 }
2250
2251 #[test]
2252 fn declared_parent_pid_preserves_hierarchical_pid_format() {
2253 // Format-preservation pin: the hierarchical PID path
2254 // (dotted-segment form `seph.1.7`, matching the ported
2255 // `convergence-controller/src/identity.rs` scheme) reaches
2256 // the caller with segments and separators byte-identical
2257 // to the persisted `String`. A regression that inserted a
2258 // canonicalization pass (a segment-count validator, a
2259 // separator swap `.` → `/`, a leading/trailing whitespace
2260 // trim) would silently misroute the SIGTERM cascade's
2261 // `declared_parent_pid() == Some(pid)` comparator against
2262 // children whose `parent` field was authored in the ported
2263 // scheme's exact form — the SAME children the observed_pid
2264 // primitive is pinned to match on the other side of the
2265 // axis pair.
2266 for parent in ["seph", "seph.1", "seph.1.7", "seph.1.7.42"] {
2267 let p = process_with_declared_parent(Some(parent));
2268 assert_eq!(p.declared_parent_pid(), Some(parent));
2269 }
2270 }
2271
2272 #[test]
2273 fn declared_parent_pid_composes_with_observed_pid_for_child_fanout_filter() {
2274 // Cross-axis coherence pin against the sibling
2275 // [`Self::observed_pid`] on the (spec-declared × status-
2276 // observed) axis pair: a child's `.declared_parent_pid()`
2277 // and its parent's `.observed_pid()` compose through the
2278 // SAME borrow-form `Option<&str>` skeleton so the
2279 // `handle_exiting` cascade filter's equality gate holds
2280 // structurally. A regression that skewed EITHER primitive's
2281 // return-form (return-shape, borrow discipline, empty-slot
2282 // collapse) would silently misroute every SIGTERM cascade
2283 // on the parent-child pair. This pin re-reads both primitives
2284 // at test time so the composition holds iff both live paths
2285 // are the current implementation.
2286 // Parent Process: has an observed PID.
2287 let mut parent = Process::new("parent-proc", empty_spec());
2288 parent.status = Some(ProcessStatus {
2289 pid: Some("seph.1".to_string()),
2290 ..Default::default()
2291 });
2292 // Child Process: declared parent matches parent's observed PID.
2293 let child = process_with_declared_parent(Some("seph.1"));
2294 // The `handle_exiting` filter's equality gate:
2295 // `child.declared_parent_pid() == Some(parent.observed_pid()?)`.
2296 let parent_pid = parent.observed_pid().expect("parent has PID");
2297 assert_eq!(child.declared_parent_pid(), Some(parent_pid));
2298 // Sibling Process with an unrelated declared parent must NOT
2299 // match the same parent — pins that the filter's SKIP branch
2300 // holds on the other side of the axis pair.
2301 let sibling = process_with_declared_parent(Some("seph.2"));
2302 assert_ne!(sibling.declared_parent_pid(), Some(parent_pid));
2303 }
2304
2305 // ─── Process::declared_name_override substrate pins ──────────────
2306 //
2307 // Pins the borrow-form spec-projection primitive on the declared
2308 // name-override sub-axis of the declared-identity axis that owns
2309 // the `.spec.identity.name_override.as_deref()` chain the two
2310 // hand-authored `tatara-reconciler::phase_machine` sites
2311 // (`handle_pending` DECLARE composer + `handle_forking` ALLOCATE-
2312 // PID rehydration branch) restated by hand pre-lift. Peer to the
2313 // sibling `declared_parent_pid_*` pin family on the (parent ×
2314 // name-override) sub-axis pair; both compose the same borrow-form
2315 // `Option<&str>` return-shape skeleton on distinct slots
2316 // (`spec.identity.name_override` vs `spec.identity.parent`).
2317 // Fail-before-pass-after granularity: `declared_name_override`
2318 // did not exist pre-lift, so any test invoking it fails to
2319 // compile pre-lift and passes post-lift.
2320 fn process_with_declared_name_override(name_override: Option<&str>) -> Process {
2321 let mut spec = empty_spec();
2322 spec.identity.name_override = name_override.map(str::to_string);
2323 Process::new("some-proc", spec)
2324 }
2325
2326 #[test]
2327 fn declared_name_override_returns_none_when_slot_is_none() {
2328 // Empty-slot corner pin: the primitive collapses the no-
2329 // override case to `None`, matching the pre-lift `.as_deref()`
2330 // chain's `None` byte-identically at both reconciler consumer
2331 // sites. Semantically corresponds to a Process authored
2332 // WITHOUT the human-name-override escape hatch — the default;
2333 // `derive_identity` then computes the name from the content
2334 // hash and stamps `name_override: false` on the resulting
2335 // [`Identity`].
2336 let p = process_with_declared_name_override(None);
2337 assert!(p.declared_name_override().is_none());
2338 }
2339
2340 #[test]
2341 fn declared_name_override_returns_borrowed_str_when_slot_is_populated() {
2342 // Happy-path pin: with a populated `spec.identity
2343 // .name_override` slot, the primitive returns a borrowed
2344 // `&str` whose contents match the persisted `String`. A
2345 // regression that filtered / reshaped / canonicalized the
2346 // string at the primitive (as opposed to inside
2347 // `derive_identity`, where the trim/empty-filter lives today)
2348 // would surface here rather than as silent skew between the
2349 // DECLARE composer and the ALLOCATE-PID rehydration branch on
2350 // the SAME Process spec.
2351 let p = process_with_declared_name_override(Some("observability-stack"));
2352 assert_eq!(p.declared_name_override(), Some("observability-stack"));
2353 }
2354
2355 #[test]
2356 fn declared_name_override_is_a_zero_copy_borrow_projection() {
2357 // Borrow-discipline pin: the returned `&str` borrows the
2358 // persisted `String`'s underlying byte buffer in place —
2359 // NOT a fresh allocation or a clone. Peer to the sibling
2360 // `declared_parent_pid_is_a_zero_copy_borrow_projection` pin
2361 // on the other side of the (parent × name-override) sub-axis
2362 // pair; the borrow discipline holds structurally on BOTH
2363 // sub-axes so a future `declared_identity` composite that
2364 // returns both halves together can compose them without
2365 // dropping into an owning form.
2366 let p = process_with_declared_name_override(Some("observability-stack"));
2367 let borrowed = p.declared_name_override().expect("populated slot");
2368 let persisted = p.spec.identity.name_override.as_ref().unwrap();
2369 assert!(std::ptr::eq(borrowed.as_ptr(), persisted.as_ptr()));
2370 }
2371
2372 #[test]
2373 fn declared_name_override_is_a_pure_projection() {
2374 // Purity pin: calling the projection twice on the same
2375 // `Process` returns byte-identical `&str`s (same pointer,
2376 // same length). A regression that introduced state — a
2377 // lazy-cached slice materialized on first call, a
2378 // normalization step that ran once and cached — would
2379 // surface here rather than as silent drift between the
2380 // DECLARE composer and the ALLOCATE-PID rehydration branch
2381 // within one reconcile pass.
2382 let p = process_with_declared_name_override(Some("gateway-primary"));
2383 let a = p.declared_name_override().expect("populated slot");
2384 let b = p.declared_name_override().expect("populated slot");
2385 assert!(std::ptr::eq(a.as_ptr(), b.as_ptr()));
2386 assert_eq!(a.len(), b.len());
2387 }
2388
2389 #[test]
2390 fn declared_name_override_matches_pre_lift_reconciler_chain_shape() {
2391 // Byte-identical parity pin between the borrow-form primitive
2392 // here and the pre-lift `tatara-reconciler::phase_machine`
2393 // `.spec.identity.name_override.as_deref()` chain shape.
2394 // Sweeps every corner every callsite plausibly encounters
2395 // (empty slot, populated with a bare name, populated with a
2396 // whitespace-containing name that `derive_identity`'s
2397 // internal trim would collapse, populated with an explicitly
2398 // empty string that `derive_identity`'s internal
2399 // `!s.is_empty()` filter would reject). A regression that
2400 // inserted a normalization step at the primitive the pre-
2401 // lift chain does NOT apply — or vice versa — surfaces here
2402 // rather than as silent drift between the pre-lift consumer
2403 // sites and the ONE substrate owner they now route through.
2404 // Peer to
2405 // `declared_parent_pid_matches_pre_lift_reconciler_chain_shape`
2406 // on the sibling sub-axis's borrow-form primitive.
2407 fn pre_lift(p: &Process) -> Option<&str> {
2408 p.spec.identity.name_override.as_deref()
2409 }
2410 // Empty slot.
2411 let p = process_with_declared_name_override(None);
2412 assert_eq!(p.declared_name_override(), pre_lift(&p));
2413 // Populated with a bare name.
2414 let p = process_with_declared_name_override(Some("observability-stack"));
2415 assert_eq!(p.declared_name_override(), pre_lift(&p));
2416 // Populated with a whitespace-containing name.
2417 let p = process_with_declared_name_override(Some(" observability-stack "));
2418 assert_eq!(p.declared_name_override(), pre_lift(&p));
2419 // Populated with an explicitly empty string. Distinct from
2420 // the missing-slot `None` corner both at the primitive here
2421 // and at the pre-lift chain (the trim/filter that collapses
2422 // these two into the same `false`-branched
2423 // `Identity { name_override: false, .. }` lives INSIDE
2424 // `derive_identity`, NOT at the borrow site) — the primitive
2425 // MUST preserve the distinction so a future lift of the trim/
2426 // filter OUT of `derive_identity` INTO the primitive is a
2427 // conscious substrate change, not a silent one.
2428 let p = process_with_declared_name_override(Some(""));
2429 assert_eq!(p.declared_name_override(), pre_lift(&p));
2430 }
2431
2432 #[test]
2433 fn declared_name_override_preserves_raw_slot_verbatim() {
2434 // Invariance-under-`derive_identity`-normalization pin: the
2435 // primitive returns the slot's raw byte contents verbatim —
2436 // no trim, no empty-string filter, no case fold, no
2437 // normalization of any kind. `derive_identity` internally
2438 // applies `.map(str::trim).filter(|s| !s.is_empty())` before
2439 // dispatching on `Some(non_empty)` vs `None | Some(empty |
2440 // whitespace)`, but that transform lives IN `derive_identity`,
2441 // NOT at the borrow site. A regression that pulled the trim/
2442 // filter forward INTO the primitive would silently collapse
2443 // three currently-distinct corners at the borrow site (bare
2444 // populated → `Some(name)`; whitespace-only → `Some(" ")`;
2445 // empty → `Some("")`) into two (bare → `Some(name)`; the
2446 // other two → `None`). That collapse might be an intentional
2447 // substrate change some future run wants to make; if so, it
2448 // lands as a conscious edit here (with this pin updated in
2449 // the same commit) rather than as silent behavior drift.
2450 for value in ["bare", " padded ", "\ttabs\t", " ", ""] {
2451 let p = process_with_declared_name_override(Some(value));
2452 assert_eq!(
2453 p.declared_name_override(),
2454 Some(value),
2455 "declared_name_override must preserve raw slot verbatim for value {value:?}"
2456 );
2457 }
2458 }
2459
2460 #[test]
2461 fn declared_name_override_composes_with_derive_identity_call_shape() {
2462 // Cross-primitive coherence pin against the [`derive_identity`]
2463 // consumer: the two live `tatara-reconciler::phase_machine`
2464 // callsites feed `p.declared_name_override()` as the second
2465 // positional argument to `derive_identity(&p.spec, …)`. This
2466 // pin exercises that exact call shape at test time so a
2467 // regression that skewed the primitive's return-form (return-
2468 // shape, borrow discipline, empty-slot collapse) surfaces
2469 // here as a shape mismatch at the [`derive_identity`] call
2470 // site rather than as silent operator-facing skew between the
2471 // DECLARE composer and the ALLOCATE-PID rehydration branch.
2472 // Populated with a bare non-empty name: `derive_identity`
2473 // dispatches on `Some(non_empty)` and stamps
2474 // `name_override: true` on the resulting [`Identity`], with
2475 // the resulting `.name` equal to the raw slot value.
2476 let p = process_with_declared_name_override(Some("gateway-primary"));
2477 let id = crate::identity::derive_identity(&p.spec, p.declared_name_override());
2478 assert!(id.name_override);
2479 assert_eq!(id.name, "gateway-primary");
2480 // Empty slot: `derive_identity` dispatches on `None` and
2481 // stamps `name_override: false` on the resulting [`Identity`],
2482 // with the resulting `.name` derived from the content hash
2483 // (NOT equal to any operator-authored slot value).
2484 let p = process_with_declared_name_override(None);
2485 let id = crate::identity::derive_identity(&p.spec, p.declared_name_override());
2486 assert!(!id.name_override);
2487 }
2488
2489 // ─── Process::observed_flux_resources substrate pins ───────────────
2490 //
2491 // Pins the borrow-form status-projection primitive that owns the
2492 // 5-line `.status.as_ref().map(|s| s.flux_resources.clone())
2493 // .unwrap_or_default()` chain the two hand-authored
2494 // `tatara-reconciler::phase_machine` sites (`handle_running` +
2495 // `handle_attested`) restated by hand pre-lift. Fail-before-pass-
2496 // after granularity: a regression that widened the missing-`status`
2497 // corner, dropped the slot, or drifted the borrow discipline
2498 // surfaces here rather than as silent operator-facing skew between
2499 // the VERIFY-phase readiness probe and the ATTEST-heartbeat drift
2500 // detector.
2501
2502 fn sample_flux_ref(name: &str) -> FluxResourceRef {
2503 // Distinct slot values so a swap between adjacent tuple
2504 // positions surfaces as an equality failure at the assertion
2505 // site — a slot-inversion regression cannot masquerade as
2506 // identity by accident. Peer to the sibling
2507 // `tatara_process::status::tests::sample_flux_ref` discipline
2508 // on the fetch-coords axis.
2509 FluxResourceRef {
2510 api_version: "kustomize.toolkit.fluxcd.io/v1".to_string(),
2511 kind: "Kustomization".to_string(),
2512 name: name.to_string(),
2513 namespace: "flux-system".to_string(),
2514 ready: false,
2515 message: None,
2516 last_check: None,
2517 }
2518 }
2519
2520 fn process_with_flux_resources(refs: Vec<FluxResourceRef>) -> Process {
2521 let mut p = Process::new("api-gateway", empty_spec());
2522 p.metadata.namespace = Some("prod".into());
2523 let mut status = ProcessStatus::default();
2524 status.flux_resources = refs;
2525 p.status = Some(status);
2526 p
2527 }
2528
2529 #[test]
2530 fn observed_flux_resources_returns_empty_slice_when_status_is_none() {
2531 // Missing-`status` corner pin: the primitive collapses the
2532 // no-status case to `&[]` so downstream `.is_empty()` /
2533 // `.len()` / iteration behave identically on a `Process`
2534 // whose status field is `None` and on one whose status
2535 // carries an empty `flux_resources` slot. Matches the
2536 // pre-lift `.unwrap_or_default()`'s empty-`Vec` corner
2537 // byte-identically at every reconciler consumer's downstream
2538 // shape.
2539 let mut p = Process::new("api", empty_spec());
2540 p.status = None;
2541 assert!(p.observed_flux_resources().is_empty());
2542 assert_eq!(p.observed_flux_resources().len(), 0);
2543 }
2544
2545 #[test]
2546 fn observed_flux_resources_returns_empty_slice_when_flux_resources_is_empty() {
2547 // Zero-refs-under-populated-status corner pin: the primitive
2548 // returns an empty slice, matching the missing-`status`
2549 // corner byte-identically. A regression that treated the two
2550 // corners differently (a `None`-vs-empty signal that
2551 // downstream consumers could grep on) would silently promote
2552 // an internal representation detail (whether the reconciler
2553 // has ever written a status subresource) into observable
2554 // behavior.
2555 let p = process_with_flux_resources(vec![]);
2556 assert!(p.observed_flux_resources().is_empty());
2557 assert_eq!(p.observed_flux_resources().len(), 0);
2558 }
2559
2560 #[test]
2561 fn observed_flux_resources_returns_slice_of_persisted_vec() {
2562 // Happy-path pin: with a populated `status.flux_resources`
2563 // slot, the primitive returns a borrowed slice whose length
2564 // and per-element identity match the persisted vector. A
2565 // regression that filtered / reshaped / deduplicated the
2566 // slice would surface here rather than as silent skew at the
2567 // downstream fetch consumers.
2568 let refs = vec![
2569 sample_flux_ref("observability-stack"),
2570 sample_flux_ref("gateway"),
2571 ];
2572 let p = process_with_flux_resources(refs.clone());
2573 let observed = p.observed_flux_resources();
2574 assert_eq!(observed.len(), 2);
2575 assert_eq!(observed[0].name, "observability-stack");
2576 assert_eq!(observed[1].name, "gateway");
2577 }
2578
2579 #[test]
2580 fn observed_flux_resources_is_a_zero_copy_borrow_projection() {
2581 // Borrow-discipline pin: the returned slice borrows the
2582 // persisted `Vec<FluxResourceRef>` in place — NOT a fresh
2583 // allocation or a clone. A regression that switched the
2584 // projection to owned refs (via `.clone()` or `.to_vec()`)
2585 // would defeat the zero-copy contract the lift's primary
2586 // strict-widening delivers (the pre-lift 5-line chain
2587 // eagerly cloned the whole vector per reconcile pass; the
2588 // post-lift primitive borrows). Peer to the sibling
2589 // `flux_resource_ref_fetch_coords_returns_borrows_of_owned_slots`
2590 // pin on the per-ref borrow-projection axis.
2591 let refs = vec![sample_flux_ref("observability-stack")];
2592 let p = process_with_flux_resources(refs);
2593 let observed = p.observed_flux_resources();
2594 let persisted = &p.status.as_ref().unwrap().flux_resources;
2595 assert!(std::ptr::eq(observed.as_ptr(), persisted.as_ptr()));
2596 }
2597
2598 #[test]
2599 fn observed_flux_resources_is_a_pure_projection() {
2600 // Purity pin: calling the projection twice on the same
2601 // `Process` returns byte-identical slices (same pointer,
2602 // same length). A regression that introduced state — a
2603 // lazy-cached slice materialized on first call, a
2604 // normalization step that ran once and cached — would
2605 // surface here rather than as silent drift between the
2606 // VERIFY-phase and ATTEST-heartbeat consumers on the SAME
2607 // `Process` within one reconcile pass.
2608 let refs = vec![sample_flux_ref("observability-stack")];
2609 let p = process_with_flux_resources(refs);
2610 let a = p.observed_flux_resources();
2611 let b = p.observed_flux_resources();
2612 assert!(std::ptr::eq(a.as_ptr(), b.as_ptr()));
2613 assert_eq!(a.len(), b.len());
2614 }
2615
2616 #[test]
2617 fn observed_flux_resources_matches_pre_lift_reconciler_chain_shape() {
2618 // Byte-identical parity pin between the borrow-form primitive
2619 // here and the pre-lift `tatara-reconciler::phase_machine`
2620 // 5-line chain shape. Sweeps every corner every callsite
2621 // plausibly encounters (missing status, empty flux_resources,
2622 // populated flux_resources with one ref, populated with
2623 // multiple refs). A regression that inserted a normalization
2624 // step at the primitive the pre-lift chain does NOT apply —
2625 // or vice versa — surfaces here rather than as silent drift
2626 // between the pre-lift consumer sites and the ONE substrate
2627 // owner they now route through. Peer to
2628 // `coordinates_or_none_matches_pre_lift_reconciler_helper_shape`
2629 // on the metadata axis's borrow-form primitive.
2630 // `FluxResourceRef` does not derive `PartialEq` — the parity
2631 // check walks the per-ref fetch-coords tuple (the same 4-slot
2632 // borrow projection every downstream fetch consumer routes
2633 // through) so a regression that reshaped ANY slot at ANY
2634 // index surfaces here through the sibling
2635 // `FluxResourceRef::fetch_coords` typed projection.
2636 fn pre_lift(p: &Process) -> Vec<FluxResourceRef> {
2637 p.status
2638 .as_ref()
2639 .map(|s| s.flux_resources.clone())
2640 .unwrap_or_default()
2641 }
2642 fn coord_shape(refs: &[FluxResourceRef]) -> Vec<(String, String, String, String)> {
2643 refs.iter()
2644 .map(|r| {
2645 let (ns, av, kind, name) = r.fetch_coords();
2646 (
2647 ns.to_string(),
2648 av.to_string(),
2649 kind.to_string(),
2650 name.to_string(),
2651 )
2652 })
2653 .collect()
2654 }
2655 // Missing status.
2656 let mut p = Process::new("api", empty_spec());
2657 p.status = None;
2658 assert_eq!(
2659 coord_shape(p.observed_flux_resources()),
2660 coord_shape(&pre_lift(&p))
2661 );
2662 // Populated status, empty slot.
2663 let p = process_with_flux_resources(vec![]);
2664 assert_eq!(
2665 coord_shape(p.observed_flux_resources()),
2666 coord_shape(&pre_lift(&p))
2667 );
2668 // Populated status, one ref.
2669 let p = process_with_flux_resources(vec![sample_flux_ref("obs")]);
2670 assert_eq!(
2671 coord_shape(p.observed_flux_resources()),
2672 coord_shape(&pre_lift(&p))
2673 );
2674 // Populated status, multiple refs.
2675 let p = process_with_flux_resources(vec![
2676 sample_flux_ref("obs"),
2677 sample_flux_ref("gw"),
2678 sample_flux_ref("api"),
2679 ]);
2680 assert_eq!(
2681 coord_shape(p.observed_flux_resources()),
2682 coord_shape(&pre_lift(&p))
2683 );
2684 }
2685
2686 #[test]
2687 fn observed_flux_resources_missing_status_and_empty_slot_collapse_to_the_same_slice_shape() {
2688 // Cross-corner coherence pin: the missing-`status` corner and
2689 // the populated-empty-slot corner return slices whose
2690 // `.is_empty()` / `.len()` observations are IDENTICAL. A
2691 // regression that promoted the missing-`status` corner to
2692 // returning `None` (via a signature change) — or that widened
2693 // the empty-slot corner to a synthetic single-element slice
2694 // — would surface here rather than as silent operator-facing
2695 // divergence between a never-status-written Process and a
2696 // status-emptied Process.
2697 let mut p_no_status = Process::new("api", empty_spec());
2698 p_no_status.status = None;
2699 let p_empty_status = process_with_flux_resources(vec![]);
2700 assert_eq!(
2701 p_no_status.observed_flux_resources().len(),
2702 p_empty_status.observed_flux_resources().len()
2703 );
2704 assert_eq!(
2705 p_no_status.observed_flux_resources().is_empty(),
2706 p_empty_status.observed_flux_resources().is_empty()
2707 );
2708 }
2709
2710 #[test]
2711 fn observed_flux_resources_slice_preserves_persisted_ordering() {
2712 // Ordering-preservation pin: the borrowed slice preserves
2713 // the exact insertion order of the persisted vector — no
2714 // sort, no dedup, no reshape. A regression that inserted a
2715 // sort or reordering would silently misroute per-ref
2716 // observations at the downstream VERIFY-phase / ATTEST-
2717 // heartbeat consumers, both of which walk the slice
2718 // positionally and correlate the position to the observed
2719 // readiness.
2720 let refs = vec![
2721 sample_flux_ref("z-last"),
2722 sample_flux_ref("a-first"),
2723 sample_flux_ref("m-middle"),
2724 ];
2725 let p = process_with_flux_resources(refs);
2726 let observed = p.observed_flux_resources();
2727 assert_eq!(observed[0].name, "z-last");
2728 assert_eq!(observed[1].name, "a-first");
2729 assert_eq!(observed[2].name, "m-middle");
2730 }
2731
2732 // ─── Process::observed_pid substrate pins ─────────────────────────
2733 //
2734 // Pins the borrow-form status-projection primitive on the PID axis
2735 // that owns the 3-line `.status.as_ref().and_then(|s| s.pid.clone())`
2736 // chain the two hand-authored `tatara-reconciler::phase_machine`
2737 // sites (`handle_forking` ALLOCATE-PID gate + `handle_exiting`
2738 // SIGTERM cascade) restated by hand pre-lift. Peer to the sibling
2739 // `observed_flux_resources_*` pin family on the flux-resources
2740 // axis; both compose the missing-`status` fallback + borrow-form
2741 // return-shape skeleton on distinct `ProcessStatus` slots. Fail-
2742 // before-pass-after granularity: `observed_pid` did not exist
2743 // pre-lift, so any test invoking it fails to compile pre-lift and
2744 // passes post-lift.
2745
2746 fn process_with_pid(pid: Option<&str>) -> Process {
2747 let mut p = Process::new("api-gateway", empty_spec());
2748 p.metadata.namespace = Some("prod".into());
2749 let mut status = ProcessStatus::default();
2750 status.pid = pid.map(str::to_string);
2751 p.status = Some(status);
2752 p
2753 }
2754
2755 #[test]
2756 fn observed_pid_returns_none_when_status_is_none() {
2757 // Missing-`status` corner pin: the primitive collapses the
2758 // no-status case to `None` so downstream `.is_some()` /
2759 // `if let Some(_)` / `.map(...)` behave identically on a
2760 // `Process` whose status field is `None` and on one whose
2761 // status carries an unpopulated `pid` slot. Matches the
2762 // pre-lift `.and_then(...)` chain's `None` byte-identically
2763 // at every reconciler consumer's downstream shape.
2764 let mut p = Process::new("api", empty_spec());
2765 p.status = None;
2766 assert!(p.observed_pid().is_none());
2767 }
2768
2769 #[test]
2770 fn observed_pid_returns_none_when_pid_slot_is_none() {
2771 // Empty-slot-under-populated-status corner pin: the
2772 // primitive returns `None`, matching the missing-`status`
2773 // corner byte-identically. A regression that treated the
2774 // two corners differently (a `None`-vs-`Some("")` signal
2775 // that downstream consumers could grep on) would silently
2776 // promote an internal representation detail (whether the
2777 // reconciler has ever written a status subresource) into
2778 // observable behavior at the ALLOCATE-PID gate.
2779 let p = process_with_pid(None);
2780 assert!(p.observed_pid().is_none());
2781 }
2782
2783 #[test]
2784 fn observed_pid_returns_borrowed_str_when_pid_slot_is_populated() {
2785 // Happy-path pin: with a populated `status.pid` slot, the
2786 // primitive returns a borrowed `&str` whose contents match
2787 // the persisted `String`. A regression that filtered /
2788 // reshaped / canonicalized the string would surface here
2789 // rather than as silent skew at the downstream cascade
2790 // comparator's `.as_deref() == Some(...)` equality check.
2791 let p = process_with_pid(Some("seph.1.7"));
2792 assert_eq!(p.observed_pid(), Some("seph.1.7"));
2793 }
2794
2795 #[test]
2796 fn observed_pid_is_a_zero_copy_borrow_projection() {
2797 // Borrow-discipline pin: the returned `&str` borrows the
2798 // persisted `String`'s underlying byte buffer in place —
2799 // NOT a fresh allocation or a clone. A regression that
2800 // switched the projection to an owned `String` (via
2801 // `.clone()` or `.to_owned()`) would defeat the zero-copy
2802 // contract the lift's primary strict-widening delivers
2803 // (the pre-lift 3-line chain eagerly cloned the `String`
2804 // per reconcile pass at BOTH call sites even though the
2805 // ALLOCATE-PID gate immediately dropped the clone and the
2806 // SIGTERM cascade only re-borrowed it via `.as_str()`; the
2807 // post-lift primitive borrows). Peer to the sibling
2808 // `observed_flux_resources_is_a_zero_copy_borrow_projection`
2809 // pin on the flux-resources borrow-projection axis.
2810 let p = process_with_pid(Some("seph.1.7"));
2811 let observed = p.observed_pid().expect("populated slot");
2812 let persisted = p.status.as_ref().unwrap().pid.as_ref().unwrap();
2813 assert!(std::ptr::eq(observed.as_ptr(), persisted.as_ptr()));
2814 }
2815
2816 #[test]
2817 fn observed_pid_is_a_pure_projection() {
2818 // Purity pin: calling the projection twice on the same
2819 // `Process` returns byte-identical `&str`s (same pointer,
2820 // same length). A regression that introduced state — a
2821 // lazy-cached slice materialized on first call, a
2822 // normalization step that ran once and cached — would
2823 // surface here rather than as silent drift between the
2824 // ALLOCATE-PID gate and the SIGTERM cascade on the SAME
2825 // `Process` within one reconcile pass.
2826 let p = process_with_pid(Some("seph.1.7"));
2827 let a = p.observed_pid().expect("populated slot");
2828 let b = p.observed_pid().expect("populated slot");
2829 assert!(std::ptr::eq(a.as_ptr(), b.as_ptr()));
2830 assert_eq!(a.len(), b.len());
2831 }
2832
2833 #[test]
2834 fn observed_pid_matches_pre_lift_reconciler_chain_shape() {
2835 // Byte-identical parity pin between the borrow-form
2836 // primitive here and the pre-lift `tatara-reconciler
2837 // ::phase_machine` 3-line chain shape. Sweeps every corner
2838 // every callsite plausibly encounters (missing status,
2839 // empty pid slot, populated pid slot). A regression that
2840 // inserted a normalization step at the primitive the pre-
2841 // lift chain does NOT apply — or vice versa — surfaces
2842 // here rather than as silent drift between the pre-lift
2843 // consumer sites and the ONE substrate owner they now
2844 // route through. Peer to
2845 // `observed_flux_resources_matches_pre_lift_reconciler_chain_shape`
2846 // on the flux-resources axis's borrow-form primitive.
2847 fn pre_lift(p: &Process) -> Option<String> {
2848 p.status.as_ref().and_then(|s| s.pid.clone())
2849 }
2850 // Missing status.
2851 let mut p = Process::new("api", empty_spec());
2852 p.status = None;
2853 assert_eq!(p.observed_pid().map(str::to_string), pre_lift(&p));
2854 // Populated status, empty pid slot.
2855 let p = process_with_pid(None);
2856 assert_eq!(p.observed_pid().map(str::to_string), pre_lift(&p));
2857 // Populated status, populated pid slot.
2858 let p = process_with_pid(Some("seph.1.7"));
2859 assert_eq!(p.observed_pid().map(str::to_string), pre_lift(&p));
2860 }
2861
2862 #[test]
2863 fn observed_pid_missing_status_and_empty_slot_collapse_to_the_same_option_shape() {
2864 // Cross-corner coherence pin: the missing-`status` corner
2865 // and the populated-empty-slot corner return `Option`s whose
2866 // `.is_none()` observations are IDENTICAL. A regression
2867 // that promoted the missing-`status` corner to returning a
2868 // typed error (via a signature change to `Result<_, _>`) —
2869 // or that widened the empty-slot corner to a synthetic
2870 // `Some("")` — would surface here rather than as silent
2871 // operator-facing divergence between a never-status-
2872 // written Process and a status-emptied Process on the
2873 // ALLOCATE-PID gate.
2874 let mut p_no_status = Process::new("api", empty_spec());
2875 p_no_status.status = None;
2876 let p_empty_slot = process_with_pid(None);
2877 assert_eq!(
2878 p_no_status.observed_pid().is_none(),
2879 p_empty_slot.observed_pid().is_none()
2880 );
2881 assert_eq!(
2882 p_no_status.observed_pid().is_some(),
2883 p_empty_slot.observed_pid().is_some()
2884 );
2885 }
2886
2887 #[test]
2888 fn observed_pid_preserves_hierarchical_pid_format() {
2889 // Format-preservation pin: the hierarchical PID path
2890 // (dotted-segment form `seph.1.7`, matching the ported
2891 // `convergence-controller/src/identity.rs` scheme) reaches
2892 // the caller with segments and separators byte-identical
2893 // to the persisted `String`. A regression that inserted a
2894 // canonicalization pass (a segment-count validator, a
2895 // separator swap `.` → `/`, a leading/trailing whitespace
2896 // trim) would silently misroute the SIGTERM cascade's
2897 // `spec.identity.parent == Some(pid)` comparator against
2898 // children whose `parent` field was authored in the ported
2899 // scheme's exact form.
2900 for pid in ["seph", "seph.1", "seph.1.7", "seph.1.7.42"] {
2901 let p = process_with_pid(Some(pid));
2902 assert_eq!(p.observed_pid(), Some(pid));
2903 }
2904 }
2905
2906 // ─── Process::observed_attestation substrate pins ─────────────────
2907 //
2908 // Pins the borrow-form status-projection primitive on the
2909 // attestation-chain axis that owns the 3-line
2910 // `.status.as_ref().and_then(|s| s.attestation.as_ref())` chain
2911 // the two hand-authored `tatara-reconciler` sites
2912 // (`phase_machine::advance_to_attested` ATTEST composer +
2913 // `render::render_export_jobs` export-Job builder) restated by
2914 // hand pre-lift. Peer to the sibling `observed_pid_*` +
2915 // `observed_flux_resources_*` pin families; all three compose
2916 // the missing-`status` fallback + borrow-form return-shape
2917 // skeleton on distinct `ProcessStatus` slots. Fail-before-pass-
2918 // after granularity: `observed_attestation` did not exist
2919 // pre-lift, so any test invoking it fails to compile pre-lift
2920 // and passes post-lift.
2921
2922 fn sample_attestation(artifact: &str, intent: &str) -> ProcessAttestation {
2923 // Distinct pillar strings so a regression that swapped the
2924 // artifact / intent pillars silently surfaces as an
2925 // equality failure at the composed-root parity pin.
2926 ProcessAttestation::initial(artifact.to_string(), None, intent.to_string())
2927 }
2928
2929 fn process_with_attestation(attestation: Option<ProcessAttestation>) -> Process {
2930 let mut p = Process::new("api-gateway", empty_spec());
2931 p.metadata.namespace = Some("prod".into());
2932 let mut status = ProcessStatus::default();
2933 status.attestation = attestation;
2934 p.status = Some(status);
2935 p
2936 }
2937
2938 #[test]
2939 fn observed_attestation_returns_none_when_status_is_none() {
2940 // Missing-`status` corner pin: the primitive collapses the
2941 // no-status case to `None` so downstream `.is_some()` /
2942 // `if let Some(_)` / `.map(...)` behave identically on a
2943 // `Process` whose status field is `None` and on one whose
2944 // status carries an unpopulated `attestation` slot.
2945 // Matches the pre-lift `.and_then(...)` chain's `None`
2946 // byte-identically at every reconciler consumer's
2947 // downstream shape.
2948 let mut p = Process::new("api", empty_spec());
2949 p.status = None;
2950 assert!(p.observed_attestation().is_none());
2951 }
2952
2953 #[test]
2954 fn observed_attestation_returns_none_when_attestation_slot_is_none() {
2955 // Empty-slot-under-populated-status corner pin: the
2956 // primitive returns `None`, matching the missing-`status`
2957 // corner byte-identically. A regression that treated the
2958 // two corners differently (a `None`-vs-`Some(_)` signal
2959 // that downstream consumers could grep on) would silently
2960 // promote an internal representation detail (whether the
2961 // reconciler has ever written a status subresource) into
2962 // observable behavior at the ATTEST composer's
2963 // seed-vs-chain branch.
2964 let p = process_with_attestation(None);
2965 assert!(p.observed_attestation().is_none());
2966 }
2967
2968 #[test]
2969 fn observed_attestation_returns_borrow_when_slot_is_populated() {
2970 // Happy-path pin: with a populated `status.attestation`
2971 // slot, the primitive returns a borrowed
2972 // `&ProcessAttestation` whose fields match the persisted
2973 // record. A regression that filtered / reshaped /
2974 // canonicalized the record would surface here rather than
2975 // as silent skew at the downstream `prior.next(pillars)`
2976 // chain composer + the ephemeral-export receipt's
2977 // `previous_root` linker.
2978 let att = sample_attestation("art-1", "int-1");
2979 let composed_root = att.composed_root.clone();
2980 let p = process_with_attestation(Some(att));
2981 let observed = p.observed_attestation().expect("populated slot");
2982 assert_eq!(observed.artifact_hash, "art-1");
2983 assert_eq!(observed.intent_hash, "int-1");
2984 assert_eq!(observed.composed_root, composed_root);
2985 assert_eq!(observed.generation, 0);
2986 assert!(observed.previous_root.is_none());
2987 }
2988
2989 #[test]
2990 fn observed_attestation_is_a_zero_copy_borrow_projection() {
2991 // Borrow-discipline pin: the returned reference points at
2992 // the persisted `ProcessAttestation` in place — NOT a fresh
2993 // allocation or a clone. A regression that switched the
2994 // projection to an owned `ProcessAttestation` (via
2995 // `.clone()`) would defeat the zero-copy contract the
2996 // lift's primary strict-widening delivers (the pre-lift
2997 // 3-line chain returned a borrow, but the export-Job
2998 // builder then cloned `composed_root` off it; the post-
2999 // lift primitive preserves the borrow all the way to the
3000 // consumer's own cloning choice). Peer to the sibling
3001 // `observed_pid_is_a_zero_copy_borrow_projection` +
3002 // `observed_flux_resources_is_a_zero_copy_borrow_projection`
3003 // pins on the PID + flux-resources borrow-projection axes.
3004 let att = sample_attestation("art-1", "int-1");
3005 let p = process_with_attestation(Some(att));
3006 let observed = p.observed_attestation().expect("populated slot") as *const _;
3007 let persisted = p.status.as_ref().unwrap().attestation.as_ref().unwrap() as *const _;
3008 assert!(std::ptr::eq(observed, persisted));
3009 }
3010
3011 #[test]
3012 fn observed_attestation_is_a_pure_projection() {
3013 // Purity pin: calling the projection twice on the same
3014 // `Process` returns byte-identical borrows (same pointer).
3015 // A regression that introduced state — a lazy-cached
3016 // reference materialized on first call, a normalization
3017 // step that ran once and cached — would surface here
3018 // rather than as silent drift between the ATTEST composer
3019 // and the ephemeral-export receipt chain on the SAME
3020 // `Process` within one reconcile pass.
3021 let att = sample_attestation("art-1", "int-1");
3022 let p = process_with_attestation(Some(att));
3023 let a = p.observed_attestation().expect("populated slot") as *const _;
3024 let b = p.observed_attestation().expect("populated slot") as *const _;
3025 assert!(std::ptr::eq(a, b));
3026 }
3027
3028 #[test]
3029 fn observed_attestation_matches_pre_lift_reconciler_chain_shape() {
3030 // Byte-identical parity pin between the borrow-form
3031 // primitive here and the pre-lift `tatara-reconciler`
3032 // 3-line chain shape. Sweeps every corner every callsite
3033 // plausibly encounters (missing status, empty attestation
3034 // slot, populated attestation slot). A regression that
3035 // inserted a normalization step at the primitive the pre-
3036 // lift chain does NOT apply — or vice versa — surfaces
3037 // here rather than as silent drift between the pre-lift
3038 // consumer sites and the ONE substrate owner they now
3039 // route through. Peer to
3040 // `observed_pid_matches_pre_lift_reconciler_chain_shape` +
3041 // `observed_flux_resources_matches_pre_lift_reconciler_chain_shape`
3042 // on the PID + flux-resources axes.
3043 // `ProcessAttestation` does not derive `PartialEq` — the
3044 // parity check walks the `composed_root` field (the
3045 // byte-string every downstream consumer keys off) so a
3046 // regression that reshaped the record without touching
3047 // the composed-root observation surfaces here through
3048 // the receipt-chain projection.
3049 fn pre_lift(p: &Process) -> Option<String> {
3050 p.status
3051 .as_ref()
3052 .and_then(|s| s.attestation.as_ref())
3053 .map(|a| a.composed_root.clone())
3054 }
3055 // Missing status.
3056 let mut p = Process::new("api", empty_spec());
3057 p.status = None;
3058 assert_eq!(
3059 p.observed_attestation().map(|a| a.composed_root.clone()),
3060 pre_lift(&p)
3061 );
3062 // Populated status, empty attestation slot.
3063 let p = process_with_attestation(None);
3064 assert_eq!(
3065 p.observed_attestation().map(|a| a.composed_root.clone()),
3066 pre_lift(&p)
3067 );
3068 // Populated status, populated attestation slot.
3069 let p = process_with_attestation(Some(sample_attestation("art-1", "int-1")));
3070 assert_eq!(
3071 p.observed_attestation().map(|a| a.composed_root.clone()),
3072 pre_lift(&p)
3073 );
3074 }
3075
3076 #[test]
3077 fn observed_attestation_missing_status_and_empty_slot_collapse_to_the_same_option_shape() {
3078 // Cross-corner coherence pin: the missing-`status` corner
3079 // and the populated-empty-slot corner return `Option`s
3080 // whose `.is_none()` observations are IDENTICAL. A
3081 // regression that promoted the missing-`status` corner to
3082 // returning a typed error (via a signature change to
3083 // `Result<_, _>`) — or that widened the empty-slot corner
3084 // to a synthetic `Some(default_attestation)` — would
3085 // surface here rather than as silent operator-facing
3086 // divergence between a never-status-written Process and
3087 // an attestation-emptied Process on the ATTEST composer's
3088 // seed-vs-chain branch.
3089 let mut p_no_status = Process::new("api", empty_spec());
3090 p_no_status.status = None;
3091 let p_empty_slot = process_with_attestation(None);
3092 assert_eq!(
3093 p_no_status.observed_attestation().is_none(),
3094 p_empty_slot.observed_attestation().is_none()
3095 );
3096 assert_eq!(
3097 p_no_status.observed_attestation().is_some(),
3098 p_empty_slot.observed_attestation().is_some()
3099 );
3100 }
3101
3102 #[test]
3103 fn observed_attestation_preserves_chain_generation_field() {
3104 // Generation-preservation pin: a chained attestation
3105 // (`prior.next(...)` at generation N ≥ 1 with a
3106 // `previous_root` linked to `prior.composed_root`) reaches
3107 // the caller with its `generation` counter + `previous_root`
3108 // link byte-identical to the persisted record. The pre-lift
3109 // ATTEST composer discriminated exactly on this borrow's
3110 // `Some(prior)` vs `None` arm; a regression that dropped
3111 // the chain's `generation` counter (say, by folding
3112 // `next(...)` into a fresh `initial(...)` on every
3113 // reconcile pass) would silently reset every chain and
3114 // orphan every downstream `previous_root` link, but that
3115 // drift is invisible to a Process CRD reader who only
3116 // observes the LATEST composed_root.
3117 let prior = sample_attestation("art-0", "int-0");
3118 let chained = prior.next("art-1".to_string(), None, "int-1".to_string());
3119 let expected_generation = chained.generation;
3120 let expected_previous = chained.previous_root.clone();
3121 let p = process_with_attestation(Some(chained));
3122 let observed = p.observed_attestation().expect("populated slot");
3123 assert_eq!(observed.generation, expected_generation);
3124 assert_eq!(observed.generation, 1);
3125 assert_eq!(observed.previous_root, expected_previous);
3126 assert_eq!(
3127 observed.previous_root.as_deref(),
3128 Some(prior.composed_root.as_str())
3129 );
3130 }
3131
3132 // ─── Process::observed_identity substrate pins ────────────────────
3133 //
3134 // The borrow-form status-projection primitive on the resolved-
3135 // identity axis. Collapses the paired 3-line `.status.as_ref()
3136 // .and_then(|s| s.identity.<clone|as_ref>())` chain every
3137 // consumer in `tatara-reconciler` restated by hand pre-lift at
3138 // TWO sites (`phase_machine::handle_forking` seed +
3139 // `ssapply::inject_annotations` content-hash annotation
3140 // composer). Peer to the sibling `observed_pid_*` +
3141 // `observed_attestation_*` + `observed_flux_resources_*` pin
3142 // families; all four compose the same missing-`status` fallback
3143 // + borrow-form return-shape skeleton on distinct
3144 // `ProcessStatus` slots. Each pin fails-before-pass-after
3145 // granularity: `observed_identity` did not exist pre-lift, so
3146 // any test invoking it fails to compile pre-lift and passes
3147 // post-lift.
3148
3149 fn sample_identity(name: &str) -> Identity {
3150 // Distinct name + content_hash + override flag so a
3151 // regression that reshaped one slot surfaces at the
3152 // populated-slot pin's field-equality check without
3153 // aliasing the sibling slots.
3154 Identity {
3155 name: name.to_string(),
3156 content_hash: "a".repeat(26),
3157 name_override: true,
3158 }
3159 }
3160
3161 fn process_with_identity(identity: Option<Identity>) -> Process {
3162 let mut p = Process::new("api-gateway", empty_spec());
3163 p.metadata.namespace = Some("prod".into());
3164 let mut status = ProcessStatus::default();
3165 status.identity = identity;
3166 p.status = Some(status);
3167 p
3168 }
3169
3170 #[test]
3171 fn observed_identity_returns_none_when_status_is_none() {
3172 // Missing-`status` corner pin: the primitive collapses the
3173 // no-status case to `None` so downstream `.is_some()` /
3174 // `if let Some(_)` / `.cloned().unwrap_or_else(...)` behave
3175 // identically on a `Process` whose status field is `None`
3176 // and on one whose status carries an unpopulated `identity`
3177 // slot. Matches the pre-lift `.and_then(...)` chain's `None`
3178 // byte-identically at every reconciler consumer's
3179 // downstream shape.
3180 let mut p = Process::new("api", empty_spec());
3181 p.status = None;
3182 assert!(p.observed_identity().is_none());
3183 }
3184
3185 #[test]
3186 fn observed_identity_returns_none_when_identity_slot_is_none() {
3187 // Empty-slot-under-populated-status corner pin: the
3188 // primitive returns `None`, matching the missing-`status`
3189 // corner byte-identically. A regression that treated the
3190 // two corners differently (a `None`-vs-`Some(_)` signal
3191 // that downstream consumers could grep on) would silently
3192 // promote an internal representation detail (whether the
3193 // reconciler has ever written a status subresource) into
3194 // observable behavior at the FORK-time `derive_identity`
3195 // fallback branch.
3196 let p = process_with_identity(None);
3197 assert!(p.observed_identity().is_none());
3198 }
3199
3200 #[test]
3201 fn observed_identity_returns_borrow_when_slot_is_populated() {
3202 // Happy-path pin: with a populated `status.identity` slot,
3203 // the primitive returns a borrowed `&Identity` whose fields
3204 // match the persisted record. A regression that filtered /
3205 // reshaped / canonicalized the record would surface here
3206 // rather than as silent skew at the FORK-time seed's
3207 // `.cloned().unwrap_or_else(derive_identity)` composition
3208 // + the SSA-time content-hash annotation stamp on the SAME
3209 // Process.
3210 let id = sample_identity("seph");
3211 let expected = id.clone();
3212 let p = process_with_identity(Some(id));
3213 let observed = p.observed_identity().expect("populated slot");
3214 assert_eq!(observed, &expected);
3215 assert_eq!(observed.name, "seph");
3216 assert_eq!(observed.content_hash, "a".repeat(26));
3217 assert!(observed.name_override);
3218 }
3219
3220 #[test]
3221 fn observed_identity_is_a_zero_copy_borrow_projection() {
3222 // Borrow-discipline pin: the returned reference points at
3223 // the persisted `Identity` in place — NOT a fresh
3224 // allocation or a clone. A regression that switched the
3225 // projection to an owned `Identity` (via `.clone()`) would
3226 // defeat the zero-copy contract the lift's primary strict-
3227 // widening delivers (the SSA-time consumer never clones the
3228 // whole `Identity`, only the `content_hash` field it stamps
3229 // onto the annotation map, so the borrow-form return
3230 // shape's happy-path allocation count is exactly ZERO).
3231 // Peer to the sibling
3232 // `observed_attestation_is_a_zero_copy_borrow_projection`
3233 // + `observed_pid_is_a_zero_copy_borrow_projection` +
3234 // `observed_flux_resources_is_a_zero_copy_borrow_projection`
3235 // pins on the attestation-chain + PID + flux-resources
3236 // borrow-projection axes.
3237 let id = sample_identity("seph");
3238 let p = process_with_identity(Some(id));
3239 let observed = p.observed_identity().expect("populated slot") as *const _;
3240 let persisted = p.status.as_ref().unwrap().identity.as_ref().unwrap() as *const _;
3241 assert!(std::ptr::eq(observed, persisted));
3242 }
3243
3244 #[test]
3245 fn observed_identity_is_a_pure_projection() {
3246 // Purity pin: calling the projection twice on the same
3247 // `Process` returns byte-identical borrows (same pointer).
3248 // A regression that introduced state — a lazy-cached
3249 // reference materialized on first call, a normalization
3250 // step that ran once and cached — would surface here
3251 // rather than as silent drift between the FORK-time
3252 // identity seed and the SSA-time content-hash annotation
3253 // stamp on the SAME `Process` within one reconcile pass.
3254 let p = process_with_identity(Some(sample_identity("seph")));
3255 let a = p.observed_identity().expect("populated slot") as *const _;
3256 let b = p.observed_identity().expect("populated slot") as *const _;
3257 assert!(std::ptr::eq(a, b));
3258 }
3259
3260 #[test]
3261 fn observed_identity_matches_pre_lift_reconciler_chain_shape() {
3262 // Byte-identical parity pin between the borrow-form
3263 // primitive here and the pre-lift `tatara-reconciler`
3264 // 3-line chain shape. Sweeps every corner every callsite
3265 // plausibly encounters (missing status, empty identity
3266 // slot, populated identity slot). A regression that
3267 // inserted a normalization step at the primitive the pre-
3268 // lift chain does NOT apply — or vice versa — surfaces
3269 // here rather than as silent drift between the pre-lift
3270 // consumer sites and the ONE substrate owner they now
3271 // route through. Peer to
3272 // `observed_attestation_matches_pre_lift_reconciler_chain_shape`
3273 // + `observed_pid_matches_pre_lift_reconciler_chain_shape`
3274 // + `observed_flux_resources_matches_pre_lift_reconciler_chain_shape`
3275 // on the attestation-chain + PID + flux-resources axes.
3276 fn pre_lift(p: &Process) -> Option<Identity> {
3277 p.status.as_ref().and_then(|s| s.identity.clone())
3278 }
3279 // Missing status.
3280 let mut p = Process::new("api", empty_spec());
3281 p.status = None;
3282 assert_eq!(p.observed_identity().cloned(), pre_lift(&p));
3283 // Populated status, empty identity slot.
3284 let p = process_with_identity(None);
3285 assert_eq!(p.observed_identity().cloned(), pre_lift(&p));
3286 // Populated status, populated identity slot.
3287 let p = process_with_identity(Some(sample_identity("seph")));
3288 assert_eq!(p.observed_identity().cloned(), pre_lift(&p));
3289 }
3290
3291 #[test]
3292 fn observed_identity_missing_status_and_empty_slot_collapse_to_the_same_option_shape() {
3293 // Cross-corner coherence pin: the missing-`status` corner
3294 // and the populated-empty-slot corner return `Option`s
3295 // whose `.is_none()` observations are IDENTICAL. A
3296 // regression that promoted the missing-`status` corner to
3297 // returning a typed error (via a signature change to
3298 // `Result<_, _>`) — or that widened the empty-slot corner
3299 // to a synthetic `Some(derive_identity(default_spec))` —
3300 // would surface here rather than as silent operator-facing
3301 // divergence between a never-status-written Process and an
3302 // identity-cleared Process on the FORK-time seed branch.
3303 let mut p_no_status = Process::new("api", empty_spec());
3304 p_no_status.status = None;
3305 let p_empty_slot = process_with_identity(None);
3306 assert_eq!(
3307 p_no_status.observed_identity().is_none(),
3308 p_empty_slot.observed_identity().is_none()
3309 );
3310 assert_eq!(
3311 p_no_status.observed_identity().is_some(),
3312 p_empty_slot.observed_identity().is_some()
3313 );
3314 }
3315
3316 #[test]
3317 fn observed_identity_cloned_composes_with_derive_identity_fallback() {
3318 // Cross-primitive composition pin: the borrow-form
3319 // primitive threaded through `.cloned().unwrap_or_else(||
3320 // derive_identity(...))` reproduces the pre-lift FORK-time
3321 // seed's owned-`Identity` shape at every corner. Binds the
3322 // exact composition the `phase_machine::handle_forking`
3323 // consumer performs: on the populated-slot corner the
3324 // reconciler-persisted `Identity` is returned verbatim (the
3325 // fallback never fires), and on both empty corners
3326 // (missing-status + empty-slot) the fallback fires
3327 // producing a fresh `derive_identity(&spec,
3328 // name_override)`. A regression that (a) swapped the
3329 // fallback direction, (b) made `.cloned()` re-derive
3330 // instead of clone, or (c) made the empty-slot corner
3331 // return a synthetic `Some(default_identity)` collides
3332 // with the fallback surfaces here rather than as silent
3333 // FORK-time PID allocator skew.
3334 let spec = empty_spec();
3335 let fallback_expected = crate::identity::derive_identity(&spec, None);
3336 // Populated-slot corner: the seed returns the persisted
3337 // identity, NOT the derive fallback.
3338 let persisted = sample_identity("seph");
3339 let p = process_with_identity(Some(persisted.clone()));
3340 let seed = p.observed_identity().cloned().unwrap_or_else(|| {
3341 crate::identity::derive_identity(&p.spec, p.declared_name_override())
3342 });
3343 assert_eq!(seed, persisted);
3344 assert_ne!(seed, fallback_expected);
3345 // Empty-slot corner: the seed fires the derive fallback.
3346 let p = process_with_identity(None);
3347 let seed = p.observed_identity().cloned().unwrap_or_else(|| {
3348 crate::identity::derive_identity(&p.spec, p.declared_name_override())
3349 });
3350 assert_eq!(seed, fallback_expected);
3351 // Missing-status corner: the seed fires the derive
3352 // fallback, byte-identical to the empty-slot corner.
3353 let mut p = Process::new("api-gateway", empty_spec());
3354 p.metadata.namespace = Some("prod".into());
3355 p.status = None;
3356 let seed = p.observed_identity().cloned().unwrap_or_else(|| {
3357 crate::identity::derive_identity(&p.spec, p.declared_name_override())
3358 });
3359 assert_eq!(seed, fallback_expected);
3360 }
3361
3362 // ─── Process::observed_phase substrate pins ───────────────────────
3363 //
3364 // The copy-form status-projection primitive on the phase axis.
3365 // Collapses the paired 3-line `.status.as_ref().map(|s| s.phase)`
3366 // chain every consumer in `tatara-reconciler` restated by hand
3367 // pre-lift at FIVE sites. Peer to the borrow-form
3368 // `observed_pid_*` + `observed_flux_resources_*` +
3369 // `observed_attestation_*` pin families; all four compose the
3370 // same missing-`status` fallback skeleton on distinct
3371 // `ProcessStatus` slots, with the phase-axis form returning
3372 // `Option<ProcessPhase>` (copy of a `Copy` scalar) rather than
3373 // `Option<&T>` (borrow) because the underlying slot is a bare
3374 // `ProcessPhase` — no allocation to borrow past, and the enum
3375 // is one byte on the wire. Each pin fails-before-pass-after
3376 // granularity: `observed_phase` did not exist pre-lift, so any
3377 // test invoking it fails to compile pre-lift and passes
3378 // post-lift.
3379
3380 fn process_with_phase(phase: Option<ProcessPhase>) -> Process {
3381 let mut p = Process::new("api-gateway", empty_spec());
3382 p.metadata.namespace = Some("prod".into());
3383 if let Some(ph) = phase {
3384 let mut status = ProcessStatus::default();
3385 status.phase = ph;
3386 p.status = Some(status);
3387 }
3388 p
3389 }
3390
3391 #[test]
3392 fn observed_phase_returns_none_when_status_is_none() {
3393 // Missing-`status` corner pin: the primitive collapses the
3394 // no-status case to `None` so downstream `.unwrap_or(...)`
3395 // at every reconciler consumer chooses the default
3396 // deliberately (`Pending` for the top-level dispatch seed
3397 // + boundary evaluator + routing groupby; `Attested` for
3398 // the released-from annotation composer). Matches the
3399 // pre-lift `.map(|s| s.phase)` chain's `None`
3400 // byte-identically at every consumer's downstream shape.
3401 let mut p = Process::new("api", empty_spec());
3402 p.status = None;
3403 assert!(p.observed_phase().is_none());
3404 }
3405
3406 #[test]
3407 fn observed_phase_returns_some_default_when_status_is_populated_with_default_phase() {
3408 // Populated-status corner pin: the primitive returns
3409 // `Some(ProcessPhase::default())` — a `ProcessStatus`
3410 // constructed via `default()` carries `phase: Pending`
3411 // because the phase field is a bare `ProcessPhase` (not
3412 // `Option<ProcessPhase>`), so there is NO "empty slot"
3413 // corner peer to the borrow-form projections' empty-slot
3414 // pins. A regression that reshaped the return type to
3415 // filter out `Pending` (treating it as "unset") would
3416 // surface here and silently break the top-level
3417 // dispatcher's Pending → Forking transition on a Process
3418 // freshly written by the reconciler.
3419 let p = process_with_phase(Some(ProcessPhase::default()));
3420 assert_eq!(p.observed_phase(), Some(ProcessPhase::Pending));
3421 assert_eq!(p.observed_phase(), Some(ProcessPhase::default()));
3422 }
3423
3424 #[test]
3425 fn observed_phase_returns_persisted_phase_when_status_is_populated() {
3426 // Happy-path pin: with a populated `status.phase` slot,
3427 // the primitive returns the persisted `ProcessPhase`.
3428 // A regression that filtered / reshaped / canonicalized
3429 // the phase would surface here rather than as silent
3430 // skew at the top-level dispatcher's phase handler
3431 // dispatch on the SAME Process.
3432 let p = process_with_phase(Some(ProcessPhase::Running));
3433 assert_eq!(p.observed_phase(), Some(ProcessPhase::Running));
3434 }
3435
3436 #[test]
3437 fn observed_phase_is_a_pure_projection() {
3438 // Purity pin: two consecutive calls return byte-identical
3439 // `Option<ProcessPhase>` values (no lazy materialization,
3440 // no interior mutation of `self`). Peer to the sibling
3441 // `observed_pid_is_a_pure_projection` +
3442 // `observed_flux_resources_is_a_pure_projection` +
3443 // `observed_attestation_is_a_pure_projection` pins; all
3444 // four bind the pure-projection discipline on the ONE
3445 // substrate accessor per status slot.
3446 let p = process_with_phase(Some(ProcessPhase::Attested));
3447 let a = p.observed_phase();
3448 let b = p.observed_phase();
3449 assert_eq!(a, b);
3450 assert_eq!(a, Some(ProcessPhase::Attested));
3451 }
3452
3453 #[test]
3454 fn observed_phase_matches_pre_lift_reconciler_chain_shape() {
3455 // Parity pin: sweeps the two corners every pre-lift
3456 // consumer plausibly encountered (missing status,
3457 // populated status with a particular phase) and compares
3458 // the substrate call against a hand-authored pre-lift
3459 // chain byte-identically. A regression that reshaped ANY
3460 // of the two corners would surface here rather than as
3461 // silent operator-facing skew between the top-level
3462 // dispatcher and any of the four other reconciler
3463 // consumers on the SAME `Process`.
3464 fn pre_lift(p: &Process) -> Option<ProcessPhase> {
3465 p.status.as_ref().map(|s| s.phase)
3466 }
3467 let mut p = Process::new("api", empty_spec());
3468 p.status = None;
3469 assert_eq!(p.observed_phase(), pre_lift(&p));
3470 let p = process_with_phase(Some(ProcessPhase::Running));
3471 assert_eq!(p.observed_phase(), pre_lift(&p));
3472 let p = process_with_phase(Some(ProcessPhase::Attested));
3473 assert_eq!(p.observed_phase(), pre_lift(&p));
3474 let p = process_with_phase(Some(ProcessPhase::Failed));
3475 assert_eq!(p.observed_phase(), pre_lift(&p));
3476 }
3477
3478 #[test]
3479 fn observed_phase_default_unwrap_matches_pre_lift_pending_default() {
3480 // Callsite-shape pin: three of the FIVE pre-lift consumers
3481 // (`controller::reconcile`, `boundary::evaluate_process_phase`,
3482 // `table_controller::stable_name_group_key`) closed the
3483 // 3-line chain with `.unwrap_or(ProcessPhase::Pending)`
3484 // (identical to `.unwrap_or_default()`). This pin binds
3485 // that call-site shape: `observed_phase().unwrap_or
3486 // (Pending)` returns `Pending` on missing status and the
3487 // persisted phase otherwise. A regression that swapped
3488 // the `None` sentinel's downstream default would surface
3489 // here rather than as silent skew at three of the five
3490 // consumer sites.
3491 let mut p = Process::new("api", empty_spec());
3492 p.status = None;
3493 assert_eq!(
3494 p.observed_phase().unwrap_or(ProcessPhase::Pending),
3495 ProcessPhase::Pending
3496 );
3497 let p = process_with_phase(Some(ProcessPhase::Running));
3498 assert_eq!(
3499 p.observed_phase().unwrap_or(ProcessPhase::Pending),
3500 ProcessPhase::Running
3501 );
3502 }
3503
3504 #[test]
3505 fn observed_phase_attested_unwrap_matches_pre_lift_released_from_default() {
3506 // Callsite-shape pin: the ONE pre-lift consumer
3507 // (`phase_machine::p_current_phase_str` — the
3508 // released-from annotation composer) closed the 3-line
3509 // chain with `.unwrap_or(ProcessPhase::Attested)` rather
3510 // than the `Default` (`Pending`). This pin binds that
3511 // call-site shape: `observed_phase().unwrap_or(Attested)`
3512 // returns `Attested` on missing status and the persisted
3513 // phase otherwise. A regression that folded the
3514 // `Attested`-default consumer into the `Pending`-default
3515 // majority would break the SIGSTOP/SIGCONT release gate's
3516 // "which annotation label to emit" branch — the pin binds
3517 // the primitive at the raw `Option<ProcessPhase>` form so
3518 // this default choice stays local at the callsite.
3519 let mut p = Process::new("api", empty_spec());
3520 p.status = None;
3521 assert_eq!(
3522 p.observed_phase().unwrap_or(ProcessPhase::Attested),
3523 ProcessPhase::Attested
3524 );
3525 let p = process_with_phase(Some(ProcessPhase::Failed));
3526 assert_eq!(
3527 p.observed_phase().unwrap_or(ProcessPhase::Attested),
3528 ProcessPhase::Failed
3529 );
3530 }
3531
3532 #[test]
3533 fn observed_phase_preserves_every_process_phase_variant() {
3534 // Round-trip pin: every `ProcessPhase` variant round-
3535 // trips through the primitive unchanged. Peer to the
3536 // sibling `observed_pid_preserves_hierarchical_pid_format`
3537 // pin's dotted-segment sweep; this pin sweeps the closed
3538 // set of `ProcessPhase` variants directly so a
3539 // canonicalization pass that dropped or reshaped one
3540 // (e.g. folded `Reconverging` back into `Execing`, or
3541 // remapped `Zombie` to `Reaped`) surfaces here rather
3542 // than as silent skew at the SIGSTOP/SIGCONT release
3543 // gate's phase-name annotation branch. Covers every
3544 // variant the `ProcessPhase::DeriveClosedSet` enumerates
3545 // so a future variant addition surfaces via the closed-
3546 // set macro rather than at a silent partial sweep.
3547 for phase in [
3548 ProcessPhase::Pending,
3549 ProcessPhase::Forking,
3550 ProcessPhase::Execing,
3551 ProcessPhase::Running,
3552 ProcessPhase::Attested,
3553 ProcessPhase::Reconverging,
3554 ProcessPhase::Releasing,
3555 ProcessPhase::Exiting,
3556 ProcessPhase::Failed,
3557 ProcessPhase::Zombie,
3558 ProcessPhase::Reaped,
3559 ] {
3560 let p = process_with_phase(Some(phase));
3561 assert_eq!(
3562 p.observed_phase(),
3563 Some(phase),
3564 "phase variant {phase:?} did not round-trip"
3565 );
3566 }
3567 }
3568
3569 // ─── Process::is_being_deleted substrate pins ───────────────────────
3570 //
3571 // Pins the copy-form metadata-projection primitive on the
3572 // deletion-tombstone axis. Peer to the borrow-form + copy-form
3573 // metadata-fallback family (`namespace_or_default`,
3574 // `name_or_placeholder`, `uid_or_empty`, `coordinates_or_defaults`,
3575 // `coordinates_or_none`, `owned_coordinates_or_err`, `annotation`);
3576 // this one opens the presence-probe corner for the tombstone slot.
3577 // Fail-before-pass-after granularity: `is_being_deleted` did not
3578 // exist pre-lift, so any test invoking it fails to compile pre-
3579 // lift and passes post-lift.
3580
3581 fn tombstoned_process() -> Process {
3582 let mut p = Process::new("api-gateway", empty_spec());
3583 p.metadata.namespace = Some("prod".into());
3584 p.metadata.deletion_timestamp = Some(k8s_openapi::apimachinery::pkg::apis::meta::v1::Time(
3585 Utc::now(),
3586 ));
3587 p
3588 }
3589
3590 #[test]
3591 fn is_being_deleted_returns_false_when_deletion_timestamp_is_absent() {
3592 // Missing-tombstone corner pin: the primitive collapses the
3593 // no-tombstone case to `false` so the SIGTERM preempt at
3594 // `controller::reconcile` skips the `→ Exiting` forcing
3595 // branch and the DELETE-skip at `handle_exiting`'s child
3596 // fan-out does NOT `continue` past a child that is still
3597 // healthy. Matches the pre-lift `.is_some()` chain's `false`
3598 // byte-identically at every consumer's downstream gate.
3599 let mut p = Process::new("api", empty_spec());
3600 p.metadata.deletion_timestamp = None;
3601 assert!(!p.is_being_deleted());
3602 }
3603
3604 #[test]
3605 fn is_being_deleted_returns_true_when_deletion_timestamp_is_present() {
3606 // Present-tombstone corner pin: the primitive returns
3607 // `true` on any populated `metadata.deletionTimestamp`
3608 // slot regardless of the timestamp payload — the two
3609 // consumers only read the tombstone's PRESENCE, never
3610 // its RFC-3339 timestamp value. A regression that gated
3611 // the `true` return on the timestamp being non-epoch, or
3612 // parsed the timestamp before returning, would surface
3613 // here rather than as silent skew at the SIGTERM preempt
3614 // or child-fan-out DELETE-skip on the SAME `Process`.
3615 let p = tombstoned_process();
3616 assert!(p.is_being_deleted());
3617 }
3618
3619 #[test]
3620 fn is_being_deleted_is_a_pure_projection() {
3621 // Purity pin: two consecutive calls return byte-identical
3622 // `bool` values (no lazy materialization, no interior
3623 // mutation of `self`). Peer to the sibling
3624 // `observed_phase_is_a_pure_projection` +
3625 // `observed_pid_is_a_pure_projection` +
3626 // `observed_flux_resources_is_a_pure_projection` +
3627 // `observed_attestation_is_a_pure_projection` pins; all
3628 // five bind the pure-projection discipline on the ONE
3629 // substrate accessor per metadata / status slot.
3630 let p = tombstoned_process();
3631 let a = p.is_being_deleted();
3632 let b = p.is_being_deleted();
3633 assert_eq!(a, b);
3634 assert!(a);
3635 }
3636
3637 #[test]
3638 fn is_being_deleted_matches_pre_lift_reconciler_chain_shape() {
3639 // Parity pin: sweeps the two corners every pre-lift
3640 // consumer plausibly encountered (missing tombstone,
3641 // present tombstone) and compares the substrate call
3642 // against a hand-authored pre-lift chain byte-identically.
3643 // A regression that reshaped either corner would surface
3644 // here rather than as silent operator-facing skew between
3645 // the top-level dispatcher's SIGTERM preempt and the
3646 // SIGTERM cascade's child-fan-out DELETE-skip on the
3647 // SAME `Process` within one reconcile pass.
3648 fn pre_lift(p: &Process) -> bool {
3649 p.metadata.deletion_timestamp.is_some()
3650 }
3651 let mut p = Process::new("api", empty_spec());
3652 p.metadata.deletion_timestamp = None;
3653 assert_eq!(p.is_being_deleted(), pre_lift(&p));
3654 let p = tombstoned_process();
3655 assert_eq!(p.is_being_deleted(), pre_lift(&p));
3656 }
3657
3658 #[test]
3659 fn is_being_deleted_composes_with_process_phase_is_alive_at_reconcile_preempt() {
3660 // Call-site-shape pin: the `controller::reconcile` SIGTERM
3661 // preempt composes `is_being_deleted() && current_phase
3662 // .is_alive()` — the tombstone-presence probe AND the
3663 // alive-phase gate must BOTH hold to force `→ Exiting`.
3664 // A dead-phase (`Zombie` / `Reaped` / `Failed`) Process
3665 // that carries a tombstone still runs its normal handler,
3666 // not the preempt. This pin binds that composition shape
3667 // at the primitive so a regression that flipped either
3668 // half of the `&&` (or that broadened the tombstone probe
3669 // to include the `is_alive` half implicitly) surfaces
3670 // here rather than as silent skew at the top-level
3671 // dispatch on the SAME `Process`.
3672 let mut p = tombstoned_process();
3673 // Alive + tombstoned → preempt fires.
3674 let mut alive = ProcessStatus::default();
3675 alive.phase = ProcessPhase::Running;
3676 p.status = Some(alive);
3677 assert!(p.is_being_deleted());
3678 assert!(p.observed_phase().unwrap_or_default().is_alive());
3679 // Dead + tombstoned → preempt does NOT fire (composition
3680 // with `is_alive` returns false).
3681 let mut dead = ProcessStatus::default();
3682 dead.phase = ProcessPhase::Reaped;
3683 p.status = Some(dead);
3684 assert!(p.is_being_deleted());
3685 assert!(!p.observed_phase().unwrap_or_default().is_alive());
3686 }
3687}