tatara_process/flux_resource.rs
1//! Closed-set of FluxCD resource kinds emitted or consumed by the
2//! tatara reconciler + typed projections for their `(apiVersion, kind)`
3//! pair — the substrate primitive that owns the workspace-wide (variant
4//! → wire-form identity) mapping every Flux-facing site would otherwise
5//! restate by hand.
6//!
7//! Pre-lift the `(apiVersion, kind)` pairing was hand-authored across
8//! FIVE production sites in `tatara-reconciler` past the ★★
9//! PRIME-DIRECTIVE ≥ 2 duplication threshold, split across two axes:
10//!
11//! * `Kustomization` — two production sites:
12//! * `tatara-reconciler::boundary::evaluate` — the
13//! `ConditionKind::KustomizationHealthy` arm's
14//! `evaluate_flux_ready(..., "kustomize.toolkit.fluxcd.io/v1",
15//! "Kustomization")` call.
16//! * `tatara-reconciler::render::render_flux` — the emitted
17//! `json!({"apiVersion": "kustomize.toolkit.fluxcd.io/v1",
18//! "kind": "Kustomization", ...})` block.
19//! * `HelmRelease` — two production sites:
20//! * `tatara-reconciler::boundary::evaluate` — the
21//! `ConditionKind::HelmReleaseReleased` arm's
22//! `evaluate_flux_ready(..., "helm.toolkit.fluxcd.io/v2",
23//! "HelmRelease")` call.
24//! * `tatara-reconciler::render::render_aplicacao` — the emitted
25//! `json!({"apiVersion": "helm.toolkit.fluxcd.io/v2",
26//! "kind": "HelmRelease", ...})` block.
27//! * `OCIRepository` — two production sites (both in
28//! `tatara-reconciler::render::render_aplicacao`):
29//! * The emitted `json!({"apiVersion":
30//! "source.toolkit.fluxcd.io/v1beta2", "kind": "OCIRepository",
31//! ...})` block for the `oci://` chart-ref branch.
32//! * The inline `chartRef.kind = "OCIRepository"` slot on the
33//! sibling HelmRelease spec.
34//!
35//! Every pre-lift site restated the axis-typed `&'static str` slot
36//! byte-identically; a regression that swapped ONE arm's `apiVersion`
37//! from `v1` to `v1beta1` (a Flux API-version bump that reaches only
38//! one of the two sites) or misspelled `Kustomization` at ONE site
39//! would silently mis-route the reconciler's SSA-fetch against the
40//! K8s API server (fetch under one apiVersion + apply under another
41//! is a 404 at wire time). Post-lift every axis binds at ONE closed-
42//! set owner and rustc enforces every consumer reads the pair from
43//! the SAME variant.
44//!
45//! Sibling to the same-shape `IntentKind` closed set on the
46//! `Intent` tagged-union axis (in `crate::intent`): both project a
47//! closed set of variant → wire-form identity slots through named
48//! per-variant methods; `IntentKind::as_str` returns the camelCase
49//! wire key of a serde variant, while `FluxResource::api_version` +
50//! `FluxResource::kind` return the K8s `apiVersion` + `kind` wire
51//! strings of a Flux resource variant. Peer to the sibling
52//! substrate primitives `owner_reference_json` / `api_version()` /
53//! `PROCESS_KIND` in `crate::lib` on the same "K8s wire-form
54//! identity" axis but for the tatara `Process` CRD itself, rather
55//! than for the Flux resources tatara's reconciler emits or
56//! consumes.
57//!
58//! Extension: a fourth Flux resource kind (a `Bucket` for
59//! S3-backed sources, a `Receiver` for webhook-triggered
60//! reconciliation, an `Alert` / `Provider` for notification-
61//! controller integration, an `ImagePolicy` / `ImageRepository`
62//! for image-driven reconciliation, a hypothetical Flux
63//! `GitRepository` variant we don't yet reach for at the wire) lands
64//! as ONE variant + ONE `api_version` arm + ONE `kind` arm + ONE
65//! `ALL` entry, all four exhaustively enforced by rustc's match
66//! coverage. Every downstream Flux-facing consumer inherits the
67//! extension mechanically through the same `FluxResource::X.api_version()`
68//! / `.kind()` dispatch pair.
69//!
70//! Theory grounding: THEORY.md §VI.1 (generation over composition —
71//! the (apiVersion, kind) pairing recurred at five hand-authored sites
72//! past the PRIME-DIRECTIVE ≥ 2 duplication trigger, and is lifted to
73//! ONE closed-set owner per axis here). THEORY.md §II.1 invariant 5
74//! (composition preserves proofs — the two per-axis mappings live at
75//! ONE typed algebra projection apiece; a regression that drifted the
76//! apiVersion at ONE consumer would fail-loudly at this module's byte-
77//! shape pins rather than as silent SSA-fetch-vs-emit skew at the
78//! reconciler wire).
79
80/// Closed set of FluxCD resource kinds this repo's reconciler emits or
81/// consumes at the wire. The three variants partition the current
82/// Flux-facing surface:
83///
84/// * `Kustomization` — `kustomize-controller` reconciles a set of
85/// K8s manifests from a source (`GitRepository` / `OCIRepository`).
86/// Emitted by [`crate::intent::FluxIntent`]; consumed by
87/// `ConditionKind::KustomizationHealthy`.
88/// * `HelmRelease` — `helm-controller` installs / upgrades a Helm
89/// chart from a source. Emitted by [`crate::intent::AplicacaoIntent`];
90/// consumed by `ConditionKind::HelmReleaseReleased`.
91/// * `OCIRepository` — `source-controller` pulls a chart from an OCI
92/// registry. Emitted by [`crate::intent::AplicacaoIntent`] for
93/// `oci://` chart refs, referenced by the sibling HelmRelease's
94/// `chartRef` slot.
95///
96/// Every variant carries a stable `(api_version, kind)` pair through
97/// its typed projections; both projections are `const fn` so the
98/// pair reduces to a `&'static str` slot at every callsite with no
99/// runtime overhead.
100#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
101pub enum FluxResource {
102 Kustomization,
103 HelmRelease,
104 OCIRepository,
105}
106
107impl FluxResource {
108 /// The closed set of Flux resource variants this substrate binds.
109 /// Enumerable so a sweep-test (or a future coherence check) can
110 /// walk every variant without a hand-maintained list on the caller
111 /// side.
112 pub const ALL: [Self; 3] = [Self::Kustomization, Self::HelmRelease, Self::OCIRepository];
113
114 /// K8s wire-form `apiVersion` string for this Flux resource
115 /// variant. Matches the canonical group+version pair the Flux
116 /// controllers expose today:
117 ///
118 /// * `Kustomization` → `kustomize.toolkit.fluxcd.io/v1`
119 /// * `HelmRelease` → `helm.toolkit.fluxcd.io/v2`
120 /// * `OCIRepository` → `source.toolkit.fluxcd.io/v1beta2`
121 ///
122 /// A future Flux upgrade that bumps any group's version lands at
123 /// ONE arm here; every downstream emit or fetch site inherits the
124 /// upgrade mechanically through the same `.api_version()` call.
125 pub const fn api_version(self) -> &'static str {
126 match self {
127 Self::Kustomization => "kustomize.toolkit.fluxcd.io/v1",
128 Self::HelmRelease => "helm.toolkit.fluxcd.io/v2",
129 Self::OCIRepository => "source.toolkit.fluxcd.io/v1beta2",
130 }
131 }
132
133 /// K8s wire-form `kind` string for this Flux resource variant —
134 /// the PascalCase identifier the K8s API server matches against
135 /// the resource's `kind:` slot.
136 ///
137 /// A regression that misspelled any of the three PascalCase
138 /// identifiers would silently mis-route SSA-fetch against SSA-
139 /// apply (a `HelmRelese` emit against a `HelmRelease` fetch is
140 /// a 404 at wire time); post-lift a rename lands at ONE arm
141 /// here rather than at every emit or fetch site.
142 pub const fn kind(self) -> &'static str {
143 match self {
144 Self::Kustomization => "Kustomization",
145 Self::HelmRelease => "HelmRelease",
146 Self::OCIRepository => "OCIRepository",
147 }
148 }
149
150 /// Project this variant onto the typed
151 /// [`crate::k8s_wire_identity::K8sWireIdentity`] pair — the
152 /// substrate primitive that owns the workspace-wide
153 /// `(apiVersion, kind)` pairing every emit or fetch site
154 /// composes against. Pre-lift the reconciler's three
155 /// `render_flux` / `render_aplicacao` emit sites hand-authored
156 /// the pair as two adjacent `json!` slots (`"apiVersion":
157 /// FluxResource::X.api_version(), "kind": FluxResource::X.kind()`)
158 /// mentioning the same variant twice; post-lift the emit site
159 /// names the variant ONCE via
160 /// `.wire_identity().resource_json(json!({...}))` and the pair
161 /// binds structurally at the closed-set owner.
162 ///
163 /// `const fn` so a caller can bind the pair into a `const` slot
164 /// — a future coherence sweep or a compile-time cache reads the
165 /// same pair with zero runtime overhead.
166 pub const fn wire_identity(self) -> crate::k8s_wire_identity::K8sWireIdentity {
167 crate::k8s_wire_identity::K8sWireIdentity::new(self.api_version(), self.kind())
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn all_enumerates_every_variant_exactly_once() {
177 // A future 4th variant added without an `ALL` entry (or a
178 // duplicate entry that skewed the sweep) surfaces here.
179 assert_eq!(FluxResource::ALL.len(), 3);
180 // Every variant appears exactly once — no duplicates, no gaps.
181 let mut seen = std::collections::HashSet::new();
182 for v in FluxResource::ALL {
183 assert!(seen.insert(v), "duplicate variant in ALL: {v:?}");
184 }
185 }
186
187 #[test]
188 fn kustomization_api_version_is_kustomize_toolkit_v1() {
189 // Byte-identity pin: the exact wire-form string every pre-lift
190 // callsite hand-authored (both `boundary::evaluate` and
191 // `render::render_flux`). A drift that renamed the group or
192 // bumped the version at ONE site would silently mis-route
193 // the fetch-vs-apply pairing.
194 assert_eq!(
195 FluxResource::Kustomization.api_version(),
196 "kustomize.toolkit.fluxcd.io/v1"
197 );
198 }
199
200 #[test]
201 fn helm_release_api_version_is_helm_toolkit_v2() {
202 assert_eq!(
203 FluxResource::HelmRelease.api_version(),
204 "helm.toolkit.fluxcd.io/v2"
205 );
206 }
207
208 #[test]
209 fn oci_repository_api_version_is_source_toolkit_v1beta2() {
210 assert_eq!(
211 FluxResource::OCIRepository.api_version(),
212 "source.toolkit.fluxcd.io/v1beta2"
213 );
214 }
215
216 #[test]
217 fn kustomization_kind_is_pascalcase_kustomization() {
218 assert_eq!(FluxResource::Kustomization.kind(), "Kustomization");
219 }
220
221 #[test]
222 fn helm_release_kind_is_pascalcase_helm_release() {
223 assert_eq!(FluxResource::HelmRelease.kind(), "HelmRelease");
224 }
225
226 #[test]
227 fn oci_repository_kind_is_pascalcase_oci_repository() {
228 assert_eq!(FluxResource::OCIRepository.kind(), "OCIRepository");
229 }
230
231 #[test]
232 fn every_variants_api_version_and_kind_are_distinct_across_the_closed_set() {
233 // Cross-variant coherence pin: no two variants may share an
234 // `api_version` or a `kind`. A future extension that added a
235 // variant duplicating an existing wire-form pair (e.g. a
236 // `HelmChart` variant that reused the HelmRelease apiVersion
237 // in a copy-paste) would surface here.
238 let mut api_versions = std::collections::HashSet::new();
239 let mut kinds = std::collections::HashSet::new();
240 for v in FluxResource::ALL {
241 assert!(
242 api_versions.insert(v.api_version()),
243 "duplicate api_version at {v:?}: {}",
244 v.api_version()
245 );
246 assert!(
247 kinds.insert(v.kind()),
248 "duplicate kind at {v:?}: {}",
249 v.kind()
250 );
251 }
252 }
253
254 #[test]
255 fn api_version_and_kind_are_const_fn_reachable() {
256 // Compile-time reachability pin: every variant's projections
257 // are `const fn`, so a caller can bind them into a `const`
258 // slot. A regression that dropped the `const` qualifier
259 // would fail-loudly here rather than as a wrong-slot runtime
260 // dispatch at every callsite.
261 const K_AV: &str = FluxResource::Kustomization.api_version();
262 const K_K: &str = FluxResource::Kustomization.kind();
263 const H_AV: &str = FluxResource::HelmRelease.api_version();
264 const H_K: &str = FluxResource::HelmRelease.kind();
265 const O_AV: &str = FluxResource::OCIRepository.api_version();
266 const O_K: &str = FluxResource::OCIRepository.kind();
267 assert_eq!(K_AV, "kustomize.toolkit.fluxcd.io/v1");
268 assert_eq!(K_K, "Kustomization");
269 assert_eq!(H_AV, "helm.toolkit.fluxcd.io/v2");
270 assert_eq!(H_K, "HelmRelease");
271 assert_eq!(O_AV, "source.toolkit.fluxcd.io/v1beta2");
272 assert_eq!(O_K, "OCIRepository");
273 }
274
275 #[test]
276 fn wire_identity_pairs_api_version_and_kind_per_variant() {
277 // Projection pin: every variant's `wire_identity()` binds the
278 // SAME closed-set variant's `api_version` and `kind` into a
279 // typed pair. A regression that projected the pair from two
280 // different variants (a copy-paste that referenced
281 // `Kustomization.api_version()` alongside `HelmRelease.kind()`
282 // at the projection body) would surface here rather than as
283 // a silent wire-form skew at every reconciler emit site.
284 for v in FluxResource::ALL {
285 let id = v.wire_identity();
286 assert_eq!(id.api_version, v.api_version());
287 assert_eq!(id.kind, v.kind());
288 }
289 }
290
291 #[test]
292 fn wire_identity_is_const_reachable() {
293 // Compile-time reachability pin: `wire_identity` is `const
294 // fn` so a caller can bind the pair into a `const` slot at
295 // compile time. A regression that dropped the `const`
296 // qualifier would fail-loudly here rather than as a runtime
297 // dispatch surfacing at every projection site.
298 const K: crate::k8s_wire_identity::K8sWireIdentity =
299 FluxResource::Kustomization.wire_identity();
300 const H: crate::k8s_wire_identity::K8sWireIdentity =
301 FluxResource::HelmRelease.wire_identity();
302 const O: crate::k8s_wire_identity::K8sWireIdentity =
303 FluxResource::OCIRepository.wire_identity();
304 assert_eq!(K.api_version, "kustomize.toolkit.fluxcd.io/v1");
305 assert_eq!(K.kind, "Kustomization");
306 assert_eq!(H.api_version, "helm.toolkit.fluxcd.io/v2");
307 assert_eq!(H.kind, "HelmRelease");
308 assert_eq!(O.api_version, "source.toolkit.fluxcd.io/v1beta2");
309 assert_eq!(O.kind, "OCIRepository");
310 }
311
312 #[test]
313 fn projections_are_pure_functions_of_the_variant() {
314 // Purity pin: calling the projections repeatedly on the same
315 // variant returns byte-identical `&'static str`s. Guards against
316 // an implementation that lazily materialized an interned key
317 // per-call and hashed against runtime state.
318 for v in FluxResource::ALL {
319 assert_eq!(v.api_version(), v.api_version());
320 assert_eq!(v.kind(), v.kind());
321 }
322 }
323}