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}