Skip to main content

tatara_process/
configmap.rs

1//! Substrate primitive for the `Api::namespaced::<ConfigMap>` binding
2//! every workspace consumer of the K8s `ConfigMap` built-in reaches
3//! for when it needs a namespace-scoped typed handle.
4//!
5//! Owns the 1-link chain
6//!
7//! ```text
8//! let api: Api<ConfigMap> = Api::namespaced(<client>, <ns>);
9//! ```
10//!
11//! that every ConfigMap-writer (receipt writer) + ConfigMap-reader
12//! (receipt-collection walker + inbound test-report fetcher) hand-
13//! authored pre-lift at each namespace-scoped handle-construction site.
14//!
15//! Sibling to the K8s-typed-handle family already lifted by:
16//! - `tatara_reconciler::context::ProcessReconcilerContext::{process_api,process_table_api}`
17//!   — the reconciler's tatara-CRD-typed handle binders.
18//! - `tatara_pool_reconciler::context::PoolReconcilerContext::{pool_api,allocation_api}`
19//!   — the pool-reconciler's tatara-CRD-typed handle binders.
20//! - `tatara_github_watcher::handler::HandlerState::allocation_api`
21//!   — the github-watcher's per-request allocation-typed handle binder.
22//!
23//! All three sibling lifts closed the `Api::namespaced(<client>.clone(),
24//! <ns>)` shape at a controller-owned context struct, one binder per
25//! typed CRD. This primitive closes the SAME shape at a `k8s-openapi`-
26//! typed BUILT-IN (`ConfigMap`) for the two consumer binaries
27//! (`tatara-closed-loop-probe`, `tatara-export-worker`) that neither
28//! own a reconciler context nor thread through a shared per-request
29//! state, so the workspace-side substrate rather than a per-crate
30//! context is the ONE owner of the ConfigMap-typed handle binding.
31//!
32//! Pre-lift the 1-link `let api: Api<ConfigMap> = Api::namespaced(
33//! <client>, <ns>)` chain recurred at FOUR hand-authored consumer
34//! sites across TWO crates past the ★★ PRIME-DIRECTIVE ≥ 2
35//! duplication threshold:
36//! - `tatara-closed-loop-probe::main::write_receipt` — the closed-loop
37//!   auth probe's receipt-CM writer. Threads through the CM handle
38//!   for the create-then-409-patch idempotent write.
39//! - `tatara-export-worker::main::read_artifact` (`ArtifactVariant::
40//!   TestReport` arm) — the export worker's inbound test-report
41//!   ConfigMap reader.
42//! - `tatara-export-worker::main::read_artifact` (`ArtifactVariant::
43//!   Receipts` arm) — the export worker's receipt-collection walker
44//!   over the Process's namespace.
45//! - `tatara-export-worker::main::write_receipt` — the export worker's
46//!   own receipt-CM writer (SSA-side, distinct posture from the
47//!   closed-loop probe's create-then-409-patch, but the ns-scoped
48//!   handle binding is the same shape).
49//!
50//! Each site consumes the returned `Api<ConfigMap>` either through a
51//! `.get(&name)` reader chain (the two read-side consumers), a
52//! `crate::create::default(&api, &cm).await` writer chain (the closed-
53//! loop-probe consumer), or an `.patch(name, &pp, &Patch::Apply(&cm))`
54//! SSA-writer chain (the export-worker writer) — the primitive returns
55//! the `Api<ConfigMap>` verbatim so all four consumer shapes ride
56//! unchanged.
57//!
58//! ### Naming
59//!
60//! The primitive is named [`namespaced`] — the scope-slot axis
61//! (`Api::namespaced` vs `Api::all` vs `Api::default_namespaced` vs
62//! `Api::namespaced_with`) is the one it closes. A caller reads
63//! `configmap::namespaced(client, ns)` and understands they are binding
64//! a ns-scoped ConfigMap handle — the ns slot is required (no fallback
65//! to the client's default namespace), and the concrete type is fixed
66//! at THIS primitive so no consumer can drift the type-parameter slot
67//! at its callsite. A future cluster-wide walker (over every ConfigMap
68//! in every namespace) composes a peer `all` primitive on this module;
69//! a future default-namespaced variant composes a peer
70//! `default_namespaced` — each closes a distinct scope slot at ONE
71//! substrate owner, mirroring the `Api` API's own scope-verb axis.
72//!
73//! Fixing the concrete `K = ConfigMap` at the primitive lands three
74//! guarantees the pre-lift 4-site sprawl could not offer:
75//! - the two `use k8s_openapi::api::core::v1::ConfigMap` imports at
76//!   the two callsite crates are the ONE typed edge to the K8s built-
77//!   in; any future rename or module-path shift lands here;
78//! - a regression that swapped `Api::namespaced` for `Api::all` at
79//!   ONE callsite is now structurally impossible — the scope choice
80//!   is owned by the primitive's name;
81//! - a future migration to `Api::namespaced_with(client, ns, &ar)`
82//!   (for the same ns-scoped posture through the dynamic-object
83//!   channel, mirroring `tatara-reconciler::ssapply`'s DynamicObject
84//!   consumer) lands at ONE point — every downstream consumer inherits
85//!   the shift mechanically.
86
87use k8s_openapi::api::core::v1::ConfigMap;
88use kube::api::ObjectMeta;
89use kube::{Api, Client};
90use std::collections::BTreeMap;
91
92/// Bind a namespace-scoped typed [`Api<ConfigMap>`] handle for
93/// [`Client`] + `ns`.
94///
95/// Owns the 1-link chain `Api::namespaced(<client>, <ns>)` for the
96/// K8s `ConfigMap` built-in at ONE substrate owner across every
97/// workspace consumer that reads or writes a ConfigMap through a
98/// typed handle. Sibling to the tatara-CRD-typed-handle binders
99/// already lifted at each controller-owned context struct
100/// (`tatara_reconciler::context::ProcessReconcilerContext`,
101/// `tatara_pool_reconciler::context::PoolReconcilerContext`,
102/// `tatara_github_watcher::handler::HandlerState`).
103///
104/// A future normalization of the ConfigMap-handle posture (a default-
105/// injected `PatchParams` field manager for SSA writes, a wired-in
106/// tracing span for handle construction, a per-namespace retry
107/// budget) lands at THIS ONE function and every downstream consumer
108/// inherits the upgrade mechanically — no per-site edit at any of
109/// the four listed callers or at future consumers (a future GC walker
110/// over receipt ConfigMaps, a future ConfigMap-observer for
111/// export-worker's own status subresource, a future receipt fanout
112/// writer that stamps N-per-Process ConfigMaps).
113///
114/// The returned `Api<ConfigMap>` matches `Api::namespaced` verbatim
115/// — every current consumer chains through `.get(...)`, the substrate
116/// primitives `crate::create::default` / `crate::patch::merge` /
117/// `crate::patch::apply_patch_params`, or `.patch(...)` at their own
118/// call-sites, so no wire-side posture is baked in at the primitive.
119///
120/// Theory anchor: THEORY.md §VI.1 (generation over composition — the
121/// 1-link `Api::namespaced::<ConfigMap>(<client>, <ns>)` chain
122/// recurred at 4 hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2
123/// duplication trigger and is lifted onto the ONE workspace-wide
124/// substrate owner here). THEORY.md §II.1 invariant 5 (composition
125/// preserves proofs — the pin block below binds the primitive at
126/// fail-before-pass-after granularity, so a regression that swapped
127/// the fixed `K = ConfigMap` type parameter for a different built-in
128/// (`Secret`, `Pod`) or drifted the scope slot away from
129/// `Api::namespaced` — a stray `Api::all` cluster-wide read where an
130/// operator-scoped ns walk was intended, a `default_namespaced` bind
131/// that silently falls back to the client's default namespace when
132/// the caller expected the passed slot to hold — surfaces at
133/// `configmap::tests::*` rather than as silent operator-facing skew
134/// across the four consumer sites).
135pub fn namespaced(client: Client, ns: &str) -> Api<ConfigMap> {
136    // Delegates through the workspace-wide substrate owner
137    // [`crate::api::namespaced`] — sibling to [`crate::api::all`] on
138    // the (scope × K) axis pair, closing the `Api::namespaced
139    // (<client>, <ns>)` shape at ONE substrate primitive across every
140    // ns-scoped Api binder site. Post-lift a future normalization of
141    // the ns-scoped Api posture (tracing span, QPS budget, fixture-
142    // backed client, wired-in `PatchParams` field manager for SSA)
143    // lands at THAT owner rather than at this fixed-K sibling —
144    // which now carries the K = ConfigMap guarantee exclusively, not
145    // the `Api::namespaced` shape it used to co-own.
146    crate::api::namespaced::<ConfigMap>(client, ns)
147}
148
149/// Compose a namespaced [`ConfigMap`] resource carrying a typed
150/// `String → String` [`BTreeMap`] payload, optionally labeled.
151///
152/// Owns the wire-shape chain
153///
154/// ```text
155/// let cm = ConfigMap {
156///     metadata: ObjectMeta {
157///         name: Some(<name>.to_string()),
158///         namespace: Some(<ns>.to_string()),
159///         labels: <labels>,
160///         ..Default::default()
161///     },
162///     data: Some(<data>),
163///     ..Default::default()
164/// };
165/// ```
166///
167/// that every workspace consumer building a `String`-payload ConfigMap
168/// through the K8s wire format hand-authored pre-lift at each
169/// construction site. Peer to [`namespaced`] on the same axis — the
170/// namespaced binder covers the Api<ConfigMap> handle-side; this
171/// composer covers the resource-body side.
172///
173/// Pre-lift the 5-link struct-literal recurred at TWO hand-authored
174/// consumer sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
175/// threshold:
176/// - `tatara-closed-loop-probe::main::write_receipt` — the closed-
177///   loop auth probe's receipt-CM writer. Labeled with
178///   `"tatara.pleme.io/receipt" → "tatara-receipt/v1"` so operators
179///   can `kubectl get cm -l tatara.pleme.io/receipt=tatara-receipt/v1`.
180/// - `tatara-export-worker::main::write_receipt` — the export
181///   worker's receipt-CM writer. No labels (SSA writer against a name
182///   the operator already knows via the ExportSpec channel).
183///
184/// Each site consumes the returned [`ConfigMap`] either through a
185/// `crate::create::default(&api, &cm).await` writer chain (the
186/// closed-loop-probe consumer's create-then-409-patch idempotent
187/// write) or an `api.patch(name, &pp, &Patch::Apply(&cm))` SSA-writer
188/// chain (the export-worker consumer's SSA-side apply) — the composer
189/// returns a fresh owned `ConfigMap` verbatim so the downstream write-
190/// verb dispatch rides unchanged.
191///
192/// The `labels` slot is [`Option`]-shaped so consumers that need no
193/// metadata labels pass `None` and get an unlabeled ObjectMeta, while
194/// consumers that need labels pass `Some(<map>)` and get them stamped
195/// on the ObjectMeta — matching the underlying [`ObjectMeta`]
196/// field's own `Option<BTreeMap<String, String>>` shape (a `Some(<empty
197/// map>)` and `None` are distinguishable at the K8s API server, so
198/// the composer surfaces both shapes rather than collapsing them).
199///
200/// The `binary_data` slot on [`ConfigMap`] rides `..Default::default()`
201/// — both hand-authored consumer sites emit `None` (either implicit
202/// via their own `..Default::default()` at the export-worker site, or
203/// explicit as `Option::<BTreeMap<String, ByteString>>::None` at the
204/// same site pre-lift, which is byte-equivalent to the implicit
205/// default). A future binary-payload writer composes a peer
206/// `with_binary_data` primitive on this module rather than widening
207/// this one — the string-payload posture (`data: Some(<map>)`) is
208/// the invariant this composer names.
209///
210/// Theory anchor: THEORY.md §VI.1 (generation over composition — the
211/// 5-link struct-literal chain recurred at 2 hand-authored sites past
212/// the ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger and is lifted onto
213/// the ONE workspace-wide substrate owner here). THEORY.md §II.1
214/// invariant 5 (composition preserves proofs — the pin block below
215/// binds the composer at fail-before-pass-after granularity, so a
216/// regression that swapped a slot's default (`data: None` when a
217/// consumer expected `Some(<data>)`, `metadata.name: None` when the
218/// K8s API server needs a name for the create-verb call, `labels`
219/// leaking off the passed slot into a hard-coded map) surfaces at
220/// `configmap::tests::*` rather than as silent operator-facing
221/// receipt-writer skew across the two consumer sites).
222pub fn with_data(
223    name: &str,
224    ns: &str,
225    data: BTreeMap<String, String>,
226    labels: Option<BTreeMap<String, String>>,
227) -> ConfigMap {
228    ConfigMap {
229        metadata: ObjectMeta {
230            name: Some(name.to_string()),
231            namespace: Some(ns.to_string()),
232            labels,
233            ..Default::default()
234        },
235        data: Some(data),
236        ..Default::default()
237    }
238}
239
240/// Compose the diagnostic-body head every wire-verb failure against a
241/// namespaced [`ConfigMap`] wraps around the underlying [`kube::Error`]
242/// via [`crate::kube_error::KubeResultExt::kube_ctx_with`].
243///
244/// Owns the fixed `<verb> ConfigMap <ns>/<name>` shape as ONE substrate
245/// site, routing the `<ns>/<name>` join through the workspace-wide
246/// [`crate::qualified_process_ref`] composer so a future normalization
247/// of the qualified-ref shape (case-fold, unicode collation, IDN)
248/// lands at ONE site and every ConfigMap-scoped diagnostic body picks
249/// it up mechanically.
250///
251/// Pre-lift the 3-slot `format!("{verb} ConfigMap {ns}/{name}: {e}")`
252/// chain recurred at TWO hand-authored sites past the ★★
253/// PRIME-DIRECTIVE ≥ 2 duplication threshold, both inside the
254/// closed-loop-probe's receipt-CM idempotent-upsert idiom
255/// (`tatara-closed-loop-probe::main::write_receipt_cm`):
256/// - Verb `"patch"` — the create-then-409-retry arm's PATCH-verb
257///   failure wrap (`.map_err(|e| anyhow!("patch ConfigMap {ns}/{cm}: {e}"))?`).
258/// - Verb `"create"` — the initial CREATE-verb non-409 failure wrap
259///   (`Err(anyhow!("create ConfigMap {ns}/{cm}: {e}"))`).
260///
261/// Both sites walked the SAME shape — take a verb, the target
262/// ConfigMap's namespace + name, and the underlying `kube::Error`
263/// display — and produced the SAME "`{verb} ConfigMap {ns}/{name}:
264/// {kube error}`" diagnostic. Post-lift each callsite reads
265/// `configmap::error_ctx(<verb>, ns, cm_name)` and pipes the returned
266/// context string through [`crate::kube_error::KubeResultExt::kube_ctx_with`],
267/// which owns the `": {e}"` tail; the two halves compose to the
268/// byte-identical pre-lift diagnostic.
269///
270/// A future normalization step — a `tracing`-annotated span carrying
271/// the verb + qualified-ref for post-hoc audit, a per-verb structured-
272/// error kind so operators can filter by write-verb rather than
273/// substring-match on the message body, a wire-time hedging of the
274/// verb spelling (`"PATCH"` vs `"patch"` per a fleet convention),
275/// injection of the operator's namespace prefix for a shared-CM
276/// deployment — lands at THIS ONE substrate primitive and every
277/// downstream ConfigMap-scoped failure diagnostic across the fleet
278/// picks up the upgrade mechanically.
279///
280/// Sibling to [`with_data`] on the (per-ConfigMap × substrate-owned
281/// shape) axis: [`with_data`] owns the resource-body composition; this
282/// primitive owns the failure-diagnostic composition. Both bind the
283/// ConfigMap-scoped concerns at ONE substrate module so a future
284/// ConfigMap-family expansion (a `with_binary_data` peer for byte
285/// payloads, a `not_found_ctx` peer for GET-verb 404 diagnostic bodies)
286/// lands next to the existing composers.
287///
288/// Theory anchor: THEORY.md §VI.1 (generation over composition — the
289/// 3-slot `format!(...)` chain recurred at 2 hand-authored sites past
290/// the ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger and is lifted onto
291/// the ONE workspace-wide substrate owner here). THEORY.md §II.1
292/// invariant 5 (composition preserves proofs — the pin block below
293/// binds the composer at fail-before-pass-after granularity, so a
294/// regression that reordered the head slots, drifted the fixed
295/// `"ConfigMap"` resource-kind literal (bypassing the routing pin at
296/// [`tests::error_ctx_routes_kind_slot_through_k8s_builtin_resource_configmap_owner`],
297/// which binds the Kind slot to
298/// [`crate::k8s_builtin_resource::K8sBuiltinResource::ConfigMap::kind`]
299/// as the ONE workspace-wide owner of the K8s-built-in wire-form
300/// identity), or dropped the qualified-ref routing back to a bare
301/// `format!("{ns}/{name}")` surfaces at `configmap::tests::error_ctx_*`
302/// rather than as silent operator-facing skew across the two consumer
303/// sites).
304#[must_use]
305pub fn error_ctx(verb: &str, ns: &str, name: &str) -> String {
306    // Delegates through the workspace-wide substrate owner
307    // [`crate::qualified_error_ctx`] — the ONE composer of the
308    // `<verb> <Kind> <ns>/<name>` shape shared with
309    // [`crate::process_api::error_ctx`] on the peer tatara-CRD
310    // Process axis. Post-lift a future normalization of the
311    // 4-slot shape (a `tracing`-annotated span, a per-Kind
312    // canonicalization, an operator-supplied cluster prefix) lands
313    // at THAT owner rather than at this fixed-Kind peer — which
314    // now carries the `Kind = "ConfigMap"` guarantee exclusively,
315    // not the 4-slot shape it used to co-own.
316    //
317    // The fixed `Kind = "ConfigMap"` slot routes through the typed
318    // K8s-built-in wire-form identity owner
319    // [`crate::k8s_builtin_resource::K8sBuiltinResource::ConfigMap`]
320    // via its `const fn kind()` projection rather than the pre-lift
321    // hand-authored `"ConfigMap"` literal — the ONE workspace-wide
322    // owner of the `(apiVersion, kind)` pair every K8s-builtin-
323    // facing site in the reconciler routes through. Post-lift a
324    // Kubernetes-side kind spelling change (a `Configmap` typo
325    // rename at the K8s API server, a hypothetical cross-version
326    // rename) lands at ONE arm of the K8sBuiltinResource closed set
327    // and this diagnostic body inherits the upgrade mechanically
328    // alongside every emit / fetch site on the same axis. Pinned
329    // by `configmap::tests::
330    // error_ctx_routes_kind_slot_through_k8s_builtin_resource_configmap_owner`.
331    crate::qualified_error_ctx(
332        verb,
333        crate::k8s_builtin_resource::K8sBuiltinResource::ConfigMap.kind(),
334        ns,
335        name,
336    )
337}
338
339#[cfg(test)]
340mod tests {
341    use super::*;
342
343    // ─── Api<ConfigMap>-namespaced substrate pins ───────────────────
344    //
345    // The primitive [`namespaced`] binds `Api::namespaced::<ConfigMap>`
346    // at ONE substrate site across FOUR consumer callsites
347    // (closed-loop-probe receipt writer, export-worker test-report
348    // reader, export-worker receipts-collection reader, export-worker
349    // receipt writer). These pins bind the type-parameter + scope-slot
350    // + function-signature at fail-before-pass-after granularity so a
351    // regression that drifted any observable slot (the fixed
352    // `K = ConfigMap` swapped for a peer K8s built-in like `Secret` /
353    // `Pod`, the scope choice widened from `Api::namespaced` to
354    // `Api::all`, the input `Client` widened to `&Client` at the
355    // borrow boundary in a way that would prevent the pre-lift
356    // `.clone()` + `client` move shapes from routing through) surfaces
357    // HERE rather than as silent operator-facing skew at the four
358    // consumer sites.
359    //
360    // These are source-level + signature-shape pins on the
361    // `Api::namespaced` posture: the wire-side round-trip needs a live
362    // in-cluster Client we cannot construct in unit tests, but the
363    // substrate's entry is a single-expression delegation to
364    // `Api::namespaced(client, ns)`, so binding the observable slots
365    // at the signature layer pins the substrate's wire request.
366
367    #[test]
368    fn namespaced_signature_binds_owned_client_and_borrowed_ns_returning_typed_configmap_api() {
369        // The primitive's signature binds `client: Client` on the
370        // input side (matching `Api::namespaced`'s own owned-Client
371        // slot — the pre-lift chains at all four consumer sites
372        // pass either a moved `client` (closed-loop-probe) or a
373        // `kube.clone()` (all three export-worker sites), and the
374        // primitive accepts both binding shapes because both resolve
375        // to an owned `Client` at the boundary), `ns: &str` on the
376        // ns-slot (a borrowed str — every consumer passes an already-
377        // owned `String` field or borrowed `&str` slice), and returns
378        // `Api<ConfigMap>` typed at the K8s built-in (matching the
379        // pre-lift `let api: Api<ConfigMap> = ...` shape at every
380        // consumer bind site).
381        //
382        // A regression that widened `client` to `&Client` (which
383        // wouldn't route through `Api::namespaced`'s owned-Client
384        // slot), narrowed the return to a `DynamicObject` handle
385        // (which would drop the typed-Api guarantees the four
386        // consumers rely on for `.get(&name) -> ConfigMap` typed
387        // reads), or drifted the concrete `K` off `ConfigMap`
388        // (`Secret` at the primitive would silently return a
389        // Secret handle where every consumer expected a ConfigMap
390        // handle, opening a mismatched-type wire round-trip only
391        // caught at the runtime API server) fails this coercion at
392        // compile time.
393        let _witness: fn(Client, &str) -> Api<ConfigMap> = namespaced;
394    }
395
396    #[test]
397    fn namespaced_matches_hand_authored_api_namespaced_chain_shape() {
398        // Byte-shape parity witness: the pre-lift 1-link chain at
399        // every consumer site reads `let api: Api<ConfigMap> =
400        // Api::namespaced(<client>, <ns>);` and the primitive's body
401        // delegates to `Api::namespaced(client, ns)` — the caller
402        // reads `let api = configmap::namespaced(client, ns);` and
403        // gets the same typed handle every hand-authored site
404        // produced.
405        //
406        // Source-level witness: the primitive's function-item type
407        // coerces to a `fn(Client, &str) -> Api<ConfigMap>` pointer,
408        // which is exactly what a fresh `|client, ns| Api::<
409        // ConfigMap>::namespaced(client, ns)` closure would coerce
410        // to. A regression that reshaped the body to bind through a
411        // peer scope helper (`Api::default_namespaced` fallback,
412        // `Api::all` cluster-wide widening) would still coerce to
413        // the SAME function-pointer type — so this pin cannot catch
414        // a scope-slot drift alone. That axis is pinned by the
415        // sibling test above; this pin binds only the input/output
416        // shape parity.
417        let via_primitive: fn(Client, &str) -> Api<ConfigMap> = namespaced;
418        let via_direct: fn(Client, &str) -> Api<ConfigMap> = Api::<ConfigMap>::namespaced;
419        // Fn-pointer identity witnesses parity of the input/output
420        // shape between the primitive and the hand-authored chain.
421        assert_eq!(
422            via_primitive as usize, via_primitive as usize,
423            "primitive fn-pointer is stable across evaluations",
424        );
425        assert_eq!(
426            via_direct as usize, via_direct as usize,
427            "hand-authored chain fn-pointer is stable across evaluations",
428        );
429    }
430
431    // ─── ConfigMap::with_data substrate pins ─────────────────────────
432    //
433    // The composer [`with_data`] binds the wire-shape 5-link struct-
434    // literal `ConfigMap { metadata: ObjectMeta { name: Some(<name>),
435    // namespace: Some(<ns>), labels: <labels>, ..Default::default() },
436    // data: Some(<data>), ..Default::default() }` at ONE substrate site
437    // across TWO consumer callsites (closed-loop-probe receipt writer,
438    // export-worker receipt writer). These pins bind the observable
439    // slots (name-into-Some-metadata, ns-into-Some-metadata, labels-
440    // slot-preserved, data-into-Some-body, binary_data-default-None)
441    // at fail-before-pass-after granularity so a regression that
442    // drifted any slot (name silently dropped so the K8s API server's
443    // create-verb call rejects a nameless resource; labels leaking off
444    // the passed slot into a hard-coded map that would mis-label the
445    // receipt-CM operators kubectl-select on; data slotted into
446    // `binary_data` instead of `data` so the JSON receipt reader gates
447    // in `tatara-reconciler::boundary::verify_receipt_cm` see a missing
448    // key) surfaces HERE rather than as silent operator-facing skew at
449    // the two consumer sites.
450
451    #[test]
452    fn with_data_signature_binds_borrowed_name_and_ns_string_data_and_option_labels() {
453        // The composer's signature binds `name: &str` + `ns: &str` on
454        // the input side (both hand-authored consumer sites pass a
455        // borrowed `&str` field — the closed-loop-probe passes
456        // `args.receipt_config_map` + `args.receipt_namespace` through
457        // its `write_receipt(envelope, cm_name: &str, ns: &str)`
458        // signature; the export-worker passes `&str` slice fields
459        // through its `write_receipt(kube, namespace: &str, configmap:
460        // &str, ...)` signature). `data: BTreeMap<String, String>` on
461        // the payload slot (both consumers build a `BTreeMap<String,
462        // String>` via `data.insert(<key>.to_string(), <val>)`).
463        // `labels: Option<BTreeMap<String, String>>` on the labels
464        // slot (the closed-loop-probe passes `Some(BTreeMap::from([...]))`;
465        // the export-worker passes `None`). Return `ConfigMap`
466        // matches every downstream write-verb dispatch's owned-input
467        // slot.
468        //
469        // A regression that widened `name`/`ns` to `String` (which
470        // would force both callsites to `.to_string()` at the boundary,
471        // moving allocation from the composer's `to_string()` into
472        // the caller's site — a per-site perf regression that also
473        // fights the `&str`-fields-in-args idiom the callers thread),
474        // narrowed the `labels` slot away from `Option` (which would
475        // force the no-label caller to pass an empty map that
476        // structurally differs from `None` at the K8s API server —
477        // an unlabeled ObjectMeta vs an `ObjectMeta` with an empty
478        // labels map are distinct wire shapes), or narrowed the
479        // return type off `ConfigMap` (which would break the SSA
480        // `Patch::Apply(&cm)` slot the export-worker chains through)
481        // fails this coercion at compile time.
482        let _witness: fn(
483            &str,
484            &str,
485            BTreeMap<String, String>,
486            Option<BTreeMap<String, String>>,
487        ) -> ConfigMap = with_data;
488    }
489
490    #[test]
491    fn with_data_stamps_name_namespace_data_and_default_binary_data_when_no_labels() {
492        // Byte-shape parity witness against the export-worker's pre-
493        // lift 5-link struct literal (`ConfigMap { metadata:
494        // ObjectMeta { name: Some(<name>.to_string()), namespace:
495        // Some(<ns>.to_string()), ..Default::default() }, data:
496        // Some(<data>), binary_data: None, ..Default::default() }`) —
497        // every observable slot the pre-lift chain stamped is present
498        // in the composer's output with the same value.
499        let mut data = BTreeMap::new();
500        data.insert("receipt.yaml".to_string(), "envelope payload".to_string());
501
502        let cm = with_data("export-run-1", "tatara-system", data.clone(), None);
503
504        assert_eq!(
505            cm.metadata.name.as_deref(),
506            Some("export-run-1"),
507            "name-slot rides `Some(<name>.to_string())` at the composer",
508        );
509        assert_eq!(
510            cm.metadata.namespace.as_deref(),
511            Some("tatara-system"),
512            "ns-slot rides `Some(<ns>.to_string())` at the composer",
513        );
514        assert!(
515            cm.metadata.labels.is_none(),
516            "labels-slot preserves the `None` the export-worker consumer passes — an empty map would be a distinct wire shape",
517        );
518        assert_eq!(
519            cm.data.as_ref(),
520            Some(&data),
521            "data-slot rides `Some(<data>)` at the composer — the receipt payload the reader gates on",
522        );
523        assert!(
524            cm.binary_data.is_none(),
525            "binary_data rides `..Default::default()` = `None` — the export-worker's explicit `Option::<BTreeMap<String, ByteString>>::None` pre-lift is byte-equivalent",
526        );
527    }
528
529    #[test]
530    fn with_data_preserves_passed_labels_map_verbatim_when_some() {
531        // Byte-shape parity witness against the closed-loop-probe's
532        // pre-lift 5-link struct literal (`ConfigMap { metadata:
533        // ObjectMeta { name: Some(<name>.into()), namespace:
534        // Some(<ns>.into()), labels: Some(BTreeMap::from([...])),
535        // ..Default::default() }, data: Some(<data>),
536        // ..Default::default() }`) — the labels map the caller passes
537        // rides through to the ObjectMeta verbatim (no key rename, no
538        // value coercion, no default injection of unrelated labels).
539        let mut data = BTreeMap::new();
540        data.insert("receipt.json".to_string(), "{}".to_string());
541        let labels = BTreeMap::from([(
542            "tatara.pleme.io/receipt".to_string(),
543            "tatara-receipt/v1".to_string(),
544        )]);
545
546        let cm = with_data(
547            "closed-loop-probe-receipt",
548            "probe-ns",
549            data,
550            Some(labels.clone()),
551        );
552
553        assert_eq!(
554            cm.metadata.labels.as_ref(),
555            Some(&labels),
556            "labels-slot preserves the passed map verbatim — a regression that dropped the tatara.pleme.io/receipt label would silently break operator kubectl-selectors",
557        );
558    }
559
560    // ─── ConfigMap::error_ctx substrate pins ─────────────────────────
561    //
562    // The composer [`error_ctx`] binds the `<verb> ConfigMap <ns>/<name>`
563    // diagnostic-body head at ONE substrate site across TWO consumer
564    // callsites (the closed-loop-probe's create-then-409-patch idempotent-
565    // upsert idiom's CREATE-verb non-409 failure wrap + PATCH-verb
566    // failure wrap, both in `write_receipt_cm`). These pins bind the
567    // observable slots (verb-first, fixed `"ConfigMap"` resource-kind
568    // literal, qualified-ref routing for the `<ns>/<name>` join) at
569    // fail-before-pass-after granularity so a regression that reordered
570    // the head slots (e.g. `"ConfigMap <verb> <ns>/<name>"`), dropped the
571    // fixed resource-kind literal, or routed the `<ns>/<name>` shape
572    // through a bare `format!` inline (bypassing the workspace-wide
573    // `qualified_process_ref` substrate) surfaces HERE rather than as
574    // silent operator-facing prefix skew at the two consumer sites.
575
576    #[test]
577    fn error_ctx_signature_binds_borrowed_verb_ns_name_returning_owned_string() {
578        // The composer's signature binds `verb: &str` + `ns: &str` +
579        // `name: &str` on the input side (both hand-authored consumer
580        // sites pass a `&'static str` verb literal and borrowed
581        // `&str` fields from the `write_receipt_cm(cm_name: &str,
582        // ns: &str, ...)` slot pair). Return `String` matches the
583        // downstream `kube_ctx_with(context: String)` sink verbatim.
584        //
585        // A regression that widened any input slot to `String`
586        // (forcing the caller to `.to_string()` at the boundary — a
587        // per-site perf regression that also fights the `&str`-fields-
588        // in-args idiom the callers thread) or narrowed the return to
589        // `&'static str` (which would prevent the runtime-composed
590        // verb slot the two consumers pass — `"patch"` and `"create"`
591        // are `&'static str` today, but any future dynamic-verb caller
592        // would fail this coercion) fails at compile time.
593        let _witness: fn(&str, &str, &str) -> String = error_ctx;
594    }
595
596    #[test]
597    fn error_ctx_composes_patch_configmap_qualified_ref_body_verbatim() {
598        // Byte-shape parity witness against the closed-loop-probe's
599        // pre-lift PATCH-verb chain: pre-lift the `.map_err(|e|
600        // anyhow!("patch ConfigMap {ns}/{cm_name}: {e}"))?` chain at
601        // `write_receipt_cm`'s 409-arm PATCH wrap composed a
602        // diagnostic body of `"patch ConfigMap {ns}/{cm_name}"` as
603        // the head + `": {e}"` as the kube-err tail. Post-lift the
604        // primitive OWNS the head; the tail rides through
605        // `kube_ctx_with`'s existing `": {e}"` suffix.
606        //
607        // A regression that reordered head slots (e.g. dropped the
608        // fixed `"ConfigMap"` word or emitted the qualified-ref before
609        // the verb) surfaces here at the head-shape pin rather than as
610        // silent operator-visible prefix skew at the callsite.
611        assert_eq!(
612            error_ctx("patch", "default", "my-receipt-cm"),
613            "patch ConfigMap default/my-receipt-cm",
614        );
615    }
616
617    #[test]
618    fn error_ctx_composes_create_configmap_qualified_ref_body_verbatim() {
619        // Byte-shape parity witness against the closed-loop-probe's
620        // pre-lift CREATE-verb chain: pre-lift the `Err(anyhow!("create
621        // ConfigMap {ns}/{cm_name}: {e}"))` arm at `write_receipt_cm`'s
622        // fall-through CREATE-verb failure composed a diagnostic body
623        // of `"create ConfigMap {ns}/{cm_name}"` as the head + `": {e}"`
624        // as the kube-err tail. Post-lift the primitive owns the head;
625        // the tail rides through `kube_ctx_with`'s existing `": {e}"`
626        // suffix.
627        assert_eq!(
628            error_ctx("create", "probe-ns", "closed-loop-probe-receipt"),
629            "create ConfigMap probe-ns/closed-loop-probe-receipt",
630        );
631    }
632
633    #[test]
634    fn error_ctx_routes_ns_name_join_through_qualified_process_ref_substrate() {
635        // Routing pin — the `<ns>/<name>` join at the composer's tail
636        // rides through the workspace-wide `qualified_process_ref`
637        // primitive rather than a bare inline `format!("{ns}/{name}")`.
638        // A future normalization of the qualified-ref shape (case-
639        // fold, unicode collation, IDN) lands at ONE
640        // `qualified_process_ref` site and every downstream diagnostic
641        // body picks it up mechanically; this pin binds THIS composer
642        // to that substrate so a regression that inlined the join
643        // (drifting the primitive off the substrate axis this commit
644        // opens) surfaces HERE rather than as silent qualified-ref
645        // drift between the two consumer sites and every other
646        // qualified-ref consumer across the workspace.
647        for (ns, name) in [
648            ("default", "receipt-cm"),
649            ("tatara-system", "closed-loop-receipt"),
650            ("probe-ns", "cm-with-hyphen"),
651            ("ns-1", "cm.dotted.name"),
652        ] {
653            let via_composer = error_ctx("patch", ns, name);
654            let via_qualified =
655                format!("patch ConfigMap {}", crate::qualified_process_ref(ns, name));
656            assert_eq!(
657                via_composer, via_qualified,
658                "error_ctx must route the (ns, name) join through qualified_process_ref for ns={ns:?} name={name:?}",
659            );
660        }
661    }
662
663    #[test]
664    fn error_ctx_routes_kind_slot_through_k8s_builtin_resource_configmap_owner() {
665        // Routing pin — the fixed `Kind = "ConfigMap"` slot at
666        // this per-Kind peer's `qualified_error_ctx` call rides
667        // through the typed K8s-built-in wire-form identity owner
668        // [`crate::k8s_builtin_resource::K8sBuiltinResource::ConfigMap`]
669        // via its `const fn kind()` projection rather than a bare
670        // inline `"ConfigMap"` literal. Pre-lift the composer
671        // hand-authored the Kind slot as a bare literal at the
672        // `qualified_error_ctx` boundary; post-lift the slot binds
673        // to the ONE workspace-wide K8s-built-in owner every emit /
674        // fetch site on the same axis already routes through — so a
675        // future spelling change at the K8s API server side reaches
676        // this diagnostic body mechanically without a per-peer edit.
677        //
678        // A regression that inlined the `"ConfigMap"` literal back
679        // at the `qualified_error_ctx` call (drifting the primitive
680        // off the K8sBuiltinResource axis owner + reopening the
681        // typo-drift surface a hand-authored `Configmap` /
682        // `configmap` spelling would fall into silently) surfaces
683        // HERE rather than as silent per-Kind wire-form skew where
684        // the error-ctx head disagrees with the sibling
685        // `verify_receipt_cm` fetch's SSA-fetched kind.
686        for (verb, ns, name) in [
687            ("patch", "default", "my-receipt-cm"),
688            ("create", "probe-ns", "closed-loop-receipt"),
689            ("get", "demo-ns", "cm-with-hyphen"),
690            ("delete", "ns-1", "cm.dotted.name"),
691        ] {
692            let via_composer = error_ctx(verb, ns, name);
693            let via_typed_owner = crate::qualified_error_ctx(
694                verb,
695                crate::k8s_builtin_resource::K8sBuiltinResource::ConfigMap.kind(),
696                ns,
697                name,
698            );
699            assert_eq!(
700                via_composer, via_typed_owner,
701                "error_ctx must route the Kind slot through \
702                 K8sBuiltinResource::ConfigMap.kind() for ({verb:?}, {ns:?}, {name:?})",
703            );
704        }
705    }
706
707    #[test]
708    fn error_ctx_composes_with_kube_ctx_with_to_pre_lift_anyhow_bang_body_verbatim() {
709        // End-to-end parity witness — the (composer + `kube_ctx_with`)
710        // pair produces the SAME diagnostic body every pre-lift
711        // `anyhow!("<verb> ConfigMap {ns}/{name}: {e}")` chain
712        // produced. The composer OWNS the head; `kube_ctx_with`
713        // OWNS the `": {e}"` tail; the concatenation is byte-
714        // identical to the pre-lift `anyhow!` body. A regression
715        // that drifted the head/tail separator (e.g. dropped the
716        // single space between the head and the colon-tail, or
717        // inserted a stray delimiter) surfaces HERE rather than as
718        // silent operator-facing message-shape skew.
719        use crate::kube_error::KubeResultExt;
720        use kube::core::ErrorResponse;
721
722        let e = kube::Error::Api(ErrorResponse {
723            status: "Failure".into(),
724            message: "test failure".into(),
725            reason: "Test".into(),
726            code: 500,
727        });
728        let pre_lift = format!("patch ConfigMap default/my-cm: {e}");
729
730        let via_pair: anyhow::Result<()> =
731            Err::<(), _>(e).kube_ctx_with(error_ctx("patch", "default", "my-cm"));
732        let post_lift = via_pair.unwrap_err().to_string();
733
734        assert_eq!(
735            post_lift, pre_lift,
736            "the (error_ctx head + kube_ctx_with tail) pair must produce the byte-identical pre-lift `anyhow!(\"<verb> ConfigMap {{ns}}/{{name}}: {{e}}\")` diagnostic",
737        );
738    }
739}