Skip to main content

tatara_process/
finalizers.rs

1//! Workspace-canonical finalizer keys the tatara controllers stamp on
2//! their owned CRDs to gate cascade-delete + orphan-reap on graceful
3//! teardown.
4//!
5//! # Family
6//!
7//! The reconciler binaries in this workspace own three top-level CRDs
8//! that participate in the K8s garbage-collection contract via the
9//! `metadata.finalizers` list:
10//!
11//! - [`PROCESS`] — the [`crate::prelude::Process`] reconciler's
12//!   finalizer key. Stamped by
13//!   `tatara-reconciler::phase_machine::handle_pending` via
14//!   [`crate::patch::ensure_finalizer`]; stripped by `handle_reaped`
15//!   via `remove_finalizer`.
16//! - [`ALLOCATION`] — the [`crate::prelude::EphemeralAllocation`]
17//!   controller's finalizer key. Reserved by
18//!   `tatara-pool-reconciler::controller_allocation::reconcile` for
19//!   the future Bound → Releasing → Released cascade-delete gate.
20//! - [`POOL`] — the [`crate::prelude::EphemeralPool`] controller's
21//!   finalizer key. Reserved by
22//!   `tatara-pool-reconciler::controller_pool::build_member_process`
23//!   for the future member-Process orphan-reap gate.
24//!
25//! [`ALL`] enumerates the three consts in stable declaration order so
26//! downstream sweeps (a fleet-wide finalizer audit binary, an ops
27//! diagnostic dumping every tatara-owned finalizer on a cluster, a
28//! per-key coverage report) iterate through ONE substrate slice
29//! rather than three hand-typed literals.
30//!
31//! # Peer axes
32//!
33//! Peer to [`crate::annotations`] on the "K8s metadata key that pins
34//! a reconciliation contract" axis-family — where [`crate::annotations`]
35//! owns keys the reconciler READS or WRITES on OWNED resources
36//! (FluxCD `HelmRelease`, `Kustomization`, member `Process` on a pool
37//! member), this module owns keys the reconciler stamps on the CRDs
38//! IT OWNS (the [`crate::prelude::Process`],
39//! [`crate::prelude::EphemeralAllocation`],
40//! [`crate::prelude::EphemeralPool`] `metadata.finalizers` list).
41//! Both families carry the same `tatara.pleme.io/<slug>` wire-form
42//! shape; the finalizer family additionally carries the shared
43//! `<owner>-finalizer` suffix — a convention pinned by
44//! [`tests::all_carry_finalizer_suffix`] so a future addition to the
45//! family cannot silently drift the suffix.
46//!
47//! # Compounding
48//!
49//! Pre-lift the three wire-form finalizer literals were spread across
50//! three files (the [`crate::PROCESS_FINALIZER`] top-level const at
51//! [`crate`] + private `POOL_FINALIZER` / `ALLOC_FINALIZER` consts in
52//! `tatara-pool-reconciler`), with no cross-owner coherence pin
53//! binding the shared `tatara.pleme.io/<x>-finalizer` shape. A future
54//! rename that shifted (e.g.) the group segment to `v2/` or the
55//! per-owner suffix to `-guard` had to be applied at three separate
56//! files coherently — a partial edit would silently orphan or
57//! double-finalize whichever owner missed the update. Post-lift the
58//! three consts live at ONE substrate owner + one closed [`ALL`]
59//! slice + four family-invariant pins (per-key wire-form parity,
60//! per-key `-finalizer` suffix, per-key `tatara.pleme.io/` prefix,
61//! cross-key uniqueness).
62//!
63//! Theory grounding: THEORY.md §VI.1 (generation over composition —
64//! the `tatara.pleme.io/<owner>-finalizer` wire-form shape recurred
65//! at THREE hand-authored declaration sites past the ★★
66//! PRIME-DIRECTIVE ≥ 2 duplication threshold; lifted to ONE
67//! per-owner const + ONE `ALL` slice + four family-invariant pins
68//! here). THEORY.md §II.1 invariant 5 (composition preserves proofs
69//! — the invariant pins bind the family shape at fail-before-pass-
70//! after granularity; a future rename that drifted the group prefix
71//! or the suffix at ONE arm surfaces at [`tests::all_share_group_prefix`]
72//! or [`tests::all_carry_finalizer_suffix`] rather than as silent
73//! operator-facing skew at whichever owner's finalizer no longer
74//! matches the K8s-side registered wire form).
75
76/// Finalizer key stamped on the [`crate::prelude::Process`]
77/// `metadata.finalizers` list by
78/// `tatara-reconciler::phase_machine::handle_pending`. The reconciler
79/// blocks K8s garbage collection until it has emitted the terminal
80/// [`crate::prelude::ProcessAttestation`] receipt and stripped the
81/// finalizer at `handle_reaped`.
82///
83/// Re-exported at the crate root as [`crate::PROCESS_FINALIZER`] for
84/// consumers that predate this module.
85pub const PROCESS: &str = "tatara.pleme.io/process-finalizer";
86
87/// Finalizer key reserved for the
88/// [`crate::prelude::EphemeralAllocation`] controller
89/// (`tatara-pool-reconciler::controller_allocation`). Once the Bound
90/// → Releasing → Released cascade is wired the controller will
91/// stamp/strip this key through the same
92/// [`crate::patch::ensure_finalizer`] / `remove_finalizer` primitives
93/// the Process reconciler already routes through for [`PROCESS`].
94pub const ALLOCATION: &str = "tatara.pleme.io/allocation-finalizer";
95
96/// Finalizer key reserved for the [`crate::prelude::EphemeralPool`]
97/// controller (`tatara-pool-reconciler::controller_pool`). Once the
98/// member-Process orphan-reap gate is wired the controller will
99/// stamp/strip this key at pool birth + pool teardown through the
100/// same [`crate::patch::ensure_finalizer`] / `remove_finalizer`
101/// primitives.
102pub const POOL: &str = "tatara.pleme.io/pool-finalizer";
103
104/// Closed family of tatara-owned finalizer keys in declaration
105/// order: [`PROCESS`], [`ALLOCATION`], [`POOL`]. Downstream sweeps
106/// (a per-owner coverage report, a fleet-wide finalizer audit
107/// binary, an ops diagnostic dumping every tatara-owned finalizer
108/// on a cluster) iterate through this ONE slice rather than
109/// re-listing the three consts by hand.
110///
111/// Pinned to length 3 by [`tests::all_matches_declared_family`] so
112/// a future addition to the family cannot slip past the coherence
113/// harness without landing here first.
114pub const ALL: &[&str] = &[PROCESS, ALLOCATION, POOL];
115
116/// Shared group prefix every finalizer key in the [`ALL`] family
117/// carries — the same `tatara.pleme.io/` group segment every
118/// tatara-owned CRD annotation + finalizer key rides through, matching
119/// the `#[kube(group = "tatara.pleme.io", …)]` derive slot on the CRD
120/// structs. Pinned across every arm by
121/// [`tests::all_share_group_prefix`] so a future rename of the group
122/// (a shift to `v2/` under a migration, a per-fleet override) lands
123/// at ONE substrate const here + the paired CRD derive slots rather
124/// than at three independent finalizer wire forms.
125pub const GROUP_PREFIX: &str = "tatara.pleme.io/";
126
127/// Shared owner-suffix every finalizer key in the [`ALL`] family
128/// carries — the convention `<owner>-finalizer` distinguishes a
129/// finalizer key from a signal/label/annotation key on the same
130/// group. Pinned across every arm by
131/// [`tests::all_carry_finalizer_suffix`].
132pub const FINALIZER_SUFFIX: &str = "-finalizer";
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    // ─── per-const wire-form pins ─────────────────────────────────
139    //
140    // Each pin binds ONE const to its exact wire-form string at
141    // fail-before-pass-after granularity. A regression that renamed
142    // the const's wire form (a group-segment shift, a suffix swap,
143    // an accidental case drift) surfaces HERE rather than as silent
144    // K8s-API-server-side skew at whichever owner's finalizer no
145    // longer matches the registered key on the live cluster.
146
147    #[test]
148    fn process_wire_form_pin() {
149        assert_eq!(PROCESS, "tatara.pleme.io/process-finalizer");
150    }
151
152    #[test]
153    fn allocation_wire_form_pin() {
154        assert_eq!(ALLOCATION, "tatara.pleme.io/allocation-finalizer");
155    }
156
157    #[test]
158    fn pool_wire_form_pin() {
159        assert_eq!(POOL, "tatara.pleme.io/pool-finalizer");
160    }
161
162    // ─── family-invariant pins ────────────────────────────────────
163    //
164    // The three consts form a closed family. These pins bind the
165    // family-wide invariants (fixed length, no dupes across arms,
166    // shared group prefix, shared `-finalizer` suffix) so a future
167    // addition to the family that violated ANY invariant surfaces at
168    // this module's tests rather than as silent skew at some
169    // downstream site.
170
171    #[test]
172    fn all_matches_declared_family() {
173        // A regression that added a fourth const to the family
174        // without appending it to [`ALL`] (or shrank the family
175        // without pruning the slice) would surface here. The
176        // three-arm listing pins the declaration order the pool
177        // reconciler + downstream sweep binaries iterate through.
178        assert_eq!(ALL, &[PROCESS, ALLOCATION, POOL]);
179        assert_eq!(ALL.len(), 3);
180    }
181
182    #[test]
183    fn all_share_group_prefix() {
184        // Every arm of the family MUST start with the shared
185        // `tatara.pleme.io/` group segment — the SAME prefix every
186        // tatara-owned CRD annotation + finalizer key rides through,
187        // matching the `#[kube(group = "tatara.pleme.io", …)]` slot
188        // on every CRD struct. A future rename that drifted ONE
189        // arm's prefix (a copy-paste that landed on `tatara.io/`)
190        // would silently escape the K8s garbage-collection contract
191        // for that CRD; the pin surfaces the drift at compile-time
192        // rather than at operator-visible orphan cascade skew.
193        assert_eq!(GROUP_PREFIX, "tatara.pleme.io/");
194        for key in ALL {
195            assert!(
196                key.starts_with(GROUP_PREFIX),
197                "{key} must start with {GROUP_PREFIX}"
198            );
199        }
200    }
201
202    #[test]
203    fn all_carry_finalizer_suffix() {
204        // Every arm of the family MUST end with `-finalizer` — the
205        // convention that distinguishes a finalizer key from a
206        // signal/label/annotation key on the same group prefix. A
207        // future addition that omitted the suffix (e.g. a bare
208        // `tatara.pleme.io/pool` accidentally landing in the
209        // family) would collide with an existing annotation key on
210        // the peer [`crate::annotations`] axis and silently
211        // double-book the K8s metadata slot.
212        assert_eq!(FINALIZER_SUFFIX, "-finalizer");
213        for key in ALL {
214            assert!(
215                key.ends_with(FINALIZER_SUFFIX),
216                "{key} must end with {FINALIZER_SUFFIX}"
217            );
218        }
219    }
220
221    #[test]
222    fn all_are_pairwise_unique() {
223        // No two arms of the family may share the same wire form —
224        // a copy-paste regression that duplicated the [`PROCESS`]
225        // wire form at the [`POOL`] arm would silently rejoin the
226        // three cascade-delete gates onto ONE finalizer, breaking
227        // the per-CRD garbage-collection contract at the moment
228        // whichever owner stripped its shared finalizer first.
229        let mut seen: Vec<&str> = Vec::new();
230        for key in ALL {
231            assert!(!seen.contains(key), "duplicate finalizer key in ALL: {key}");
232            seen.push(key);
233        }
234        assert_eq!(seen.len(), ALL.len());
235    }
236
237    // ─── crate-root re-export coherence pin ───────────────────────
238    //
239    // The pre-existing [`crate::PROCESS_FINALIZER`] top-level const
240    // now routes through this module's [`PROCESS`] owner. Pin the
241    // coherence so a regression that decoupled the re-export
242    // surfaces here rather than as silent skew between the reconciler
243    // production callsites (which reach through
244    // `tatara_process::PROCESS_FINALIZER`) and any future consumer
245    // reaching through `tatara_process::finalizers::PROCESS` directly.
246
247    #[test]
248    fn process_finalizer_top_level_reexport_routes_through_finalizers_process() {
249        assert_eq!(crate::PROCESS_FINALIZER, PROCESS);
250    }
251}