Skip to main content

tatara_process/
json_object.rs

1//! Substrate primitive over `serde_json::Value` — the ONE substrate
2//! owner of the `.as_object_mut().ok_or_else(|| anyhow::anyhow!(
3//! "<slot> is not an object"))` guard-shape every JSON-mutating helper
4//! restates by hand at the "walk this `Value` slot into its
5//! `serde_json::Map` interior or fail loud" boundary.
6//!
7//! Peer of the trait family that already lives in this crate on the
8//! wrap-shape axis:
9//!
10//! * [`crate::kube_error::KubeResultExt`] — the `kube::Error → anyhow`
11//!   display-prefix wrap.
12//! * [`crate::hostname::HostnameResultExt`] — the `HostnameError →
13//!   anyhow` display-prefix wrap.
14//! * [`crate::anyhow_flatten::FlattenCtxExt`] — the `anyhow::Error →
15//!   anyhow` display-prefix flatten.
16//! * This module — the `Option<&mut Map> → anyhow::Result<&mut Map>`
17//!   type-guard, partitioned from the three above by SOURCE (`None`
18//!   from the slot-typecheck, not a lifted error type) but sharing the
19//!   `.map_err(|_| anyhow!("<slug>: …"))?` display-prefix wire format.
20//!   The module also owns the READ-side [`ValueGetExt`] projector
21//!   (`.get_i64(<key>) -> Option<i64>`) — sibling of the three
22//!   MUTATION-side traits below on the (read, mutate) axis, closing
23//!   the READ half of the `serde_json::Value` substrate the four
24//!   traits jointly own.
25//!
26//! Pre-lift the shape was hand-authored at THREE adjacent private
27//! helpers in `tatara-reconciler::ssapply` past the ★★ PRIME-DIRECTIVE
28//! ≥ 2 duplication threshold:
29//!
30//! * `metadata_object_mut(resource)` — the root-guard step
31//!   (`resource.as_object_mut().ok_or_else(|| anyhow!("resource is not
32//!   an object"))?`) that opens the SSA-time
33//!   `resource → &mut metadata` walk shared by `inject_owner_reference`
34//!   + `inject_annotations`.
35//! * `metadata_object_mut(resource)` — the metadata-slot type-check
36//!   step (`metadata.as_object_mut().ok_or_else(|| anyhow!("metadata
37//!   is not an object"))?`) that closes the same walk — a resource
38//!   whose author mistyped the `metadata` slot as an array / string
39//!   surfaces as an error rather than as a silent
40//!   `.as_object_mut() → None → skip` no-op.
41//! * `inject_annotations(resource, process)` — the annotations-slot
42//!   type-check step (`annot.as_object_mut().ok_or_else(|| anyhow!(
43//!   "annotations is not an object"))?`) that opens the SSA-time
44//!   `metadata → &mut annotations` walk before the ownership tag +
45//!   observed-* primitive family drops its keys into the map.
46//!
47//! All three restated the SAME 2-line shape verbatim: `.as_object_mut()`
48//! on a `serde_json::Value` handle already known to be non-null, then
49//! `.ok_or_else(|| anyhow!("<slot-name> is not an object"))` wrap
50//! whose slot name matched the walk step's semantic role (`"resource"`
51//! / `"metadata"` / `"annotations"`). THREE byte-for-byte identical
52//! guard blocks past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold,
53//! differing only in the `&'static str` slot name each callsite
54//! stamped.
55//!
56//! Post-lift each callsite reads
57//! `<value>.as_object_mut_or("<slot>")?` and the guard-shape lives at
58//! ONE substrate owner here. The composed `anyhow::Error`'s `Display`
59//! is byte-identical to the pre-lift chain (`"<slot> is not an
60//! object"`), so operator-facing log output and any error-chain greps
61//! still match bytewise. A regression that drifts the message (a
62//! `"<slot> is not a JSON object"` synonym, a swapped `<slot>` slot,
63//! a promotion to a chain-form `source` that only surfaces via the
64//! alternate `{e:#}` formatter) surfaces at the tests below rather
65//! than as silent operator-facing drift across the three pre-lift
66//! consumers.
67//!
68//! ### Naming — `as_object_mut_or`, not `as_object_mut`
69//!
70//! Same discipline as the three sibling traits above — the trait
71//! method deliberately does NOT share a name with the inherent
72//! `serde_json::Value::as_object_mut` method (which returns
73//! `Option<&mut Map>`), because a name collision would let a caller
74//! who has `ValueObjectExt` in scope resolve to the inherent method
75//! by accident (inherent methods win over trait methods in method
76//! resolution) and silently drop the type-guard wrap altogether. The
77//! `_or` suffix names the intent: guard the `Option → Result` step
78//! at the same call, matching the pre-lift `.as_object_mut().
79//! ok_or_else(...)` chain.
80//!
81//! ### `#[must_use]`
82//!
83//! Every consumer threads the `?` short-circuit onto its handler's
84//! `Result<_, anyhow::Error>` return — dropping the guard swallows
85//! the underlying type-mismatch entirely, which is never the intended
86//! semantic at any of the three pre-lift consumers (each downstream
87//! `md.entry(...).or_insert_with(...)` / `annot.insert(...)` mutation
88//! depends on the returned `&mut Map` reference).
89//!
90//! Theory anchor: THEORY.md §VI.1 (generation over composition — the
91//! `.as_object_mut().ok_or_else(|| anyhow!("<slot> is not an
92//! object"))` guard-shape recurred at three hand-authored sites past
93//! the ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger, and is lifted to
94//! ONE substrate owner here). THEORY.md §II.1 invariant 5 (composition
95//! preserves proofs — a regression that drifts the guard message
96//! wording at ONE site surfaces here at the substrate pin rather than
97//! as silent operator-facing skew across every SSA-time
98//! `metadata_object_mut` + `inject_annotations` mutation).
99
100use serde_json::{Map, Value};
101
102/// Substrate extension trait over `serde_json::Value` — the ONE
103/// substrate owner of the `.as_object_mut().ok_or_else(|| anyhow!(
104/// "<slot> is not an object"))` guard-shape. See the module docs for
105/// the full callsite audit + the naming rationale (why
106/// `as_object_mut_or` and not `as_object_mut`).
107pub trait ValueObjectExt {
108    /// Borrow the [`Value`] as a mutable JSON object [`Map`], or fail
109    /// loud with an [`anyhow::Error`] whose `Display` reads exactly
110    /// `"<slot> is not an object"` — the pre-lift wire format every
111    /// consumer's `tracing::error!(error = %e, ...)` log line already
112    /// encoded.
113    #[must_use = "an object-guard that isn't threaded via `?` swallows the underlying type mismatch"]
114    fn as_object_mut_or(&mut self, slot: &'static str) -> anyhow::Result<&mut Map<String, Value>>;
115}
116
117impl ValueObjectExt for Value {
118    #[inline]
119    fn as_object_mut_or(&mut self, slot: &'static str) -> anyhow::Result<&mut Map<String, Value>> {
120        self.as_object_mut()
121            .ok_or_else(|| anyhow::anyhow!("{slot} is not an object"))
122    }
123}
124
125/// Substrate extension trait over `serde_json::Map<String, Value>` —
126/// the ONE substrate owner of the `map.insert(<key>.into(),
127/// Value::String(<val>.into()))` string-slot insertion shape every
128/// JSON-mutating helper in the workspace hand-authored at each callsite.
129///
130/// Peer of [`ValueObjectExt`] above on the JSON-mutation axis, split
131/// by SHAPE: [`ValueObjectExt::as_object_mut_or`] owns the "walk into
132/// this `Value`'s object-shape interior or fail loud" guard;
133/// [`JsonMapStrExt::insert_str`] owns the "stamp a `Value::String` at
134/// a string-typed key" write shape that every consumer downstream of
135/// the guard uses to populate the returned `&mut Map`.
136///
137/// Pre-lift the shape was hand-authored at SEVENTEEN production emit
138/// sites across `tatara-reconciler` past the ★★ PRIME-DIRECTIVE ≥ 2
139/// duplication threshold:
140///
141/// * `ssapply::inject_annotations` × 4 — the SSA-time observed-*
142///   annotation stamp family (`PID`, `CONTENT_HASH`, `GENERATION`,
143///   `ATTESTATION_ROOT`) each restated the 2-line `annot.insert(
144///   <annotation-const>.to_string(), Value::String(<val>.<coerce>))`
145///   shape verbatim.
146/// * `render::render_flux` × 3 — the Flux `Kustomization.spec` seeds
147///   (`interval`, `path`, `targetNamespace`), each restating the same
148///   `spec.insert("<key>".into(), Value::String(<val>))` shape.
149/// * `render::render_aplicacao` × 3 — the Flux `HelmRelease.spec`
150///   seeds (`releaseName`, `targetNamespace`) plus the values-overlay
151///   `profile` slot, each restating the same insert shape.
152/// * `render::render_export_job` × 2 — the export-Job outer label map
153///   (`ROLE`, `EXPORT_INDEX`) each restating the same insert shape.
154/// * `edges::IngressEdge::render` × 1 — the cert-manager
155///   `cluster-issuer` annotation, restating the same insert shape.
156/// * `edges::routing_edge_labels` × 2 — the routing-edge `metadata.
157///   labels` map's non-ownership slots (`APP`, `ROUTING_FORM`), each
158///   restating the same `labels.insert(<annotation-const>.to_string(),
159///   Value::String(<val>.into()))` shape past the ★★ PRIME-DIRECTIVE
160///   ≥ 2 duplication threshold — one of TWO adjacent bypass sites the
161///   substrate owner reaches through this lift.
162/// * `render::mark_resources_as_adopting` × 2 — the `encapsulation-
163///   mode` + `adopted-release` annotation stamps on adoption-mode
164///   render, each restating the same `anns_obj.insert("<key>".into(),
165///   Value::String(<val>.into()))` shape past the ★★ PRIME-DIRECTIVE
166///   ≥ 2 duplication threshold. The adopted-release value slot also
167///   routes its `<ns>/<release>` join through the sibling
168///   [`crate::qualified_process_ref`] substrate composer, closing a
169///   bare `format!("{ns}/{name}")` bypass on the `<ns>/<name>` join
170///   axis at this same callsite.
171///
172/// All SEVENTEEN pre-lift sites restated the SAME 2-line shape verbatim,
173/// differing only in the `&'static str` / `String` key + the `&str` /
174/// `String` value at each callsite. A copy-paste that dropped the
175/// `Value::String(...)` wrap (a caller who reached for
176/// `.insert(k, v)` after refactoring from a `Value` slot to a plain
177/// `String` value slot) would type-check silently at every callsite —
178/// `Map<String, Value>::insert` expects a `Value`, and `String:
179/// Into<Value>` is provided by `serde_json` via the `Value::String`
180/// arm's `From` impl, so the naive `.insert(k, v.to_string())` compiles
181/// AND writes the byte-identical JSON. Post-lift each callsite reads
182/// `<map>.insert_str(<key>, <val>)` and the string-slot write shape
183/// lives at ONE substrate owner here.
184///
185/// ### Composability
186///
187/// * Key slot accepts any `impl Into<String>`: `&str` (via
188///   `String::from`), `String` (identity), `Cow<'_, str>`, so a
189///   callsite with a static `annotations::PID` (`&'static str`) reads
190///   `insert_str(annotations::PID, …)` with no `.to_string()` per site.
191/// * Value slot accepts any `impl Into<String>`: `&str`, `String`,
192///   `Cow<'_, str>`. Numeric or non-string values still need an
193///   explicit `.to_string()` at the callsite — same as pre-lift, so
194///   the wrapping shape stays visible in the caller's grep footprint.
195/// * Returns `Option<Value>` matching the inherent
196///   `Map<String, Value>::insert` return semantics: `None` on new-key,
197///   `Some(prev)` on overwrite of an existing slot.
198///
199/// ### Naming — `insert_str`, not `insert`
200///
201/// Same discipline as [`ValueObjectExt::as_object_mut_or`] above — the
202/// trait method deliberately does NOT collide with the inherent
203/// `Map::insert` (which takes `(String, Value)` positionally). A name
204/// collision would let a caller who has `JsonMapStrExt` in scope
205/// resolve to the inherent method by accident (inherent methods win
206/// over trait methods in method resolution) and silently drop the
207/// `Value::String` wrap, stamping the value bytes straight into the
208/// map under a different `Value` variant. The `_str` suffix names the
209/// intent: the value slot IS the `Value::String` arm at this write.
210///
211/// Theory anchor: THEORY.md §VI.1 (generation over composition — the
212/// `.insert(<k>.into(), Value::String(<v>.into()))` shape recurred at
213/// SEVENTEEN hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2
214/// duplication trigger, and is lifted to ONE substrate owner here).
215/// THEORY.md §II.1 invariant 5 (composition preserves proofs — a
216/// regression that drifts the string-slot write shape at ONE consumer
217/// surfaces at the substrate pin rather than as silent per-emit skew
218/// across every ssapply / render / edges JSON emit site).
219pub trait JsonMapStrExt {
220    /// Insert a `Value::String(<val>.into())` at `<key>.into()` into
221    /// this JSON object map. Returns `Option<Value>` matching the
222    /// underlying `Map::insert` semantics — `None` for a new key,
223    /// `Some(prev)` for an overwrite.
224    fn insert_str(&mut self, key: impl Into<String>, value: impl Into<String>) -> Option<Value>;
225}
226
227impl JsonMapStrExt for Map<String, Value> {
228    #[inline]
229    fn insert_str(&mut self, key: impl Into<String>, value: impl Into<String>) -> Option<Value> {
230        self.insert(key.into(), Value::String(value.into()))
231    }
232}
233
234/// Substrate extension trait over `serde_json::Map<String, Value>` —
235/// the ONE substrate owner of the `.entry(<key>).or_insert_with(||
236/// Value::Object(<empty>))` seed-then-guard shape every JSON-mutating
237/// helper hand-authored at the "walk into this object slot on the
238/// parent map, seeding an empty object if the slot is absent, or fail
239/// loud if the slot exists but is a non-object" boundary.
240///
241/// Peer of [`ValueObjectExt::as_object_mut_or`] and
242/// [`JsonMapStrExt::insert_str`] on the JSON-mutation axis; split by
243/// SHAPE + SITE. [`ValueObjectExt::as_object_mut_or`] owns the "guard
244/// a `Value` handle into its object interior" step at ONE level;
245/// [`JsonMapStrExt::insert_str`] owns the "stamp a `Value::String` at
246/// a string-typed key" write shape; this trait owns the compound
247/// "get-or-seed the object at a slot, then guard" step every SSA-time
248/// re-injection walks when the caller intends to reach a nested
249/// object slot without asserting whether the parent has already
250/// populated it (a caller composing a fresh resource-body carries
251/// no `metadata` / `metadata.annotations` slot pre-seed; a caller
252/// composing atop a pre-populated resource does — both paths reach
253/// the same primitive).
254///
255/// Pre-lift the compound shape was hand-authored at TWO adjacent
256/// private helpers in `tatara-reconciler::ssapply` past the ★★
257/// PRIME-DIRECTIVE ≥ 2 duplication threshold, both walking the SAME
258/// 3-step `let X = <map>.entry(<slot>).or_insert_with(|| Value::Object
259/// (<empty>)); X.as_object_mut_or(<slot>)?` incantation:
260///
261/// * `metadata_object_mut(resource)` — the `metadata` slot seed-then-
262///   guard step at the root of every SSA-time re-injection walk
263///   (`inject_owner_reference` + `inject_annotations` reach it).
264/// * `inject_annotations(resource, process)` — the `annotations`
265///   slot seed-then-guard step nested one level deeper under the
266///   `metadata` object the primitive above returned.
267///
268/// Both restated the SAME 3-line shape verbatim: `.entry(<slot>)` on
269/// a `Map<String, Value>` handle known to be an object, then
270/// `.or_insert_with(|| Value::Object(<empty>))` to synthesize an
271/// empty object at the slot when absent, then a `.as_object_mut_or
272/// (<slot>)?` guard on the returned `&mut Value` to fail loud when
273/// the existing slot is a non-object. TWO byte-for-byte identical
274/// blocks past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold,
275/// differing only in the `&'static str` slot name each callsite
276/// stamped (`"metadata"` / `"annotations"`) — and the slot name is
277/// used at BOTH the entry key AND the guard error message so a
278/// regression that drifted the two apart at one callsite (a typo
279/// stamping `"metadata"` into the entry key + `"metadatas"` into
280/// the error message) would silently pass one pin and fail the
281/// other. Post-lift each callsite reads `<map>.object_slot_mut_or
282/// (<slot>)?` and the compound shape lives at ONE substrate owner
283/// here — the slot name is stamped ONCE per call and reaches both
284/// the entry key and the guard error slot mechanically.
285///
286/// ### Composability
287///
288/// * Slot name is `&'static str` — pre-lift both callsites stamped
289///   `&'static str` literals (`"metadata"` / `"annotations"`); a
290///   dynamic-slot caller (a callsite that reached this primitive
291///   with a `String` key computed at runtime) has no pre-lift
292///   precedent in the ssapply/render axis, so the `&'static str`
293///   bound stays honest to the pre-lift shape. A future caller
294///   needing a runtime slot name can widen this to
295///   `impl Into<String>` at the substrate; the pre-lift consumers
296///   inherit it mechanically.
297/// * Returns `anyhow::Result<&mut Map<String, Value>>` — matches the
298///   sibling [`ValueObjectExt::as_object_mut_or`] shape so the
299///   downstream `.entry(...).or_insert_with(...)` / `.insert(...)`
300///   mutation threads through `?` onto the caller's
301///   `Result<_, anyhow::Error>` return exactly as pre-lift.
302/// * Ok-arm returns the SAME `&mut Map<String, Value>` the pre-lift
303///   `.as_object_mut_or(<slot>)` step returned — no clone, no key-
304///   order reshape, no synthesis.
305///
306/// ### Naming — `object_slot_mut_or`, not `entry_object` or
307/// `get_or_insert_object_mut`
308///
309/// Same discipline as the two sibling traits above — the trait method
310/// deliberately does NOT collide with the inherent `Map::entry` /
311/// `Map::get_mut` / `Map::insert` methods (any of which a caller who
312/// has this trait in scope could resolve to by accident, silently
313/// dropping the type-guard step). The `_or` suffix names the intent
314/// (guard the `Option → Result` step at the same call, matching the
315/// pre-lift `.as_object_mut_or(<slot>)?` guard); `object_slot_mut`
316/// names the target shape (return an `&mut` object-typed `Map` at
317/// the slot). Together they read as "guard the slot into a mutable
318/// object interior or fail loud", matching the pre-lift semantics
319/// exactly.
320///
321/// ### `#[must_use]`
322///
323/// Every consumer threads the `?` short-circuit onto its handler's
324/// `Result<_, anyhow::Error>` return — dropping the guard swallows
325/// the underlying type-mismatch entirely, which is never the intended
326/// semantic at either pre-lift consumer (each downstream
327/// `.entry(...).or_insert_with(...)` / `.insert(...)` mutation
328/// depends on the returned `&mut Map` reference).
329///
330/// Theory anchor: THEORY.md §VI.1 (generation over composition — the
331/// 3-line `.entry(<slot>).or_insert_with(|| Value::Object(<empty>))
332/// .as_object_mut_or(<slot>)?` compound shape recurred at two
333/// hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
334/// trigger, and is lifted to ONE substrate owner here). THEORY.md
335/// §II.1 invariant 5 (composition preserves proofs — a regression
336/// that drifted the entry-key slot vs. the guard-error slot at ONE
337/// site would silently pass one downstream pin and fail the other;
338/// post-lift the primitive stamps the slot ONCE per call so the
339/// substrate itself owns the entry-key ↔ guard-error name coherence).
340pub trait JsonMapObjectEntryExt {
341    /// Get-or-seed the object at `slot` in this JSON map, then guard
342    /// that the resulting handle is an object; returns
343    /// `&mut Map<String, Value>` on the object arm, and an
344    /// [`anyhow::Error`] whose `Display` reads
345    /// `"<slot> is not an object"` on the non-object arm (byte-
346    /// identical to the pre-lift `.as_object_mut_or(<slot>)?` guard,
347    /// sourced from the sibling [`ValueObjectExt::as_object_mut_or`]).
348    #[must_use = "an object-slot guard that isn't threaded via `?` swallows the underlying type mismatch"]
349    fn object_slot_mut_or(&mut self, slot: &'static str)
350        -> anyhow::Result<&mut Map<String, Value>>;
351}
352
353impl JsonMapObjectEntryExt for Map<String, Value> {
354    #[inline]
355    fn object_slot_mut_or(
356        &mut self,
357        slot: &'static str,
358    ) -> anyhow::Result<&mut Map<String, Value>> {
359        self.entry(slot)
360            .or_insert_with(|| Value::Object(Map::new()))
361            .as_object_mut_or(slot)
362    }
363}
364
365/// Substrate extension trait over `serde_json::Value` — the ONE
366/// substrate owner of the paired `.get(<key>).and_then(|v| v.as_<T>())`
367/// two-link READ chain every downstream projection walks to pull a
368/// typed leaf off a Kubernetes-status blob (or an equivalent
369/// rendered-resource JSON object) without asserting the slot is
370/// present, without asserting its variant, and without asserting the
371/// slot fits the target scalar type.
372///
373/// The trait carries ONE method per typed READ axis; the axis-family
374/// is [`Self::get_i64`] (integer counters) + [`Self::get_str`]
375/// (string slots) + [`Self::get_array`] (JSON array slots) +
376/// [`Self::get_bool`] (boolean flags). Adding a further axis
377/// (a `get_object` for `Value::Object`, a `get_f64` for
378/// `Value::Number` truncated to `f64`) lands as ONE new method here
379/// + ONE impl arm per receiver shape, inheriting the naming,
380/// `#[must_use]`, and inline discipline the existing axes pin.
381/// Never open a peer trait for a new axis — keep every READ
382/// projection on the ONE substrate owner so a caller who imports
383/// `ValueGetExt` reaches every axis through the same trait handle.
384///
385/// READ-side counterpart to the three MUTATION-side siblings already in
386/// this module — [`ValueObjectExt::as_object_mut_or`],
387/// [`JsonMapStrExt::insert_str`], [`JsonMapObjectEntryExt::object_slot_mut_or`]
388/// — partitioning the substrate along the (read, mutate) axis on the
389/// same `serde_json::Value` / `serde_json::Map<String, Value>` carrier
390/// pair.
391///
392/// Pre-lift the two-link chain was hand-authored at THREE adjacent
393/// slots inside `tatara-reconciler::boundary::fetch_job_status`, each
394/// projecting one `batch/v1::Job` `status.<counter>` field out of the
395/// fetched `serde_json::Value` object into a private `JobStatusView`
396/// row past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold:
397///
398/// * `status.get("succeeded").and_then(|v| v.as_i64())` — the
399///   Job-completion counter every `JobAttested` + `ClosedLoopAuth`
400///   postcondition evaluator gates on (`succeeded < 1` short-circuits
401///   to `Satisfaction::Unsatisfied("… still running (…)")`).
402/// * `status.get("failed").and_then(|v| v.as_i64())` — the
403///   Job-failure counter the same evaluators gate on
404///   (`failed > 0` short-circuits to
405///   `Satisfaction::Unsatisfied("… failed (status.failed={n})")`).
406/// * `status.get("active").and_then(|v| v.as_i64())` — the
407///   Job-in-flight counter the "still running" diagnostic tail
408///   reports as `(succeeded={s}, active={a})`.
409///
410/// All THREE sites walked the SAME two-link chain — `.get(<key>)` on a
411/// `serde_json::Value` already known to be the status object, then
412/// `.and_then(|v| v.as_i64())` on the returned `Option<&Value>` — and
413/// each was followed by an `if let Some(...)` write into the
414/// [`JobStatusView`] row initialised from `Default::default()`. Post-
415/// lift each callsite reads `status.get_i64(<key>)` and the two-link
416/// READ chain lives at ONE substrate owner here.
417///
418/// ### Naming — `get_i64`, not `as_i64` or `i64_at`
419///
420/// Same discipline as the three sibling traits above — the trait method
421/// deliberately does NOT collide with `serde_json::Value::as_i64` (the
422/// inherent projection on a single `Value` handle) nor with
423/// `serde_json::Value::get` (the inherent slot-lookup returning
424/// `Option<&Value>`). A name collision would let a caller who has
425/// `ValueGetExt` in scope resolve to one of the inherent methods by
426/// accident (inherent methods win over trait methods in method
427/// resolution) and silently drop half of the paired chain. The
428/// `get_i64(<key>)` shape names the intent: look up the slot at
429/// `<key>`, project the returned handle to `i64`, in ONE call.
430///
431/// ### `#[must_use]`
432///
433/// Every consumer either binds the returned `Option<i64>` into a
434/// downstream `if let Some(n) = ...` / `.unwrap_or_default()` / struct-
435/// field construction. Dropping the return silently discards the
436/// projection entirely, which is never the intended semantic at the
437/// three pre-lift consumers (each downstream write depends on the
438/// returned counter).
439///
440/// ### Composability
441///
442/// * Key slot is `&str` — matches every pre-lift `.get("<literal>")`
443///   callsite and the inherent `serde_json::Value::get`'s primary
444///   `str`-index arm. A caller with a runtime-computed key (a
445///   `String` produced by a template composer) reaches through
446///   `.get_i64(&s)` mechanically via `Deref<Target = str>`.
447/// * Returns `Option<i64>` matching the composed inherent chain's own
448///   return; a consumer wanting the "absent or non-integer → 0"
449///   fallback composes `.unwrap_or_default()` (or `.unwrap_or(0)`) at
450///   the callsite, keeping the "should this counter default to 0 or
451///   fail loud" decision at the caller rather than baking it into the
452///   primitive.
453/// * Non-object receivers (a `Value::String`, a `Value::Null`) return
454///   `None` verbatim via the inherent `Value::get`'s own non-object-
455///   arm behaviour, matching the pre-lift chain's semantics on the
456///   corner where the caller's status blob is malformed.
457///
458/// A future normalization — a per-fleet clamp that rejects negative
459/// counters (the K8s API server never emits them, but a fixture
460/// authoring bug could), a `Value::Number` fallback that accepts
461/// `f64` counters truncated to `i64`, a `checked` overflow arm that
462/// promotes an out-of-range integer to a diagnostic rather than a
463/// silent `None` — lands at THIS ONE substrate primitive and every
464/// downstream Job-status / Deployment-replica / HPA-desired-count
465/// counter reader inherits the upgrade mechanically. No per-site edit
466/// at any of the 3 listed callers or at future consumers (a
467/// Deployment `readyReplicas` projection, an HPA `currentReplicas`
468/// gate, a StatefulSet `updatedReplicas` freshness check).
469///
470/// Theory anchor: THEORY.md §VI.1 (generation over composition — the
471/// two-link `.get(<key>).and_then(|v| v.as_i64())` chain recurred at
472/// three hand-authored sites past the ★★ PRIME-DIRECTIVE ≥ 2
473/// duplication trigger, and is lifted to ONE substrate owner here).
474/// THEORY.md §II.1 invariant 5 (composition preserves proofs — a
475/// regression that drifted the projection axis at ONE site — a swap
476/// of `as_i64` for `as_u64` narrowing the accepted range, a swap of
477/// `.get(<key>)` for `.pointer("<key>")` losing the direct-child
478/// semantics — would silently pass every downstream `JobStatusView`
479/// composition and surface as a wrong counter at operator-facing
480/// diagnostic wording; post-lift the projection lives at ONE typed
481/// owner so a regression surfaces at [`tests::get_i64_null_arm_returns_none`]
482/// / peers rather than as silent operator-facing drift).
483pub trait ValueGetExt {
484    /// Look up `key` on this JSON object and project the returned
485    /// handle to `i64`; returns `None` when the slot is absent, when
486    /// the receiver is not a JSON object, or when the slot's variant
487    /// is not integer-shaped.
488    #[must_use = "a JSON i64 projection that isn't bound swallows the counter entirely"]
489    fn get_i64(&self, key: &str) -> Option<i64>;
490
491    /// Look up `key` on this JSON object and project the returned
492    /// handle to `&str`; returns `None` when the slot is absent, when
493    /// the receiver is not a JSON object, or when the slot's variant
494    /// is not `Value::String`.
495    ///
496    /// String-axis sibling of [`Self::get_i64`] on the same
497    /// `.get(<key>).and_then(|v| v.as_<T>())` READ-chain lift. Pre-lift
498    /// the two-link chain was hand-authored at SEVEN production sites
499    /// across two crates past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
500    /// threshold:
501    ///
502    /// * `tatara-process::status::RenderedResourceCoords::from_json`
503    ///   — FOUR paired reads (`apiVersion`, `kind`, `metadata.name`,
504    ///   `metadata.namespace`) that project the four rendered-resource
505    ///   coordinate slots off a `serde_json::Value` rendered manifest
506    ///   into the typed `RenderedResourceCoords` row; the required
507    ///   three (`apiVersion` / `kind` / `metadata.name`) compose with
508    ///   `.ok_or_else(|| anyhow!("rendered resource missing X"))?
509    ///   .to_string()`, and the optional `metadata.namespace` composes
510    ///   with `.map(str::to_string)`.
511    /// * `tatara-reconciler::ssapply::ready_condition_value` — THREE
512    ///   paired reads (`type`, `status`, `message`) inside the
513    ///   condition-walker's per-condition classifier, each pulling a
514    ///   `Value::String` slot off a K8s Condition object off the
515    ///   `status.conditions[]` array.
516    ///
517    /// All seven sites walked the SAME two-link chain — `.get(<key>)`
518    /// on a `serde_json::Value` already known to be an object, then
519    /// `.and_then(|v| v.as_str())` on the returned `Option<&Value>` —
520    /// and each composed different downstream tails (fallible
521    /// `.ok_or_else(...)?.to_string()`, optional `.map(String::from)`,
522    /// pattern-match `Some("True")` / `Some("False")` / `_`). Post-lift
523    /// each callsite reads `<value>.get_str(<key>)` and the two-link
524    /// READ chain lives at ONE substrate owner here.
525    ///
526    /// ### Naming — `get_str`, not `as_str` or `str_at`
527    ///
528    /// Same discipline as [`Self::get_i64`] — the trait method
529    /// deliberately does NOT collide with `serde_json::Value::as_str`
530    /// (the inherent projection on a single `Value` handle) nor with
531    /// `serde_json::Value::get` (the inherent slot-lookup returning
532    /// `Option<&Value>`). A name collision would let a caller who has
533    /// [`ValueGetExt`] in scope resolve to one of the inherent methods
534    /// by accident (inherent methods win over trait methods in method
535    /// resolution) and silently drop half of the paired chain. The
536    /// `get_str(<key>)` shape names the intent: look up the slot at
537    /// `<key>`, project the returned handle to `&str`, in ONE call.
538    ///
539    /// ### `#[must_use]`
540    ///
541    /// Every pre-lift consumer binds the returned `Option<&str>` into
542    /// a downstream `.ok_or_else(...)?.to_string()` / `.map(String::from)`
543    /// / `.map(str::to_string)` / pattern-match arm. Dropping the
544    /// return silently discards the projection entirely, which is
545    /// never the intended semantic at any of the seven pre-lift
546    /// consumers.
547    ///
548    /// ### Return lifetime
549    ///
550    /// The `&str` borrows the same buffer the underlying
551    /// `Value::String` variant owns; the `Option<&str>` is bounded by
552    /// the receiver's lifetime (`&'_ self`), so a caller holding onto
553    /// the returned slice keeps the receiver borrowed. Matches the
554    /// pre-lift chain's own borrow shape (`v.as_str()` borrows through
555    /// the `&Value`).
556    ///
557    /// A future normalization on the projection — a Unicode
558    /// normalization pass (NFC-folding annotation values), a
559    /// per-fleet trim of leading/trailing whitespace, a rejection of
560    /// empty-string arms as "the caller meant absent" — lands at THIS
561    /// ONE substrate primitive and every downstream `apiVersion` /
562    /// `kind` / `metadata.name` / K8s-condition-string reader
563    /// inherits the upgrade mechanically.
564    ///
565    /// Theory anchor: THEORY.md §VI.1 (generation over composition —
566    /// the two-link `.get(<key>).and_then(|v| v.as_str())` chain
567    /// recurred at SEVEN production sites across two crates past the
568    /// ★★ PRIME-DIRECTIVE ≥ 2 duplication trigger, and is lifted to
569    /// ONE substrate owner here on the string axis of the same
570    /// READ-chain axis-family the `get_i64` sibling opened for the
571    /// integer axis). THEORY.md §II.1 invariant 5 (composition
572    /// preserves proofs — a regression that drifted the projection
573    /// axis at ONE site would silently pass every downstream
574    /// composition and surface as a wrong slot at operator-facing
575    /// diagnostic wording; post-lift the projection lives at ONE
576    /// typed owner so a regression surfaces at
577    /// [`tests::get_str_present_string_slot_returns_the_slice`] /
578    /// peers rather than as silent operator-facing drift).
579    #[must_use = "a JSON &str projection that isn't bound swallows the slot entirely"]
580    fn get_str(&self, key: &str) -> Option<&str>;
581
582    /// Look up `key` on this JSON object and project the returned
583    /// handle to `&Vec<Value>`; returns `None` when the slot is
584    /// absent, when the receiver is not a JSON object, or when the
585    /// slot's variant is not `Value::Array`.
586    ///
587    /// Array-axis sibling of [`Self::get_i64`] + [`Self::get_str`]
588    /// on the same `.get(<key>).and_then(|v| v.as_<T>())` READ-chain
589    /// axis-family. Pre-lift the two-link chain was hand-authored at
590    /// TWO production sites across two crates past the ★★
591    /// PRIME-DIRECTIVE ≥ 2 duplication threshold:
592    ///
593    /// * `tatara-reconciler::ssapply::ready_condition_value` — the
594    ///   tail of the `data.get("status").and_then(|s|
595    ///   s.get("conditions")).and_then(|c| c.as_array())` walker that
596    ///   opens the K8s Condition classifier every DynamicObject
597    ///   readiness probe rides through.
598    /// * `tatara-closed-loop-probe::probe::count_jwks_keys` — the
599    ///   JWKS-response walker that counts issuer-side public keys off
600    ///   the `keys` slot for the closed-loop probe's per-run
601    ///   `jwks_key_count` diagnostic.
602    ///
603    /// Both sites walked the SAME two-link chain — `.get(<key>)` on a
604    /// `serde_json::Value` already known to be an object, then
605    /// `.and_then(|v| v.as_array())` on the returned `Option<&Value>`
606    /// — and composed different downstream tails (`Some(conditions)`
607    /// pattern-match on the reconciler side, `.map(|xs| xs.len() as
608    /// u64)` on the probe side). Post-lift each callsite reads
609    /// `<value>.get_array(<key>)` and the two-link READ chain lives at
610    /// ONE substrate owner here. The probe-side variant additionally
611    /// sheds the pre-lift `.get("keys").cloned()` allocation because
612    /// this primitive borrows through the receiver rather than
613    /// cloning.
614    ///
615    /// ### Naming — `get_array`, not `as_array` or `array_at`
616    ///
617    /// Same discipline as [`Self::get_i64`] + [`Self::get_str`] — the
618    /// trait method deliberately does NOT collide with
619    /// `serde_json::Value::as_array` (the inherent projection on a
620    /// single `Value` handle) nor with `serde_json::Value::get` (the
621    /// inherent slot-lookup returning `Option<&Value>`). A name
622    /// collision would let a caller who has [`ValueGetExt`] in scope
623    /// resolve to one of the inherent methods by accident (inherent
624    /// methods win over trait methods in method resolution) and
625    /// silently drop half of the paired chain. The `get_array(<key>)`
626    /// shape names the intent: look up the slot at `<key>`, project
627    /// the returned handle to `&Vec<Value>`, in ONE call.
628    ///
629    /// ### `#[must_use]`
630    ///
631    /// Every pre-lift consumer binds the returned `Option<&Vec<Value>>`
632    /// into a downstream `let Some(...) = ... else { return ... }`
633    /// short-circuit or a `.map(|xs| xs.len() as u64).unwrap_or(0)`
634    /// counter composition. Dropping the return silently discards the
635    /// projection entirely, which is never the intended semantic at
636    /// either pre-lift consumer.
637    ///
638    /// ### Return lifetime
639    ///
640    /// The `&Vec<Value>` borrows the same buffer the underlying
641    /// `Value::Array` variant owns; the `Option<&Vec<Value>>` is
642    /// bounded by the receiver's lifetime (`&'_ self`), so a caller
643    /// iterating the returned slice keeps the receiver borrowed.
644    /// Matches the pre-lift chain's own borrow shape (`v.as_array()`
645    /// borrows through the `&Value`), and in the probe.rs case
646    /// eliminates the pre-lift `.cloned()` on the intermediate
647    /// `Value` that only existed to sidestep the borrow.
648    ///
649    /// A future normalization on the projection — a rejection of
650    /// empty arrays as "the caller meant absent", an accept-scalar
651    /// coercion (a `Value::String` promoted to a one-element array),
652    /// a per-fleet cap on array length that short-circuits pathological
653    /// payloads — lands at THIS ONE substrate primitive and every
654    /// downstream K8s-Condition classifier / JWKS-array counter /
655    /// future array-slot reader inherits the upgrade mechanically.
656    ///
657    /// Theory anchor: THEORY.md §VI.1 (generation over composition —
658    /// the two-link `.get(<key>).and_then(|v| v.as_array())` chain
659    /// recurred at two production sites across two crates past the ★★
660    /// PRIME-DIRECTIVE ≥ 2 duplication trigger, and is lifted to ONE
661    /// substrate owner here on the array axis of the same READ-chain
662    /// axis-family the `get_i64` + `get_str` siblings already own).
663    /// THEORY.md §II.1 invariant 5 (composition preserves proofs — a
664    /// regression that drifted the projection axis at ONE site would
665    /// silently pass every downstream composition and surface as a
666    /// wrong slot at operator-facing diagnostic wording; post-lift the
667    /// projection lives at ONE typed owner so a regression surfaces
668    /// at [`tests::get_array_present_array_slot_returns_the_slice`] /
669    /// peers rather than as silent operator-facing drift).
670    #[must_use = "a JSON array projection that isn't bound swallows the slot entirely"]
671    fn get_array(&self, key: &str) -> Option<&Vec<Value>>;
672
673    /// Look up `key` on this JSON object and project the returned
674    /// handle to `bool`; returns `None` when the slot is absent, when
675    /// the receiver is not a JSON object, or when the slot's variant
676    /// is not `Value::Bool`.
677    ///
678    /// Boolean-axis sibling of [`Self::get_i64`] / [`Self::get_str`] /
679    /// [`Self::get_array`] on the same `.get(<key>).and_then(|v|
680    /// v.as_<T>())` READ-chain axis-family. Completes the axis-family
681    /// coverage over the four most-common `Value` scalar / collection
682    /// shapes an operator reads out of a K8s status / spec blob or a
683    /// rendered-resource JSON object: integer counter (`succeeded`,
684    /// `failed`, `active`, `replicas`), string slot (`apiVersion`,
685    /// `kind`, `metadata.name`, `type`, `status`, `message`), array
686    /// slot (`conditions`, `finalizers`, `keys`), and boolean flag
687    /// (`controller`, `blockOwnerDeletion`, `spec.suspended`,
688    /// `hostNetwork`, `automountServiceAccountToken`,
689    /// `identity.name_override`).
690    ///
691    /// The axis was named as the next extension point in the
692    /// `ValueGetExt` docstring's own guidance ("Adding a new axis
693    /// (a `get_bool` for `Value::Bool`, …) lands as ONE new method
694    /// here + ONE impl arm"), and this method opens it. A future
695    /// consumer walking a `blockOwnerDeletion` / `controller` bit off
696    /// a K8s OwnerReference JSON, an `identity.name_override` flag off
697    /// a `phase_status_with(phase, "identity", …)` patch body, or a
698    /// `spec.suspended` gate off a SIGSTOP-driven spec toggle reaches
699    /// this substrate rather than re-authoring the two-link
700    /// `.get(<key>).and_then(|v| v.as_bool())` chain by hand.
701    ///
702    /// ### Naming — `get_bool`, not `as_bool` or `bool_at`
703    ///
704    /// Same discipline as the three sibling axes — the trait method
705    /// deliberately does NOT collide with `serde_json::Value::as_bool`
706    /// (the inherent projection on a single `Value` handle) nor with
707    /// `serde_json::Value::get` (the inherent slot-lookup returning
708    /// `Option<&Value>`). A name collision would let a caller who has
709    /// [`ValueGetExt`] in scope resolve to one of the inherent methods
710    /// by accident (inherent methods win over trait methods in method
711    /// resolution) and silently drop half of the paired chain. The
712    /// `get_bool(<key>)` shape names the intent: look up the slot at
713    /// `<key>`, project the returned handle to `bool`, in ONE call.
714    ///
715    /// ### `#[must_use]`
716    ///
717    /// Every consumer either binds the returned `Option<bool>` into a
718    /// downstream `if let Some(b) = ...` gate, a
719    /// `.unwrap_or_default()` / `.unwrap_or(false)` fallback, or a
720    /// pattern-match arm. Dropping the return silently discards the
721    /// projection entirely, which is never the intended semantic at
722    /// any downstream boolean-flag consumer.
723    ///
724    /// ### Composability
725    ///
726    /// * Key slot is `&str` — matches the sibling axes verbatim;
727    ///   `&'static str` literals and runtime-composed `String`
728    ///   handles both coerce.
729    /// * Returns `Option<bool>` matching the composed inherent chain's
730    ///   own return; a consumer wanting the "absent or non-bool →
731    ///   false" fallback composes `.unwrap_or_default()` (or
732    ///   `.unwrap_or(false)`) at the callsite, keeping the "should
733    ///   this flag default to false or fail loud" decision at the
734    ///   caller rather than baking it into the primitive.
735    /// * Non-object receivers (a `Value::String`, a `Value::Null`)
736    ///   return `None` verbatim via the inherent `Value::get`'s own
737    ///   non-object-arm behaviour, matching the pre-lift chain's
738    ///   semantics on the corner where the caller's status blob is
739    ///   malformed.
740    ///
741    /// A future normalization on the projection — a stricter
742    /// `Value::String("true")` / `Value::String("false")` coercion for
743    /// K8s wire-form drift (K8s occasionally serialises booleans as
744    /// stringified values in edge cases), a per-fleet default policy
745    /// for the absent-slot corner, a `checked` corner that fails loud
746    /// on `Value::Number(0)` / `Value::Number(1)` coercion attempts —
747    /// lands at THIS ONE substrate primitive and every downstream
748    /// boolean-flag reader inherits the upgrade mechanically. No
749    /// per-site edit at any consumer that adopts this primitive.
750    ///
751    /// Theory anchor: THEORY.md §II.1 invariant 5 (composition
752    /// preserves proofs — the projection lives at ONE typed owner on
753    /// the same axis-family the three sibling axes already open; a
754    /// regression that drifted the projection axis at ONE site would
755    /// silently pass every downstream composition and surface as a
756    /// wrong flag at operator-facing gate wording). THEORY.md §III
757    /// (typescape — the axis-family completes coverage over the four
758    /// most-common `Value` shapes any K8s status / spec / manifest
759    /// reader projects, so a caller who imports `ValueGetExt` reaches
760    /// integer counters, string slots, array slots, AND boolean
761    /// flags through ONE trait handle).
762    #[must_use = "a JSON bool projection that isn't bound swallows the flag entirely"]
763    fn get_bool(&self, key: &str) -> Option<bool>;
764}
765
766impl ValueGetExt for Value {
767    #[inline]
768    fn get_i64(&self, key: &str) -> Option<i64> {
769        self.get(key).and_then(Value::as_i64)
770    }
771
772    #[inline]
773    fn get_str(&self, key: &str) -> Option<&str> {
774        self.get(key).and_then(Value::as_str)
775    }
776
777    #[inline]
778    fn get_array(&self, key: &str) -> Option<&Vec<Value>> {
779        self.get(key).and_then(Value::as_array)
780    }
781
782    #[inline]
783    fn get_bool(&self, key: &str) -> Option<bool> {
784        self.get(key).and_then(Value::as_bool)
785    }
786}
787
788/// Receiver-shape widening of the READ-projection axis-family — the
789/// same three methods extended from `Value` (the `Value::Object` arm's
790/// walker) to `Map<String, Value>` (the object interior itself),
791/// closing the receiver-shape gap so a caller who already holds an
792/// `&Map<String, Value>` handle (via `.as_object().unwrap()`, via
793/// [`ValueObjectExt::as_object_mut_or`], via the two `JsonMap*Ext`
794/// siblings' returns, or via a helper like `ssapply::ownership_kv_pair`
795/// that composes and returns a `Map` directly) reaches the SAME
796/// `get_i64` / `get_str` / `get_array` methods without a
797/// `Value::Object(m)` rewrap detour.
798///
799/// Pre-lift the `.get(<key>).and_then(Value::as_<T>)` two-link chain
800/// was hand-authored at 30 `Map<String, Value>`-receiver sites past the
801/// ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold — 19 in
802/// `tatara-reconciler::patch` tests (the phase-status wire-shape pins
803/// walking `obj = v.as_object().unwrap()` and `metadata = obj.get(
804/// "metadata").and_then(Value::as_object).unwrap()` receivers) plus
805/// 11 in `tatara-reconciler::ssapply` tests (the ownership-tag +
806/// composed-coord pins walking the `Map` handles returned by
807/// `ownership_annotations` / `ownership_labels` /
808/// `ownership_annotations_by_coord`). Post-lift each callsite reads
809/// `<map>.get_str(<key>)` / `<map>.get_array(<key>)` and the READ
810/// chain rides through the SAME substrate owner the `Value`-receiver
811/// callers already threaded through.
812///
813/// The axis-family invariant (a caller who imports `ValueGetExt`
814/// reaches every axis through the same trait handle — pinned at
815/// [`tests::get_array_axis_family_reaches_i64_str_and_array_through_one_trait_import`],
816/// its `get_str` sibling, and the fourth-axis sibling
817/// [`tests::get_bool_axis_family_reaches_i64_str_array_and_bool_through_one_trait_import`])
818/// extends verbatim to the `Map` receiver: a single
819/// `use tatara_process::json_object::ValueGetExt;` unlocks every
820/// axis on both receiver shapes. A future new axis (e.g. a
821/// `get_object` for `Value::Object` slots, a `get_f64` for
822/// `Value::Number` truncated to `f64`) adds one method on the trait
823/// and inherits both impls; there is no separate `MapGetExt` peer to
824/// keep in sync.
825///
826/// Theory anchor: THEORY.md §VI.1 (generation over composition — the
827/// two-link chain recurred at 30 `Map`-receiver sites past the ★★
828/// PRIME-DIRECTIVE ≥ 2 duplication trigger, and rides through the
829/// same substrate owner the pre-existing `Value`-receiver impl above
830/// already pinned). THEORY.md §II.1 invariant 5 (composition preserves
831/// proofs — the receiver-shape widening carries the axis-family
832/// invariant across without splitting it into two traits).
833impl ValueGetExt for Map<String, Value> {
834    #[inline]
835    fn get_i64(&self, key: &str) -> Option<i64> {
836        self.get(key).and_then(Value::as_i64)
837    }
838
839    #[inline]
840    fn get_str(&self, key: &str) -> Option<&str> {
841        self.get(key).and_then(Value::as_str)
842    }
843
844    #[inline]
845    fn get_array(&self, key: &str) -> Option<&Vec<Value>> {
846        self.get(key).and_then(Value::as_array)
847    }
848
849    #[inline]
850    fn get_bool(&self, key: &str) -> Option<bool> {
851        self.get(key).and_then(Value::as_bool)
852    }
853}
854
855/// Receiver-shape widening of the READ-projection axis-family — the
856/// same four methods extended from `Value` / `Map<String, Value>` to
857/// `Option<&Value>`, closing the outer-optionality gap so a caller who
858/// has already threaded an inherent `Value::get(<key>) → Option<&Value>`
859/// walk into a nested slot (or otherwise holds an `Option<&Value>` from
860/// a prior projection) reaches the SAME `get_i64` / `get_str` /
861/// `get_array` / `get_bool` methods through the SAME trait handle
862/// without an intermediate `.and_then(|v| v.get_<T>(<key>))` closure.
863///
864/// Pre-lift the `<opt>.and_then(|v| v.get_<T>(<key>))` outer-
865/// optionality closure was hand-authored at THREE production callsites
866/// past the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold:
867///
868/// * `tatara-process::status::RenderedResourceCoords::from_json` — the
869///   `metadata.and_then(|m| m.get_str("namespace"))` walk that projects
870///   the optional `metadata.namespace` slot off a rendered manifest's
871///   `metadata` handle (`metadata: Option<&Value>`, since a K8s
872///   manifest MAY omit the `metadata` slot altogether — a cluster-
873///   scoped resource, a template-authored intermediate spec).
874/// * `tatara-process::status::RenderedResourceCoords::required_str` —
875///   the private required-extract helper's `v.and_then(|x|
876///   x.get_str(key))` walk (`v: Option<&Value>`), the sink every
877///   `apiVersion` / `kind` / `metadata.name` required extract fans
878///   through.
879/// * `tatara-reconciler::ssapply::ready_condition_value` — the
880///   `data.get("status").and_then(|s| s.get_array("conditions"))` walk
881///   that opens the K8s Condition classifier every DynamicObject
882///   readiness probe rides through; the outer `Option<&Value>` comes
883///   from the inherent `Value::get("status")` step.
884///
885/// All three sites walked the SAME `.and_then(|<v>| <v>.get_<T>
886/// (<key>))` closure shape, differing only in the axis (`get_str` at
887/// two sites, `get_array` at the third), the slot name, and the
888/// closure-argument binding. Post-lift each callsite reads `<opt>
889/// .get_<T>(<key>)` and the outer-optionality unwrap-then-project
890/// lives at ONE substrate owner here — the closure disappears, the
891/// method-call surface stays identical to the two pre-existing
892/// receiver-shape impls.
893///
894/// ### Composability
895///
896/// * Chains directly off an inherent `Value::get(<key>)` step —
897///   `data.get("status").get_array("conditions")` reads as one
898///   left-to-right walk, no nested closure.
899/// * Composes bytewise with the pre-lift `.and_then(|v| v.get_<T>
900///   (<key>))` chain — the impl body IS `(*self).and_then(|v|
901///   v.get_<T>(key))`, so the returned `Option` is bit-for-bit what
902///   the pre-lift closure produced.
903/// * `Option<&Value>` is `Copy` (every `&T` is `Copy`, so
904///   `Option<&Value>: Copy`), so the `(*self)` deref inside the impl
905///   is a bare bitwise copy — no clone, no additional allocation.
906///
907/// ### Return lifetime
908///
909/// The returned `Option<&str>` / `Option<&Vec<Value>>` borrows through
910/// the underlying `&Value` handle that lived inside the outer `Option`;
911/// the lifetime is bounded by `&self` (elided per the trait method
912/// signatures), matching the two pre-existing receiver impls. A caller
913/// that consumes the borrow before the outer `Option<&Value>` handle
914/// expires sees no observable difference in borrow scope from the
915/// pre-lift `.and_then(|v| v.get_<T>(<key>))` chain.
916///
917/// ### Axis-family invariant
918///
919/// The axis-family invariant carries verbatim from the two pre-existing
920/// impls: a single `use tatara_process::json_object::ValueGetExt;`
921/// unlocks every axis (`get_i64` / `get_str` / `get_array` / `get_bool`)
922/// on all three receiver shapes (`Value`, `Map<String, Value>`,
923/// `Option<&Value>`). A future new axis (`get_object` for
924/// `Value::Object` slots, `get_f64` for `Value::Number` truncated to
925/// `f64`) adds ONE method on the trait and inherits all three impls;
926/// there is no separate `OptGetExt` peer to keep in sync. Pinned at
927/// [`tests::option_ref_value_axis_family_reaches_all_four_axes_through_one_trait_import`].
928///
929/// Theory anchor: THEORY.md §VI.1 (generation over composition — the
930/// outer-optionality `.and_then(|v| v.get_<T>(<key>))` closure recurred
931/// at three production sites past the ★★ PRIME-DIRECTIVE ≥ 2
932/// duplication trigger, and is lifted to ONE substrate owner here).
933/// THEORY.md §II.1 invariant 5 (composition preserves proofs — the
934/// receiver-shape widening carries the axis-family invariant across
935/// without splitting it into two traits; a regression that specialised
936/// one axis at ONE receiver but not the other would silently split the
937/// three receiver shapes' behaviour and break the "widening preserves
938/// semantics" invariant at
939/// [`tests::option_ref_value_get_str_matches_value_arm_bytewise_when_some`]
940/// and its per-axis peers).
941impl ValueGetExt for Option<&Value> {
942    #[inline]
943    fn get_i64(&self, key: &str) -> Option<i64> {
944        (*self).and_then(|v| v.get_i64(key))
945    }
946
947    #[inline]
948    fn get_str(&self, key: &str) -> Option<&str> {
949        (*self).and_then(|v| v.get_str(key))
950    }
951
952    #[inline]
953    fn get_array(&self, key: &str) -> Option<&Vec<Value>> {
954        (*self).and_then(|v| v.get_array(key))
955    }
956
957    #[inline]
958    fn get_bool(&self, key: &str) -> Option<bool> {
959        (*self).and_then(|v| v.get_bool(key))
960    }
961}
962
963#[cfg(test)]
964mod tests {
965    use super::*;
966    use serde_json::json;
967
968    // ─── ValueObjectExt::as_object_mut_or substrate pins ─────────────
969    //
970    // Fail-before-pass-after granularity: the `ValueObjectExt::
971    // as_object_mut_or` trait method did not exist before this commit,
972    // so each test below fails to compile pre-lift. Post-lift they
973    // collectively pin the object-guard shape at ONE substrate owner —
974    // a regression that drifts the error message wording, swaps the
975    // `<slot>` slot, wraps the source in a chain-form `source` (which
976    // would change `Display` output when downstream tracing formatters
977    // interpolate `{e}` rather than the chain-walking `{e:#}`), or
978    // promotes the pass-through arm to synthesis (a `None → Ok(&mut
979    // Map::default())` fallthrough that silently swallows a mistyped
980    // slot) surfaces HERE rather than as silent operator-facing skew
981    // across the three `ssapply.rs` pre-lift consumers whose log
982    // output already encoded the flat `"<slot> is not an object"`
983    // shape.
984
985    #[test]
986    fn as_object_mut_or_object_arm_returns_the_inner_map_mutably() {
987        // Ok-arm invariant: a `Value::Object` handle threaded through
988        // `as_object_mut_or("<slot>")` MUST return `Ok(&mut Map)`
989        // whose interior is the SAME `serde_json::Map` the underlying
990        // `serde_json::Value::as_object_mut` would return — no clone,
991        // no reshape, no synthesis. The `&mut` return is load-bearing
992        // at every consumer (each threads a downstream `.entry(...).
993        // or_insert_with(...)` / `.insert(...)` mutation onto the
994        // returned reference), so a regression that returned a fresh
995        // owned `Map` here would silently drop every downstream write.
996        let mut v = json!({ "existing_key": "existing_value" });
997        let map = v.as_object_mut_or("resource").expect("Value::Object");
998        map.insert("new_key".to_string(), json!("new_value"));
999        assert_eq!(v["existing_key"], "existing_value");
1000        assert_eq!(v["new_key"], "new_value");
1001    }
1002
1003    #[test]
1004    fn as_object_mut_or_null_arm_errors_with_pre_lift_display_bytewise() {
1005        // Byte-shape parity pin: the wrap output of `as_object_mut_or
1006        // ("<slot>")` on a `Value::Null` handle MUST be `Display`-
1007        // identical to the pre-lift hand-authored `.as_object_mut().
1008        // ok_or_else(|| anyhow!("<slot> is not an object"))?` chain.
1009        // A regression that inserted a synonym (`"<slot> is not a
1010        // JSON object"`), reshaped the slot position (`"not an
1011        // object: <slot>"`), or dropped the leading `<slot>` slot
1012        // surfaces HERE rather than as silent drift at every
1013        // downstream log-output consumer.
1014        let mut v = Value::Null;
1015        let err = v.as_object_mut_or("resource").unwrap_err();
1016        assert_eq!(format!("{err}"), "resource is not an object");
1017    }
1018
1019    #[test]
1020    fn as_object_mut_or_array_arm_errors_with_pre_lift_display_bytewise() {
1021        // Sibling to the null-arm byte-shape pin — a mistyped
1022        // `metadata` slot authored as a JSON array (kubectl accepts
1023        // `metadata: []` in a YAML manifest with no schema, though the
1024        // apiserver later rejects it) surfaces the same guard error.
1025        // Pins the "non-object variants ALL error via the same wire
1026        // format" invariant — a regression that special-cased the
1027        // array variant (returning a fresh empty map, silently
1028        // coercing) surfaces HERE.
1029        let mut v = json!(["not", "an", "object"]);
1030        let err = v.as_object_mut_or("metadata").unwrap_err();
1031        assert_eq!(format!("{err}"), "metadata is not an object");
1032    }
1033
1034    #[test]
1035    fn as_object_mut_or_string_arm_errors_with_pre_lift_display_bytewise() {
1036        // Sibling to the null / array pins — a mistyped `annotations`
1037        // slot authored as a JSON string (a common apiserver-layer
1038        // authoring bug in kubectl-generated manifests where a
1039        // stringified JSON object leaks through) surfaces the same
1040        // guard error. Pins the "every non-object variant errors via
1041        // the same wire format" invariant across the full
1042        // `serde_json::Value` sum.
1043        let mut v = json!("stringified");
1044        let err = v.as_object_mut_or("annotations").unwrap_err();
1045        assert_eq!(format!("{err}"), "annotations is not an object");
1046    }
1047
1048    #[test]
1049    fn as_object_mut_or_threads_the_slot_slug_verbatim_across_all_three_pre_lift_labels() {
1050        // Cross-slot coherence pin: the three pre-lift consumers in
1051        // `tatara-reconciler::ssapply` stamped THREE distinct slot
1052        // slugs (`"resource"` / `"metadata"` / `"annotations"`), and
1053        // the wrap-shape MUST honor each one verbatim as the leading
1054        // slot in the `Display` output. A regression that hard-coded
1055        // one slug (say `"resource"`) across every callsite would
1056        // pass the first pin above and fail HERE — the three
1057        // downstream error-stream greps operators run to bisect a
1058        // "which SSA-time mutation faulted" alert would ALL collapse
1059        // to the same slug.
1060        for slot in ["resource", "metadata", "annotations"] {
1061            let mut v = Value::Null;
1062            let err = v.as_object_mut_or(slot).unwrap_err();
1063            assert_eq!(format!("{err}"), format!("{slot} is not an object"));
1064        }
1065    }
1066
1067    #[test]
1068    fn as_object_mut_or_object_arm_matches_inherent_as_object_mut_bytewise() {
1069        // Cross-substrate coherence pin: on the Ok arm the trait
1070        // method MUST return the SAME `&mut Map` the inherent
1071        // `serde_json::Value::as_object_mut` would — no diverging
1072        // view, no clone, no key-order reshape. A regression that
1073        // introduced a normalization pass here (sorting keys,
1074        // stripping a null-valued entry, coercing a nested string
1075        // to a JSON scalar) would surface as silent per-consumer
1076        // schema drift at the SSA-time mutation — an ownerReferences
1077        // append that no longer landed in the same slot the apiserver
1078        // reads, an annotations insert whose key ordering diverged
1079        // from kubectl's canonical form.
1080        let mut via_trait = json!({ "key": "value", "nested": { "inner": 1 } });
1081        let mut via_inherent = via_trait.clone();
1082        assert_eq!(
1083            via_trait
1084                .as_object_mut_or("resource")
1085                .expect("Value::Object")
1086                .clone(),
1087            via_inherent.as_object_mut().expect("Value::Object").clone(),
1088        );
1089    }
1090
1091    // ─── JsonMapStrExt::insert_str substrate pins ─────────────────
1092    //
1093    // Fail-before-pass-after granularity: the `JsonMapStrExt::insert_str`
1094    // trait method did not exist before this commit, so each test below
1095    // fails to compile pre-lift. Post-lift they collectively pin the
1096    // string-slot write shape at ONE substrate owner — a regression that
1097    // dropped the `Value::String` wrap (silently coercing to a bare
1098    // `Value::from(&str)` — byte-identical in the `Object` arm today but
1099    // divergent for any future non-`&str` numeric caller who reached for
1100    // `insert_str(k, n.to_string())`), swapped the key + value slot
1101    // orientation, or drifted the return semantics from the inherent
1102    // `Map::insert` (which returns the previous value on overwrite —
1103    // load-bearing at any future caller that inspects the return) would
1104    // surface HERE rather than as silent per-emit skew across the
1105    // thirteen pre-lift `ssapply` + `render` + `edges` consumers.
1106
1107    #[test]
1108    fn insert_str_new_key_returns_none_and_stamps_value_string() {
1109        // New-key arm: matches inherent `Map::insert` return
1110        // semantics — `None` for a fresh key — and stamps a
1111        // `Value::String` (NOT `Value::from(&str)`, though they're
1112        // byte-identical today) at the slot.
1113        let mut m = Map::new();
1114        let prev = m.insert_str("key", "value");
1115        assert!(prev.is_none(), "new key returns None");
1116        assert_eq!(m.get("key"), Some(&Value::String("value".to_string())));
1117        assert!(matches!(m.get("key"), Some(Value::String(_))));
1118    }
1119
1120    #[test]
1121    fn insert_str_overwrite_returns_prior_value_and_stamps_new() {
1122        // Overwrite arm: matches inherent `Map::insert` return
1123        // semantics — `Some(prev)` on overwrite. Load-bearing for
1124        // any future consumer that inspects the return to detect a
1125        // slot collision (a fleet-wide sweep that flagged a
1126        // duplicate SSA-time annotation stamp, for example).
1127        let mut m = Map::new();
1128        m.insert_str("key", "old");
1129        let prev = m.insert_str("key", "new");
1130        assert_eq!(prev, Some(Value::String("old".to_string())));
1131        assert_eq!(m.get("key"), Some(&Value::String("new".to_string())));
1132    }
1133
1134    #[test]
1135    fn insert_str_accepts_str_and_owned_string_at_both_slots() {
1136        // Composability pin: both slots MUST accept `&str` and
1137        // `String` interchangeably — the pre-lift callsite inventory
1138        // mixes both (SSA-time `annotations::PID` static + a
1139        // `pid.to_string()` runtime String at the value slot;
1140        // `spec.insert("interval".into(), Value::String("1m".into()))`
1141        // with two `&str` slots). A regression that constrained
1142        // either slot to one shape would break the callsite parity
1143        // that motivated this substrate primitive.
1144        let mut m1 = Map::new();
1145        m1.insert_str("a", "b");
1146        let mut m2 = Map::new();
1147        m2.insert_str(String::from("a"), String::from("b"));
1148        let mut m3 = Map::new();
1149        m3.insert_str("a", String::from("b"));
1150        let mut m4 = Map::new();
1151        m4.insert_str(String::from("a"), "b");
1152        assert_eq!(m1, m2);
1153        assert_eq!(m2, m3);
1154        assert_eq!(m3, m4);
1155    }
1156
1157    #[test]
1158    fn insert_str_matches_pre_lift_hand_authored_shape_bytewise() {
1159        // Byte-shape parity pin: `insert_str(k, v)` MUST emit the
1160        // SAME `Map` entry the pre-lift hand-authored `.insert(
1161        // <k>.into(), Value::String(<v>.into()))` chain produced.
1162        // Sweeps the four (str × String) × (str × String) key/value
1163        // shape quadrants so a regression at the primitive that
1164        // broke the byte identity with the pre-lift shape at ONE
1165        // quadrant surfaces here rather than as a subtle per-emit
1166        // divergence at that quadrant.
1167        for (k_str, v_str) in [("a", "b"), ("x", ""), ("", "y"), ("", "")] {
1168            // (str, str) quadrant
1169            let mut via_primitive = Map::new();
1170            via_primitive.insert_str(k_str, v_str);
1171            let mut via_pre_lift = Map::new();
1172            via_pre_lift.insert(k_str.into(), Value::String(v_str.into()));
1173            assert_eq!(via_primitive, via_pre_lift);
1174
1175            // (String, String) quadrant
1176            let mut via_primitive = Map::new();
1177            via_primitive.insert_str(String::from(k_str), String::from(v_str));
1178            let mut via_pre_lift = Map::new();
1179            via_pre_lift.insert(String::from(k_str), Value::String(String::from(v_str)));
1180            assert_eq!(via_primitive, via_pre_lift);
1181        }
1182    }
1183
1184    #[test]
1185    fn insert_str_empty_value_stamps_empty_string_not_null() {
1186        // Semantic pin: an empty value slot MUST stamp
1187        // `Value::String("")`, NEVER `Value::Null`. Load-bearing at
1188        // any callsite that stamps a placeholder empty-string
1189        // annotation (say a `content_hash` slot pre-derive) where a
1190        // `Null` slot would fail-loud at the K8s apiserver's
1191        // annotation-value type check.
1192        let mut m = Map::new();
1193        m.insert_str("empty", "");
1194        assert_eq!(m.get("empty"), Some(&Value::String(String::new())));
1195        assert!(!matches!(m.get("empty"), Some(Value::Null)));
1196    }
1197
1198    // ─── JsonMapObjectEntryExt::object_slot_mut_or substrate pins ─────
1199    //
1200    // Fail-before-pass-after granularity: the
1201    // `JsonMapObjectEntryExt::object_slot_mut_or` trait method did not
1202    // exist before this commit, so each test below fails to compile
1203    // pre-lift. Post-lift they collectively pin the compound
1204    // seed-then-guard shape at ONE substrate owner — a regression that
1205    // dropped the seed step (leaving an absent slot to fall through the
1206    // guard as `None → Err`), skipped the guard step (silently returning
1207    // an `&mut Value` when the existing slot is a non-object variant),
1208    // drifted the entry-key slot vs. the guard-error slot (a copy-paste
1209    // typo that stamped `"metadata"` into the entry and `"metadatas"`
1210    // into the guard error message), or drifted the empty-seed shape
1211    // (a `Value::Null` fallback where `Value::Object(Map::new())` is
1212    // load-bearing at the downstream `.entry(...).or_insert_with(...)`
1213    // / `.insert(...)` mutation) would surface HERE rather than as
1214    // silent per-emit skew across the two pre-lift `ssapply.rs`
1215    // consumers.
1216
1217    #[test]
1218    fn object_slot_mut_or_absent_slot_seeds_empty_object_and_returns_it() {
1219        // Absent-slot arm: the pre-lift `.entry(<slot>).or_insert_with
1220        // (|| Value::Object(Default::default()))` step MUST seed the
1221        // slot with an EMPTY `Value::Object` when the slot is not
1222        // present in the parent map. The returned handle is the fresh
1223        // empty map, MUTABLY, so a downstream `.insert(...)` writes
1224        // land in the parent map's `<slot>` object post-return.
1225        let mut parent = Map::new();
1226        {
1227            let child = parent
1228                .object_slot_mut_or("metadata")
1229                .expect("absent slot seeds an object");
1230            assert!(child.is_empty(), "fresh-seeded slot is an empty object");
1231            child.insert("name".into(), Value::String("demo".into()));
1232        }
1233        // The write landed in the parent map's metadata slot.
1234        assert_eq!(parent["metadata"]["name"], "demo");
1235        assert!(matches!(parent.get("metadata"), Some(Value::Object(_))));
1236    }
1237
1238    #[test]
1239    fn object_slot_mut_or_present_object_slot_returns_existing_interior_mutably() {
1240        // Present-object-slot arm: when the slot is already populated
1241        // with a `Value::Object`, the primitive MUST return the
1242        // EXISTING map interior mutably — no synthesis, no reshape, no
1243        // key-order rewrite. The downstream `.insert(...)` writes MUST
1244        // merge into the pre-existing keys rather than replace them.
1245        let mut parent = Map::new();
1246        parent.insert(
1247            "metadata".into(),
1248            serde_json::json!({ "existing_key": "existing_value" }),
1249        );
1250        {
1251            let child = parent
1252                .object_slot_mut_or("metadata")
1253                .expect("present-object slot returns Ok");
1254            assert_eq!(
1255                child.get("existing_key"),
1256                Some(&Value::String("existing_value".into()))
1257            );
1258            child.insert("new_key".into(), Value::String("new_value".into()));
1259        }
1260        assert_eq!(parent["metadata"]["existing_key"], "existing_value");
1261        assert_eq!(parent["metadata"]["new_key"], "new_value");
1262    }
1263
1264    #[test]
1265    fn object_slot_mut_or_present_non_object_slot_errors_with_pre_lift_display() {
1266        // Fail-loud arm: when the slot is present but holds a non-
1267        // object variant (a `Value::String` from a hand-authored
1268        // YAML manifest where `metadata: "malformed"` slipped past
1269        // kubectl's schema check), the primitive MUST fail with a
1270        // `Display` byte-identical to the pre-lift
1271        // `.as_object_mut_or(<slot>)?` guard — the sibling
1272        // [`ValueObjectExt::as_object_mut_or`] guard's wire format.
1273        // A regression that special-cased this arm (overwriting the
1274        // slot with a fresh empty object, silently coercing) would
1275        // silently swallow the operator's authoring error at the
1276        // SSA-time re-injection step.
1277        let mut parent = Map::new();
1278        parent.insert("metadata".into(), Value::String("malformed".into()));
1279        let err = parent.object_slot_mut_or("metadata").unwrap_err();
1280        assert_eq!(format!("{err}"), "metadata is not an object");
1281    }
1282
1283    #[test]
1284    fn object_slot_mut_or_threads_the_slot_slug_verbatim_across_both_pre_lift_labels() {
1285        // Cross-slot coherence pin: the TWO pre-lift consumers in
1286        // `tatara-reconciler::ssapply` stamped TWO distinct slot slugs
1287        // (`"metadata"` at the resource root, `"annotations"` at the
1288        // metadata child), and the wrap-shape MUST honor each one
1289        // verbatim as the leading slot in the `Display` output. A
1290        // regression that hard-coded one slug across every callsite
1291        // would pass the fail-loud pin above (on the `"metadata"` slug)
1292        // and fail HERE — the two downstream error-stream greps
1293        // operators run to bisect a "which SSA-time slot mutation
1294        // faulted" alert would ALL collapse to the same slug, hiding
1295        // whether the fault was at the resource-root object walk or
1296        // the metadata-child annotations walk.
1297        for slot in ["metadata", "annotations"] {
1298            let mut parent = Map::new();
1299            parent.insert(slot.into(), Value::Null);
1300            let err = parent.object_slot_mut_or(slot).unwrap_err();
1301            assert_eq!(format!("{err}"), format!("{slot} is not an object"));
1302        }
1303    }
1304
1305    #[test]
1306    fn object_slot_mut_or_present_empty_object_returns_existing_reference_not_synthesized() {
1307        // Precedence pin: a present slot holding an EMPTY
1308        // `Value::Object` MUST return the pre-existing empty map
1309        // interior — not a freshly-synthesized replacement. The
1310        // pre-lift `.entry(<slot>).or_insert_with(||...)` step's
1311        // short-circuit on the present-slot arm skips the closure
1312        // entirely; a regression that always evaluated the closure
1313        // (unconditionally overwriting an existing empty-object slot
1314        // with a fresh empty object) would type-check silently at
1315        // every callsite AND write byte-identical JSON at the empty-
1316        // slot corner, but it would break a hypothetical future
1317        // consumer that reached the primitive on a map whose slot
1318        // was seeded upstream with metadata (a caller intending to
1319        // preserve any keys the parent-composer already dropped in).
1320        let mut parent = Map::new();
1321        parent.insert("metadata".into(), Value::Object(Map::new()));
1322        let addr_before = parent.get("metadata").unwrap() as *const Value;
1323        {
1324            let _child = parent.object_slot_mut_or("metadata").unwrap();
1325        }
1326        let addr_after = parent.get("metadata").unwrap() as *const Value;
1327        assert_eq!(
1328            addr_before, addr_after,
1329            "present empty-object slot must return the pre-existing reference, not a fresh synthesis",
1330        );
1331    }
1332
1333    #[test]
1334    fn object_slot_mut_or_matches_pre_lift_hand_authored_compound_shape_bytewise() {
1335        // Byte-shape parity pin: `object_slot_mut_or(<slot>)?` MUST
1336        // produce the SAME `&mut Map` (and, on the non-object arm, the
1337        // SAME `Display`-shaped error) the pre-lift 3-line `.entry
1338        // (<slot>).or_insert_with(|| Value::Object(Default::default()))
1339        // .as_object_mut_or(<slot>)?` chain produced. Sweeps the three
1340        // pre-lift-reachable input corners (absent slot / present
1341        // object / present non-object) so a regression at the primitive
1342        // that broke byte identity with the pre-lift chain at ONE
1343        // corner surfaces here rather than as a subtle per-emit
1344        // divergence.
1345        for slot in ["metadata", "annotations"] {
1346            // (1) Absent-slot corner: both routes seed empty-object at
1347            //     the slot AND return the same empty map interior.
1348            let mut via_primitive = Map::new();
1349            let mut via_pre_lift = Map::new();
1350            {
1351                let _ = via_primitive.object_slot_mut_or(slot).unwrap();
1352                let _ = via_pre_lift
1353                    .entry(slot.to_string())
1354                    .or_insert_with(|| Value::Object(Map::new()))
1355                    .as_object_mut_or(slot)
1356                    .unwrap();
1357            }
1358            assert_eq!(via_primitive, via_pre_lift);
1359
1360            // (2) Present-object corner: both routes read back the
1361            //     same pre-populated interior mutably.
1362            let mut via_primitive = Map::new();
1363            via_primitive.insert(slot.into(), serde_json::json!({ "k": "v" }));
1364            let mut via_pre_lift = via_primitive.clone();
1365            {
1366                let a = via_primitive.object_slot_mut_or(slot).unwrap();
1367                let b = via_pre_lift
1368                    .entry(slot.to_string())
1369                    .or_insert_with(|| Value::Object(Map::new()))
1370                    .as_object_mut_or(slot)
1371                    .unwrap();
1372                assert_eq!(a, b);
1373            }
1374
1375            // (3) Present-non-object corner: both routes fail loud
1376            //     with the same wire-format Display shape.
1377            let mut via_primitive = Map::new();
1378            via_primitive.insert(slot.into(), Value::Bool(true));
1379            let mut via_pre_lift = via_primitive.clone();
1380            let err_primitive = via_primitive.object_slot_mut_or(slot).unwrap_err();
1381            let err_pre_lift = via_pre_lift
1382                .entry(slot.to_string())
1383                .or_insert_with(|| Value::Object(Map::new()))
1384                .as_object_mut_or(slot)
1385                .unwrap_err();
1386            assert_eq!(format!("{err_primitive}"), format!("{err_pre_lift}"));
1387        }
1388    }
1389
1390    // ─── ValueGetExt::get_i64 substrate pins ─────────────────────────
1391    //
1392    // Fail-before-pass-after granularity: the `ValueGetExt::get_i64`
1393    // trait method did not exist before this commit, so each test below
1394    // fails to compile pre-lift. Post-lift they collectively pin the
1395    // paired READ-shape at ONE substrate owner — a regression that
1396    // narrowed the projection to `as_u64` (silently losing every
1397    // negative counter K8s fixtures can carry for a JSON authoring
1398    // bug), swapped the slot lookup to `.pointer(<key>)` (losing the
1399    // direct-child semantics), promoted a present-but-non-integer
1400    // corner to `Some(0)` (silently paving over a malformed status
1401    // blob), or drifted the receiver-non-object arm from `None → Some(default)`
1402    // (silently synthesising a zero counter on a null status blob)
1403    // surfaces HERE rather than as silent operator-facing skew across
1404    // the three `boundary.rs::fetch_job_status` pre-lift consumers
1405    // whose JobStatusView row initialised at `Default::default()` and
1406    // conditionally overwrote each field on `Some(i64)`.
1407
1408    #[test]
1409    fn get_i64_present_integer_slot_returns_the_value() {
1410        // Primary Ok-arm invariant: a `Value::Number(i)` present at the
1411        // slot projects to `Some(i)`. Sweeps the three representative
1412        // counters every pre-lift `JobStatusView` field carried (a
1413        // completed Job's `succeeded=1`, a failed Job's `failed=3`, a
1414        // freshly-scheduled Job's `active=5`) so a regression at ONE
1415        // counter axis surfaces here rather than at the downstream
1416        // diagnostic.
1417        let status = json!({ "succeeded": 1, "failed": 3, "active": 5 });
1418        assert_eq!(status.get_i64("succeeded"), Some(1));
1419        assert_eq!(status.get_i64("failed"), Some(3));
1420        assert_eq!(status.get_i64("active"), Some(5));
1421    }
1422
1423    #[test]
1424    fn get_i64_absent_slot_returns_none() {
1425        // Absent-slot corner: a fresh `batch/v1::Job` before its
1426        // controller has stamped any counter into `status` (the JSON
1427        // is `{}` or missing the counter key). Every pre-lift consumer
1428        // routed this corner through the `if let Some(...)` guard so
1429        // the `JobStatusView` field kept its `Default::default()` `0`
1430        // seed. A regression that returned `Some(0)` on the absent
1431        // corner would collapse the "not yet reported" ↔ "reported
1432        // zero" distinction the K8s status protocol keeps.
1433        let status = json!({});
1434        assert_eq!(status.get_i64("succeeded"), None);
1435        assert_eq!(status.get_i64("any_missing_key"), None);
1436    }
1437
1438    #[test]
1439    fn get_i64_present_but_non_integer_slot_returns_none() {
1440        // Present-but-non-integer corner: a `Value::String`, a
1441        // `Value::Bool`, a `Value::Object`, or a `Value::Array` at the
1442        // slot ALL fall through to `None` — matches the pre-lift
1443        // `.and_then(|v| v.as_i64())` chain exactly. A regression that
1444        // promoted a `Value::String("1")` to `Some(1)` (adding a
1445        // parse-string fallback) would silently accept a malformed
1446        // status blob whose author stringified a counter.
1447        let status = json!({
1448            "stringy": "1",
1449            "boolean": true,
1450            "object": {},
1451            "array": [],
1452            "null_valued": null,
1453        });
1454        assert_eq!(status.get_i64("stringy"), None);
1455        assert_eq!(status.get_i64("boolean"), None);
1456        assert_eq!(status.get_i64("object"), None);
1457        assert_eq!(status.get_i64("array"), None);
1458        assert_eq!(status.get_i64("null_valued"), None);
1459    }
1460
1461    #[test]
1462    fn get_i64_negative_counter_survives_the_projection() {
1463        // Negative-integer corner: `as_i64` accepts negatives; `as_u64`
1464        // does not. A regression that narrowed the projection to
1465        // `as_u64` under a mistaken "K8s counters are always non-
1466        // negative" refactor would silently drop every negative
1467        // counter a JSON authoring bug could stamp — hiding the bug
1468        // rather than surfacing it as a counter the diagnostic reports
1469        // verbatim.
1470        let status = json!({ "n": -1 });
1471        assert_eq!(status.get_i64("n"), Some(-1));
1472    }
1473
1474    #[test]
1475    fn get_i64_non_object_receiver_returns_none_verbatim() {
1476        // Non-object receiver corner: a caller who reached this
1477        // primitive on a `Value::Null` / `Value::Bool` / `Value::Array`
1478        // handle (a malformed fetch response, an upstream default-value
1479        // fallback) MUST get `None` back rather than a panic or a
1480        // synthesized `Some(default)`. Matches the pre-lift chain's
1481        // behaviour: `Value::get` on a non-object receiver returns
1482        // `None`, `and_then` short-circuits.
1483        assert_eq!(Value::Null.get_i64("any"), None);
1484        assert_eq!(Value::Bool(true).get_i64("any"), None);
1485        assert_eq!(json!([1, 2, 3]).get_i64("any"), None);
1486        assert_eq!(json!("scalar").get_i64("any"), None);
1487    }
1488
1489    #[test]
1490    fn get_i64_matches_pre_lift_hand_authored_chain_shape() {
1491        // Byte-shape parity pin: `<value>.get_i64(<key>)` MUST return
1492        // the SAME `Option<i64>` the pre-lift hand-authored
1493        // `.get(<key>).and_then(|v| v.as_i64())` chain produced.
1494        // Sweeps the six pre-lift-reachable input corners (the three
1495        // "value present" + three "value absent/malformed" arms every
1496        // fetch_job_status callsite reached) so a regression at the
1497        // primitive that broke byte identity with the pre-lift chain at
1498        // ONE corner surfaces here rather than as a per-counter
1499        // divergence at the fetched-Job projection.
1500        let status = json!({
1501            "succeeded": 2,
1502            "failed": 0,
1503            "active": 7,
1504            "stringy": "1",
1505            "null_valued": null,
1506        });
1507        for key in [
1508            "succeeded",
1509            "failed",
1510            "active",
1511            "stringy",
1512            "null_valued",
1513            "missing",
1514        ] {
1515            let via_primitive = status.get_i64(key);
1516            let via_pre_lift = status.get(key).and_then(|v| v.as_i64());
1517            assert_eq!(
1518                via_primitive, via_pre_lift,
1519                "corner `{key}` must round-trip through both shapes",
1520            );
1521        }
1522    }
1523
1524    #[test]
1525    fn get_i64_composes_with_unwrap_or_default_at_default_seed_shape() {
1526        // Downstream composition pin: the canonical caller shape
1527        // post-lift is `<status>.get_i64(<key>).unwrap_or_default()` —
1528        // matches the pre-lift `JobStatusView::default()` seed +
1529        // conditional `if let Some(n)` write pattern. A regression that
1530        // reshaped the return form (an `i64` bare default, a
1531        // `Result<i64, _>` fallible arm) would break this composition.
1532        let status = json!({ "succeeded": 4 });
1533        // Absent slot composes to the type default (0 for i64).
1534        assert_eq!(status.get_i64("missing").unwrap_or_default(), 0_i64);
1535        // Present slot composes to the projected counter.
1536        assert_eq!(status.get_i64("succeeded").unwrap_or_default(), 4_i64);
1537    }
1538
1539    // ─── ValueGetExt::get_str substrate pins ─────────────────────────
1540    //
1541    // Fail-before-pass-after granularity: the `ValueGetExt::get_str`
1542    // trait method did not exist before this commit, so each test below
1543    // fails to compile pre-lift. Post-lift they collectively pin the
1544    // paired READ-shape at ONE substrate owner — a regression that
1545    // narrowed the projection to the wrong variant (accepting
1546    // `Value::Number`-stringified slots via a fallback, or accepting
1547    // `Value::Null` as `Some("")`), swapped the slot lookup to
1548    // `.pointer(<key>)` (losing the direct-child semantics), promoted
1549    // an absent slot to `Some("")` (silently paving over a missing
1550    // required slot), or drifted the receiver-non-object arm from
1551    // `None` (silently synthesising an empty string on a null status
1552    // blob) surfaces HERE rather than as silent operator-facing skew
1553    // across the SEVEN pre-lift consumers (`status::from_json`'s four
1554    // rendered-resource coordinate reads + `ssapply::ready_condition_value`'s
1555    // three K8s Condition slot reads).
1556
1557    #[test]
1558    fn get_str_present_string_slot_returns_the_slice() {
1559        // Primary Ok-arm invariant: a `Value::String(s)` present at the
1560        // slot projects to `Some(s.as_str())`. Sweeps the four
1561        // representative slots the pre-lift `RenderedResourceCoords::
1562        // from_json` consumer walked (`apiVersion`, `kind`,
1563        // `metadata.name`, `metadata.namespace`) so a regression at
1564        // ONE axis surfaces here rather than at the downstream
1565        // typed row's coordinate.
1566        let manifest = json!({
1567            "apiVersion": "helm.toolkit.fluxcd.io/v2",
1568            "kind": "HelmRelease",
1569            "name": "demo-app",
1570            "namespace": "demo",
1571        });
1572        assert_eq!(
1573            manifest.get_str("apiVersion"),
1574            Some("helm.toolkit.fluxcd.io/v2"),
1575        );
1576        assert_eq!(manifest.get_str("kind"), Some("HelmRelease"));
1577        assert_eq!(manifest.get_str("name"), Some("demo-app"));
1578        assert_eq!(manifest.get_str("namespace"), Some("demo"));
1579    }
1580
1581    #[test]
1582    fn get_str_absent_slot_returns_none() {
1583        // Absent-slot corner: a rendered manifest whose author forgot
1584        // the `apiVersion` slot (a common authoring bug) MUST return
1585        // `None` so `RenderedResourceCoords::from_json` fails loud
1586        // rather than silently synthesising an empty apiVersion. A
1587        // regression that returned `Some("")` on the absent corner
1588        // would collapse the "not authored" ↔ "authored empty"
1589        // distinction the fail-loud gate depends on.
1590        let manifest = json!({ "kind": "HelmRelease" });
1591        assert_eq!(manifest.get_str("apiVersion"), None);
1592        assert_eq!(manifest.get_str("any_missing_key"), None);
1593    }
1594
1595    #[test]
1596    fn get_str_present_but_non_string_slot_returns_none() {
1597        // Present-but-non-string corner: a `Value::Number`,
1598        // `Value::Bool`, `Value::Object`, `Value::Array`, or
1599        // `Value::Null` at the slot ALL fall through to `None` —
1600        // matches the pre-lift `.and_then(|v| v.as_str())` chain
1601        // exactly. A regression that stringified a `Value::Number`
1602        // (adding a `to_string()` fallback) would silently accept a
1603        // malformed manifest whose author numeric-typed a
1604        // conventionally-string slot.
1605        let manifest = json!({
1606            "numeric": 1,
1607            "boolean": true,
1608            "object": {},
1609            "array": [],
1610            "null_valued": null,
1611        });
1612        assert_eq!(manifest.get_str("numeric"), None);
1613        assert_eq!(manifest.get_str("boolean"), None);
1614        assert_eq!(manifest.get_str("object"), None);
1615        assert_eq!(manifest.get_str("array"), None);
1616        assert_eq!(manifest.get_str("null_valued"), None);
1617    }
1618
1619    #[test]
1620    fn get_str_empty_string_slot_survives_the_projection() {
1621        // Empty-string corner: a `Value::String("")` present at the
1622        // slot MUST project to `Some("")` — matches the pre-lift
1623        // `.and_then(|v| v.as_str())` chain exactly, keeping the
1624        // "authored empty" arm distinct from the "not authored" arm
1625        // upstream. A regression that promoted `Some("")` to `None`
1626        // under a "reject empty strings" refactor would silently
1627        // collapse the two arms and turn a valid empty `metadata.
1628        // namespace` (a cluster-scoped resource) into a fail-loud
1629        // error at the required-slot gates.
1630        let manifest = json!({ "namespace": "" });
1631        assert_eq!(manifest.get_str("namespace"), Some(""));
1632    }
1633
1634    #[test]
1635    fn get_str_non_object_receiver_returns_none_verbatim() {
1636        // Non-object receiver corner: a caller who reached this
1637        // primitive on a `Value::Null` / `Value::Bool` / `Value::Array`
1638        // handle (a malformed fetch response, an upstream default-value
1639        // fallback, a `serde_json::Value::Null` metadata slot chained
1640        // through `.and_then`) MUST get `None` back rather than a
1641        // panic or a synthesized `Some("")`. Matches the pre-lift
1642        // chain's behaviour: `Value::get` on a non-object receiver
1643        // returns `None`, `and_then` short-circuits.
1644        assert_eq!(Value::Null.get_str("any"), None);
1645        assert_eq!(Value::Bool(true).get_str("any"), None);
1646        assert_eq!(json!([1, 2, 3]).get_str("any"), None);
1647        assert_eq!(json!("scalar").get_str("any"), None);
1648    }
1649
1650    #[test]
1651    fn get_str_matches_pre_lift_hand_authored_chain_shape() {
1652        // Byte-shape parity pin: `<value>.get_str(<key>)` MUST return
1653        // the SAME `Option<&str>` the pre-lift hand-authored
1654        // `.get(<key>).and_then(|v| v.as_str())` chain produced.
1655        // Sweeps every pre-lift-reachable input corner (three
1656        // "value present" + three "value absent/malformed" arms every
1657        // status.rs / ssapply.rs callsite reached) so a regression at
1658        // the primitive that broke byte identity with the pre-lift
1659        // chain at ONE corner surfaces here rather than as a
1660        // per-slot divergence downstream.
1661        let manifest = json!({
1662            "apiVersion": "v1",
1663            "kind": "ConfigMap",
1664            "type": "Ready",
1665            "numeric": 1,
1666            "null_valued": null,
1667        });
1668        for key in [
1669            "apiVersion",
1670            "kind",
1671            "type",
1672            "numeric",
1673            "null_valued",
1674            "missing",
1675        ] {
1676            let via_primitive = manifest.get_str(key);
1677            let via_pre_lift = manifest.get(key).and_then(|v| v.as_str());
1678            assert_eq!(
1679                via_primitive, via_pre_lift,
1680                "corner `{key}` must round-trip through both shapes",
1681            );
1682        }
1683    }
1684
1685    #[test]
1686    fn get_str_composes_with_ok_or_else_at_from_json_shape() {
1687        // Downstream composition pin: the canonical caller shape at
1688        // `RenderedResourceCoords::from_json` is
1689        // `<manifest>.get_str(<key>).ok_or_else(|| anyhow!("rendered
1690        // resource missing X"))?.to_string()`. A regression that
1691        // reshaped the return form (an `&str` bare default, a
1692        // `Result<&str, _>` fallible arm) would break this
1693        // composition. Additionally sweeps the peer
1694        // `.map(String::from)` / `.map(str::to_string)` optional-slot
1695        // arm the `namespace` slot uses.
1696        let manifest = json!({ "apiVersion": "v1" });
1697        let ok_arm: String = manifest
1698            .get_str("apiVersion")
1699            .ok_or_else(|| anyhow::anyhow!("missing"))
1700            .unwrap()
1701            .to_string();
1702        assert_eq!(ok_arm, "v1");
1703        let err_arm = manifest
1704            .get_str("kind")
1705            .ok_or_else(|| anyhow::anyhow!("rendered resource missing kind"))
1706            .unwrap_err();
1707        assert_eq!(format!("{err_arm}"), "rendered resource missing kind");
1708        let opt_present: Option<String> = manifest.get_str("apiVersion").map(str::to_string);
1709        assert_eq!(opt_present.as_deref(), Some("v1"));
1710        let opt_absent: Option<String> = manifest.get_str("kind").map(String::from);
1711        assert!(opt_absent.is_none());
1712    }
1713
1714    #[test]
1715    fn get_str_return_lifetime_borrows_receiver_not_owned() {
1716        // Return-lifetime pin: the `&str` MUST borrow the receiver's
1717        // buffer rather than a fresh owned `String`. A regression that
1718        // reshaped the return to `Option<String>` (adding a
1719        // `to_string()` inside the primitive) would inflate every
1720        // callsite's allocation count and break `metadata.and_then(|m|
1721        // m.get_str("name"))`'s per-lookup zero-alloc guarantee. Bind
1722        // the invariant structurally: the borrow reaches back through
1723        // the receiver.
1724        let manifest = json!({ "apiVersion": "helm.toolkit.fluxcd.io/v2" });
1725        let s: &str = manifest.get_str("apiVersion").unwrap();
1726        let raw: &str = manifest.get("apiVersion").and_then(|v| v.as_str()).unwrap();
1727        assert!(std::ptr::eq(s.as_ptr(), raw.as_ptr()));
1728    }
1729
1730    #[test]
1731    fn get_str_axis_family_reaches_i64_and_str_through_one_trait_import() {
1732        // Axis-family pin: a caller who imports `ValueGetExt` reaches
1733        // BOTH the string axis (`get_str`) and the integer axis
1734        // (`get_i64`) through the SAME trait handle. A regression that
1735        // opened a peer `ValueGetStrExt` (or a peer trait per axis)
1736        // would break this — the caller would have to import each
1737        // trait separately and a partial import would silently miss
1738        // one axis at method-resolution time.
1739        //
1740        // Structurally: a bound `T: ValueGetExt` reaches both methods.
1741        fn probe<T: ValueGetExt>(t: &T) -> (Option<i64>, Option<&str>) {
1742            (t.get_i64("n"), t.get_str("s"))
1743        }
1744        let mixed = json!({ "n": 7, "s": "hello" });
1745        let (n, s) = probe(&mixed);
1746        assert_eq!(n, Some(7));
1747        assert_eq!(s, Some("hello"));
1748    }
1749
1750    // ─── ValueGetExt::get_array substrate pins ───────────────────────
1751    //
1752    // Fail-before-pass-after granularity: the `ValueGetExt::get_array`
1753    // trait method did not exist before this commit, so each test below
1754    // fails to compile pre-lift. Post-lift they collectively pin the
1755    // paired READ-shape at ONE substrate owner — a regression that
1756    // narrowed the projection to the wrong variant (accepting an
1757    // object slot via a `.values().collect()` synthesis, promoting an
1758    // absent slot to `Some(&Vec::new())`), swapped the slot lookup to
1759    // `.pointer(<key>)` (losing the direct-child semantics), or
1760    // drifted the receiver-non-object arm from `None` (silently
1761    // synthesising an empty array on a null status blob) surfaces
1762    // HERE rather than as silent operator-facing skew across the two
1763    // pre-lift consumers (`ssapply::ready_condition_value`'s
1764    // `status.conditions` walker + `probe::count_jwks_keys`'s `keys`
1765    // counter).
1766
1767    #[test]
1768    fn get_array_present_array_slot_returns_the_slice() {
1769        // Primary Ok-arm invariant: a `Value::Array` present at the
1770        // slot projects to `Some(&Vec::new())`-shaped borrow. Sweeps
1771        // the two representative shapes the pre-lift consumers walked
1772        // (a K8s `status.conditions` array of Condition objects on the
1773        // reconciler side; a JWKS `keys` array of key objects on the
1774        // probe side).
1775        let status = json!({
1776            "conditions": [
1777                { "type": "Ready", "status": "True" },
1778                { "type": "Progressing", "status": "False" },
1779            ],
1780        });
1781        let via = status.get_array("conditions").expect("Value::Array");
1782        assert_eq!(via.len(), 2);
1783        assert_eq!(via[0]["type"], "Ready");
1784
1785        let jwks = json!({
1786            "keys": [
1787                { "kty": "RSA", "kid": "1" },
1788                { "kty": "RSA", "kid": "2" },
1789                { "kty": "EC",  "kid": "3" },
1790            ],
1791        });
1792        assert_eq!(
1793            jwks.get_array("keys").map(Vec::len),
1794            Some(3),
1795            "probe count_jwks_keys composition must reach the same tail as pre-lift",
1796        );
1797    }
1798
1799    #[test]
1800    fn get_array_absent_slot_returns_none() {
1801        // Absent-slot corner: a fresh K8s status blob whose controller
1802        // has not stamped `conditions` yet (the `data.get("status")`
1803        // walker yields an object without the slot) MUST return
1804        // `None` so the caller's `let Some(...) = ... else { return
1805        // ReadyState::Unknown }` short-circuit fires. A regression that
1806        // returned `Some(&Vec::new())` on the absent corner would
1807        // silently drive the caller into an empty for-loop and skip
1808        // the fail-safe.
1809        let status = json!({});
1810        assert_eq!(status.get_array("conditions"), None);
1811        assert_eq!(status.get_array("any_missing_key"), None);
1812    }
1813
1814    #[test]
1815    fn get_array_present_but_non_array_slot_returns_none() {
1816        // Present-but-non-array corner: a `Value::String`,
1817        // `Value::Number`, `Value::Bool`, `Value::Object`, or
1818        // `Value::Null` at the slot ALL fall through to `None` —
1819        // matches the pre-lift `.and_then(|v| v.as_array())` chain
1820        // exactly. A regression that wrapped a scalar in a single-
1821        // element array under a "tolerant" refactor would silently
1822        // accept a malformed status blob whose author collapsed the
1823        // conditions array to a single scalar.
1824        let status = json!({
1825            "stringy": "ready",
1826            "numeric": 1,
1827            "boolean": true,
1828            "object": { "nested": true },
1829            "null_valued": null,
1830        });
1831        assert_eq!(status.get_array("stringy"), None);
1832        assert_eq!(status.get_array("numeric"), None);
1833        assert_eq!(status.get_array("boolean"), None);
1834        assert_eq!(status.get_array("object"), None);
1835        assert_eq!(status.get_array("null_valued"), None);
1836    }
1837
1838    #[test]
1839    fn get_array_empty_array_slot_survives_the_projection() {
1840        // Empty-array corner: a `Value::Array` with zero elements at
1841        // the slot MUST project to `Some(&Vec::new())` — matches the
1842        // pre-lift chain exactly, keeping the "authored empty" arm
1843        // distinct from the "not authored" arm upstream. The probe
1844        // consumer's `.map(|xs| xs.len() as u64).unwrap_or(0)` tail
1845        // depends on this: an authored-empty JWKS array reports 0
1846        // keys, distinct from a JWKS response missing the `keys` slot
1847        // altogether (which the caller could later choose to log
1848        // differently).
1849        let jwks = json!({ "keys": [] });
1850        let arr = jwks.get_array("keys").expect("Value::Array");
1851        assert!(arr.is_empty());
1852        assert_eq!(jwks.get_array("keys").map(Vec::len), Some(0));
1853    }
1854
1855    #[test]
1856    fn get_array_non_object_receiver_returns_none_verbatim() {
1857        // Non-object receiver corner: a caller who reached this
1858        // primitive on a `Value::Null` / `Value::Bool` / `Value::Array`
1859        // handle (a malformed fetch response, an upstream default-value
1860        // fallback, a `serde_json::Value::Null` intermediate chained
1861        // through `.and_then`) MUST get `None` back rather than a
1862        // panic or a synthesized `Some(&Vec::new())`. Matches the
1863        // pre-lift chain's behaviour: `Value::get` on a non-object
1864        // receiver returns `None`, `and_then` short-circuits.
1865        assert_eq!(Value::Null.get_array("any"), None);
1866        assert_eq!(Value::Bool(true).get_array("any"), None);
1867        assert_eq!(json!([1, 2, 3]).get_array("any"), None);
1868        assert_eq!(json!("scalar").get_array("any"), None);
1869    }
1870
1871    #[test]
1872    fn get_array_matches_pre_lift_hand_authored_chain_shape() {
1873        // Byte-shape parity pin: `<value>.get_array(<key>)` MUST return
1874        // the SAME `Option<&Vec<Value>>` the pre-lift hand-authored
1875        // `.get(<key>).and_then(|v| v.as_array())` chain produced.
1876        // Sweeps every pre-lift-reachable input corner (three
1877        // "value present" + three "value absent/malformed" arms
1878        // covering the two pre-lift consumers) so a regression at the
1879        // primitive that broke byte identity with the pre-lift chain
1880        // at ONE corner surfaces here rather than as a per-slot
1881        // divergence downstream.
1882        let manifest = json!({
1883            "conditions": [{ "type": "Ready" }],
1884            "keys": [{ "kid": "1" }, { "kid": "2" }],
1885            "empty": [],
1886            "stringy": "not-an-array",
1887            "null_valued": null,
1888        });
1889        for key in [
1890            "conditions",
1891            "keys",
1892            "empty",
1893            "stringy",
1894            "null_valued",
1895            "missing",
1896        ] {
1897            let via_primitive = manifest.get_array(key);
1898            let via_pre_lift = manifest.get(key).and_then(|v| v.as_array());
1899            assert_eq!(
1900                via_primitive, via_pre_lift,
1901                "corner `{key}` must round-trip through both shapes",
1902            );
1903        }
1904    }
1905
1906    #[test]
1907    fn get_array_composes_with_len_map_at_probe_count_jwks_keys_shape() {
1908        // Downstream composition pin: the canonical caller shape at
1909        // `probe::count_jwks_keys` is `<body_val>.get_array(<key>).
1910        // map(|xs| xs.len() as u64).unwrap_or(0)` — matches the
1911        // pre-lift `.get(<key>).cloned().and_then(|k| k.as_array().
1912        // map(|xs| xs.len() as u64)).unwrap_or(0)` chain shed of its
1913        // pre-lift `.cloned()` allocation. A regression that reshaped
1914        // the return form (an `Option<Vec<Value>>` owned, a
1915        // `Result<...>` fallible arm) would break this composition
1916        // AND reintroduce the eliminated allocation.
1917        let jwks = json!({ "keys": [{ "kid": "1" }, { "kid": "2" }, { "kid": "3" }] });
1918        let n: u64 = jwks
1919            .get_array("keys")
1920            .map(|xs| xs.len() as u64)
1921            .unwrap_or(0);
1922        assert_eq!(n, 3);
1923        // Missing slot composes to 0 through the same unwrap_or arm.
1924        let empty = json!({});
1925        let z: u64 = empty
1926            .get_array("keys")
1927            .map(|xs| xs.len() as u64)
1928            .unwrap_or(0);
1929        assert_eq!(z, 0);
1930    }
1931
1932    #[test]
1933    fn get_array_composes_with_let_else_short_circuit_at_ready_condition_shape() {
1934        // Downstream composition pin: the canonical caller shape at
1935        // `ssapply::ready_condition_value` is `let Some(conditions) =
1936        // <data>.get("status").and_then(|s| s.get_array("conditions"))
1937        // else { return ReadyState::Unknown; }` — the walker rides
1938        // the `get_array` primitive on the tail of a nested walk. A
1939        // regression that changed the return to `Option<Vec<Value>>`
1940        // owned would break the `for c in conditions` borrow-iterate
1941        // pattern downstream (each `c` borrows through the receiver).
1942        let data = json!({
1943            "status": {
1944                "conditions": [
1945                    { "type": "Ready",       "status": "True" },
1946                    { "type": "Progressing", "status": "False" },
1947                ],
1948            },
1949        });
1950        let conditions = data
1951            .get("status")
1952            .and_then(|s| s.get_array("conditions"))
1953            .expect("nested walk resolves");
1954        assert_eq!(conditions.len(), 2);
1955        // Verifies borrow-through-receiver: iterate without cloning.
1956        let types: Vec<&str> = conditions
1957            .iter()
1958            .filter_map(|c| c.get_str("type"))
1959            .collect();
1960        assert_eq!(types, vec!["Ready", "Progressing"]);
1961    }
1962
1963    #[test]
1964    fn get_array_return_lifetime_borrows_receiver_not_owned() {
1965        // Return-lifetime pin: the `&Vec<Value>` MUST borrow the
1966        // receiver's buffer rather than a fresh owned `Vec`. A
1967        // regression that reshaped the return to `Option<Vec<Value>>`
1968        // (adding a `.clone()` inside the primitive) would inflate
1969        // every callsite's allocation count and — for the
1970        // ssapply.rs caller — reintroduce a per-reconcile clone of
1971        // every K8s Condition on every DynamicObject readiness probe.
1972        // Bind the invariant structurally: the borrow reaches back
1973        // through the receiver.
1974        let manifest = json!({ "keys": [{ "kid": "1" }, { "kid": "2" }] });
1975        let via_primitive: &Vec<Value> = manifest.get_array("keys").unwrap();
1976        let via_raw: &Vec<Value> = manifest.get("keys").and_then(|v| v.as_array()).unwrap();
1977        assert!(std::ptr::eq(via_primitive.as_ptr(), via_raw.as_ptr()));
1978    }
1979
1980    #[test]
1981    fn get_array_axis_family_reaches_i64_str_and_array_through_one_trait_import() {
1982        // Axis-family pin: a caller who imports `ValueGetExt` reaches
1983        // the integer axis (`get_i64`), the string axis (`get_str`),
1984        // AND the array axis (`get_array`) through the SAME trait
1985        // handle. A regression that opened a peer `ValueGetArrayExt`
1986        // (or a peer trait per axis) would break this — the caller
1987        // would have to import each trait separately and a partial
1988        // import would silently miss one axis at method-resolution
1989        // time.
1990        //
1991        // Structurally: a bound `T: ValueGetExt` reaches all three
1992        // methods. This test extends the pre-existing
1993        // `get_str_axis_family_reaches_i64_and_str_through_one_trait_import`
1994        // sibling to cover the new axis; either drops means the
1995        // axis-family invariant no longer holds.
1996        fn probe<T: ValueGetExt>(t: &T) -> (Option<i64>, Option<&str>, Option<&Vec<Value>>) {
1997            (t.get_i64("n"), t.get_str("s"), t.get_array("a"))
1998        }
1999        let mixed = json!({ "n": 7, "s": "hello", "a": [1, 2, 3] });
2000        let (n, s, a) = probe(&mixed);
2001        assert_eq!(n, Some(7));
2002        assert_eq!(s, Some("hello"));
2003        assert_eq!(a.map(Vec::len), Some(3));
2004    }
2005
2006    // ─── ValueGetExt::get_bool substrate pins ───────────────────────
2007    //
2008    // Fail-before-pass-after granularity: the `ValueGetExt::get_bool`
2009    // trait method did not exist before this commit, so each test
2010    // below fails to compile pre-lift (a bare `Value` receiver has no
2011    // `.get_bool(<key>)` inherent method — only the upstream
2012    // `.get(<key>).and_then(|v| v.as_bool())` chain). Post-lift they
2013    // collectively pin the boolean-axis projection at ONE substrate
2014    // owner — a regression that drifted the projection axis
2015    // (`as_bool` → `as_str` narrowing the accepted variant, `.get()`
2016    // → `.pointer()` losing the direct-child semantics), promoted the
2017    // absent-slot corner to a synthesis (`None → Ok(false)`
2018    // fallthrough that would silently swallow a mistyped slot), or
2019    // narrowed the `Option<bool>` return to a bare `bool` (dropping
2020    // the "absent vs false" distinction) surfaces HERE rather than
2021    // as silent operator-facing skew across every downstream K8s
2022    // boolean-flag consumer (a `controller` / `blockOwnerDeletion`
2023    // OwnerReference gate, a `spec.suspended` SIGSTOP toggle read,
2024    // an `identity.name_override` phase-status probe, a
2025    // `hostNetwork` pod-spec gate).
2026
2027    #[test]
2028    fn get_bool_present_bool_slot_returns_the_flag() {
2029        // Ok-arm invariant on both polarities: a `Value::Bool(true)`
2030        // slot projects to `Some(true)` and a `Value::Bool(false)`
2031        // slot projects to `Some(false)`. A regression that only
2032        // returned `Some(true)` on the truthy arm and folded the
2033        // falsy arm to `None` (a "presence + truth" conflation) would
2034        // silently gate every downstream `spec.suspended = false`
2035        // resume-arm consumer as "flag absent" and mis-fire the
2036        // heartbeat pause release.
2037        let obj = json!({ "on": true, "off": false });
2038        assert_eq!(obj.get_bool("on"), Some(true));
2039        assert_eq!(obj.get_bool("off"), Some(false));
2040    }
2041
2042    #[test]
2043    fn get_bool_absent_slot_returns_none() {
2044        // Absent-slot arm: a missing key returns `None` verbatim,
2045        // matching the composed inherent chain's semantics. A
2046        // regression that promoted the absent corner to `Some(false)`
2047        // (folding "the operator didn't set the flag" into "the
2048        // operator set the flag false") would silently invert the
2049        // meaning at every consumer whose `unwrap_or(true)` fallback
2050        // expected the absent corner to reach the true arm.
2051        let obj = json!({ "on": true });
2052        assert!(obj.get_bool("missing").is_none());
2053    }
2054
2055    #[test]
2056    fn get_bool_wrong_variant_returns_none() {
2057        // Wrong-variant arm: a slot present but non-boolean
2058        // (`Value::String`, `Value::Number`, `Value::Null`,
2059        // `Value::Array`, `Value::Object`) projects to `None` — the
2060        // primitive does NOT coerce a `Value::String("true")` /
2061        // `Value::Number(1)` into a boolean, matching the inherent
2062        // `Value::as_bool` semantics. A regression that added truthy
2063        // coercion would silently promote a K8s wire-form drift (a
2064        // stringified boolean) into an accepted flag at every
2065        // consumer, which is never the intended semantic at any
2066        // downstream boolean-flag reader — a K8s API server returning
2067        // a stringified boolean signals wire-form drift the consumer
2068        // should notice.
2069        let obj = json!({
2070            "stringy": "true",
2071            "numeric": 1,
2072            "null_valued": null,
2073            "arrayed": [true],
2074            "nested": { "on": true },
2075        });
2076        assert!(obj.get_bool("stringy").is_none());
2077        assert!(obj.get_bool("numeric").is_none());
2078        assert!(obj.get_bool("null_valued").is_none());
2079        assert!(obj.get_bool("arrayed").is_none());
2080        assert!(obj.get_bool("nested").is_none());
2081    }
2082
2083    #[test]
2084    fn get_bool_non_object_receiver_returns_none() {
2085        // Non-object receiver arm: a `Value::String` / `Value::Null`
2086        // / `Value::Array` / `Value::Number` / `Value::Bool` receiver
2087        // returns `None` verbatim via the inherent `Value::get`'s
2088        // own non-object-arm behaviour — the primitive doesn't
2089        // special-case the case where the caller's status blob is
2090        // malformed at the receiver level. Matches the sibling axis
2091        // methods' `get_i64` / `get_str` / `get_array` behaviour on
2092        // the same corner.
2093        assert!(Value::Null.get_bool("k").is_none());
2094        assert!(Value::String("hi".into()).get_bool("k").is_none());
2095        assert!(json!([true, false]).get_bool("k").is_none());
2096        assert!(json!(1).get_bool("k").is_none());
2097        assert!(json!(true).get_bool("k").is_none());
2098    }
2099
2100    #[test]
2101    fn get_bool_matches_pre_lift_hand_authored_chain_bytewise() {
2102        // Byte-shape parity pin: `<value>.get_bool(<key>)` MUST return
2103        // the SAME `Option<bool>` the pre-lift `.get(<key>).and_then(
2104        // Value::as_bool)` two-link chain produced. Sweeps every
2105        // reachable corner (both polarities present, wrong-variant,
2106        // absent) so a regression at the primitive that broke byte
2107        // identity with the pre-lift chain at ONE corner surfaces
2108        // here rather than as a subtle per-slot divergence at
2109        // downstream K8s-flag readers.
2110        let v = json!({
2111            "on": true,
2112            "off": false,
2113            "stringy": "true",
2114            "numeric": 1,
2115            "null_valued": null,
2116        });
2117        for key in ["on", "off", "stringy", "numeric", "null_valued", "missing"] {
2118            let via_primitive = v.get_bool(key);
2119            let via_pre_lift = v.get(key).and_then(Value::as_bool);
2120            assert_eq!(
2121                via_primitive, via_pre_lift,
2122                "corner `{key}` on Value receiver must round-trip through both shapes",
2123            );
2124        }
2125    }
2126
2127    #[test]
2128    fn get_bool_axis_family_reaches_i64_str_array_and_bool_through_one_trait_import() {
2129        // Axis-family completion pin: a caller who imports
2130        // `ValueGetExt` reaches the integer axis (`get_i64`), the
2131        // string axis (`get_str`), the array axis (`get_array`), AND
2132        // the boolean axis (`get_bool`) through the SAME trait
2133        // handle. Structurally: a bound `T: ValueGetExt` reaches all
2134        // four methods. Extends the pre-existing
2135        // `get_array_axis_family_reaches_i64_str_and_array_through_one_trait_import`
2136        // sibling to cover the fourth axis; a regression that
2137        // opened a peer `ValueGetBoolExt` (or split the trait into
2138        // per-axis peers) would fail this bound at compile time
2139        // rather than surface as a silent "one axis is missing on
2140        // one receiver" drift at every downstream consumer.
2141        fn probe<T: ValueGetExt>(
2142            t: &T,
2143        ) -> (Option<i64>, Option<&str>, Option<&Vec<Value>>, Option<bool>) {
2144            (
2145                t.get_i64("n"),
2146                t.get_str("s"),
2147                t.get_array("a"),
2148                t.get_bool("b"),
2149            )
2150        }
2151        let mixed = json!({ "n": 7, "s": "hello", "a": [1, 2, 3], "b": true });
2152        let (n, s, a, b) = probe(&mixed);
2153        assert_eq!(n, Some(7));
2154        assert_eq!(s, Some("hello"));
2155        assert_eq!(a.map(Vec::len), Some(3));
2156        assert_eq!(b, Some(true));
2157    }
2158
2159    #[test]
2160    fn map_receiver_get_bool_matches_pre_lift_hand_authored_chain_bytewise() {
2161        // Receiver-parity pin on the boolean axis: an `&Map<String,
2162        // Value>` handle reaches `.get_bool(<key>)` and returns the
2163        // SAME `Option<bool>` the `Value` receiver's arm produces for
2164        // an equivalent `Value::Object(m)` walk. Sibling to
2165        // `map_receiver_get_i64_matches_pre_lift_hand_authored_chain_bytewise`
2166        // on the integer axis; both close the "widening preserves
2167        // semantics" invariant across all four axes of the family.
2168        let obj: Map<String, Value> = json!({
2169            "on": true,
2170            "off": false,
2171            "stringy": "true",
2172            "numeric": 1,
2173            "null_valued": null,
2174        })
2175        .as_object()
2176        .unwrap()
2177        .clone();
2178        for key in ["on", "off", "stringy", "numeric", "null_valued", "missing"] {
2179            let via_primitive = obj.get_bool(key);
2180            let via_pre_lift = obj.get(key).and_then(Value::as_bool);
2181            assert_eq!(
2182                via_primitive, via_pre_lift,
2183                "corner `{key}` on Map receiver's boolean axis must round-trip through both shapes",
2184            );
2185        }
2186    }
2187
2188    #[test]
2189    fn map_receiver_get_bool_matches_value_object_arm_bytewise() {
2190        // Cross-receiver coherence pin on the boolean axis: an
2191        // `&Map<String, Value>` receiver's `.get_bool(<key>)` MUST
2192        // return the SAME `Option<bool>` walking the equivalent
2193        // `Value::Object(m)` through the pre-existing `Value` impl
2194        // would. Sibling to
2195        // `map_receiver_get_str_matches_value_object_arm_bytewise` on
2196        // the string axis — a regression that specialised the Map
2197        // arm's boolean projection at ONE receiver but not the other
2198        // would silently split the two receiver shapes' behaviour
2199        // and break the "widening preserves semantics" invariant on
2200        // the boolean axis specifically.
2201        let v: Value = json!({
2202            "controller": true,
2203            "blockOwnerDeletion": true,
2204            "suspended": false,
2205            "nested": { "on": true },
2206        });
2207        let m: &Map<String, Value> = v.as_object().unwrap();
2208        for key in [
2209            "controller",
2210            "blockOwnerDeletion",
2211            "suspended",
2212            "nested",
2213            "missing",
2214        ] {
2215            assert_eq!(
2216                <Map<String, Value> as ValueGetExt>::get_bool(m, key),
2217                <Value as ValueGetExt>::get_bool(&v, key),
2218                "receiver-shape parity: `{key}` must project identically through both impls",
2219            );
2220        }
2221    }
2222
2223    #[test]
2224    fn map_receiver_axis_family_reaches_all_four_axes_through_one_trait_import() {
2225        // Axis-family completion pin on the Map receiver: a generic
2226        // `T: ValueGetExt` bound reaches ALL FOUR axes on the Map
2227        // arm — the SAME structural invariant the sibling
2228        // `get_bool_axis_family_reaches_i64_str_array_and_bool_through_one_trait_import`
2229        // pins for the `Value` receiver. Walking the SAME `probe`-
2230        // style generic through the Map arm here means a regression
2231        // that split the trait into per-axis peers would break the
2232        // invariant on both receiver shapes simultaneously.
2233        fn probe<T: ValueGetExt>(
2234            t: &T,
2235        ) -> (Option<i64>, Option<&str>, Option<&Vec<Value>>, Option<bool>) {
2236            (
2237                t.get_i64("n"),
2238                t.get_str("s"),
2239                t.get_array("a"),
2240                t.get_bool("b"),
2241            )
2242        }
2243        let m: Map<String, Value> = json!({ "n": 7, "s": "hello", "a": [1, 2, 3], "b": true })
2244            .as_object()
2245            .unwrap()
2246            .clone();
2247        let (n, s, a, b) = probe(&m);
2248        assert_eq!(n, Some(7));
2249        assert_eq!(s, Some("hello"));
2250        assert_eq!(a.map(Vec::len), Some(3));
2251        assert_eq!(b, Some(true));
2252    }
2253
2254    // ─── ValueGetExt receiver-shape widening — Map impl pins ─────────
2255    //
2256    // Fail-before-pass-after granularity: `impl ValueGetExt for
2257    // Map<String, Value>` did not exist before this commit, so each
2258    // test below fails to compile pre-lift (a bare `Map<String, Value>`
2259    // receiver has no `.get_str(<key>)` inherent method — only the
2260    // upstream `.get(<key>).and_then(Value::as_str)` chain — so the
2261    // callsite fails method resolution). Post-lift they collectively
2262    // pin the widening at ONE substrate owner — a regression that
2263    // dropped the `Map` impl and re-forced every `&Map` receiver into
2264    // a `Value::Object(m.clone())` rewrap detour would surface HERE
2265    // rather than as silent per-emit skew across the 30
2266    // `Map`-receiver pre-lift consumers in `tatara-reconciler::
2267    // {patch,ssapply}` tests.
2268
2269    #[test]
2270    fn map_receiver_reaches_str_i64_and_array_axes_through_the_same_trait() {
2271        // Receiver-parity pin: an `&Map<String, Value>` handle reaches
2272        // the SAME three axes (`get_str`, `get_i64`, `get_array`) the
2273        // `&Value` receiver already exposes. A regression that
2274        // implemented only one axis on the Map arm (a copy-paste
2275        // omission at the impl block) would surface here as one of the
2276        // three assertions failing to compile / returning `None`.
2277        let obj: Map<String, Value> = json!({
2278            "s": "hello",
2279            "n": 42,
2280            "a": [1, 2, 3],
2281        })
2282        .as_object()
2283        .unwrap()
2284        .clone();
2285        assert_eq!(obj.get_str("s"), Some("hello"));
2286        assert_eq!(obj.get_i64("n"), Some(42));
2287        assert_eq!(obj.get_array("a").map(Vec::len), Some(3));
2288    }
2289
2290    #[test]
2291    fn map_receiver_get_str_matches_pre_lift_hand_authored_chain_bytewise() {
2292        // Byte-shape parity pin: `<map>.get_str(<key>)` on a
2293        // `&Map<String, Value>` MUST return the SAME `Option<&str>` the
2294        // pre-lift `.get(<key>).and_then(Value::as_str)` chain
2295        // produced. Sweeps every pre-lift-reachable corner (present
2296        // string, present non-string, absent) so a regression at the
2297        // Map impl that broke byte identity with the pre-lift chain at
2298        // ONE corner surfaces here rather than as a per-slot divergence
2299        // at every `patch::phase_status_*` / `ssapply::ownership_*` pin.
2300        let obj: Map<String, Value> = json!({
2301            "phase": "Running",
2302            "phaseSince": "2026-01-01T00:00:00Z",
2303            "message": "",
2304            "numeric": 7,
2305            "null_valued": null,
2306        })
2307        .as_object()
2308        .unwrap()
2309        .clone();
2310        for key in [
2311            "phase",
2312            "phaseSince",
2313            "message",
2314            "numeric",
2315            "null_valued",
2316            "missing",
2317        ] {
2318            let via_primitive = obj.get_str(key);
2319            let via_pre_lift = obj.get(key).and_then(Value::as_str);
2320            assert_eq!(
2321                via_primitive, via_pre_lift,
2322                "corner `{key}` on Map receiver must round-trip through both shapes",
2323            );
2324        }
2325    }
2326
2327    #[test]
2328    fn map_receiver_get_array_matches_pre_lift_hand_authored_chain_bytewise() {
2329        // Sibling to the `get_str` byte-parity pin on the array axis
2330        // — sweeps present-array / present-non-array / absent so a
2331        // regression at the Map impl's `get_array` arm surfaces here
2332        // rather than as silent drift at
2333        // `patch::finalizers_metadata_patch_wraps_list_in_two_slot_metadata_body`
2334        // and its peers whose `metadata.get("finalizers").and_then(
2335        // Value::as_array)` chain lifts through this substrate.
2336        let obj: Map<String, Value> = json!({
2337            "finalizers": ["tatara.pleme.io/process-finalizer", "other.io/finalizer"],
2338            "fluxResources": [],
2339            "stringy": "not-array",
2340        })
2341        .as_object()
2342        .unwrap()
2343        .clone();
2344        for key in ["finalizers", "fluxResources", "stringy", "missing"] {
2345            let via_primitive = obj.get_array(key);
2346            let via_pre_lift = obj.get(key).and_then(Value::as_array);
2347            assert_eq!(
2348                via_primitive, via_pre_lift,
2349                "corner `{key}` on Map receiver's array axis must round-trip through both shapes",
2350            );
2351        }
2352    }
2353
2354    #[test]
2355    fn map_receiver_get_i64_matches_pre_lift_hand_authored_chain_bytewise() {
2356        // Sibling to the `get_str` / `get_array` byte-parity pins on
2357        // the integer axis — closes the third axis of the family and
2358        // pins that a Map-receiver caller reaching this arm gets the
2359        // SAME `Option<i64>` the pre-lift chain produced.
2360        let obj: Map<String, Value> = json!({
2361            "succeeded": 2,
2362            "failed": 0,
2363            "active": 5,
2364            "stringy": "1",
2365            "null_valued": null,
2366        })
2367        .as_object()
2368        .unwrap()
2369        .clone();
2370        for key in [
2371            "succeeded",
2372            "failed",
2373            "active",
2374            "stringy",
2375            "null_valued",
2376            "missing",
2377        ] {
2378            let via_primitive = obj.get_i64(key);
2379            let via_pre_lift = obj.get(key).and_then(Value::as_i64);
2380            assert_eq!(
2381                via_primitive, via_pre_lift,
2382                "corner `{key}` on Map receiver's integer axis must round-trip through both shapes",
2383            );
2384        }
2385    }
2386
2387    #[test]
2388    fn map_receiver_get_str_matches_value_object_arm_bytewise() {
2389        // Cross-receiver coherence pin: an `&Map<String, Value>`
2390        // receiver's `.get_str(<key>)` MUST return the SAME
2391        // `Option<&str>` that walking the equivalent `Value::Object(m)`
2392        // through the pre-existing `Value` impl would. A regression
2393        // that specialised the Map arm (a slot-name-normalisation
2394        // pass, a per-fleet trim) at ONE receiver but not the other
2395        // would silently split the two receiver shapes' behaviour and
2396        // break the "widening preserves semantics" invariant.
2397        let v: Value = json!({
2398            "apiVersion": "v1",
2399            "kind": "ConfigMap",
2400            "phase": "Running",
2401        });
2402        let m: &Map<String, Value> = v.as_object().unwrap();
2403        for key in ["apiVersion", "kind", "phase", "missing"] {
2404            assert_eq!(
2405                <Map<String, Value> as ValueGetExt>::get_str(m, key),
2406                <Value as ValueGetExt>::get_str(&v, key),
2407                "receiver-shape parity: `{key}` must project identically through both impls",
2408            );
2409        }
2410    }
2411
2412    #[test]
2413    fn map_receiver_axis_family_reaches_all_three_axes_through_one_trait_import() {
2414        // Axis-family + receiver-shape pin combined: a generic
2415        // `T: ValueGetExt` bound reaches ALL THREE axes on the Map
2416        // receiver — the SAME structural invariant the pre-existing
2417        // `get_array_axis_family_reaches_...` sibling pins for the
2418        // `Value` receiver. This test walks the SAME `probe`-style
2419        // generic through the Map arm, so a regression that split the
2420        // trait into per-axis peers would break the invariant on both
2421        // receiver shapes simultaneously.
2422        fn probe<T: ValueGetExt>(t: &T) -> (Option<i64>, Option<&str>, Option<&Vec<Value>>) {
2423            (t.get_i64("n"), t.get_str("s"), t.get_array("a"))
2424        }
2425        let m: Map<String, Value> = json!({ "n": 7, "s": "hello", "a": [1, 2, 3] })
2426            .as_object()
2427            .unwrap()
2428            .clone();
2429        let (n, s, a) = probe(&m);
2430        assert_eq!(n, Some(7));
2431        assert_eq!(s, Some("hello"));
2432        assert_eq!(a.map(Vec::len), Some(3));
2433    }
2434
2435    // ─── ValueGetExt receiver-shape widening — Option<&Value> impl pins ─
2436    //
2437    // Fail-before-pass-after granularity: `impl ValueGetExt for
2438    // Option<&Value>` did not exist before this commit, so each test
2439    // below fails to compile pre-lift (a bare `Option<&Value>` receiver
2440    // has no `.get_str(<key>)` inherent method — only the upstream
2441    // `<opt>.and_then(|v| v.get_str(<key>))` closure chain). Post-lift
2442    // they collectively pin the outer-optionality widening at ONE
2443    // substrate owner — a regression that dropped the Option arm and
2444    // re-forced every `Option<&Value>` caller into an
2445    // `.and_then(|v| v.get_<T>(<key>))` closure would surface HERE
2446    // rather than as silent per-emit skew across the three pre-lift
2447    // consumers (`RenderedResourceCoords::from_json` walking
2448    // `metadata.namespace`, `RenderedResourceCoords::required_str`
2449    // walking each required slot, `ssapply::ready_condition_value`
2450    // walking `status.conditions`).
2451    //
2452    // The impl body is `(*self).and_then(|v| v.get_<T>(key))` for each
2453    // axis, matching the pre-lift chain byte-for-byte on every corner
2454    // reachable by an `Option<&Value>` receiver. The tests below sweep
2455    // the (Some(non-object), Some(object with slot), Some(object w/o
2456    // slot), Some(object with wrong-variant slot), None) axis-family
2457    // corner cube on each of the four typed axes and pin the byte-
2458    // parity invariant.
2459
2460    #[test]
2461    fn option_ref_value_get_str_present_string_slot_returns_the_slice() {
2462        // Ok-arm invariant: a `Some(&Value)` handle whose interior
2463        // carries a JSON object with a `Value::String` at `<key>`
2464        // projects to `Some(<slice>)` — matching the pre-lift
2465        // `<opt>.and_then(|v| v.get_str(<key>))` chain byte-for-byte.
2466        // Pins the "unwrap outer optionality → project through the
2467        // Value arm's get_str" composition.
2468        let v: Value = json!({ "namespace": "kube-system" });
2469        let opt: Option<&Value> = Some(&v);
2470        assert_eq!(opt.get_str("namespace"), Some("kube-system"));
2471    }
2472
2473    #[test]
2474    fn option_ref_value_get_str_none_receiver_returns_none() {
2475        // None-arm invariant: a `None` outer optionality short-circuits
2476        // to `None` on every axis without touching the inner projection.
2477        // Matches the pre-lift `<none>.and_then(_)` chain byte-for-byte
2478        // (`Option::and_then` on `None` returns `None` verbatim).
2479        let opt: Option<&Value> = None;
2480        assert!(opt.get_str("any-key").is_none());
2481        assert!(opt.get_i64("any-key").is_none());
2482        assert!(opt.get_array("any-key").is_none());
2483        assert!(opt.get_bool("any-key").is_none());
2484    }
2485
2486    #[test]
2487    fn option_ref_value_get_str_matches_pre_lift_and_then_chain_bytewise() {
2488        // Byte-shape parity pin on the string axis: `<opt>.get_str
2489        // (<key>)` on an `Option<&Value>` MUST return the SAME
2490        // `Option<&str>` the pre-lift `<opt>.and_then(|v| v.get_str
2491        // (<key>))` chain produced. Sweeps every reachable outer-arm
2492        // (`None`, `Some(&<obj>)`) crossed with every inner-arm
2493        // corner (present string, wrong-variant, absent) so a
2494        // regression at the Option impl that broke byte identity at
2495        // ONE cross-product cell surfaces here rather than as silent
2496        // drift at any of the three pre-lift consumers.
2497        let v: Value = json!({
2498            "namespace": "kube-system",
2499            "numeric": 7,
2500            "null_valued": null,
2501        });
2502        let some: Option<&Value> = Some(&v);
2503        let none: Option<&Value> = None;
2504        for key in ["namespace", "numeric", "null_valued", "missing"] {
2505            let via_primitive = some.get_str(key);
2506            let via_pre_lift = some.and_then(|v| v.get_str(key));
2507            assert_eq!(
2508                via_primitive, via_pre_lift,
2509                "Some(&Value) corner `{key}` must round-trip through both shapes",
2510            );
2511        }
2512        assert_eq!(
2513            none.get_str("namespace"),
2514            None.and_then(|v: &Value| v.get_str("namespace"))
2515        );
2516    }
2517
2518    #[test]
2519    fn option_ref_value_get_i64_matches_pre_lift_and_then_chain_bytewise() {
2520        // Byte-shape parity pin on the integer axis — sibling to the
2521        // `get_str` pin above. Sweeps the same (outer × inner) cross-
2522        // product so a regression at the Option impl's `get_i64` arm
2523        // surfaces here rather than as silent drift at any future
2524        // consumer that walks an `Option<&Value>` into an integer
2525        // counter slot (a wrapped Job-status projection, an HPA
2526        // desired-count read).
2527        let v: Value = json!({
2528            "succeeded": 3,
2529            "failed": 0,
2530            "stringy": "1",
2531            "null_valued": null,
2532        });
2533        let some: Option<&Value> = Some(&v);
2534        let none: Option<&Value> = None;
2535        for key in ["succeeded", "failed", "stringy", "null_valued", "missing"] {
2536            let via_primitive = some.get_i64(key);
2537            let via_pre_lift = some.and_then(|v| v.get_i64(key));
2538            assert_eq!(
2539                via_primitive, via_pre_lift,
2540                "Some(&Value) corner `{key}` on integer axis must round-trip through both shapes",
2541            );
2542        }
2543        assert_eq!(
2544            none.get_i64("succeeded"),
2545            None.and_then(|v: &Value| v.get_i64("succeeded"))
2546        );
2547    }
2548
2549    #[test]
2550    fn option_ref_value_get_array_matches_pre_lift_and_then_chain_bytewise() {
2551        // Byte-shape parity pin on the array axis — the axis the
2552        // `ssapply::ready_condition_value` pre-lift consumer walks
2553        // (`data.get("status").and_then(|s| s.get_array("conditions"))`
2554        // → `data.get("status").get_array("conditions")`). Sweeps the
2555        // same (outer × inner) cross-product; a regression at the
2556        // Option impl's `get_array` arm surfaces here rather than as
2557        // silent drift at the K8s Condition classifier.
2558        let v: Value = json!({
2559            "conditions": [
2560                { "type": "Ready", "status": "True" },
2561                { "type": "Available", "status": "False" },
2562            ],
2563            "finalizers": [],
2564            "stringy": "not-array",
2565        });
2566        let some: Option<&Value> = Some(&v);
2567        let none: Option<&Value> = None;
2568        for key in ["conditions", "finalizers", "stringy", "missing"] {
2569            let via_primitive = some.get_array(key);
2570            let via_pre_lift = some.and_then(|v| v.get_array(key));
2571            assert_eq!(
2572                via_primitive, via_pre_lift,
2573                "Some(&Value) corner `{key}` on array axis must round-trip through both shapes",
2574            );
2575        }
2576        assert_eq!(
2577            none.get_array("conditions"),
2578            None.and_then(|v: &Value| v.get_array("conditions"))
2579        );
2580    }
2581
2582    #[test]
2583    fn option_ref_value_get_bool_matches_pre_lift_and_then_chain_bytewise() {
2584        // Byte-shape parity pin on the boolean axis — closes the
2585        // fourth axis of the family on the Option receiver. Sweeps the
2586        // same (outer × inner) cross-product; a regression at the
2587        // Option impl's `get_bool` arm surfaces here rather than as
2588        // silent drift at any future consumer that walks an
2589        // `Option<&Value>` into a boolean flag slot (an OwnerReference
2590        // `controller` / `blockOwnerDeletion` projection, an
2591        // `identity.name_override` gate).
2592        let v: Value = json!({
2593            "on": true,
2594            "off": false,
2595            "stringy": "true",
2596            "null_valued": null,
2597        });
2598        let some: Option<&Value> = Some(&v);
2599        let none: Option<&Value> = None;
2600        for key in ["on", "off", "stringy", "null_valued", "missing"] {
2601            let via_primitive = some.get_bool(key);
2602            let via_pre_lift = some.and_then(|v| v.get_bool(key));
2603            assert_eq!(
2604                via_primitive, via_pre_lift,
2605                "Some(&Value) corner `{key}` on boolean axis must round-trip through both shapes",
2606            );
2607        }
2608        assert_eq!(
2609            none.get_bool("on"),
2610            None.and_then(|v: &Value| v.get_bool("on"))
2611        );
2612    }
2613
2614    #[test]
2615    fn option_ref_value_get_str_matches_value_arm_bytewise_when_some() {
2616        // Cross-receiver coherence pin on the string axis: `Some(&v)
2617        // .get_str(<key>)` MUST project identically to the underlying
2618        // `<v> as &Value`'s own `get_str(<key>)` — the widening MUST
2619        // add no per-axis specialisation on the Option arm. Sibling to
2620        // `map_receiver_get_str_matches_value_object_arm_bytewise` on
2621        // the Map receiver; both close the "widening preserves
2622        // semantics" invariant across all three receiver shapes.
2623        let v: Value = json!({
2624            "apiVersion": "v1",
2625            "kind": "ConfigMap",
2626            "phase": "Running",
2627        });
2628        let opt: Option<&Value> = Some(&v);
2629        for key in ["apiVersion", "kind", "phase", "missing"] {
2630            assert_eq!(
2631                <Option<&Value> as ValueGetExt>::get_str(&opt, key),
2632                <Value as ValueGetExt>::get_str(&v, key),
2633                "receiver-shape parity: `{key}` on Option<&Value> must project identically to &Value",
2634            );
2635        }
2636    }
2637
2638    #[test]
2639    fn option_ref_value_axis_family_reaches_all_four_axes_through_one_trait_import() {
2640        // Axis-family + receiver-shape pin combined: a generic
2641        // `T: ValueGetExt` bound reaches ALL FOUR axes on the
2642        // `Option<&Value>` receiver — the SAME structural invariant
2643        // the pre-existing `map_receiver_axis_family_reaches_...` and
2644        // `get_bool_axis_family_reaches_i64_str_array_and_bool...`
2645        // siblings pin for the `Map` and `Value` receivers. Walks the
2646        // SAME `probe`-style generic through the Option arm so a
2647        // regression that split the trait into per-axis peers would
2648        // break the invariant on all three receiver shapes
2649        // simultaneously.
2650        fn probe<T: ValueGetExt>(
2651            t: &T,
2652        ) -> (Option<i64>, Option<&str>, Option<&Vec<Value>>, Option<bool>) {
2653            (
2654                t.get_i64("n"),
2655                t.get_str("s"),
2656                t.get_array("a"),
2657                t.get_bool("b"),
2658            )
2659        }
2660        let v: Value = json!({ "n": 7, "s": "hello", "a": [1, 2, 3], "b": true });
2661        let opt: Option<&Value> = Some(&v);
2662        let (n, s, a, b) = probe(&opt);
2663        assert_eq!(n, Some(7));
2664        assert_eq!(s, Some("hello"));
2665        assert_eq!(a.map(Vec::len), Some(3));
2666        assert_eq!(b, Some(true));
2667    }
2668
2669    #[test]
2670    fn option_ref_value_projects_through_stored_intermediate_left_to_right() {
2671        // Ergonomic pin — a caller who binds an inherent
2672        // `Value::get(<key>)` step to a `let` (the shape the two
2673        // pre-lift `RenderedResourceCoords` consumers already walk,
2674        // and the shape a rewritten `ssapply::ready_condition_value`
2675        // adopts) reaches the axis-family method on the stored
2676        // `Option<&Value>` handle bytewise-identically to the
2677        // pre-lift `<opt>.and_then(|s| s.get_<T>(<key>))` closure.
2678        // Sweeps the string / array / boolean axes on the same
2679        // `Option<&Value>` intermediate so a regression at the impl
2680        // that broke composition through a stored optionality handle
2681        // (a shadow on `Option::get_*` from a future std addition, a
2682        // lifetime-bound tightening that rejected the borrow through
2683        // the intermediate `&Value`) surfaces here rather than as a
2684        // per-callsite recompile failure across every downstream
2685        // reader.
2686        //
2687        // Note: a temporary `Option<&Value>` (as in `data.get("status")
2688        // .get_array("conditions")` on ONE line) cannot outlive the
2689        // enclosing statement because the trait method's return
2690        // lifetime is elided to `&self`; consumers that want to chain
2691        // directly must bind the intermediate to a `let` first, as
2692        // this test does — the substrate widening trades the closure
2693        // syntax for a stored-intermediate discipline, matching how
2694        // the two `RenderedResourceCoords` consumers already spelled
2695        // the walk.
2696        let data = json!({
2697            "status": {
2698                "conditions": [
2699                    { "type": "Ready", "status": "True" },
2700                ],
2701                "phase": "Running",
2702            },
2703            "spec": { "suspended": false },
2704        });
2705        let status = data.get("status");
2706        let spec = data.get("spec");
2707
2708        let via_primitive = status.get_array("conditions");
2709        let via_pre_lift = status.and_then(|s| s.get_array("conditions"));
2710        assert_eq!(via_primitive, via_pre_lift);
2711        assert_eq!(via_primitive.map(Vec::len), Some(1));
2712
2713        let via_primitive_str = status.get_str("phase");
2714        let via_pre_lift_str = status.and_then(|s| s.get_str("phase"));
2715        assert_eq!(via_primitive_str, via_pre_lift_str);
2716        assert_eq!(via_primitive_str, Some("Running"));
2717
2718        let via_primitive_bool = spec.get_bool("suspended");
2719        let via_pre_lift_bool = spec.and_then(|s| s.get_bool("suspended"));
2720        assert_eq!(via_primitive_bool, via_pre_lift_bool);
2721        assert_eq!(via_primitive_bool, Some(false));
2722    }
2723
2724    #[test]
2725    fn option_ref_value_reconciler_status_conditions_walk_sweeps_absent_shapes() {
2726        // Substrate pin on the EXACT walk shape
2727        // `tatara_reconciler::ssapply::ready_condition_value` consumes
2728        // post-lift: `data.get("status").get_array("conditions")` via
2729        // the stored `status: Option<&Value>` intermediate the substrate
2730        // documents as the sole ergonomic constraint of the widening.
2731        // The sibling pin at
2732        // `option_ref_value_projects_through_stored_intermediate_left_
2733        // to_right` sweeps ONE fully-populated fixture across three
2734        // axes; this pin sweeps the OTHER cross-product edge — the FIVE
2735        // absent / degenerate `status`-slot shapes a live DynamicObject
2736        // handle can carry — along the single array axis the reconciler
2737        // consumer walks. A regression at the widened impl's `None` arm
2738        // (or at the inner `.as_array()` guard on a non-object `status`
2739        // slot) surfaces HERE rather than as a silent ReadyState skew
2740        // at every downstream K8s-Condition classifier that shares the
2741        // same walk (Deployment `Available`, HPA `AbleToScale`,
2742        // StatefulSet `Ready`, HelmRelease `Released`).
2743        //
2744        // Cases swept, each pinned via byte-parity against the pre-lift
2745        // `<opt>.and_then(|s| s.get_array("conditions"))` closure the
2746        // reconciler consumer hand-authored before the outer-optionality
2747        // widening (4b8683b) closed the receiver-shape triangle:
2748        //
2749        //   1. status present, conditions present + non-empty
2750        //   2. status absent entirely (no `status` key on `data`)
2751        //   3. status present, conditions absent (empty status object)
2752        //   4. status present, but null (JSON null in the slot)
2753        //   5. status present, but a non-object variant (string here)
2754        //   6. status present, conditions present but wrong-typed
2755        //      (a stringified list rather than a JSON array)
2756        //
2757        // Each parity assertion carries a message naming the fixture so
2758        // a regression pinpoints WHICH edge of the (Option outer × axis
2759        // inner) cross-product drifted rather than a generic mismatch.
2760        let fixtures: &[(&str, Value)] = &[
2761            (
2762                "status-present-with-conditions",
2763                json!({
2764                    "metadata": { "name": "x" },
2765                    "status": { "conditions": [
2766                        { "type": "Ready", "status": "True" }
2767                    ]}
2768                }),
2769            ),
2770            ("status-absent", json!({ "metadata": { "name": "x" } })),
2771            (
2772                "status-object-no-conditions",
2773                json!({ "metadata": { "name": "x" }, "status": {} }),
2774            ),
2775            (
2776                "status-null",
2777                json!({ "metadata": { "name": "x" }, "status": null }),
2778            ),
2779            (
2780                "status-non-object",
2781                json!({ "metadata": { "name": "x" }, "status": "not-an-object" }),
2782            ),
2783            (
2784                "status-object-conditions-wrong-type",
2785                json!({
2786                    "metadata": { "name": "x" },
2787                    "status": { "conditions": "not-an-array" }
2788                }),
2789            ),
2790        ];
2791        for (label, data) in fixtures {
2792            let status = data.get("status");
2793            let via_primitive = status.get_array("conditions");
2794            let via_pre_lift = status.and_then(|s| s.get_array("conditions"));
2795            assert_eq!(
2796                via_primitive, via_pre_lift,
2797                "reconciler walk parity: fixture `{label}` must round-trip \
2798                 through the widened `Option<&Value>::get_array` arm \
2799                 bytewise-identically to the pre-lift `<opt>.and_then(|s| \
2800                 s.get_array(\"conditions\"))` closure",
2801            );
2802        }
2803        // Only fixture #1 yields Some; every other fixture yields None
2804        // through both projection shapes. Pin the discriminant so a
2805        // regression that flipped Some/None on any absent-status edge
2806        // (a mistaken `unwrap_or_default` on the empty-object slot, a
2807        // `Some(&[])` fabrication on the missing-conditions slot)
2808        // surfaces here rather than as a phase-classifier flip.
2809        let mut some_labels: Vec<&str> = fixtures
2810            .iter()
2811            .filter_map(|(label, data)| data.get("status").get_array("conditions").map(|_| *label))
2812            .collect();
2813        some_labels.sort_unstable();
2814        assert_eq!(some_labels, vec!["status-present-with-conditions"]);
2815    }
2816}