Skip to main content

tatara_process/
patch.rs

1//! Substrate primitive for the merge-patch idiom over the `/status`
2//! subresource of any kube [`Resource`].
3//!
4//! Owns the 2-link chain
5//!
6//! ```text
7//! let body = json!({ "status": <typed> });
8//! api.patch_status(name, &PatchParams::default(), &Patch::Merge(&body)).await
9//! ```
10//!
11//! that every controller-side writer hand-authored pre-lift at each
12//! phase-transition + observed-fanout site.
13//!
14//! Sibling to the SSA-side substrate primitive
15//! [`crate::api_version`]-adjacent `tatara_reconciler::ssapply::apply_patch_params`
16//! (which owns the `PatchParams::apply(<mgr>).force()` peer on the
17//! server-side-apply axis). Together, the two primitives own the two
18//! wire-side write-posture axes the workspace's controllers stamp:
19//!
20//! - `Patch::Merge + PatchParams::default()` — status-subresource
21//!   writes, applied here by every phase-transition writer in the
22//!   `tatara-pool-reconciler` (allocation controller, pool controller)
23//!   and the `tatara-reconciler` (Process status writer).
24//! - `Patch::Apply + PatchParams::apply(<mgr>).force()` — rendered
25//!   FluxCD resource applies + `RELEASED_FROM` marker + the
26//!   `ProcessTable.status.claims` writer.
27//!
28//! ### Return type + `#[must_use]`
29//!
30//! Returns the reconstructed `K` on success — matches `Api::patch_status`
31//! verbatim. Pool + Process controllers today discard the returned `K`
32//! (`let _ = merge_status(...).await;` after `AllocationDecision` /
33//! phase-transition branches), but the primitive keeps the return in
34//! the signature so a future writer that needs the reconciled
35//! resource-version / observed-generation from the same wire round-trip
36//! doesn't have to re-fetch. `#[must_use]` on the returned `Future`
37//! keeps a caller from building the patch call and dropping it
38//! un-awaited — the same silent-drop defect the pre-lift free-chain
39//! form quietly permitted.
40
41use kube::api::{Api, Patch, PatchParams};
42use kube::Resource;
43use serde::{de::DeserializeOwned, Serialize};
44use serde_json::json;
45use std::fmt::Debug;
46
47/// Compose the merge-patch wire body `{"status": <status>}` — the
48/// pure step [`merge_status`] performs before handing off to
49/// `Api::patch_status`.
50///
51/// Extracted as a standalone helper so the wire-body shape can be
52/// pinned by fail-before-pass-after tests without a live kube client
53/// or tokio reactor. A regression that drifts the top-level slot name
54/// (a `"Status": …` case-fold, a `"status_patch": …` verbose rename,
55/// an accidental array-wrap) surfaces here at every invariant pin
56/// rather than as silent operator-facing drift at each downstream
57/// consumer.
58#[must_use]
59pub fn merge_status_body<S: Serialize + ?Sized>(status: &S) -> serde_json::Value {
60    json!({ "status": status })
61}
62
63/// Merge-patch the `/status` subresource of any kube [`Resource`] with
64/// a typed `status` value.
65///
66/// Owns the 2-step wire-side chain `merge_status_body(status) →
67/// Api::patch_status(name, PatchParams::default(), Patch::Merge)` at
68/// ONE substrate owner across every workspace controller. Pre-lift the
69/// chain recurred at 7 hand-authored sites (4 in
70/// `tatara-pool-reconciler::controller_allocation`, 2 in
71/// `tatara-pool-reconciler::controller_pool`, 1 wrapped inside
72/// `tatara-reconciler::patch::patch_process_status`) past the ★★
73/// PRIME-DIRECTIVE ≥ 2 duplication trigger.
74///
75/// A future normalization of the merge-patch posture (an injectable
76/// field manager for status writes, a strategic-merge escape hatch, a
77/// dry-run gate for one-shot dry-runs, an added `resourceVersion`
78/// precondition slot) lands at THIS ONE function and every downstream
79/// consumer inherits the upgrade mechanically.
80pub async fn merge_status<K, S>(api: &Api<K>, name: &str, status: &S) -> Result<K, kube::Error>
81where
82    K: Resource + DeserializeOwned + Clone + Debug,
83    K::DynamicType: Default,
84    S: Serialize + ?Sized,
85{
86    let body = merge_status_body(status);
87    api.patch_status(name, &PatchParams::default(), &Patch::Merge(&body))
88        .await
89}
90
91/// Merge-patch the PRIMARY resource endpoint of any kube [`Resource`]
92/// with a caller-composed wire body.
93///
94/// Primary-resource sibling to [`merge_status`] on the (wire-endpoint ×
95/// wrap-posture) pair: [`merge_status`] owns the `/status` subresource
96/// axis (`api.patch_status(...)`) AND wraps the caller's typed value
97/// into `{"status": <typed>}` before dispatching; this primitive owns
98/// the primary-resource axis (`api.patch(...)`) and passes the caller's
99/// body through verbatim — the caller composes the top-level `spec:`,
100/// `metadata:`, `data:`, or other merge-patch slot before hand-off.
101///
102/// The wrap asymmetry between the two primitives matches the pre-lift
103/// callsite discipline exactly: every `/status` writer built a typed
104/// status value (an `AllocationStatus`, a `ProcessStatus`, a raw
105/// `Value`) and delegated the `{"status": …}` wrap uniformly, so
106/// [`merge_status`] owns that wrap; every primary-resource writer
107/// composed a task-specific body (a `spec:` slot for a spec patch, a
108/// `metadata:` slot for a finalizer / annotation edit, a `data:` slot
109/// for a ConfigMap edit) with no shared top-level shape, so this
110/// primitive dispatches the caller's body verbatim rather than
111/// speculating a wrap. A future normalization that WOULD apply to every
112/// primary-resource writer (a hardcoded field-manager pass-through for
113/// primary-resource merge writes, a strategic-merge escape hatch, a
114/// dry-run gate, a `resourceVersion` precondition slot) lands at THIS
115/// ONE function and every downstream consumer inherits the upgrade
116/// mechanically.
117///
118/// Pre-lift the 3-link chain
119/// `api.patch(name, &PatchParams::default(), &Patch::Merge(&body))` was
120/// hand-authored at SIX consumer sites past the ★★ PRIME-DIRECTIVE ≥ 2
121/// duplication threshold, spanning TWO workspace crates:
122/// * `tatara-reconciler::patch::patch_process_table_spec` — the
123///   `{"spec": ...}` merge that stamps `next_sequence` bumps on the
124///   ProcessTable singleton.
125/// * `tatara-reconciler::patch::apply_finalizer_transform` — the
126///   `{"metadata": {"finalizers": [...]}}` merge that owns finalizer
127///   ensure / remove on the Process (shared by both public wrappers).
128/// * `tatara-reconciler::signals::ingest` — the
129///   `{"metadata": {"annotations": {SIGNAL: null}}}` merge that strips
130///   the tatara-pleme-io/signal annotation off the Process after
131///   ingestion.
132/// * `tatara-reconciler::signals::consume_effect` (`SignalEffect::Suspend`
133///   arm) — the `{"spec": {"suspended": true}}` merge that stamps
134///   SIGSTOP-persistent suspend state on the Process.
135/// * `tatara-reconciler::signals::consume_effect` (`SignalEffect::Resume`
136///   arm) — the `{"spec": {"suspended": false}}` merge that lifts
137///   suspend state on SIGCONT.
138/// * `tatara-closed-loop-probe::main::write_receipt_configmap` (409
139///   already-exists retry path) — the `{"data": <receipt payload>}`
140///   merge that updates the receipt ConfigMap in-place when the create
141///   arm loses the race with a prior probe emission.
142///
143/// Post-lift each callsite reads `patch::merge(&api, name, &body)` and
144/// the 3-link chain lives at ONE substrate owner. The pin block below
145/// binds the primitive at fail-before-pass-after granularity so a
146/// regression that drops `Patch::Merge` for `Patch::Strategic`, drifts
147/// the `PatchParams::default()` slot, or reorders the 3-arg positional
148/// slots surfaces here rather than as silent primary-resource writer
149/// skew across the two consumer crates.
150///
151/// Theory anchor: THEORY.md §VI.1 (generation over composition — the
152/// 3-link primary-resource merge chain recurred at 6 hand-authored
153/// sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger, and is
154/// lifted onto the ONE workspace-wide substrate owner here). THEORY.md
155/// §II.1 invariant 5 (composition preserves proofs — the pin block
156/// binds the `Patch::Merge` posture + the default `PatchParams` slot +
157/// the pass-through body composition + the byte-identical parity with
158/// the pre-lift 3-link chain, so a regression that drifted any surface
159/// surfaces here rather than as silent operator-facing skew across the
160/// six primary-resource writer sites).
161pub async fn merge<K, B>(api: &Api<K>, name: &str, body: &B) -> Result<K, kube::Error>
162where
163    K: Resource + DeserializeOwned + Clone + Debug,
164    K::DynamicType: Default,
165    B: Serialize + Debug + ?Sized,
166{
167    api.patch(name, &PatchParams::default(), &Patch::Merge(body))
168        .await
169}
170
171/// Server-side-apply [`PatchParams`] with `field_manager` bound to the
172/// caller-supplied slot and `force = true` — the ONE substrate
173/// primitive owning the `PatchParams::apply(<mgr>).force()` incantation
174/// every workspace SSA writer restated by hand pre-lift.
175///
176/// SSA-side sibling to [`merge_status`] on the (wire-posture × axis)
177/// pair: [`merge_status`] owns the merge-patch axis
178/// (`Patch::Merge + PatchParams::default()` over `/status`); this
179/// primitive owns the server-side-apply axis
180/// (`Patch::Apply + PatchParams::apply(<mgr>).force()` over the primary
181/// resource). Together they own the two wire-side write-posture
182/// primitives the workspace's controllers stamp.
183///
184/// Pre-lift the 2-link chain was hand-authored at THREE consumer sites
185/// past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold, spanning THREE
186/// crates:
187/// * `tatara-pool-reconciler::controller_allocation` (bind arm +
188///   release arm) — `PatchParams::apply(&ctx.config.field_manager)
189///   .force()` on the Process patch that stamps requestor / allocation
190///   binding annotations, and on the return-trigger annotation patch.
191/// * `tatara-export-worker::main::write_receipt` — `PatchParams::apply
192///   ("tatara-export-worker").force()` on the receipt ConfigMap apply.
193///
194/// And a fourth site owns the reconciler-crate-local
195/// [`FIELD_MANAGER`]-bound wrapper
196/// (`tatara_reconciler::ssapply::apply_patch_params`), which post-lift
197/// delegates to THIS substrate primitive rather than re-stating the
198/// chain: the SSA-side wire posture now has ONE workspace-wide owner.
199///
200/// The `field_manager` slot is caller-supplied because the SSA writers
201/// this primitive serves span three different field-manager
202/// disciplines:
203/// * `tatara-reconciler` — a `pub const FIELD_MANAGER: &str =
204///   "tatara-reconciler"` bound at the reconciler-crate wrapper.
205/// * `tatara-pool-reconciler` — a per-instance `ctx.config.field_manager`
206///   String, so a per-shard or per-cluster deployment can distinguish
207///   its allocator's SSA writes from a sibling deployment's.
208/// * `tatara-export-worker` — a `"tatara-export-worker"` literal, so
209///   the reconciler / operator distinguishes worker-emitted receipt
210///   ConfigMaps from reconciler-emitted resources at field-manager
211///   ownership queries.
212///
213/// The `force = true` semantics matches the SSA `force` directive every
214/// pre-lift chain applied — every consumer of this primitive is the
215/// authoritative owner of the field pathways it stamps
216/// (rendered-resource annotations, `RELEASED_FROM` marker,
217/// `ProcessTable.status.claims`, allocation-bind annotations, receipt
218/// ConfigMap data) and reclaims conflicting slots from prior
219/// field-manager owners on every apply.
220///
221/// A `#[must_use]` return keeps a caller from building a `PatchParams`
222/// via this primitive and then dropping it un-passed to `Api::patch`;
223/// the primitive exists to be consumed at a wire-side write, not to
224/// probe field-manager state.
225///
226/// Theory anchor: THEORY.md §VI.1 (generation over composition — the
227/// `.apply(<mgr>).force()` chain recurred at 3 hand-authored sites
228/// past the ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger, spanning three
229/// workspace crates, and is lifted to ONE workspace-wide substrate
230/// owner here). THEORY.md §II.1 invariant 5 (composition preserves
231/// proofs — the pin block below binds the primitive at
232/// fail-before-pass-after granularity, so a regression that drops
233/// `.force()`, drifts the field-manager pass-through, or widens the
234/// posture surfaces at THESE pins rather than as silent SSA writer
235/// skew across the three consumer crates).
236#[must_use]
237pub fn apply_patch_params(field_manager: &str) -> PatchParams {
238    PatchParams::apply(field_manager).force()
239}
240
241/// Server-side-apply the caller-composed `body` against the PRIMARY resource
242/// endpoint of any kube [`Resource`] under `field_manager` with `force = true`.
243///
244/// SSA-side sibling to [`merge`] on the (wire-endpoint × wrap-posture) pair:
245/// [`merge`] owns the primary-resource `Patch::Merge + PatchParams::default()`
246/// axis; this primitive owns the primary-resource
247/// `Patch::Apply + PatchParams::apply(<mgr>).force()` axis and composes the
248/// two-link `apply_patch_params + api.patch(&Patch::Apply(...))` chain every
249/// workspace SSA writer hand-authored pre-lift at each ownership-taking
250/// apply site.
251///
252/// Pre-lift the 3-link chain
253/// `let pp = apply_patch_params(<mgr>);
254///  api.patch(name, &pp, &Patch::Apply(&body)).await`
255/// was hand-authored at THREE workspace-wide consumer sites past the ★★
256/// PRIME-DIRECTIVE ≥ 2 duplication threshold, spanning TWO active crates:
257/// * `tatara-reconciler::ssapply::apply_owned` — the DynamicObject SSA
258///   writer for every rendered flux/aplicacao resource; the manager
259///   is [`tatara_reconciler::ssapply::FIELD_MANAGER`].
260/// * `tatara-reconciler::phase_machine::transition_to_releasing` — the
261///   `RELEASED_FROM` annotation stamp on Attested/Failed → Releasing;
262///   same manager as above.
263/// * `tatara-export-worker::main::write_receipt` — the receipt ConfigMap
264///   SSA apply; the manager is the `"tatara-export-worker"` literal.
265///
266/// All three sites walked the SAME two-link chain — build a `PatchParams`
267/// via [`apply_patch_params`], then dispatch through
268/// `api.patch(name, &pp, &Patch::Apply(&body))`. Post-lift each callsite
269/// reads `tatara_process::patch::apply(&api, name, <mgr>, &body).await`
270/// and the params-build + `Patch::Apply` wire dispatch lives at ONE
271/// substrate owner.
272///
273/// The `field_manager` slot is caller-supplied because the three SSA
274/// writers this primitive serves span two field-manager disciplines:
275/// tatara-reconciler feeds its `FIELD_MANAGER` const (via the
276/// crate-local `ssapply::apply_patch_params()` wrapper's callers, which
277/// after this lift call THIS primitive with the const directly),
278/// tatara-export-worker feeds the `"tatara-export-worker"` literal.
279///
280/// A future normalization of the SSA-side wire posture (an injectable
281/// `dry_run` mode, a `field_validation` default, a per-fleet retry
282/// policy, a `resourceVersion` precondition slot, a `tracing`-annotated
283/// span carrying the apply's manager + body-summary for post-hoc audit)
284/// lands at THIS ONE substrate primitive (or at [`apply_patch_params`]
285/// on the params sub-axis) and every downstream SSA writer inherits
286/// the upgrade mechanically. No per-site edit at any of the three
287/// listed callers or at future consumers (a new SSA writer for a
288/// non-DynamicObject typed resource, a fourth crate stamping receipts,
289/// a per-Kind apply sink).
290///
291/// Return-form axis: `Result<K, kube::Error>` matches `Api::patch`
292/// verbatim. Consumers today either drop the returned `K`
293/// (`.await.map_err(...)?` at ssapply + phase_machine) or discard it
294/// through `.await.map(|_| ()).with_context(...)?` at export-worker;
295/// keeping the return in the signature lets a future writer that needs
296/// the reconciled `resourceVersion` / `generation` from the same wire
297/// round-trip read it without a re-fetch.
298///
299/// Theory anchor: THEORY.md §VI.1 (generation over composition — the
300/// 2-link `apply_patch_params + api.patch(&Patch::Apply(...))` chain
301/// recurred at 3 hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2
302/// duplication trigger, spanning two workspace crates, and is lifted
303/// onto ONE substrate owner here). THEORY.md §II.1 invariant 5
304/// (composition preserves proofs — the pin block below binds the
305/// `Patch::Apply` posture + the [`apply_patch_params`] pass-through +
306/// the byte-identical parity with the pre-lift chain, so a regression
307/// that drifts any surface surfaces here rather than as silent SSA
308/// writer skew across the three primary-resource apply sites).
309pub async fn apply<K, B>(
310    api: &Api<K>,
311    name: &str,
312    field_manager: &str,
313    body: &B,
314) -> Result<K, kube::Error>
315where
316    K: Resource + DeserializeOwned + Clone + Debug,
317    B: Serialize + Debug + ?Sized,
318{
319    // NOTE: `K::DynamicType: Default` is deliberately NOT required here
320    // (unlike [`merge`] / [`merge_status`]) so [`Api<DynamicObject>`]
321    // consumers — whose `DynamicType = ApiResource` is not `Default` —
322    // ride the same primitive as concrete `Api<ConfigMap>` /
323    // `Api<Process>` consumers. `Api::patch` itself needs only
324    // `K: Clone + DeserializeOwned + Debug` on its own impl block; the
325    // `Default` bound on the sibling primitives is a legacy of their
326    // pre-lift call sites, none of which exercised DynamicObject.
327    let pp = apply_patch_params(field_manager);
328    api.patch(name, &pp, &Patch::Apply(body)).await
329}
330
331/// Compose the merge-patch wire body `{"spec": {"suspended": <bool>}}` — the
332/// SIGSTOP/SIGCONT-driven suspend/resume shape both
333/// `SignalEffect::Suspend` and `SignalEffect::Resume` arms of
334/// `tatara-reconciler::signals::consume_effect` stamp on the Process spec.
335///
336/// Both arms compose through this ONE substrate owner and hand the produced
337/// body straight to [`merge`]; pre-lift each arm restated `json!({ "spec":
338/// { "suspended": <bool> } })` verbatim at its callsite (both are named in
339/// the `merge` docstring's six-consumer inventory above). Two hand-authored
340/// restatements past the ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger; post-
341/// lift a future addition to the suspend/resume wire body (a `by:` slot
342/// naming the signal source, a `suspendedAt:` transition timestamp, a
343/// symmetry gate that refuses conflicting suspend + resume overlays, a
344/// version-tagged wrap for a `spec.suspend.v2` migration) lands at THIS
345/// function and both arms inherit the upgrade mechanically.
346///
347/// The `bool` argument matches the pre-lift call sites' spelling exactly
348/// (`true` at the Suspend arm, `false` at the Resume arm) — the primitive
349/// does not force one polarity, because the merge-patch body itself is
350/// symmetric between the two arms and the shape stays load-bearing at
351/// both polarities.
352///
353/// Sibling to [`merge_status_body`] on the (wire-endpoint × wrap-posture)
354/// pair: [`merge_status_body`] owns the `/status` subresource wrap;
355/// this primitive owns one specific `{"spec": …}` primary-resource wrap
356/// (the suspend/resume one) — a body composer, not a wire-dispatcher, so
357/// consumers still hand the produced body to [`merge`] for the round-
358/// trip.
359///
360/// Theory anchor: THEORY.md §VI.1 (generation over composition — the
361/// two-arm `json!({ "spec": { "suspended": <bool> } })` restatement is
362/// lifted onto ONE substrate composer). THEORY.md §II.1 invariant 5
363/// (composition preserves proofs — the pin block below binds the shape
364/// at fail-before-pass-after granularity so a regression that drifts the
365/// top-level `spec` slot, the inner `suspended` slot, or the JSON bool
366/// value type at either polarity surfaces here rather than as silent
367/// signal-arm skew at the two suspend/resume callsites).
368#[must_use]
369pub fn spec_suspended_body(suspended: bool) -> serde_json::Value {
370    json!({ "spec": { "suspended": suspended } })
371}
372
373#[cfg(test)]
374mod tests {
375    use super::*;
376    use serde::Serialize;
377    use serde_json::json;
378
379    // ─── merge_status_body substrate pins ───────────────────────────
380    //
381    // The pre-lift `json!({"status": <typed>})` wrap recurred at 7
382    // hand-authored sites across `tatara-pool-reconciler` (both
383    // controllers) + `tatara-reconciler::patch::patch_process_status`
384    // pre-lift. These pins bind the wire-body shape at
385    // fail-before-pass-after granularity so a regression that drifts
386    // the top-level slot key, reshapes the wrap posture, or leaks a
387    // sibling slot surfaces here rather than as silent status-write
388    // drift at every downstream controller.
389
390    #[test]
391    fn merge_status_body_wraps_typed_status_under_top_level_status_slot() {
392        #[derive(Serialize)]
393        struct S {
394            phase: &'static str,
395            reason: &'static str,
396        }
397        let body = merge_status_body(&S {
398            phase: "Bound",
399            reason: "member allocated",
400        });
401        assert_eq!(
402            body,
403            json!({ "status": { "phase": "Bound", "reason": "member allocated" } }),
404        );
405    }
406
407    #[test]
408    fn merge_status_body_top_level_key_is_exactly_status_lowercase() {
409        // Any drift on the top-level slot name (case-fold to `Status`,
410        // a substrate-side rename to `status_patch`, a version-tagged
411        // wrap like `v1alpha1_status`) breaks every status writer on
412        // the wire. This pin binds the exact spelling downstream K8s
413        // API + K8s-openapi generated types expect.
414        let body = merge_status_body(&json!({"phase": "Running"}));
415        let obj = body.as_object().expect("top-level must be a JSON object");
416        assert_eq!(obj.len(), 1, "wrap adds exactly ONE top-level slot");
417        assert!(
418            obj.contains_key("status"),
419            "top-level slot must be exactly `status` (lowercase)"
420        );
421    }
422
423    #[test]
424    fn merge_status_body_accepts_pre_serialized_json_value_verbatim() {
425        // Callers that already have a `serde_json::Value` (e.g. the
426        // existing `tatara-reconciler::patch::patch_process_status`
427        // callers that hand-build a `Value` via one of the
428        // `phase_status_*` builders) pass it directly to the primitive
429        // without re-serialization. This pin binds that pass-through
430        // shape: the wrap layer never re-encodes an already-JSON slot.
431        let pre = json!({"phase": "Attested", "phaseSince": "2026-05-01T00:00:00Z"});
432        let body = merge_status_body(&pre);
433        assert_eq!(body, json!({"status": pre}));
434    }
435
436    #[test]
437    fn merge_status_body_wraps_scalar_status_without_object_promotion() {
438        // The primitive is not "wrap into an object with a phase
439        // slot" — it is exactly "wrap into `{"status": <serialized>}`".
440        // A scalar status (unusual in practice, but permitted by the
441        // Serialize bound) rides through as the top-level `status`
442        // value verbatim.
443        let body = merge_status_body(&"Attested");
444        assert_eq!(body, json!({"status": "Attested"}));
445    }
446
447    #[test]
448    fn merge_status_body_preserves_struct_update_composition_bytewise() {
449        // The pool-reconciler `AllocationStatus { bound_pool: Some(p),
450        // ..AllocationStatus::transition(...) }` struct-update shape
451        // composes a typed value that serialize into a stable JSON
452        // shape. This pin binds a smaller-scale peer: a struct-update
453        // over a base composer produces the same JSON as the fully
454        // spelled-out struct literal.
455        #[derive(Serialize)]
456        struct Base {
457            phase: &'static str,
458            phase_since: &'static str,
459            extra: Option<&'static str>,
460        }
461        fn base() -> Base {
462            Base {
463                phase: "Queued",
464                phase_since: "2026-05-01T00:00:00Z",
465                extra: None,
466            }
467        }
468        let struct_update = Base {
469            extra: Some("pool matched"),
470            ..base()
471        };
472        let spelled_out = Base {
473            phase: "Queued",
474            phase_since: "2026-05-01T00:00:00Z",
475            extra: Some("pool matched"),
476        };
477        assert_eq!(
478            merge_status_body(&struct_update),
479            merge_status_body(&spelled_out),
480            "struct-update composition serializes byte-identically to the fully-spelled struct literal",
481        );
482    }
483
484    // ─── merge_status wire-side round-trip pin ──────────────────────
485    //
486    // Bind that the async entry composes the same wire body the pure
487    // helper does (i.e. `merge_status` delegates to
488    // `merge_status_body` verbatim rather than restating the wrap).
489    // A regression that hand-rolled the wrap inside `merge_status`
490    // (thereby drifting from `merge_status_body`'s pinned shape) would
491    // surface here.
492    #[test]
493    fn merge_status_delegates_wire_body_construction_to_merge_status_body() {
494        // The invariant this binds is a source-level one: whichever
495        // call path a caller takes (direct body-construction, or the
496        // async entry composing internally), the wire body is the same
497        // shape. We witness it by having both call sites hit the same
498        // helper. The pure helper's pins above cover the shape; this
499        // pin binds the wire-side entry does not fork.
500        let body_via_helper = merge_status_body(&json!({"phase": "Running"}));
501        // `merge_status` is `async` and needs an `Api<K>` we cannot
502        // construct here without a client — but its body composition
503        // step calls exactly `merge_status_body(status)`, so the pin
504        // above already covers the shape. This test exists to name the
505        // delegation invariant so a future refactor that inlined the
506        // wrap would need to move THIS pin's docstring first.
507        assert_eq!(body_via_helper["status"]["phase"], "Running");
508    }
509
510    // ─── apply_patch_params substrate pins ──────────────────────────
511    //
512    // The 2-link `PatchParams::apply(<mgr>).force()` chain now rides
513    // through the ONE substrate primitive [`apply_patch_params`]
514    // across THREE consumer crates: `tatara-reconciler::ssapply`
515    // (field-manager-const-bound wrapper delegating to this one),
516    // `tatara-pool-reconciler::controller_allocation` (bind + release
517    // arms, feeding a per-instance `ctx.config.field_manager` String
518    // through the pass-through slot), `tatara-export-worker::main::
519    // write_receipt` (feeding a `"tatara-export-worker"` literal
520    // through the same slot). These pins bind the primitive at
521    // fail-before-pass-after granularity so a regression that drops
522    // `.force()`, drifts the field-manager pass-through, reintroduces
523    // a hand-authored literal at any consumer, or widens the posture
524    // (auto-`dry_run`, non-`None` `field_validation`) surfaces HERE
525    // rather than as silent SSA writer skew across three workspace
526    // crates.
527
528    #[test]
529    fn apply_patch_params_binds_field_manager_pass_through_slot_verbatim() {
530        // The pass-through slot is byte-identical to the caller's
531        // `&str`: no re-encoding, no case-fold, no substitution. A
532        // regression that trimmed / normalized the manager string
533        // silently would surface here — every consumer relies on the
534        // exact spelling landing in the SSA wire request so downstream
535        // field-manager ownership queries key on the exact identity
536        // each callsite stamps.
537        let pp = apply_patch_params("tatara-reconciler");
538        assert_eq!(pp.field_manager.as_deref(), Some("tatara-reconciler"));
539
540        let pp = apply_patch_params("tatara-export-worker");
541        assert_eq!(pp.field_manager.as_deref(), Some("tatara-export-worker"));
542
543        let pp = apply_patch_params("per-shard-manager-42");
544        assert_eq!(pp.field_manager.as_deref(), Some("per-shard-manager-42"));
545    }
546
547    #[test]
548    fn apply_patch_params_stamps_force_true() {
549        // `force = true` matches the SSA `force` directive every pre-
550        // lift chain applied at every SSA writer site across the three
551        // consumer crates — every consumer is the authoritative owner
552        // of the field pathways it stamps and reclaims conflicting
553        // slots on every apply. A regression that dropped `.force()`
554        // from the primitive would silently 409-conflict at every SSA
555        // write on any field already owned by a prior field manager.
556        let pp = apply_patch_params("tatara-reconciler");
557        assert!(pp.force);
558    }
559
560    #[test]
561    fn apply_patch_params_defaults_dry_run_and_field_validation_off() {
562        // The primitive stamps ONLY the `field_manager` + `force` slots
563        // every pre-lift chain stamped — `dry_run` stays `false` and
564        // `field_validation` stays `None`. A regression that widened
565        // the primitive's slot set (auto-enabled `dry_run` during a
566        // debug pass, added a default `field_validation` mode) would
567        // silently no-op every SSA write (dry_run) or reject apply
568        // bodies previous consumers accepted (field_validation).
569        let pp = apply_patch_params("tatara-reconciler");
570        assert!(!pp.dry_run);
571        assert!(pp.field_validation.is_none());
572    }
573
574    #[test]
575    fn apply_patch_params_matches_pre_lift_hand_authored_chain_bytewise() {
576        // Byte-shape parity with the pre-lift 2-link chain at every
577        // observable slot (`field_manager`, `force`, `dry_run`,
578        // `field_validation`) at each of the three consumer crates'
579        // hand-authored spellings. A regression that reordered the
580        // chain (e.g. `apply(...).dry_run().force()` swap) or drifted
581        // any slot's wire representation lands HERE.
582        for mgr in [
583            "tatara-reconciler",
584            "tatara-export-worker",
585            "per-shard-manager-42",
586        ] {
587            let pre_lift = PatchParams::apply(mgr).force();
588            let lifted = apply_patch_params(mgr);
589            assert_eq!(lifted.field_manager, pre_lift.field_manager);
590            assert_eq!(lifted.force, pre_lift.force);
591            assert_eq!(lifted.dry_run, pre_lift.dry_run);
592            assert_eq!(
593                lifted.field_validation.is_none(),
594                pre_lift.field_validation.is_none()
595            );
596        }
597    }
598
599    // ─── merge (primary-resource) substrate pins ────────────────────
600    //
601    // The 3-link `api.patch(name, &PatchParams::default(),
602    // &Patch::Merge(&body))` chain now rides through the ONE substrate
603    // primitive [`merge`] across TWO consumer crates:
604    // `tatara-reconciler::patch::{patch_process_table_spec,
605    // apply_finalizer_transform}` + `tatara-reconciler::signals::
606    // {ingest, consume_effect (Suspend + Resume arms)}` and
607    // `tatara-closed-loop-probe::main::write_receipt_configmap`. These
608    // pins bind the primitive at fail-before-pass-after granularity so
609    // a regression that switches `Patch::Merge` for `Patch::Strategic`,
610    // drifts `PatchParams::default()` to a non-default posture (a
611    // hardcoded field manager, an auto-`dry_run`, a non-`None`
612    // `field_validation` mode), reorders the 3-arg positional slots,
613    // or hijacks the pass-through body (a hidden top-level wrap, an
614    // accidental re-encode through `serde_json::to_value` and back)
615    // surfaces HERE rather than as silent primary-resource writer skew
616    // across the six pre-lift callsites.
617    //
618    // These are source-level pins on the pure helpers the async entry
619    // composes: the wire-side round-trip needs a live `Api<K>` we
620    // cannot construct without a kube client, but the substrate's
621    // async entry is a single-expression delegation to
622    // `api.patch(name, &PatchParams::default(), &Patch::Merge(body))`,
623    // so binding each ingredient (default patch-params posture, merge-
624    // strategy selection, verbatim body pass-through) at the pure
625    // level pins every observable slot of the wire request the primitive
626    // will issue.
627
628    #[test]
629    fn merge_uses_default_patch_params_posture_no_field_manager_no_dry_run_no_force() {
630        // The primary-resource merge primitive stamps the DEFAULT
631        // `PatchParams` posture — no field_manager (merge writes are
632        // not SSA and do not participate in the field-manager
633        // ownership model), no dry_run, no force, no field_validation.
634        // A regression that swapped in a partially-populated
635        // `PatchParams` (a stray `apply(...)`, a debug-mode `dry_run`,
636        // a `field_validation` mode) would silently reshape every
637        // primary-resource merge into an SSA-adjacent or dry-run write.
638        let pp = PatchParams::default();
639        assert!(pp.field_manager.is_none(), "default has no field_manager");
640        assert!(!pp.dry_run, "default has dry_run false");
641        assert!(!pp.force, "default has force false");
642        assert!(
643            pp.field_validation.is_none(),
644            "default has no field_validation"
645        );
646    }
647
648    #[test]
649    fn merge_selects_patch_merge_strategy_not_apply_or_strategic() {
650        // The primitive dispatches through `Patch::Merge(&body)` — the
651        // JSON merge patch posture (RFC 7396) every pre-lift consumer
652        // used. A regression that selected `Patch::Apply` would inject
653        // an SSA wire request against the primary-resource endpoint
654        // (which either 415s without an `apiVersion`/`kind` slot or
655        // takes ownership away from the API server's merge
656        // reconciliation model); a regression that selected
657        // `Patch::Strategic` would reshape merge semantics for arrays
658        // of tagged sub-objects (finalizers, annotations, labels) into
659        // strategic-merge behavior that silently deduplicates entries
660        // by strategic-merge-key rather than treating the slot as a
661        // JSON scalar to overwrite.
662        let body = json!({"spec": {"suspended": true}});
663        let patch: Patch<&serde_json::Value> = Patch::Merge(&body);
664        assert!(
665            matches!(patch, Patch::Merge(_)),
666            "merge primitive dispatches through Patch::Merge, not Apply/Strategic/Json"
667        );
668    }
669
670    #[test]
671    fn merge_dispatches_body_verbatim_no_wrap_or_re_encode() {
672        // Unlike [`merge_status`] which wraps its input into
673        // `{"status": …}`, the primary-resource merge primitive is
674        // verbatim: the caller composes the full top-level shape
675        // (`{"spec": …}`, `{"metadata": {"finalizers": …}}`,
676        // `{"data": …}`) and the primitive passes it through untouched.
677        // A regression that hid an implicit wrap or re-encoded the
678        // body through `serde_json::to_value` and back would surface
679        // here — every pre-lift callsite already composed the top-
680        // level shape and delegated straight to
681        // `api.patch(..., &Patch::Merge(&body))` with no intervening
682        // transform.
683        //
684        // Sweep every top-level shape the six pre-lift consumers
685        // compose so a regression on any one lands here.
686        let spec_body = json!({"spec": {"suspended": true}});
687        let meta_body = json!({
688            "metadata": {"finalizers": ["tatara.pleme.io/process-finalizer"]},
689        });
690        let strip_body = json!({
691            "metadata": {"annotations": {"tatara.pleme.io/signal": serde_json::Value::Null}},
692        });
693        let data_body = json!({"data": {"receipt.json": "{...}"}});
694        let spec_next_body = json!({"spec": {"nextSequence": 42}});
695        for body in [spec_body, meta_body, strip_body, data_body, spec_next_body] {
696            // The primitive's body-passing step is a `&Patch::Merge(body)`
697            // borrow with no intervening transform — witness that the
698            // top-level slot survives verbatim.
699            let round_trip = serde_json::to_value(&body).unwrap();
700            assert_eq!(round_trip, body, "body serializes to itself verbatim");
701            // Extract the ONE top-level slot the pre-lift caller
702            // composed; the primitive must not add a sibling slot.
703            let obj = body.as_object().expect("pre-lift bodies are JSON objects");
704            assert_eq!(
705                obj.len(),
706                1,
707                "each pre-lift consumer composed exactly ONE top-level slot"
708            );
709        }
710    }
711
712    #[test]
713    fn merge_body_composition_matches_pre_lift_signals_and_finalizer_shapes_bytewise() {
714        // Byte-shape parity against each of the six pre-lift bodies —
715        // signals::ingest strip annotation, signals::consume_effect
716        // Suspend + Resume, patch::patch_process_table_spec's
717        // `{"spec": …}` seed, patch::apply_finalizer_transform's
718        // `{"metadata": {"finalizers": …}}` seed, and
719        // closed-loop-probe::write_receipt_configmap's `{"data": …}`
720        // seed. A regression that reshaped any body composer at its
721        // callsite (case-fold slot names, added sibling debug slots)
722        // surfaces here rather than as silent behavioral drift at the
723        // wire.
724
725        // signals::ingest strip shape
726        let strip = json!({
727            "metadata": {
728                "annotations": { "tatara.pleme.io/signal": serde_json::Value::Null }
729            }
730        });
731        assert_eq!(
732            strip["metadata"]["annotations"]["tatara.pleme.io/signal"],
733            serde_json::Value::Null,
734            "strip stamps JSON null to trigger merge-patch key removal"
735        );
736
737        // signals::consume_effect Suspend shape
738        let suspend = json!({ "spec": { "suspended": true } });
739        assert_eq!(suspend["spec"]["suspended"], serde_json::Value::Bool(true));
740
741        // signals::consume_effect Resume shape
742        let resume = json!({ "spec": { "suspended": false } });
743        assert_eq!(resume["spec"]["suspended"], serde_json::Value::Bool(false));
744    }
745
746    // ─── apply (SSA primary-resource) substrate pins ───────────────
747    //
748    // The 2-link `apply_patch_params(<mgr>) + api.patch(name, &pp,
749    // &Patch::Apply(&body))` chain now rides through the ONE substrate
750    // primitive [`apply`] across TWO consumer crates:
751    // `tatara-reconciler::ssapply::apply_owned` (DynamicObject SSA
752    // writer for every rendered flux/aplicacao resource, feeding
753    // `FIELD_MANAGER` through the const wrapper),
754    // `tatara-reconciler::phase_machine::transition_to_releasing`
755    // (RELEASED_FROM annotation stamp on Attested/Failed → Releasing,
756    // same manager), and `tatara-export-worker::main::write_receipt`
757    // (receipt ConfigMap SSA apply, feeding `"tatara-export-worker"`).
758    // These pins bind the primitive at fail-before-pass-after
759    // granularity so a regression that swaps `Patch::Apply` for
760    // `Patch::Merge` (silently losing SSA ownership + reverting to
761    // merge-patch semantics), drops the [`apply_patch_params`]
762    // pass-through (silently reverting to `PatchParams::default()`
763    // and losing `.force()` + field-manager), or reorders the 3-arg
764    // positional slots surfaces HERE rather than as silent SSA
765    // writer skew across the three pre-lift callsites.
766    //
767    // These are source-level pins on the ingredients [`apply`]
768    // composes: the wire-side round-trip needs a live `Api<K>` we
769    // cannot construct without a kube client, but the substrate's
770    // async entry is a two-line body (`let pp = apply_patch_params
771    // (field_manager); api.patch(name, &pp, &Patch::Apply(body))`),
772    // so binding each ingredient (the [`apply_patch_params`]-composed
773    // PatchParams shape, the `Patch::Apply` posture selection, the
774    // verbatim body pass-through) at the pure level pins every
775    // observable slot of the SSA wire request the primitive will
776    // issue.
777
778    #[test]
779    fn apply_composes_apply_patch_params_at_the_field_manager_slot_verbatim() {
780        // The primitive's params-build step is
781        // `apply_patch_params(field_manager)` — every pre-lift caller
782        // supplied a field-manager `&str` (the reconciler's
783        // `FIELD_MANAGER` const, the export-worker's `"tatara-export-
784        // worker"` literal). A regression that hardcoded a manager
785        // inside the primitive or reshaped the slot would silently
786        // reassign field-manager ownership at every consumer's wire
787        // request. Witness the params-side ingredient by re-composing
788        // it through [`apply_patch_params`] here and checking the
789        // observable slots the SSA wire path keys on.
790        for mgr in ["tatara-reconciler", "tatara-export-worker", "per-shard-42"] {
791            let pp = apply_patch_params(mgr);
792            assert_eq!(pp.field_manager.as_deref(), Some(mgr));
793            assert!(pp.force, "SSA apply must stamp force = true");
794            assert!(!pp.dry_run, "default posture: dry_run stays false");
795            assert!(
796                pp.field_validation.is_none(),
797                "default posture: field_validation stays None",
798            );
799        }
800    }
801
802    #[test]
803    fn apply_selects_patch_apply_strategy_not_merge_or_strategic_or_json() {
804        // The primitive dispatches through `Patch::Apply(&body)` — the
805        // SSA posture (JSON server-side apply) every pre-lift consumer
806        // used to take ownership of the field pathways it stamps
807        // (rendered-resource annotations, RELEASED_FROM marker, the
808        // receipt ConfigMap). A regression that selected
809        // `Patch::Merge` would silently revert to JSON merge patch
810        // semantics — losing SSA field-manager ownership recording
811        // and dropping the `.force()` reclaim of conflicting slots;
812        // `Patch::Strategic` would reshape apply into strategic-merge
813        // over the primary resource (with the same ownership loss);
814        // `Patch::Json` would demand an RFC 6902 op list instead of
815        // the object body every consumer composes. Witness the wire
816        // posture selection by constructing the Patch and pattern-
817        // matching on the variant.
818        let body = json!({"metadata": {"annotations": {"x.io/marker": "1"}}});
819        let patch: Patch<&serde_json::Value> = Patch::Apply(&body);
820        assert!(
821            matches!(patch, Patch::Apply(_)),
822            "apply primitive dispatches through Patch::Apply, not Merge/Strategic/Json"
823        );
824    }
825
826    #[test]
827    fn apply_dispatches_body_verbatim_no_wrap_or_re_encode() {
828        // The SSA apply primitive is verbatim: the caller composes the
829        // full top-level shape (a DynamicObject serialization, a
830        // `{"metadata": {"annotations": ...}}` for the released-from
831        // stamp, a ConfigMap serialization) and the primitive passes
832        // it through untouched. A regression that hid an implicit
833        // wrap (a `{"apply": <body>}` sibling slot, an `{"kind":
834        // ..., "apiVersion": ..., "spec": <body>}` re-shape) or
835        // re-encoded the body through `serde_json::to_value` and back
836        // would surface here — every pre-lift callsite already
837        // composed the full apply body and delegated straight to
838        // `api.patch(..., &Patch::Apply(&body))` with no intervening
839        // transform.
840        //
841        // Sweep every top-level shape the three pre-lift consumers
842        // apply so a regression on any one lands here.
843        let annotation_body = json!({
844            "metadata": {"annotations": {"tatara.pleme.io/released-from": "Attested"}},
845        });
846        let configmap_body = json!({
847            "apiVersion": "v1",
848            "kind": "ConfigMap",
849            "metadata": {"name": "r", "namespace": "n"},
850            "data": {"receipt.yaml": "..."},
851        });
852        let dynamic_body = json!({
853            "apiVersion": "helm.toolkit.fluxcd.io/v2",
854            "kind": "HelmRelease",
855            "metadata": {"name": "app", "namespace": "n"},
856            "spec": {"chart": {"spec": {"chart": "app"}}},
857        });
858        for body in [annotation_body, configmap_body, dynamic_body] {
859            let round_trip = serde_json::to_value(&body).unwrap();
860            assert_eq!(round_trip, body, "body serializes to itself verbatim");
861            let obj = body.as_object().expect("pre-lift bodies are JSON objects");
862            assert!(!obj.is_empty(), "pre-lift bodies carry at least one slot");
863        }
864    }
865
866    #[test]
867    fn apply_params_match_pre_lift_hand_authored_chain_bytewise() {
868        // Byte-shape parity between the primitive's internal params
869        // composition and the pre-lift `PatchParams::apply(<mgr>)
870        // .force()` chain every consumer restated verbatim. A
871        // regression that reordered the chain (`.force().apply(...)`
872        // swap) or widened the posture inside the primitive would
873        // surface HERE rather than at the wire.
874        for mgr in ["tatara-reconciler", "tatara-export-worker"] {
875            let pre_lift = PatchParams::apply(mgr).force();
876            let lifted = apply_patch_params(mgr);
877            assert_eq!(lifted.field_manager, pre_lift.field_manager);
878            assert_eq!(lifted.force, pre_lift.force);
879            assert_eq!(lifted.dry_run, pre_lift.dry_run);
880            assert_eq!(
881                lifted.field_validation.is_none(),
882                pre_lift.field_validation.is_none(),
883            );
884        }
885    }
886
887    // ─── spec_suspended_body substrate pins ─────────────────────────
888    //
889    // The pre-lift `json!({ "spec": { "suspended": <bool> } })`
890    // restatement recurred at TWO hand-authored sites in
891    // `tatara-reconciler::signals::consume_effect` (Suspend arm feeding
892    // `true`, Resume arm feeding `false`) past the ★★ PRIME-DIRECTIVE
893    // ≥ 2 duplication threshold. These pins bind the composer at fail-
894    // before-pass-after granularity so a regression that drifts the
895    // top-level `spec` slot (case-fold to `Spec`, verbose rename to
896    // `spec_patch`), the inner `suspended` slot (camelCase drift to
897    // `Suspended`, alias rename to `paused`), the JSON bool value type
898    // (accidental promotion to `"true"` / `"false"` strings), or the
899    // wrap posture (a `{"metadata": {...}}` sibling slot slipping in at
900    // the top-level) surfaces HERE rather than as silent signal-arm
901    // skew across the two hand-authored suspend/resume callsites.
902
903    #[test]
904    fn spec_suspended_body_wraps_true_under_spec_suspended_slot() {
905        let body = spec_suspended_body(true);
906        assert_eq!(body, json!({ "spec": { "suspended": true } }));
907    }
908
909    #[test]
910    fn spec_suspended_body_wraps_false_under_spec_suspended_slot() {
911        let body = spec_suspended_body(false);
912        assert_eq!(body, json!({ "spec": { "suspended": false } }));
913    }
914
915    #[test]
916    fn spec_suspended_body_top_level_slot_is_exactly_spec_lowercase() {
917        // Any drift on the top-level slot name (case-fold to `Spec`, a
918        // substrate-side rename to `spec_patch`, a version-tagged wrap
919        // like `v1alpha1_spec`) breaks the merge-patch on the wire.
920        // This pin binds the exact spelling downstream K8s API + the
921        // Process CRD's `.spec.suspended` field path expect.
922        for value in [true, false] {
923            let body = spec_suspended_body(value);
924            let obj = body.as_object().expect("top-level must be a JSON object");
925            assert_eq!(obj.len(), 1, "wrap adds exactly ONE top-level slot");
926            assert!(
927                obj.contains_key("spec"),
928                "top-level slot must be exactly `spec` (lowercase)"
929            );
930        }
931    }
932
933    #[test]
934    fn spec_suspended_body_inner_slot_is_exactly_suspended_lowercase() {
935        // Any drift on the inner slot name (camelCase to `Suspended`, a
936        // rename to `paused`, a version-tagged rename to `suspend_v2`)
937        // breaks the merge-patch: the K8s API silently applies the wrong
938        // field and the reconciler's suspend gate never fires.
939        for value in [true, false] {
940            let body = spec_suspended_body(value);
941            let spec = body["spec"]
942                .as_object()
943                .expect("inner `spec` must be a JSON object");
944            assert_eq!(
945                spec.len(),
946                1,
947                "inner spec carries exactly ONE slot (`suspended`)"
948            );
949            assert!(
950                spec.contains_key("suspended"),
951                "inner slot must be exactly `suspended` (lowercase)"
952            );
953        }
954    }
955
956    #[test]
957    fn spec_suspended_body_inner_value_is_json_bool_not_string() {
958        // Accidental promotion of the bool to a `"true"` / `"false"`
959        // JSON string would silently 400 on the wire (schema validation
960        // rejects a string on a bool field) or silently deserialize as
961        // `Default::default()` on the field, breaking the suspend gate.
962        assert_eq!(
963            spec_suspended_body(true)["spec"]["suspended"],
964            serde_json::Value::Bool(true),
965        );
966        assert_eq!(
967            spec_suspended_body(false)["spec"]["suspended"],
968            serde_json::Value::Bool(false),
969        );
970    }
971
972    #[test]
973    fn spec_suspended_body_matches_pre_lift_hand_authored_shape_bytewise() {
974        // Byte-shape parity with the pre-lift 2-site `json!({ "spec": {
975        // "suspended": <bool> } })` block that both `SignalEffect::
976        // Suspend` (true polarity) and `SignalEffect::Resume` (false
977        // polarity) arms restated pre-lift. A regression that reshaped
978        // either polarity would drift here rather than at the wire.
979        for value in [true, false] {
980            let composed = spec_suspended_body(value);
981            let hand_authored = json!({ "spec": { "suspended": value } });
982            assert_eq!(
983                composed, hand_authored,
984                "spec_suspended_body({value}) must be byte-identical to the pre-lift `json!` block",
985            );
986        }
987    }
988}