Skip to main content

tatara_process/
serde_defaults.rs

1//! Workspace-canonical serde-default owners — the ONE substrate site
2//! every `#[serde(default = "…")]` slot on a plain scalar constant
3//! (`true`, `false`, `0u32`, …) reaches for its serde-default fn.
4//!
5//! Pre-lift each shape lived as a per-file `fn default_<X>() -> <T>
6//! { <constant> }` private shim; because serde's `default = "path"`
7//! contract dispatches on a NAMED function rather than an inline
8//! literal, every consumer file grew its own local shim body — and the
9//! FIVE consumer files that all needed the `-> bool { true }` shape
10//! each spelled the SAME three-line body byte-identically. Post-lift
11//! every consumer routes through the ONE substrate owner here so a
12//! future normalization (a debug-build assertion, a per-fleet override
13//! injected via env var, a rename that folds `default_true` into a
14//! `serde_defaults::TRUE` typed handle) lands at THIS module and every
15//! downstream serde-default consumer inherits the upgrade mechanically.
16//!
17//! Peer to [`crate::lifetime::default_ephemeral_ttl`] +
18//! [`crate::lifetime::default_ephemeral_max_concurrent`] on the
19//! "workspace-canonical serde-default owners" axis. Where those two
20//! primitives own the *ephemeral-authoring-surface-specific* defaults
21//! (`"1h"` / `1u32`), this module owns the *plain-scalar-constant*
22//! defaults that no CRD-specific axis binds to a single crate — the
23//! `-> bool { true }` shape recurs anywhere a boolean field's serde
24//! default is "on" for backward-compat, safe-default, or feature-flag
25//! reasons, and each variant of that shape wants ONE substrate owner
26//! rather than a private per-file shim.
27
28/// Workspace-canonical `-> bool { true }` serde-default owner — the
29/// ONE substrate site every `#[serde(default = "…")]` slot on a `bool`
30/// field whose "on by default" invariant matches the pre-lift shape
31/// routes through.
32///
33/// Pre-lift the SAME 3-line `fn default_true() -> bool { true }` shim
34/// was hand-authored at FIVE workspace-wide sites past the ★★
35/// PRIME-DIRECTIVE ≥ 2 duplication threshold, each serving as the
36/// `#[serde(default = "default_true")]` seed on a different `bool`
37/// field:
38///
39/// * [`crate::matrix::BreatheSpec::dry_run`] — the env-matrix
40///   observability breathe-band spec's "start observe-only" flag; the
41///   safe-by-default posture for a fresh breathe sweep.
42/// * [`crate::spec::SignalPolicy::sigkill_force`] — the process-spec
43///   signal policy's "permit force-reap via SIGKILL" allowance; the
44///   permissive default matching Unix's own SIGKILL semantics.
45/// * [`crate::intent::FluxIntent::decrypt_sops`] — the flux-intent
46///   spec's SOPS-decryption toggle; the pleme-io-convention default of
47///   "SOPS envelopes decrypt on apply".
48/// * [`crate::table::ProcessTableSpec::orphan_reaping_enabled`] — the
49///   process-table spec's PID-1-adopts-orphans switch; the Unix-
50///   process-model default of "PID 1 reaps orphans".
51/// * `tatara-reconciler::ephemeral_defaults::EphemeralDefaults::emit_oci_repository`
52///   — the reconciler's operator-facing "auto-emit OCIRepository peer
53///   for `oci://` chart refs" toggle; the convenience default matching
54///   the sibling render path.
55///
56/// All FIVE sites walked the SAME 3-line body — `fn default_true()
57/// -> bool { true }` — and served the SAME "on-by-default" invariant
58/// through the SAME serde `default = "…"` contract. Post-lift every
59/// consumer's serde slot reads `default = "…serde_defaults::default_true"`
60/// (in-crate via `crate::serde_defaults::default_true`; cross-crate
61/// via `tatara_process::serde_defaults::default_true`) and the local
62/// per-file `fn default_true` shim disappears at each site.
63///
64/// Return-form axis: `bool` — matches the serde `default = "…"` slot
65/// contract exactly (serde invokes the named function and stamps its
66/// returned owned value into the field). The paired
67/// [`DEFAULT_TRUE`] const exposes the underlying `bool` for callers
68/// that want a compile-time handle (a `const fn`-visible pin, a
69/// `matches!(x, DEFAULT_TRUE)` peer check, a const-context comparison).
70///
71/// A future normalization on the workspace-canonical "on-by-default"
72/// invariant (a debug-build assertion that the caller has permission to
73/// stamp `true` at all, a per-fleet override injected via a
74/// `TATARA_DEFAULT_TRUE_<slot>` env var, a shift to a typed
75/// `SerdeDefault<bool>` newtype that carries the semantic label) lands
76/// at THIS ONE substrate primitive and every downstream serde-default
77/// consumer inherits the upgrade mechanically — no per-site edit at
78/// any of the FIVE listed callers or at future consumers (a new
79/// bool-slot serde default, a fleet-wide dashboard reading the
80/// canonical "on" wire-form, a new tatara-eval fixture).
81///
82/// Peer to [`crate::lifetime::default_ephemeral_ttl`] +
83/// [`crate::lifetime::default_ephemeral_max_concurrent`] on the
84/// workspace-canonical serde-default-owner axis — those primitives own
85/// the ephemeral-authoring-surface-specific defaults (`"1h"` / `1u32`),
86/// while this primitive owns the axis-agnostic `-> bool { true }`
87/// scalar-constant default.
88///
89/// Theory anchor: THEORY.md §VI.1 (generation over composition — the
90/// `fn default_true() -> bool { true }` 3-line body recurred at FIVE
91/// hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
92/// trigger, spanning two workspace crates + four `tatara-process`
93/// modules, and is lifted onto ONE workspace-wide substrate owner
94/// here). THEORY.md §II.1 invariant 5 (composition preserves proofs —
95/// the pins bind the wire-form at fail-before-pass-after granularity
96/// so a regression that drifted the returned bool surfaces at
97/// [`tests::default_true_returns_true_bytewise`] rather than as silent
98/// operator-facing skew across the five downstream consumers).
99#[must_use]
100pub fn default_true() -> bool {
101    DEFAULT_TRUE
102}
103
104/// Workspace-canonical `bool` handle over the same `true` value
105/// [`default_true`] returns. Use this const for compile-time
106/// comparisons and const-context readers; use [`default_true`] for
107/// the serde `default = "…"` slot contract.
108pub const DEFAULT_TRUE: bool = true;
109
110/// Workspace-canonical `-> u32 { 480 }` SIGTERM→SIGKILL grace-window
111/// serde-default owner — the ONE substrate site every
112/// `#[serde(default = "…")]` slot on a `u32` field whose semantic is
113/// "seconds to wait after delivering SIGTERM before escalating to
114/// SIGKILL" routes through.
115///
116/// Pre-lift the SAME 3-line `fn default_<slot>() -> u32 { 480 }` shim
117/// was hand-authored at TWO workspace-wide sites past the ★★
118/// PRIME-DIRECTIVE ≥ 2 duplication threshold, each serving as the
119/// `#[serde(default = "default_<slot>")]` seed on a different `u32`
120/// field carrying the SAME semantic wire-form (8-minute SIGTERM→
121/// SIGKILL grace window):
122///
123/// * [`crate::spec::SignalPolicy::sigterm_grace_seconds`] — the
124///   per-Process signal-policy's SIGTERM→SIGKILL grace window; the
125///   escalation timer the Unix-process-model reconciler honors during
126///   Exiting → Zombie.
127/// * [`crate::table::ProcessTableSpec::sigterm_timeout_seconds`] — the
128///   per-ProcessTable default SIGTERM→SIGKILL grace window; the
129///   fallback the reconciler injects when a child Process omits its
130///   own [`SignalPolicy::sigterm_grace_seconds`] slot.
131///
132/// Both sites walked the SAME 3-line body — `-> u32 { 480 }` — and
133/// served the SAME "8-minute SIGTERM→SIGKILL escalation" invariant
134/// through the SAME serde `default = "…"` contract. Post-lift every
135/// consumer's serde slot reads
136/// `default = "…serde_defaults::default_sigterm_grace_seconds"`
137/// (in-crate via `crate::serde_defaults::default_sigterm_grace_seconds`;
138/// cross-crate via
139/// `tatara_process::serde_defaults::default_sigterm_grace_seconds`)
140/// and the local per-file `fn default_sigterm_<slot>` shim disappears
141/// at each site.
142///
143/// Return-form axis: `u32` — matches the serde `default = "…"` slot
144/// contract exactly (serde invokes the named function and stamps its
145/// returned owned value into the field). The paired
146/// [`DEFAULT_SIGTERM_GRACE_SECONDS`] const exposes the underlying
147/// `u32` for callers that want a compile-time handle (a `const fn`-
148/// visible pin, a `matches!(x, DEFAULT_SIGTERM_GRACE_SECONDS)` peer
149/// check, a const-context comparison against a k8s pod-eviction
150/// grace annotation).
151///
152/// A future normalization on the workspace-canonical "SIGTERM→
153/// SIGKILL grace" invariant (a shift to the k8s-recommended 30s
154/// default, a per-fleet override injected via a `TATARA_SIGTERM_
155/// GRACE_SECONDS` env var, a shift to a typed `GracePeriod`
156/// newtype that carries the "seconds" unit) lands at THIS ONE
157/// substrate primitive and every downstream serde-default consumer
158/// inherits the upgrade mechanically — no per-site edit at either
159/// listed caller or at future consumers (a new Process-plane
160/// termination window, a per-container override in `ContainerIntent`,
161/// a `PoolSpec::sigterm_grace_seconds` slot).
162///
163/// Peer to [`default_true`] on the workspace-canonical serde-default-
164/// owner axis. Where [`default_true`] owns the `-> bool { true }`
165/// on-by-default scalar shape, this primitive owns the `-> u32
166/// { 480 }` SIGTERM-grace scalar shape.
167///
168/// Theory anchor: THEORY.md §VI.1 (generation over composition — the
169/// `fn default_<slot>() -> u32 { 480 }` 3-line body recurred at TWO
170/// hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
171/// trigger, and is lifted onto ONE workspace-wide substrate owner
172/// here). THEORY.md §II.1 invariant 5 (composition preserves proofs —
173/// the pins bind the wire-form at fail-before-pass-after granularity
174/// so a regression that drifted the returned u32 surfaces at
175/// [`tests::default_sigterm_grace_seconds_returns_480_bytewise`]
176/// rather than as silent operator-facing skew across the two
177/// downstream consumers).
178#[must_use]
179pub fn default_sigterm_grace_seconds() -> u32 {
180    DEFAULT_SIGTERM_GRACE_SECONDS
181}
182
183/// Workspace-canonical `u32` handle over the same `480` value
184/// [`default_sigterm_grace_seconds`] returns. Use this const for
185/// compile-time comparisons and const-context readers; use
186/// [`default_sigterm_grace_seconds`] for the serde `default = "…"`
187/// slot contract.
188pub const DEFAULT_SIGTERM_GRACE_SECONDS: u32 = 480;
189
190/// Workspace-canonical `-> u32 { 600 }` Zombie-phase force-reap
191/// timeout serde-default owner — the ONE substrate site every
192/// `#[serde(default = "…")]` slot on a `u32` field whose semantic is
193/// "seconds a Process is permitted to sit in `Zombie` before PID 1
194/// force-reaps it" routes through, PLUS the ONE substrate anchor
195/// every hand-authored `ProcessTableSpec` composer that pre-populates
196/// the `zombie_timeout_seconds` slot with the workspace-canonical
197/// wire-form routes through.
198///
199/// Pre-lift the SAME `600` u32 constant lived at TWO workspace-wide
200/// sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold, each
201/// encoding the SAME semantic wire-form (10-minute Zombie force-reap
202/// window):
203///
204/// * [`crate::table::ProcessTableSpec::zombie_timeout_seconds`] — the
205///   per-ProcessTable Zombie force-reap window; the `#[serde(default
206///   = "…")]` seed the wire-parser stamps when a serialized
207///   `ProcessTable` YAML omits its own `zombieTimeoutSeconds:` slot.
208/// * `tatara_reconciler::patch::ensure_process_table` — the
209///   ProcessTable-singleton bootstrap composer; the explicit
210///   `zombie_timeout_seconds: 600` slot in the hand-authored
211///   [`crate::table::ProcessTableSpec`] struct literal used to
212///   materialize a fresh singleton on first observation.
213///
214/// Both sites walked the SAME bare `600u32` constant and served the
215/// SAME "10-minute Zombie force-reap" invariant — the serde path
216/// through the `#[serde(default = "…")]` machinery, the composer path
217/// through the explicit struct-literal slot. Post-lift the serde slot
218/// reads `default = "…serde_defaults::default_zombie_timeout_seconds"`
219/// (in-crate via `crate::serde_defaults::default_zombie_timeout_seconds`;
220/// cross-crate via
221/// `tatara_process::serde_defaults::default_zombie_timeout_seconds`)
222/// and the reconciler composer feeds the substrate fn directly at the
223/// `zombie_timeout_seconds:` slot in place of the bare `600` literal.
224///
225/// Return-form axis: `u32` — matches the serde `default = "…"` slot
226/// contract exactly (serde invokes the named function and stamps its
227/// returned owned value into the field). The paired
228/// [`DEFAULT_ZOMBIE_TIMEOUT_SECONDS`] const exposes the underlying
229/// `u32` for callers that want a compile-time handle (a `const fn`-
230/// visible pin, a `matches!(x, DEFAULT_ZOMBIE_TIMEOUT_SECONDS)` peer
231/// check, a const-context comparison against a k8s pod-termination
232/// grace annotation).
233///
234/// A future normalization on the workspace-canonical "Zombie force-
235/// reap" invariant (a shift to the k8s pod-eviction-recommended 300s
236/// default, a per-fleet override injected via a
237/// `TATARA_ZOMBIE_TIMEOUT_SECONDS` env var, a shift to a typed
238/// `ReapDeadline` newtype that carries the "seconds since Zombie
239/// entry" phase-anchored semantics) lands at THIS ONE substrate
240/// primitive and every downstream serde-default consumer PLUS every
241/// hand-authored `ensure_process_table`-shaped composer inherits the
242/// upgrade mechanically — no per-site edit at either listed caller or
243/// at future consumers (a per-Process `zombieTimeoutSecondsOverride`
244/// slot, a `PoolSpec`-scoped Zombie window, a new debug-build sub-10s
245/// override for local development).
246///
247/// Peer to [`default_sigterm_grace_seconds`] on the workspace-
248/// canonical "termination-phase timing" scalar axis. Where
249/// [`default_sigterm_grace_seconds`] owns the `-> u32 { 480 }`
250/// SIGTERM→SIGKILL escalation window (the Exiting → Zombie phase
251/// transition's grace clock), this primitive owns the `-> u32 { 600 }`
252/// Zombie → Reaped force-reap window (the phase transition that
253/// follows). The two primitives compose sequentially at the
254/// reconciler: a `Process` with the default policy spends up to
255/// [`default_sigterm_grace_seconds`] seconds in Exiting, then up to
256/// [`default_zombie_timeout_seconds`] seconds in Zombie, then is
257/// force-reaped and cascaded via ownerRefs.
258///
259/// Theory anchor: THEORY.md §VI.1 (generation over composition — the
260/// bare `600u32` constant recurred at TWO hand-authored sites past
261/// the ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger, spanning two
262/// workspace crates, and is lifted onto ONE workspace-wide substrate
263/// owner here). THEORY.md §II.1 invariant 5 (composition preserves
264/// proofs — the pins bind the wire-form at fail-before-pass-after
265/// granularity so a regression that drifted the returned u32 surfaces
266/// at [`tests::default_zombie_timeout_seconds_returns_600_bytewise`]
267/// rather than as silent operator-facing skew across the two
268/// downstream consumers).
269#[must_use]
270pub fn default_zombie_timeout_seconds() -> u32 {
271    DEFAULT_ZOMBIE_TIMEOUT_SECONDS
272}
273
274/// Workspace-canonical `u32` handle over the same `600` value
275/// [`default_zombie_timeout_seconds`] returns. Use this const for
276/// compile-time comparisons and const-context readers; use
277/// [`default_zombie_timeout_seconds`] for the serde `default = "…"`
278/// slot contract.
279pub const DEFAULT_ZOMBIE_TIMEOUT_SECONDS: u32 = 600;
280
281#[cfg(test)]
282mod tests {
283    use super::*;
284
285    // ─── default_true substrate pins ─────────────────────────────────
286    //
287    // Bind [`default_true`] + the paired [`DEFAULT_TRUE`] const at
288    // fail-before-pass-after granularity so a regression that drifted
289    // the wire-form default (a shift from `true` to `false` at ONLY
290    // the const, a decoupling of the const from the fn's returned
291    // value, a rename that broke the serde `default = "…"` path
292    // resolution) surfaces HERE rather than as silent operator-visible
293    // skew across the FIVE serde-default consumers that ride through
294    // this ONE substrate owner.
295
296    #[test]
297    fn default_true_returns_true_bytewise() {
298        // Byte-shape parity with the FIVE hand-authored pre-lift shims
299        // that each returned `true`. A regression that flipped the
300        // returned bool (an accidental `false` seed, a `!true` typo
301        // that survives parse, a shift to a typed newtype without a
302        // Deref) fails HERE rather than at the five downstream
303        // consumers whose "on-by-default" invariant would silently
304        // become "off by default".
305        assert!(
306            default_true(),
307            "default_true must return `true` bytewise; a regression \
308             surfaces here rather than as five-way skew across \
309             BreatheSpec::dry_run + SignalPolicy::sigkill_force + \
310             FluxIntent::decrypt_sops + ProcessTableSpec::orphan_reaping_enabled + \
311             EphemeralDefaults::emit_oci_repository",
312        );
313    }
314
315    #[test]
316    #[allow(clippy::assertions_on_constants)]
317    // The `assert!(DEFAULT_TRUE, …)` shape IS the pin: we're binding
318    // the const's wire-form value at test time. Clippy sees a compile-
319    // time-constant assertion and flags it; the flag is exactly the
320    // corner we WANT to trip if a future edit drifts the const to
321    // `false` — the test would then fail with a comprehensible message
322    // rather than the const change slipping past.
323    fn default_true_wire_form_const_matches_fn_return_bytewise() {
324        // Cross-form coherence pin: the `pub const DEFAULT_TRUE: bool`
325        // handle and the `pub fn default_true() -> bool` owner MUST
326        // project onto the SAME wire-form value. A regression that
327        // updated one but not the other (e.g. lifted the const to
328        // `false` for a fleet-wide safe-default shift but forgot the
329        // fn body, or vice versa) would silently produce two divergent
330        // workspace-canonical defaults — the const for compile-time
331        // consumers, the fn for serde-default consumers. Pin the two
332        // projections at equality.
333        assert!(
334            DEFAULT_TRUE,
335            "DEFAULT_TRUE const must byte-match the pre-lift wire-form \
336             default `true`",
337        );
338        assert_eq!(
339            default_true(),
340            DEFAULT_TRUE,
341            "default_true() must byte-match the paired DEFAULT_TRUE \
342             const — a divergence would silently skew serde-default \
343             consumers vs compile-time const readers",
344        );
345    }
346
347    #[test]
348    fn default_true_composes_at_breathe_envelope_serde_default() {
349        // End-to-end: the [`crate::matrix::BreatheEnvelope::dry_run`]
350        // serde default MUST route through the substrate owner. A
351        // YAML fragment that omits the `dryRun:` field parses into a
352        // BreatheEnvelope whose `dry_run` reads bytewise-identical to
353        // [`default_true`]. A regression that reintroduced a local
354        // `fn default_true` shim in `matrix.rs` (bypassing the
355        // substrate) would produce a silent skew between the breathe
356        // envelope's "start observe-only" invariant and the four peer
357        // consumers.
358        let yaml = "\
359dimensions: []
360";
361        let spec: crate::matrix::BreatheEnvelope =
362            serde_yaml::from_str(yaml).expect("BreatheEnvelope YAML parses");
363        assert_eq!(
364            spec.dry_run,
365            default_true(),
366            "BreatheEnvelope serde-default for the omitted `dryRun:` slot \
367             must route through crate::serde_defaults::default_true \
368             — a private-shim reintroduction skews the breathe-envelope \
369             observability posture silently",
370        );
371    }
372
373    #[test]
374    fn default_true_composes_at_signal_policy_serde_default() {
375        // Peer to the BreatheSpec pin above — pin the
376        // [`crate::spec::SignalPolicy::sigkill_force`] serde default
377        // at the substrate owner. A YAML fragment that omits
378        // `sigkillForce:` parses into a SignalPolicy whose
379        // `sigkill_force` reads bytewise-identical to
380        // [`default_true`]. A regression that decoupled the
381        // signal-policy default from the substrate would silently
382        // flip the "permit force-reap" posture without any consumer
383        // tripping.
384        let yaml = "";
385        let sp: crate::spec::SignalPolicy =
386            serde_yaml::from_str(yaml).expect("SignalPolicy YAML parses");
387        assert_eq!(
388            sp.sigkill_force,
389            default_true(),
390            "SignalPolicy serde-default for the omitted `sigkillForce:` \
391             slot must route through crate::serde_defaults::default_true",
392        );
393    }
394
395    #[test]
396    fn default_true_composes_at_process_table_spec_serde_default() {
397        // Peer to the SignalPolicy pin above — pin the
398        // [`crate::table::ProcessTableSpec::orphan_reaping_enabled`]
399        // serde default at the substrate owner. A YAML fragment that
400        // omits `orphanReapingEnabled:` parses into a ProcessTableSpec
401        // whose `orphan_reaping_enabled` reads bytewise-identical to
402        // [`default_true`]. A regression that decoupled the
403        // process-table default from the substrate would silently
404        // flip the "PID 1 reaps orphans" posture without any consumer
405        // tripping.
406        let yaml = "";
407        let ts: crate::table::ProcessTableSpec =
408            serde_yaml::from_str(yaml).expect("ProcessTableSpec YAML parses");
409        assert_eq!(
410            ts.orphan_reaping_enabled,
411            default_true(),
412            "ProcessTableSpec serde-default for the omitted \
413             `orphanReapingEnabled:` slot must route through \
414             crate::serde_defaults::default_true",
415        );
416    }
417
418    // ─── default_sigterm_grace_seconds substrate pins ────────────────
419    //
420    // Bind [`default_sigterm_grace_seconds`] + the paired
421    // [`DEFAULT_SIGTERM_GRACE_SECONDS`] const at fail-before-pass-after
422    // granularity so a regression that drifted the wire-form default
423    // (a shift from `480` to `30` at ONLY the const, a decoupling of
424    // the const from the fn's returned value, a rename that broke the
425    // serde `default = "…"` path resolution) surfaces HERE rather than
426    // as silent operator-visible skew across the TWO serde-default
427    // consumers that ride through this ONE substrate owner.
428
429    #[test]
430    fn default_sigterm_grace_seconds_returns_480_bytewise() {
431        // Byte-shape parity with the TWO hand-authored pre-lift shims
432        // that each returned `480`. A regression that flipped the
433        // returned u32 (an accidental `48` or `4800` seed, a shift to
434        // millis without a unit rename, a shift to a typed newtype
435        // without a Deref) fails HERE rather than at the two
436        // downstream consumers whose "8-minute SIGTERM→SIGKILL grace"
437        // invariant would silently become sub-minute or 80-minute.
438        assert_eq!(
439            default_sigterm_grace_seconds(),
440            480,
441            "default_sigterm_grace_seconds must return `480` bytewise; \
442             a regression surfaces here rather than as two-way skew \
443             across SignalPolicy::sigterm_grace_seconds + \
444             ProcessTableSpec::sigterm_timeout_seconds",
445        );
446    }
447
448    #[test]
449    fn default_sigterm_grace_seconds_wire_form_const_matches_fn_return_bytewise() {
450        // Cross-form coherence pin: the `pub const
451        // DEFAULT_SIGTERM_GRACE_SECONDS: u32` handle and the `pub fn
452        // default_sigterm_grace_seconds() -> u32` owner MUST project
453        // onto the SAME wire-form value. A regression that updated
454        // one but not the other (e.g. lifted the const to `30` for a
455        // fleet-wide k8s-alignment shift but forgot the fn body, or
456        // vice versa) would silently produce two divergent workspace-
457        // canonical defaults — the const for compile-time consumers,
458        // the fn for serde-default consumers. Pin the two projections
459        // at equality.
460        assert_eq!(
461            DEFAULT_SIGTERM_GRACE_SECONDS, 480,
462            "DEFAULT_SIGTERM_GRACE_SECONDS const must byte-match the \
463             pre-lift wire-form default `480`",
464        );
465        assert_eq!(
466            default_sigterm_grace_seconds(),
467            DEFAULT_SIGTERM_GRACE_SECONDS,
468            "default_sigterm_grace_seconds() must byte-match the \
469             paired DEFAULT_SIGTERM_GRACE_SECONDS const — a divergence \
470             would silently skew serde-default consumers vs compile-\
471             time const readers",
472        );
473    }
474
475    #[test]
476    fn default_sigterm_grace_seconds_composes_at_signal_policy_serde_default() {
477        // End-to-end: the
478        // [`crate::spec::SignalPolicy::sigterm_grace_seconds`] serde
479        // default MUST route through the substrate owner. A YAML
480        // fragment that omits `sigtermGraceSeconds:` parses into a
481        // SignalPolicy whose `sigterm_grace_seconds` reads bytewise-
482        // identical to [`default_sigterm_grace_seconds`]. A regression
483        // that reintroduced a local `fn default_sigterm_grace` shim in
484        // `spec.rs` (bypassing the substrate) would produce a silent
485        // skew between the per-Process signal-policy escalation window
486        // and the ProcessTable-plane default that peers with it.
487        let yaml = "";
488        let sp: crate::spec::SignalPolicy =
489            serde_yaml::from_str(yaml).expect("SignalPolicy YAML parses");
490        assert_eq!(
491            sp.sigterm_grace_seconds,
492            default_sigterm_grace_seconds(),
493            "SignalPolicy serde-default for the omitted \
494             `sigtermGraceSeconds:` slot must route through \
495             crate::serde_defaults::default_sigterm_grace_seconds \
496             — a private-shim reintroduction skews the per-Process \
497             SIGTERM→SIGKILL escalation window silently",
498        );
499    }
500
501    #[test]
502    fn default_sigterm_grace_seconds_composes_at_process_table_spec_serde_default() {
503        // Peer to the SignalPolicy pin above — pin the
504        // [`crate::table::ProcessTableSpec::sigterm_timeout_seconds`]
505        // serde default at the substrate owner. A YAML fragment that
506        // omits `sigtermTimeoutSeconds:` parses into a
507        // ProcessTableSpec whose `sigterm_timeout_seconds` reads
508        // bytewise-identical to [`default_sigterm_grace_seconds`]. A
509        // regression that decoupled the process-table default from the
510        // substrate would silently flip the "8-minute grace" invariant
511        // at the fallback layer while the per-Process layer stayed
512        // put, producing table-scoped children with a mismatched
513        // escalation window.
514        let yaml = "";
515        let ts: crate::table::ProcessTableSpec =
516            serde_yaml::from_str(yaml).expect("ProcessTableSpec YAML parses");
517        assert_eq!(
518            ts.sigterm_timeout_seconds,
519            default_sigterm_grace_seconds(),
520            "ProcessTableSpec serde-default for the omitted \
521             `sigtermTimeoutSeconds:` slot must route through \
522             crate::serde_defaults::default_sigterm_grace_seconds",
523        );
524    }
525
526    #[test]
527    fn default_sigterm_grace_seconds_composes_at_signal_policy_impl_default() {
528        // `SignalPolicy::default()` reconstructs the same wire-form
529        // 480 through the substrate — pin it. A regression that
530        // decoupled the Default-impl path from the serde-default path
531        // (e.g. hard-coded `480` at the Default impl, then drifted the
532        // substrate owner to `30`) would produce two different
533        // "default" SignalPolicies depending on construction path.
534        let sp = crate::spec::SignalPolicy::default();
535        assert_eq!(
536            sp.sigterm_grace_seconds,
537            default_sigterm_grace_seconds(),
538            "SignalPolicy::default() must reconstruct \
539             `sigterm_grace_seconds` through the substrate owner — a \
540             hand-authored literal at the Default impl would decouple \
541             the two construction paths",
542        );
543    }
544
545    // ─── default_zombie_timeout_seconds substrate pins ───────────────
546    //
547    // Bind [`default_zombie_timeout_seconds`] + the paired
548    // [`DEFAULT_ZOMBIE_TIMEOUT_SECONDS`] const at fail-before-pass-
549    // after granularity so a regression that drifted the wire-form
550    // default (a shift from `600` to `300` at ONLY the const, a
551    // decoupling of the const from the fn's returned value, a rename
552    // that broke the serde `default = "…"` path resolution) surfaces
553    // HERE rather than as silent operator-visible skew across the TWO
554    // consumers that ride through this ONE substrate owner (the
555    // `ProcessTableSpec::zombie_timeout_seconds` serde default AND
556    // the `tatara_reconciler::patch::ensure_process_table` composer's
557    // explicit `zombie_timeout_seconds:` slot).
558
559    #[test]
560    fn default_zombie_timeout_seconds_returns_600_bytewise() {
561        // Byte-shape parity with the TWO hand-authored pre-lift sites
562        // that each encoded `600` (the serde-default fn body in
563        // `crate::table` + the composer's explicit struct-literal slot
564        // in `tatara_reconciler::patch::ensure_process_table`). A
565        // regression that flipped the returned u32 (an accidental `60`
566        // or `6000` seed, a shift to millis without a unit rename, a
567        // shift to a typed newtype without a Deref) fails HERE rather
568        // than at the two downstream consumers whose "10-minute Zombie
569        // force-reap" invariant would silently become sub-minute or
570        // 100-minute.
571        assert_eq!(
572            default_zombie_timeout_seconds(),
573            600,
574            "default_zombie_timeout_seconds must return `600` bytewise; \
575             a regression surfaces here rather than as two-way skew \
576             across ProcessTableSpec::zombie_timeout_seconds + \
577             tatara_reconciler::patch::ensure_process_table",
578        );
579    }
580
581    #[test]
582    fn default_zombie_timeout_seconds_wire_form_const_matches_fn_return_bytewise() {
583        // Cross-form coherence pin: the `pub const
584        // DEFAULT_ZOMBIE_TIMEOUT_SECONDS: u32` handle and the `pub fn
585        // default_zombie_timeout_seconds() -> u32` owner MUST project
586        // onto the SAME wire-form value. A regression that updated
587        // one but not the other (e.g. lifted the const to `300` for a
588        // fleet-wide k8s-alignment shift but forgot the fn body, or
589        // vice versa) would silently produce two divergent workspace-
590        // canonical defaults — the const for compile-time consumers,
591        // the fn for serde-default + composer consumers. Pin the two
592        // projections at equality.
593        assert_eq!(
594            DEFAULT_ZOMBIE_TIMEOUT_SECONDS, 600,
595            "DEFAULT_ZOMBIE_TIMEOUT_SECONDS const must byte-match the \
596             pre-lift wire-form default `600`",
597        );
598        assert_eq!(
599            default_zombie_timeout_seconds(),
600            DEFAULT_ZOMBIE_TIMEOUT_SECONDS,
601            "default_zombie_timeout_seconds() must byte-match the \
602             paired DEFAULT_ZOMBIE_TIMEOUT_SECONDS const — a divergence \
603             would silently skew serde-default + composer consumers \
604             vs compile-time const readers",
605        );
606    }
607
608    #[test]
609    fn default_zombie_timeout_seconds_composes_at_process_table_spec_serde_default() {
610        // End-to-end: the
611        // [`crate::table::ProcessTableSpec::zombie_timeout_seconds`]
612        // serde default MUST route through the substrate owner. A
613        // YAML fragment that omits `zombieTimeoutSeconds:` parses
614        // into a ProcessTableSpec whose `zombie_timeout_seconds`
615        // reads bytewise-identical to
616        // [`default_zombie_timeout_seconds`]. A regression that
617        // reintroduced a local `fn default_zombie_timeout` shim in
618        // `table.rs` (bypassing the substrate) would produce a silent
619        // skew between the wire-parser Zombie force-reap window and
620        // the reconciler-composer Zombie force-reap window.
621        let yaml = "";
622        let ts: crate::table::ProcessTableSpec =
623            serde_yaml::from_str(yaml).expect("ProcessTableSpec YAML parses");
624        assert_eq!(
625            ts.zombie_timeout_seconds,
626            default_zombie_timeout_seconds(),
627            "ProcessTableSpec serde-default for the omitted \
628             `zombieTimeoutSeconds:` slot must route through \
629             crate::serde_defaults::default_zombie_timeout_seconds",
630        );
631    }
632
633    #[test]
634    fn default_zombie_timeout_seconds_pairs_with_sigterm_grace_on_termination_axis() {
635        // Composition pin: the two termination-phase timing primitives
636        // ([`default_sigterm_grace_seconds`] +
637        // [`default_zombie_timeout_seconds`]) fire sequentially at the
638        // reconciler — Exiting-phase grace first, then Zombie-phase
639        // force-reap. Pin both against their canonical wire-forms in
640        // a single assertion so a regression that lifted one primitive
641        // through a rename that shadowed the peer (or that drifted
642        // both together in a coordinated typo) surfaces HERE. The
643        // Zombie window MUST also be strictly greater than the SIGTERM
644        // grace window (a process only enters Zombie once its
645        // SIGTERM grace has already elapsed, so the reap deadline
646        // must be at least the escalation deadline).
647        assert_eq!(default_sigterm_grace_seconds(), 480);
648        assert_eq!(default_zombie_timeout_seconds(), 600);
649        assert!(
650            default_zombie_timeout_seconds() > default_sigterm_grace_seconds(),
651            "Zombie force-reap window must exceed the SIGTERM grace \
652             window — the Zombie phase only begins after the SIGTERM \
653             grace has elapsed, so a Zombie deadline shorter than the \
654             SIGTERM deadline would mean force-reap fires before the \
655             process ever entered Zombie",
656        );
657    }
658}