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.
125///
126/// Compile-time alias of the crate-wide substrate owner
127/// [`crate::GROUP_PREFIX`] — the ONE `pub const &'static str` that
128/// owns the `<GROUP>/` byte-shape every tatara wire-form key uses as
129/// its reverse-DNS prefix. Pre-lift this const restated the literal
130/// `"tatara.pleme.io/"` inline as a byte-parity peer of the sibling
131/// [`crate::annotations::GROUP_PREFIX`] (pinned equal by
132/// [`crate::annotations_family_tests::group_prefix_matches_finalizers_group_prefix`]);
133/// post-lift both peer consts route through [`crate::GROUP_PREFIX`]
134/// so byte equality holds by construction (the same `&'static str`
135/// address), and a future rename of the reverse-DNS root lands at
136/// the ONE crate-root const with every peer wire-form consumer
137/// inheriting the shift mechanically rather than at three independent
138/// per-module string literals.
139pub const GROUP_PREFIX: &str = crate::GROUP_PREFIX;
140
141/// Shared owner-suffix every finalizer key in the [`ALL`] family
142/// carries — the convention `<owner>-finalizer` distinguishes a
143/// finalizer key from a signal/label/annotation key on the same
144/// group. Pinned across every arm by
145/// [`tests::all_carry_finalizer_suffix`].
146pub const FINALIZER_SUFFIX: &str = "-finalizer";
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 // ─── per-const wire-form pins ─────────────────────────────────
153 //
154 // Each pin binds ONE const to its exact wire-form string at
155 // fail-before-pass-after granularity. A regression that renamed
156 // the const's wire form (a group-segment shift, a suffix swap,
157 // an accidental case drift) surfaces HERE rather than as silent
158 // K8s-API-server-side skew at whichever owner's finalizer no
159 // longer matches the registered key on the live cluster.
160
161 #[test]
162 fn process_wire_form_pin() {
163 assert_eq!(PROCESS, "tatara.pleme.io/process-finalizer");
164 }
165
166 #[test]
167 fn allocation_wire_form_pin() {
168 assert_eq!(ALLOCATION, "tatara.pleme.io/allocation-finalizer");
169 }
170
171 #[test]
172 fn pool_wire_form_pin() {
173 assert_eq!(POOL, "tatara.pleme.io/pool-finalizer");
174 }
175
176 // ─── family-invariant pins ────────────────────────────────────
177 //
178 // The three consts form a closed family. These pins bind the
179 // family-wide invariants (fixed length, no dupes across arms,
180 // shared group prefix, shared `-finalizer` suffix) so a future
181 // addition to the family that violated ANY invariant surfaces at
182 // this module's tests rather than as silent skew at some
183 // downstream site.
184
185 #[test]
186 fn all_matches_declared_family() {
187 // A regression that added a fourth const to the family
188 // without appending it to [`ALL`] (or shrank the family
189 // without pruning the slice) would surface here. The
190 // three-arm listing pins the declaration order the pool
191 // reconciler + downstream sweep binaries iterate through.
192 assert_eq!(ALL, &[PROCESS, ALLOCATION, POOL]);
193 assert_eq!(ALL.len(), 3);
194 }
195
196 #[test]
197 fn all_share_group_prefix() {
198 // Every arm of the family MUST start with the shared
199 // `tatara.pleme.io/` group segment — the SAME prefix every
200 // tatara-owned CRD annotation + finalizer key rides through,
201 // matching the `#[kube(group = "tatara.pleme.io", …)]` slot
202 // on every CRD struct. A future rename that drifted ONE
203 // arm's prefix (a copy-paste that landed on `tatara.io/`)
204 // would silently escape the K8s garbage-collection contract
205 // for that CRD; the pin surfaces the drift at compile-time
206 // rather than at operator-visible orphan cascade skew.
207 assert_eq!(GROUP_PREFIX, "tatara.pleme.io/");
208 for key in ALL {
209 assert!(
210 key.starts_with(GROUP_PREFIX),
211 "{key} must start with {GROUP_PREFIX}"
212 );
213 }
214 }
215
216 #[test]
217 fn all_carry_finalizer_suffix() {
218 // Every arm of the family MUST end with `-finalizer` — the
219 // convention that distinguishes a finalizer key from a
220 // signal/label/annotation key on the same group prefix. A
221 // future addition that omitted the suffix (e.g. a bare
222 // `tatara.pleme.io/pool` accidentally landing in the
223 // family) would collide with an existing annotation key on
224 // the peer [`crate::annotations`] axis and silently
225 // double-book the K8s metadata slot.
226 assert_eq!(FINALIZER_SUFFIX, "-finalizer");
227 for key in ALL {
228 assert!(
229 key.ends_with(FINALIZER_SUFFIX),
230 "{key} must end with {FINALIZER_SUFFIX}"
231 );
232 }
233 }
234
235 #[test]
236 fn all_are_pairwise_unique() {
237 // No two arms of the family may share the same wire form —
238 // a copy-paste regression that duplicated the [`PROCESS`]
239 // wire form at the [`POOL`] arm would silently rejoin the
240 // three cascade-delete gates onto ONE finalizer, breaking
241 // the per-CRD garbage-collection contract at the moment
242 // whichever owner stripped its shared finalizer first.
243 let mut seen: Vec<&str> = Vec::new();
244 for key in ALL {
245 assert!(!seen.contains(key), "duplicate finalizer key in ALL: {key}");
246 seen.push(key);
247 }
248 assert_eq!(seen.len(), ALL.len());
249 }
250
251 // ─── crate-root re-export coherence pin ───────────────────────
252 //
253 // The pre-existing [`crate::PROCESS_FINALIZER`] top-level const
254 // now routes through this module's [`PROCESS`] owner. Pin the
255 // coherence so a regression that decoupled the re-export
256 // surfaces here rather than as silent skew between the reconciler
257 // production callsites (which reach through
258 // `tatara_process::PROCESS_FINALIZER`) and any future consumer
259 // reaching through `tatara_process::finalizers::PROCESS` directly.
260
261 #[test]
262 fn process_finalizer_top_level_reexport_routes_through_finalizers_process() {
263 assert_eq!(crate::PROCESS_FINALIZER, PROCESS);
264 }
265}