Skip to main content

caixa_helm/
lib.rs

1//! caixa-helm — typed renderer that emits a per-program `lareira-<name>`
2//! Helm chart from a [`Caixa`] manifest plus its `servicos/<name>.computeunit.yaml`.
3//!
4//! ## Output shape
5//!
6//! Every chart emitted here mirrors the canonical
7//! `pleme-io/helmworks/charts/lareira-<name>/` layout, which is *thin*:
8//!
9//!   Chart.yaml      ; metadata + dependency on pleme-computeunit
10//!   values.yaml     ; pleme-computeunit values block (the typed L2 ComputeUnit shape)
11//!   README.md       ; one-line elevator pitch for the chart
12//!
13//! There are no `templates/` — the rendering is delegated to the
14//! `pleme-computeunit` library chart in helmworks (per `theory/META-FRAMEWORK.md`
15//! §I, Layer 3 → Layer 2 transformation). caixa-helm's job is to derive the
16//! values block from a Caixa, not to render Kubernetes objects directly.
17//!
18//! ## Why a separate crate
19//!
20//! Same pattern as [`caixa_flake`] (renders flake.nix) and [`caixa_pangea`]
21//! (renders pangea Ruby) — `caixa-<target>` crates take a typed Caixa and
22//! emit the canonical source for `<target>`. Naming is uniform across the
23//! workspace.
24//!
25//! ## V0 contract
26//!
27//! ```rust,ignore
28//! use caixa_core::Caixa;
29//! use caixa_helm::{ChartDir, render_chart_for_servico};
30//!
31//! let caixa: Caixa = Caixa::from_lisp(src)?;
32//! let cu_yaml: serde_yaml::Value =
33//!     serde_yaml::from_str(std::fs::read_to_string("servicos/hello-rio.computeunit.yaml")?)?;
34//! let dir: ChartDir = render_chart_for_servico(&caixa, &cu_yaml)?;
35//! dir.write_to(std::path::Path::new("/tmp/lareira-hello-rio"))?;
36//! ```
37//!
38//! ## What this is NOT
39//!
40//! - Not a chart for the `caixa-operator` itself — that lives in
41//!   `pleme-io/caixa/operator-chart/`.
42//! - Not a Helm CLI wrapper — emitting bytes only; consumers (`feira chart`,
43//!   eventually) drive the I/O.
44//! - Not a renderer of K8s resources — `pleme-computeunit` library chart owns
45//!   the templates that turn this values block into ComputeUnit + Service +
46//!   ScaledObject + ConfigMap.
47
48#![allow(clippy::module_name_repetitions)]
49
50use std::collections::BTreeMap;
51use std::path::Path;
52
53use caixa_core::{Caixa, MappingExt, lareira_chart_name};
54use serde::{Deserialize, Serialize};
55use thiserror::Error;
56
57/// Errors caixa-helm can raise.
58#[derive(Debug, Error)]
59pub enum Error {
60    /// The caixa's `:kind` doesn't match what `caixa-helm` targets
61    /// (this renderer only emits per-program `lareira-<nome>` charts
62    /// for `:kind Servico`). Lifted from a prior `NotAServico(CaixaKind)`
63    /// arm to wrap [`caixa_core::KindMismatch`] so the diagnostic
64    /// names the offending caixa's `:nome` (not just its kind),
65    /// shared verbatim with `caixa-flux` and `caixa-mesh`.
66    #[error("{0}")]
67    NotAServico(#[from] caixa_core::KindMismatch),
68    /// The caixa's `:servicos` list doesn't carry exactly one entry —
69    /// the V0 contract every Servico-kind caixa satisfies (one
70    /// ComputeUnit YAML pointer per Servico, matching the one Helm
71    /// chart this renderer emits). Lifted from a prior
72    /// `UnsupportedServicoCount(usize)` arm to wrap
73    /// [`caixa_core::ServicoCountMismatch`] so the diagnostic names
74    /// the offending caixa's `:nome` (not just the count), shared
75    /// verbatim with `caixa-flux` (the peer per-Servico renderer
76    /// running the same V0 invariant on the programs.yaml-entry axis).
77    #[error("{0}")]
78    UnsupportedServicoCount(#[from] caixa_core::ServicoCountMismatch),
79    #[error("computeunit yaml missing required field: {0}")]
80    MissingField(&'static str),
81    #[error("yaml: {0}")]
82    Yaml(#[from] serde_yaml::Error),
83    #[error("render: {0}")]
84    Render(#[from] caixa_core::RenderError),
85    #[error("io: {0}")]
86    Io(#[from] std::io::Error),
87}
88
89/// One file in the rendered chart — `(path, contents)` pair every
90/// [`render_chart_for_servico`]-rendered `lareira-<nome>` chart-tree
91/// leaf lands at (`Chart.yaml`, `values.yaml`, `README.md`).
92///
93/// Type-aliased to the canonical [`caixa_core::RenderedFile`] so the
94/// substrate-side "one rendered leaf artifact" shape lives at one
95/// struct definition across every per-target renderer — the peer
96/// [`caixa_flux::BundleFile`] alias resolves to the same canonical, so
97/// a future rebrand on either axis (a per-artifact hash / provenance
98/// field addition, a per-artifact write-mode discriminator once
99/// per-cluster-writer sandboxing lands) lands at one caixa-core `pub
100/// struct RenderedFile` edit and reaches both crates by construction.
101/// Prior to this lift both crates carried an inline `pub struct
102/// <Xxx>File { pub path: PathBuf, pub contents: String }` with
103/// identical `#[derive(Debug, Clone, PartialEq, Eq)]` shapes and no
104/// per-type impls; a future per-target renderer (`caixa-otel`, the
105/// future `mesh.pleme.io/v1alpha1/Aplicacao` CR materializer, the
106/// future per-Supervisor reconciler renderer) would have carried a
107/// third and fourth clone of the same record. Type aliases preserve
108/// every existing struct-literal construction site
109/// (`ChartFile { path, contents }`), every field-access site (`f.path`,
110/// `f.contents`), and every derive-fed navigator by construction —
111/// Rust type aliases inherit the aliased type's `#[derive]`-generated
112/// `Debug`/`Clone`/`PartialEq`/`Eq` impls with no per-alias glue.
113pub type ChartFile = caixa_core::RenderedFile;
114
115/// The rendered chart — a flat list of files, plus the chart name.
116#[derive(Debug, Clone, PartialEq, Eq)]
117pub struct ChartDir {
118    /// Chart name — e.g. `lareira-hello-rio`. Used as the output dir name.
119    pub name: String,
120    pub files: Vec<ChartFile>,
121}
122
123impl ChartDir {
124    /// Write every file to `<dest>/<self.name>/`. Creates parent dirs.
125    pub fn write_to(&self, dest: &Path) -> Result<(), Error> {
126        let root = dest.join(&self.name);
127        std::fs::create_dir_all(&root)?;
128        for f in &self.files {
129            let target = root.join(&f.path);
130            if let Some(parent) = target.parent() {
131                std::fs::create_dir_all(parent)?;
132            }
133            std::fs::write(&target, &f.contents)?;
134        }
135        Ok(())
136    }
137}
138
139/// Top-level `Chart.yaml` shape for a generated lareira-<name> chart.
140///
141/// Mirrors `helmworks/charts/lareira-hello-world/Chart.yaml` 1:1 in
142/// structural slots — versions, deps, keywords, maintainers.
143#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
144pub struct ChartYaml {
145    #[serde(rename = "apiVersion")]
146    pub api_version: String,
147    pub name: String,
148    pub description: String,
149    #[serde(rename = "type")]
150    pub chart_type: String,
151    pub version: String,
152    #[serde(rename = "appVersion")]
153    pub app_version: String,
154    #[serde(default, skip_serializing_if = "Vec::is_empty")]
155    pub keywords: Vec<String>,
156    #[serde(default, skip_serializing_if = "Vec::is_empty")]
157    pub maintainers: Vec<Maintainer>,
158    #[serde(default, skip_serializing_if = "Option::is_none")]
159    pub home: Option<String>,
160    pub dependencies: Vec<ChartDependency>,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
164pub struct Maintainer {
165    pub name: String,
166    #[serde(default, skip_serializing_if = "Option::is_none")]
167    pub email: Option<String>,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
171pub struct ChartDependency {
172    pub name: String,
173    pub version: String,
174    pub repository: String,
175    #[serde(default, skip_serializing_if = "Option::is_none")]
176    pub alias: Option<String>,
177}
178
179/// Repository for the `pleme-computeunit` library chart. Defaults to the
180/// helmworks file:// path used by lareira-* charts; consumers can override
181/// via `RenderOpts::library_repo` to point at the published OCI registry.
182pub const DEFAULT_LIBRARY_REPO: &str = "file://../pleme-computeunit";
183pub const DEFAULT_LIBRARY_VERSION: &str = "~0.1.0";
184/// Canonical Helm library-chart name every `lareira-<nome>` chart depends
185/// on — re-export of the lifted [`caixa_core::DEFAULT_LIBRARY_NAME`] so
186/// the load-bearing string lives in exactly one place across every
187/// caixa renderer (caixa-helm's `RenderOpts::library_name` default
188/// here + caixa-flux's `cluster_bundle` `helmrelease.yaml` wrap key).
189/// A future per-edition library-chart fork — every entry on the
190/// absorption-roadmap that names a per-cluster / per-namespace /
191/// per-tenant variant of the canonical library chart — reaches both
192/// consumers through one `&'static str` by construction. Same shape
193/// as the [`caixa_core::DEFAULT_NAMESPACE`] (a085b26) /
194/// [`caixa_core::DEFAULT_SERVICO_PORT`] (1e22add) lifts on the peer
195/// canonical-K8s-axis-constant surface.
196pub use caixa_core::DEFAULT_LIBRARY_NAME;
197
198/// Canonical Helm 3 `Chart.yaml` `apiVersion` every rendered
199/// `lareira-<nome>` chart declares. Re-export of the lifted
200/// [`caixa_core::HELM_CHART_API_VERSION`] so the Helm-side
201/// chart-schema apiVersion — the discriminator the Helm binary's
202/// chart-schema parser (`helm dependency build`, `helm lint`,
203/// `helm template`) consults to select the schema that reads the
204/// rendered Chart.yaml — lives in exactly one place across every
205/// caixa renderer. The single production-code call site consuming
206/// it is [`build_chart_yaml`]'s `api_version` field assignment; a
207/// drifted local `pub const HELM_CHART_API_VERSION: &str = "…"` at
208/// this crate (or any sibling per-chart-schema renderer the
209/// absorption roadmap acknowledges — the future per-Aplicacao
210/// library chart, the future per-cluster snapshot chart) would
211/// silently reroute the rendered Chart.yaml through a stale
212/// chart-schema parser at `helm template` time far from the
213/// rebrand commit's source, so the equality + `&'static` static-data
214/// identity pin
215/// (`helm_chart_api_version_re_export_points_at_caixa_core_canonical`)
216/// closes the drift footgun at caixa-helm build time. Same shape as
217/// the [`DEFAULT_LIBRARY_NAME`] / [`KUBE_KEY_SPEC`] re-exports on the
218/// sibling canonical-Helm-load-bearing-string / canonical-K8s-CR-key
219/// axes.
220pub use caixa_core::HELM_CHART_API_VERSION;
221
222/// Canonical Helm 3 `Chart.yaml` `type` field per-chart-kind
223/// discriminator scalar-value every rendered `lareira-<nome>` chart
224/// declares. Re-export of the lifted
225/// [`caixa_core::HELM_CHART_TYPE_APPLICATION`] so the Helm chart-schema
226/// per-chart-kind discriminator — the scalar Helm's per-release install-
227/// shape dispatch loop keys off to select the per-chart-kind install
228/// pathway — lives in exactly one place across every caixa renderer.
229/// The single production-code call site consuming it is
230/// [`build_chart_yaml`]'s `chart_type` field assignment (the sole
231/// emitter site the prior inline `"application".into()` literal sat at);
232/// a drifted local `pub const HELM_CHART_TYPE_APPLICATION: &str = "…"`
233/// at this crate (or any sibling per-chart renderer the absorption
234/// roadmap acknowledges — the future per-Aplicacao library chart, the
235/// future per-cluster snapshot chart) would surface as one of two
236/// silent failure modes at `helm install` time: a value outside the
237/// schema's admitted set (`{"application", "library"}`) that Helm's
238/// chart-schema parser silently treats as the default `application`
239/// shape (masking the schema violation with no process-log signal), or
240/// an accidental collapse onto the sibling `"library"` shape that Helm
241/// refuses to install directly ("Error: library charts cannot be
242/// installed") with no field naming the chart-kind-drift root cause.
243/// The equality + `&'static` static-data identity pin
244/// (`helm_chart_type_application_re_export_points_at_caixa_core_canonical`)
245/// closes the drift footgun at caixa-helm build time. Peer to the
246/// [`HELM_CHART_API_VERSION`] re-export on the sibling canonical-Helm-
247/// chart-schema-axis — completes the per-Chart.yaml `(apiVersion, type)`
248/// canonical-scalar-axis re-export pair every rendered `lareira-<nome>`
249/// chart declares at its top-level Chart.yaml body.
250pub use caixa_core::HELM_CHART_TYPE_APPLICATION;
251
252/// Canonical Helm 3 `Chart.yaml` `type` field per-chart-kind discriminator
253/// scalar-value the sibling library-chart shape lands on — re-export of
254/// the lifted [`caixa_core::HELM_CHART_TYPE_LIBRARY`] so the second and
255/// only other arm of the Helm chart-schema's closed set `{"application",
256/// "library"}` lives in exactly one place across every caixa renderer.
257/// No production emitter here consumes it today — the caixa-helm
258/// renderer emits per-Servico `application`-typed `lareira-<nome>`
259/// charts, and the sibling [`DEFAULT_LIBRARY_NAME`] `pleme-computeunit`
260/// library chart is out-of-tree at `pleme-io/helmworks` (not this
261/// crate's authority) — but every future substrate-side per-chart-kind
262/// classifier and every emitter for the future per-Aplicacao library
263/// chart the [`HELM_CHART_TYPE_APPLICATION`] docstring names as a
264/// trajectory item reads the same `&'static str` by construction.
265/// Peer to the [`HELM_CHART_TYPE_APPLICATION`] re-export on the sibling
266/// closed-set arm — completes the two-arm re-export pair of the Helm
267/// chart-schema's per-chart-kind axis at the caixa-helm surface so
268/// consumers reaching for either shape read from one canonical source
269/// per arm. The paired identity pins
270/// (`helm_chart_type_library_re_export_points_at_caixa_core_canonical`)
271/// close the drift footgun at caixa-helm build time.
272pub use caixa_core::HELM_CHART_TYPE_LIBRARY;
273
274/// Canonical Helm 3 `Chart.yaml` top-level YAML axis-key naming the
275/// per-chart-kind discriminator field — re-export of the lifted
276/// [`caixa_core::HELM_CHART_KEY_TYPE`] so the top-level per-chart-kind
277/// discriminator YAML key the sibling [`HELM_CHART_TYPE_APPLICATION`] /
278/// [`HELM_CHART_TYPE_LIBRARY`] axis-value re-exports carry the closed-
279/// set admitted scalars for lives in exactly one place across every
280/// caixa renderer. The production emitter today is [`ChartYaml`]'s
281/// `chart_type` field `#[serde(rename = "type")]` attribute — Rust's
282/// attribute grammar admits only string literals so the const cannot
283/// substitute for the literal syntactically, but the drift-detection
284/// pin at
285/// [`tests::chart_yaml_serializes_type_axis_under_lifted_helm_chart_key_type`]
286/// round-trips a rendered `Chart.yaml` through
287/// `serde_yaml::from_str::<serde_yaml::Value>` and asserts the top-
288/// level `Mapping::get(HELM_CHART_KEY_TYPE)` resolves, closing the
289/// drift the attribute-literal-only grammar leaves silent (a future
290/// refactor that dropped the `#[serde(rename = "type")]` attribute
291/// would silently serialize the field as Rust's default snake_case
292/// `chart_type:`, which Helm's chart-schema parser silently ignores
293/// as an unknown top-level key, defaulting the per-chart-kind axis
294/// to `application` with no process-log drift-signal). Peer to the
295/// [`HELM_CHART_TYPE_APPLICATION`] / [`HELM_CHART_TYPE_LIBRARY`]
296/// re-exports on the sibling per-chart-kind axis-value canonical
297/// surface — completes the per-Chart.yaml per-chart-kind
298/// discriminator axis's `(key, value-set)` canonical re-export trio
299/// at the caixa-helm surface.
300pub use caixa_core::HELM_CHART_KEY_TYPE;
301
302/// Canonical Helm 3 `Chart.yaml` top-level YAML axis-key naming the
303/// per-chart underlying-application-version field — re-export of the
304/// lifted [`caixa_core::HELM_CHART_KEY_APP_VERSION`] so the top-level
305/// per-chart-app-version YAML key the [`ChartYaml`] `app_version`
306/// field's `#[serde(rename = "appVersion")]` attribute encodes lives
307/// in exactly one place across every caixa renderer. Rust's attribute
308/// grammar admits only string literals so the const cannot substitute
309/// for the literal syntactically, but the drift-detection pin at
310/// [`tests::chart_yaml_serializes_app_version_axis_under_lifted_helm_chart_key_app_version`]
311/// round-trips a rendered `Chart.yaml` and asserts the top-level
312/// `Mapping::get(HELM_CHART_KEY_APP_VERSION)` resolves, closing the
313/// drift the attribute-literal-only grammar leaves silent (a future
314/// refactor that dropped the `#[serde(rename = "appVersion")]`
315/// attribute would silently serialize the field as Rust's default
316/// snake_case `app_version:`, which Helm's chart-schema parser
317/// silently drops from the parsed chart-metadata shape, and every
318/// downstream Artifact Hub / `helm search` per-chart index falls back
319/// to "no application version" for the rendered chart far from the
320/// drift site). Peer to [`HELM_CHART_KEY_TYPE`] on the sibling
321/// per-Chart.yaml top-level YAML axis-key re-export surface —
322/// completes the per-Chart.yaml top-level YAML axis-key re-export
323/// pair at the caixa-helm surface for the two serde-rename-literal-
324/// only axes on this crate's [`ChartYaml`] struct that Rust's
325/// attribute-argument grammar leaves un-substitutable syntactically
326/// (the third top-level axis-key `apiVersion` re-exports through
327/// [`HELM_CHART_KEY_API_VERSION`] below, whose byte-shape coincides
328/// with the sibling [`caixa_core::KUBE_KEY_API_VERSION`] by Helm's
329/// design decision to inherit the K8s CR top-level shape verbatim —
330/// the paired substrate-side
331/// [`caixa_core`-side
332/// `helm_chart_key_api_version_matches_kube_key_api_version`] pin
333/// makes the byte-shape coincidence load-bearing rather than
334/// accidental).
335pub use caixa_core::HELM_CHART_KEY_APP_VERSION;
336
337/// Canonical Helm 3 `Chart.yaml` top-level YAML axis-key naming the
338/// per-chart chart-schema-apiVersion field — re-export of the lifted
339/// [`caixa_core::HELM_CHART_KEY_API_VERSION`] so the top-level chart-
340/// schema-apiVersion YAML key the [`ChartYaml`] `api_version` field's
341/// `#[serde(rename = "apiVersion")]` attribute encodes lives in
342/// exactly one place across every caixa renderer. Rust's attribute
343/// grammar admits only string literals so the const cannot substitute
344/// for the literal syntactically, but the drift-detection pin at
345/// [`tests::chart_yaml_serializes_api_version_axis_under_lifted_helm_chart_key_api_version`]
346/// round-trips a rendered `Chart.yaml` and asserts the top-level
347/// `Mapping::get(HELM_CHART_KEY_API_VERSION)` resolves, closing the
348/// drift the attribute-literal-only grammar leaves silent (a future
349/// refactor that dropped the `#[serde(rename = "apiVersion")]`
350/// attribute would silently serialize the field as Rust's default
351/// snake_case `api_version:`, which Helm's chart-schema parser
352/// rejects at `helm lint` / `helm dependency build` / `helm template`
353/// time with an "apiVersion is required" error far from the drift
354/// site). Peer to [`HELM_CHART_KEY_TYPE`] / [`HELM_CHART_KEY_APP_VERSION`]
355/// on the sibling per-Chart.yaml top-level YAML axis-key re-export
356/// surface — completes the per-Chart.yaml top-level YAML axis-key
357/// re-export trio at the caixa-helm surface for the three serde-
358/// rename-literal-only axes on this crate's [`ChartYaml`] struct.
359pub use caixa_core::HELM_CHART_KEY_API_VERSION;
360
361/// Canonical Helm 3 `Chart.yaml` top-level YAML axis-key naming the
362/// per-chart dependency-list field — re-export of the lifted
363/// [`caixa_core::HELM_CHART_KEY_DEPENDENCIES`] so the load-bearing serde
364/// field-name at [`ChartYaml`]'s `dependencies` field (the parent
365/// list-container the already-re-exported per-`dependencies[]`-entry
366/// sub-mapping tetrad [`HELM_CHART_DEPENDENCY_KEY_NAME`] /
367/// [`HELM_CHART_DEPENDENCY_KEY_VERSION`] /
368/// [`HELM_CHART_DEPENDENCY_KEY_REPOSITORY`] /
369/// [`HELM_CHART_DEPENDENCY_KEY_ALIAS`] mounts one level down under)
370/// lives in exactly one place across every caixa renderer. The Rust
371/// field name and the wire key coincide by default (no
372/// `#[serde(rename)]` attribute today), so the const doesn't substitute
373/// for the field-name syntactically at the struct definition, but the
374/// drift-detection pin at
375/// [`tests::chart_yaml_serializes_dependencies_axis_under_lifted_helm_chart_key_dependencies`]
376/// round-trips a rendered `Chart.yaml` through
377/// `serde_yaml::from_str::<serde_yaml::Value>` and asserts the top-
378/// level `Mapping::get(HELM_CHART_KEY_DEPENDENCIES)` resolves — closing
379/// the drift a future field rename (`dependencies` → `deps` /
380/// `chartDependencies`) or a `#[serde(rename_all = "camelCase")]`
381/// attribute addition on [`ChartYaml`] would otherwise leave silent
382/// (Helm's chart-schema parser silently drops the entire dep list from
383/// the parsed chart-metadata, `helm dependency build` finds no chart to
384/// vendor, and every rendered `lareira-<nome>` chart's install fails at
385/// apply time far from the drift site). Peer to
386/// [`HELM_CHART_KEY_TYPE`] / [`HELM_CHART_KEY_APP_VERSION`] /
387/// [`HELM_CHART_KEY_API_VERSION`] on the sibling per-Chart.yaml
388/// top-level YAML axis-key re-export surface — extends the per-
389/// Chart.yaml top-level YAML axis-key re-export trio at the caixa-helm
390/// surface onto the fourth top-level axis-key, the parent list-
391/// container whose per-entry sub-mapping tetrad is already re-exported
392/// under [`HELM_CHART_DEPENDENCY_KEY_*`].
393pub use caixa_core::HELM_CHART_KEY_DEPENDENCIES;
394
395/// Canonical Helm 3 `Chart.yaml` per-`dependencies[]`-entry sub-mapping
396/// YAML axis-key naming the per-dep chart-name field — re-export of the
397/// lifted [`caixa_core::HELM_CHART_DEPENDENCY_KEY_NAME`] so the load-
398/// bearing serde field-name at [`ChartDependency`]'s `name` field lives
399/// in exactly one place across every caixa renderer. Peer to
400/// [`HELM_CHART_DEPENDENCY_KEY_VERSION`] /
401/// [`HELM_CHART_DEPENDENCY_KEY_REPOSITORY`] /
402/// [`HELM_CHART_DEPENDENCY_KEY_ALIAS`] on the sibling per-dep sub-key
403/// axes. The drift-detection round-trip pin at
404/// [`tests::chart_dependency_serializes_tetrad_under_lifted_helm_chart_dependency_keys`]
405/// serializes a fully-populated [`ChartDependency`] and asserts each of
406/// the four per-dep sub-mapping wire keys resolves — closing the drift
407/// a rename of the Rust field or a `#[serde(rename_all)]` attribute
408/// addition would otherwise leave silent.
409pub use caixa_core::HELM_CHART_DEPENDENCY_KEY_NAME;
410
411/// Canonical Helm 3 `Chart.yaml` per-`dependencies[]`-entry sub-mapping
412/// YAML axis-key naming the per-dep chart-version-constraint field —
413/// re-export of the lifted [`caixa_core::HELM_CHART_DEPENDENCY_KEY_VERSION`]
414/// so the load-bearing serde field-name at [`ChartDependency`]'s
415/// `version` field lives in exactly one place across every caixa
416/// renderer. Peer to [`HELM_CHART_DEPENDENCY_KEY_NAME`] on the sibling
417/// per-dep sub-key axes. See [`HELM_CHART_DEPENDENCY_KEY_NAME`] for the
418/// shared per-entry-sub-mapping lift rationale.
419pub use caixa_core::HELM_CHART_DEPENDENCY_KEY_VERSION;
420
421/// Canonical Helm 3 `Chart.yaml` per-`dependencies[]`-entry sub-mapping
422/// YAML axis-key naming the per-dep chart-registry URL field — re-export
423/// of the lifted [`caixa_core::HELM_CHART_DEPENDENCY_KEY_REPOSITORY`]
424/// so the load-bearing serde field-name at [`ChartDependency`]'s
425/// `repository` field lives in exactly one place across every caixa
426/// renderer. Peer to [`HELM_CHART_DEPENDENCY_KEY_NAME`] on the sibling
427/// per-dep sub-key axes.
428pub use caixa_core::HELM_CHART_DEPENDENCY_KEY_REPOSITORY;
429
430/// Canonical Helm 3 `Chart.yaml` per-`dependencies[]`-entry sub-mapping
431/// YAML axis-key naming the per-dep chart-alias override field —
432/// re-export of the lifted [`caixa_core::HELM_CHART_DEPENDENCY_KEY_ALIAS`]
433/// so the load-bearing serde field-name at [`ChartDependency`]'s
434/// `alias` field lives in exactly one place across every caixa
435/// renderer. Peer to [`HELM_CHART_DEPENDENCY_KEY_NAME`] on the sibling
436/// per-dep sub-key axes.
437pub use caixa_core::HELM_CHART_DEPENDENCY_KEY_ALIAS;
438
439/// Canonical Helm 3 per-chart-directory metadata-file filename every
440/// rendered `lareira-<nome>` chart carries at its top-level directory —
441/// re-export of the lifted [`caixa_core::HELM_CHART_YAML_FILENAME`] so
442/// the fixed lookup name Helm's chart-schema parser (`helm dependency
443/// build`, `helm lint`, `helm template`, `helm install`) consults at
444/// chart-open time to locate the per-chart schema-body scalars
445/// ([`HELM_CHART_API_VERSION`], [`HELM_CHART_TYPE_APPLICATION`], the
446/// name/version/dependencies fields) lives in exactly one place across
447/// every caixa renderer. The single production-code call site
448/// consuming it is [`render_chart_for_servico`]'s `ChartDir` assembly
449/// where the metadata file's per-`ChartFile` `path` axis is set (the
450/// sole emitter site the prior inline `PathBuf::from("Chart.yaml")`
451/// literal sat at); every test-side round-trip navigator that reaches
452/// into the rendered `ChartDir` by the metadata filename (the
453/// per-chart-metadata-field sweep tests +
454/// [`ChartDir::write_to`] post-write existence pin) now consults the
455/// same `&'static str`, so a rebrand of the Helm 3 metadata-file axis
456/// (any per-fork `Chartfile.yaml` / Helm 4 metadata-file rename the
457/// upstream packaging spec might adopt) lands at one const and reaches
458/// every consumer by construction. A drifted local `pub const
459/// HELM_CHART_YAML_FILENAME: &str = "…"` at this crate — the canonical
460/// drift footgun where a sibling local `pub const` could happen to
461/// carry the same string at the source while pointing at a different
462/// `&'static` allocation — surfaces as one of two silent failure modes
463/// at chart-consumption time: Helm's chart-schema parser refuses to
464/// open the rendered chart-directory ("Error: Chart.yaml file is
465/// missing") far from the drift commit, or the sibling
466/// [`caixa_flux::cluster_bundle`]'s future per-chart-directory
467/// resolver — a per-cluster snapshot bundle that re-lists the
468/// chart-dir contents by filename — silently returns `None` at
469/// cluster-side `feira app deploy` time. The equality + `&'static`
470/// static-data identity pin
471/// (`helm_chart_yaml_filename_re_export_points_at_caixa_core_canonical`)
472/// closes the drift footgun at caixa-helm build time. Peer to the
473/// [`HELM_CHART_API_VERSION`] / [`HELM_CHART_TYPE_APPLICATION`]
474/// re-exports on the sibling canonical-Helm-chart-schema-body-axis
475/// surface — completes the per-`lareira-<nome>`-chart-directory
476/// `(filename, apiVersion, type)` canonical-scalar-axis re-export
477/// triple every rendered chart declares at its top-level metadata file.
478pub use caixa_core::HELM_CHART_YAML_FILENAME;
479
480/// Canonical Helm 3 per-chart-directory values-file filename every
481/// rendered `lareira-<nome>` chart carries at its top-level directory —
482/// re-export of the lifted [`caixa_core::HELM_VALUES_YAML_FILENAME`] so
483/// the fixed lookup name Helm's chart-schema parser (`helm dependency
484/// build`, `helm lint`, `helm template`, `helm install`) consults at
485/// chart-open time to locate the per-chart values block that
486/// [`HELM_VALUES_KEY_ENABLED`] toggles under its
487/// [`DEFAULT_LIBRARY_NAME`] wrap key lives in exactly one place across
488/// every caixa renderer. The single production-code call site
489/// consuming it is [`render_chart_for_servico`]'s `ChartDir` assembly
490/// where the values file's per-`ChartFile` `path` axis is set (the
491/// sole emitter site the prior inline `PathBuf::from("values.yaml")`
492/// literal sat at); every test-side round-trip navigator that reaches
493/// into the rendered `ChartDir` by the values filename (the
494/// per-chart-values-field sweep tests +
495/// [`ChartDir::write_to`] post-write existence pin) now consults the
496/// same `&'static str`, so a rebrand of the Helm 3 values-file axis
497/// (any per-fork `defaults.yaml` / Helm 4 values-file rename the
498/// upstream packaging spec might adopt) lands at one const and reaches
499/// every consumer by construction. A drifted local `pub const
500/// HELM_VALUES_YAML_FILENAME: &str = "…"` at this crate — the canonical
501/// drift footgun where a sibling local `pub const` could happen to
502/// carry the same string at the source while pointing at a different
503/// `&'static` allocation — surfaces as one of two silent failure modes
504/// at chart-consumption time: Helm's per-chart values-loader silently
505/// falls back to the empty values block (`helm template` emits the
506/// library chart under its admission-time defaults, the workload comes
507/// up disabled or without any per-Servico M2 overlay applied) far from
508/// the drift commit, or the sibling
509/// [`caixa_flux::cluster_bundle`]'s future per-chart-directory
510/// resolver — a per-cluster snapshot bundle that re-lists the
511/// chart-dir contents by filename to route per-cluster values overlays
512/// through the canonical values file — silently returns `None` at
513/// cluster-side `feira app deploy` time. The equality + `&'static`
514/// static-data identity pin
515/// (`helm_values_yaml_filename_re_export_points_at_caixa_core_canonical`)
516/// closes the drift footgun at caixa-helm build time. Peer to the
517/// [`HELM_CHART_YAML_FILENAME`] re-export on the sibling
518/// canonical-Helm-per-chart-directory-metadata-file-axis surface —
519/// completes the per-`lareira-<nome>`-chart-directory
520/// `(Chart.yaml, values.yaml)` canonical-per-chart-directory-filename-
521/// axis re-export pair every rendered chart declares as its two
522/// schema-load-bearing `ChartDir::files` entries.
523pub use caixa_core::HELM_VALUES_YAML_FILENAME;
524
525/// Canonical `lareira-<nome>` chart-directory human-facing readme
526/// filename every rendered chart carries at its top-level directory —
527/// re-export of the lifted [`caixa_core::HELM_CHART_README_FILENAME`] so
528/// the third leg of the canonical `{Chart.yaml, values.yaml, README.md}`
529/// per-`lareira-<nome>` chart-directory `ChartFile` triple lives in
530/// exactly one place across every caixa renderer. The single
531/// production-code call site consuming it is
532/// [`render_chart_for_servico`]'s `ChartDir` assembly where the readme
533/// file's per-`ChartFile` `path` axis is set (the sole emitter site the
534/// prior inline `"README.md"` string literal sat at); every test-side
535/// round-trip navigator that reaches into the rendered `ChartDir` by
536/// the readme filename (the [`render_chart_for_servico`] files-vec-
537/// membership pin + the [`ChartDir::write_to`] post-write existence
538/// pin — two sites) now consults the same `&'static str`. A drifted
539/// local `pub const HELM_CHART_README_FILENAME: &str = "…"` at this
540/// crate — the canonical drift footgun where a sibling local
541/// `pub const` could happen to carry the same string at the source
542/// while pointing at a different `&'static` allocation — surfaces as
543/// GitHub / Artifact Hub / any downstream per-chart README-surfacing
544/// UI silently falling back to "no README available" for the rendered
545/// `lareira-<nome>` chart far from the drift commit's source, with no
546/// field naming the readme-filename-drift root cause. The equality +
547/// `&'static` static-data identity pin
548/// (`helm_chart_readme_filename_re_export_points_at_caixa_core_canonical`)
549/// closes the drift footgun at caixa-helm build time. Peer to the
550/// [`HELM_CHART_YAML_FILENAME`] / [`HELM_VALUES_YAML_FILENAME`]
551/// re-exports on the sibling canonical-Helm-per-chart-directory-
552/// filename axes — completes the per-`lareira-<nome>`-chart-directory
553/// `(Chart.yaml, values.yaml, README.md)` canonical-per-chart-directory-
554/// filename-axis re-export triple every rendered chart declares as its
555/// three `ChartDir::files` entries.
556pub use caixa_core::HELM_CHART_README_FILENAME;
557
558/// Canonical K8s CR top-level `spec` key. Re-export of the canonical
559/// [`caixa_core::KUBE_KEY_SPEC`] so the per-kind body key lives in
560/// exactly one place across every caixa renderer — caixa-helm's
561/// `build_values_yaml` (the upstream ComputeUnit YAML's `spec.*` axis
562/// the rendered `lareira-<nome>` chart's values block re-routes
563/// through the library alias) now consults the same `&'static str` as
564/// the peer caixa-flux / caixa-mesh renderers' `KUBE_KEY_SPEC`
565/// re-exports. The prior inline `"spec"` literal at the production-
566/// code call site would have let a typo (e.g. `"Spec"`, `"specs"`,
567/// `"spec_"`) silently emit a values block that drops every typed
568/// ComputeUnit-side field (`module`, `trigger`, `capabilities`,
569/// `resources`, `serviceAccount`) at the rendered chart's landing
570/// site — the `Error::MissingField("spec")` diagnostic now threads
571/// the same `&'static str` through the diagnostic surface so the
572/// error message stays byte-identical to the key it failed to find.
573/// Same shape as the [`DEFAULT_LIBRARY_NAME`] re-export on the
574/// sibling canonical-Helm-load-bearing-string axis.
575pub use caixa_core::KUBE_KEY_SPEC;
576
577/// Canonical `pleme-computeunit` library-chart values-block enable-toggle
578/// key — re-export of the lifted [`caixa_core::HELM_VALUES_KEY_ENABLED`]
579/// so the values-block toggle every rendered `lareira-<nome>` chart's
580/// values.yaml carries under its [`DEFAULT_LIBRARY_NAME`] wrap key lives
581/// in exactly one place across every caixa renderer. The single
582/// production-code call site consuming it is [`build_values_yaml`]'s
583/// `block.insert(HELM_VALUES_KEY_ENABLED.to_string(), …)` (formerly an
584/// inline `"enabled".to_string()` literal at `caixa-helm/src/lib.rs:389`);
585/// the peer test-fixture navigators pinning the default-off round-trip
586/// (`values_yaml_wraps_under_pleme_computeunit_key`,
587/// `values_yaml_wrap_key_follows_library_name_override`) also consult the
588/// re-export so a rebrand of the library-chart's per-values enable-toggle
589/// axis lands at one const and reaches every consumer by construction.
590/// A drifted local `pub const HELM_VALUES_KEY_ENABLED: &str = "…"` (or
591/// any sibling per-renderer variant that inlined a stale
592/// `"enabled"` / `"enable"` / `"disabled"` literal) would silently emit
593/// a values block whose per-values enable-toggle lands under one key
594/// while [`caixa_flux::cluster_bundle`]'s `HelmRelease`
595/// `spec.values.<library>.enabled` per-cluster override lands under
596/// another — Helm's per-values merge treats them as sibling scalars, the
597/// enable-toggle the library chart's own template consults never sees the
598/// flip, and the workload silently comes up with the library chart's
599/// admission-time defaults instead of the per-cluster override the
600/// operator set. Same shape as the [`DEFAULT_LIBRARY_NAME`] /
601/// [`KUBE_KEY_SPEC`] / [`HELM_CHART_API_VERSION`] re-exports on the
602/// sibling canonical-Helm-load-bearing-string / canonical-K8s-CR-body-
603/// key / canonical-Helm-chart-schema-apiVersion axes.
604pub use caixa_core::HELM_VALUES_KEY_ENABLED;
605
606/// Canonical substrate-side default for the
607/// `values.<library>.enabled` scalar-value toggle every
608/// [`render_chart_for_servico`]-emitted standalone `lareira-<nome>`
609/// chart's `values.yaml` document seeds inside its per-caixa
610/// [`DEFAULT_LIBRARY_NAME`] wrap block to leave the paired
611/// [`DEFAULT_LIBRARY_NAME`] child chart opted-out at the per-cluster
612/// `helm template` / `helm install` apply step. Re-export of the
613/// canonical [`caixa_core::STANDALONE_LAREIRA_ENABLED_DEFAULT`] so the
614/// substrate-side default the standalone per-chart path seeds under
615/// the sibling [`HELM_VALUES_KEY_ENABLED`] leaf-scalar-key lives in
616/// exactly one place across every caixa renderer. Consumed by
617/// [`RenderOpts::default`]'s `enabled_default` field seed (formerly
618/// an inline `false` scalar-value literal at
619/// `caixa-helm/src/lib.rs:700`); the peer test-fixture navigators
620/// pinning the default-off round-trip also consult the re-export so a
621/// rebrand of the per-values-block child-chart-enablement-toggle scalar
622/// on the standalone per-chart path lands at one const and reaches
623/// every consumer by construction. Semantically distinct from — and
624/// inverse of — the peer
625/// [`caixa_flux::CLUSTER_BUNDLE_LAREIRA_ENABLED_DEFAULT`] on the
626/// composition per-cluster-`HelmRelease` values-overlay path (which
627/// force-ons the child chart under the substrate-side composition
628/// path); the two peer scalar-value defaults name mirror-symmetric
629/// per-path child-chart-enablement-toggle-scalar-value defaults at the
630/// exact same `values.<library>.enabled` sub-block position on the
631/// standalone per-chart-`values.yaml` path (this re-export) and the
632/// composition per-cluster-`HelmRelease` values-overlay path (the peer
633/// re-export). Same shape as the [`DEFAULT_LIBRARY_NAME`] /
634/// [`KUBE_KEY_SPEC`] / [`HELM_VALUES_KEY_ENABLED`] re-exports on the
635/// sibling canonical-Helm-load-bearing-string / canonical-K8s-CR-body-
636/// key / canonical-Helm-per-values-block-enable-toggle-key axes. See
637/// [`caixa_core::STANDALONE_LAREIRA_ENABLED_DEFAULT`] for the full lift
638/// rationale.
639pub use caixa_core::STANDALONE_LAREIRA_ENABLED_DEFAULT;
640
641/// Local re-export of the canonical
642/// [`caixa_core::COMPUTEUNIT_SPEC_KEY_MODULE`] — the
643/// `wasm.pleme.io/v1alpha1/ComputeUnit` CRD per-CR wasm-module-reference
644/// `spec.module` sub-block key every rendered `values.yaml`'s
645/// [`DEFAULT_LIBRARY_NAME`]-wrapped block carries so the
646/// `pleme-computeunit` library chart's per-Servico module-source axis
647/// binds to the exact source the caixa.lisp's `:servicos` fixture
648/// pins. Two per-values drift-detection navigators in this crate's
649/// test module (the canonical-wrap-key round-trip + the
650/// `library-name`-override wrap-key round-trip) now consult the same
651/// `&'static str` as the peer caixa-flux writer's per-Servico
652/// `programs[]`-entry module-source navigators. Same re-export shape
653/// as the peer [`HELM_VALUES_KEY_ENABLED`] / [`KUBE_KEY_SPEC`]
654/// surfaces on the sibling canonical-Helm-load-bearing-string /
655/// canonical-K8s-CR-body-key axes — extends the discipline the
656/// M2-typed-slot / K8s-CR key re-export families establish onto the
657/// substrate-side ComputeUnit-CRD per-`spec.*` sub-block axis. See
658/// [`caixa_core::COMPUTEUNIT_SPEC_KEY_MODULE`] for the full lift
659/// rationale.
660pub use caixa_core::COMPUTEUNIT_SPEC_KEY_MODULE;
661
662/// Local re-export of the canonical
663/// [`caixa_core::COMPUTEUNIT_SPEC_KEY_TRIGGER`] — the
664/// `wasm.pleme.io/v1alpha1/ComputeUnit` CRD per-CR invocation-shape
665/// `spec.trigger` sub-block key every rendered `values.yaml`'s
666/// [`DEFAULT_LIBRARY_NAME`]-wrapped block carries so the
667/// `pleme-computeunit` library chart's per-Servico
668/// `trigger.service.{port, paths, breathability}` routing binds to
669/// the exact axis the caixa.lisp's `:servicos` fixture pins. Peer of
670/// [`COMPUTEUNIT_SPEC_KEY_MODULE`] on the same ComputeUnit CRD
671/// per-`spec.*` sub-block axis — see
672/// [`caixa_core::COMPUTEUNIT_SPEC_KEY_TRIGGER`] for the full lift
673/// rationale.
674pub use caixa_core::COMPUTEUNIT_SPEC_KEY_TRIGGER;
675
676/// Local re-export of the canonical
677/// [`caixa_core::COMPUTEUNIT_SPEC_KEY_CAPABILITIES`] — the
678/// `wasm.pleme.io/v1alpha1/ComputeUnit` CRD per-CR WASI-capability-list
679/// `spec.capabilities` sub-block key every rendered `values.yaml`'s
680/// [`DEFAULT_LIBRARY_NAME`]-wrapped block carries so the
681/// `pleme-computeunit` library chart's per-Servico WASI-preview-2
682/// capability-token binding fires exactly against the axis the
683/// caixa.lisp's `:servicos` fixture pins. Peer of
684/// [`COMPUTEUNIT_SPEC_KEY_MODULE`] and [`COMPUTEUNIT_SPEC_KEY_TRIGGER`]
685/// on the same ComputeUnit CRD per-`spec.*` sub-block axis — completes
686/// the substrate-side ComputeUnit-CRD per-`spec.*` sub-block re-export
687/// triple in this crate. See
688/// [`caixa_core::COMPUTEUNIT_SPEC_KEY_CAPABILITIES`] for the full lift
689/// rationale.
690pub use caixa_core::COMPUTEUNIT_SPEC_KEY_CAPABILITIES;
691
692/// Local re-export of the canonical
693/// [`caixa_core::servico_spec_and_m2_overlay_entries`] — the composed
694/// per-Servico value-block splice helper this crate's
695/// [`build_values_yaml`] and the peer
696/// [`caixa_flux::programs_yaml_entry`] both now route their two-step
697/// `spec.*` field-splice + M2 typed-slot overlay through. The single
698/// production-code call site consuming it is [`build_values_yaml`]'s
699/// inner splice loop (formerly two hand-written for-loops chained
700/// around `string_keyed_entries` + `servico_m2_overlay`); re-exported
701/// so the shared composition contract lives in exactly one place
702/// across both per-Servico renderers — a future author reading
703/// `caixa_helm::build_values_yaml` finds the composition helper
704/// immediately without an extra `use caixa_core::…` line, and a
705/// rebrand of the composition axis (e.g. a swap of the `or_insert`
706/// precedence rule once per-Aplicacao operator overrides land)
707/// reaches both renderers through one canonical `&'static` function
708/// pointer. Same shape as the peer [`COMPUTEUNIT_SPEC_KEY_MODULE`] /
709/// [`COMPUTEUNIT_SPEC_KEY_TRIGGER`] / [`COMPUTEUNIT_SPEC_KEY_CAPABILITIES`]
710/// re-exports on the sibling canonical-ComputeUnit-CRD `spec.*` axis
711/// — extends the shared-composition discipline the per-`spec.*`
712/// sub-block re-export triple establishes onto the composed
713/// spec.*+M2 splice axis every per-Servico renderer navigates.
714pub use caixa_core::servico_spec_and_m2_overlay_entries;
715
716/// Knobs that don't come from the Caixa manifest.
717#[derive(Debug, Clone)]
718pub struct RenderOpts {
719    /// Where the library chart lives. Default = `file://../pleme-computeunit`.
720    pub library_repo: String,
721    pub library_version: String,
722    pub library_name: String,
723    /// Whether the rendered values block is `enabled: false` by default
724    /// (matching `lareira-hello-world` so cluster operators flip it on
725    /// per-cluster). Default: [`STANDALONE_LAREIRA_ENABLED_DEFAULT`]
726    /// (`false` — the substrate's chosen standalone per-chart
727    /// opt-out seed, inverse of the composition per-cluster-`HelmRelease`
728    /// values-overlay path's [`caixa_flux::CLUSTER_BUNDLE_LAREIRA_ENABLED_DEFAULT`]
729    /// force-on).
730    pub enabled_default: bool,
731}
732
733impl Default for RenderOpts {
734    fn default() -> Self {
735        Self {
736            library_repo: DEFAULT_LIBRARY_REPO.into(),
737            library_version: DEFAULT_LIBRARY_VERSION.into(),
738            library_name: DEFAULT_LIBRARY_NAME.into(),
739            enabled_default: STANDALONE_LAREIRA_ENABLED_DEFAULT,
740        }
741    }
742}
743
744/// Render a per-program lareira-<name> chart from a Caixa Servico + its
745/// loaded ComputeUnit YAML.
746///
747/// The ComputeUnit YAML is passed in as a `serde_yaml::Value` because the
748/// authoritative schema lives in the wasm-operator's CRD — we don't want
749/// caixa-helm to drift from that schema. It's enough that we can locate
750/// `spec` and pass it through.
751pub fn render_chart_for_servico(
752    caixa: &Caixa,
753    computeunit_yaml: &serde_yaml::Value,
754) -> Result<ChartDir, Error> {
755    render_chart_for_servico_with(caixa, computeunit_yaml, &RenderOpts::default())
756}
757
758/// `render_chart_for_servico` with explicit options.
759pub fn render_chart_for_servico_with(
760    caixa: &Caixa,
761    computeunit_yaml: &serde_yaml::Value,
762    opts: &RenderOpts,
763) -> Result<ChartDir, Error> {
764    caixa_core::require_v0_servico_shape::<Error>(caixa)?;
765
766    // Canonical typed `&str`-read of the per-`Caixa` `:nome`
767    // universal-axis DNS-1123-label caixa-identity scalar into
768    // the per-chart-directory `lareira-<nome>` identity composer.
769    // Peer of the sibling 4a363bf / 54bf2f3 `caixa.nome.clone()`
770    // converges on the outer-Caixa `:nome` `String`-carry axis
771    // in caixa-flux / caixa-mesh and the sibling eb912de
772    // `caixa.versao.clone()` converge on the co-resident
773    // `Caixa::versao` `String`-carry axis in this crate — this
774    // extends the "one typed dispatch on the substrate primitive,
775    // thin projections at each consumer" discipline onto the
776    // non-`.clone()` raw-field-access axis of `Caixa::nome` in
777    // caixa-helm.
778    let chart_name = lareira_chart_name(caixa.nome());
779    let chart_yaml = build_chart_yaml(caixa, &chart_name, opts);
780    let values_yaml = build_values_yaml(caixa, computeunit_yaml, opts)?;
781    let readme = build_readme(caixa, &chart_name);
782
783    // Each per-artifact leaf routes through the canonical
784    // [`caixa_core::RenderedFile::new`] `impl Into<PathBuf>` /
785    // `impl Into<String>` constructor (re-exported by the peer
786    // [`ChartFile`] alias since Rust inherent methods travel through
787    // type aliases to the aliased type at name resolution). The prior
788    // three inline `ChartFile { path: PathBuf::from(FILENAME_CONST),
789    // contents: <body> }` blocks each re-derived the same
790    // `PathBuf::from(&str)` wrap + the same two-field assembly — a
791    // byte-identical duplicate of the peer [`caixa_flux::cluster_bundle`]
792    // Flux v2 CR trio's three per-CR emit sites. Sweeping both trios
793    // onto [`RenderedFile::new`] collapses the six substrate-side
794    // per-artifact-construction sites onto one canonical constructor,
795    // so a future rebrand on the record shape (a per-artifact hash /
796    // provenance field addition, a per-artifact write-mode discriminator
797    // once per-cluster-writer sandboxing lands, the
798    // [`caixa_core::is_sandboxed_relative_path`] discipline the
799    // [`RenderedFile`] docstring acknowledges is not yet run at emit
800    // time) reaches every per-target renderer through one caixa-core
801    // edit instead of a coordinated six-site rewrite.
802    Ok(ChartDir {
803        name: chart_name,
804        files: vec![
805            ChartFile::new(
806                HELM_CHART_YAML_FILENAME,
807                serde_yaml::to_string(&chart_yaml)?,
808            ),
809            ChartFile::new(HELM_VALUES_YAML_FILENAME, values_yaml),
810            ChartFile::new(HELM_CHART_README_FILENAME, readme),
811        ],
812    })
813}
814
815fn build_chart_yaml(caixa: &Caixa, chart_name: &str, opts: &RenderOpts) -> ChartYaml {
816    let description = caixa
817        .descricao()
818        .map(str::to_owned)
819        .unwrap_or_else(|| format!("Generated chart for caixa Servico {}", caixa.nome()));
820    let keywords: Vec<String> = caixa
821        .etiquetas()
822        .iter()
823        .cloned()
824        .chain(
825            caixa_core::LAREIRA_CHART_KEYWORDS
826                .iter()
827                .copied()
828                .map(String::from),
829        )
830        .collect::<Vec<_>>()
831        .into_iter()
832        .collect::<std::collections::BTreeSet<_>>()
833        .into_iter()
834        .collect();
835    let maintainers = caixa
836        .autores()
837        .iter()
838        .map(|a| Maintainer {
839            name: a.clone(),
840            email: None,
841        })
842        .collect();
843    // Canonical typed `String`-carry of the per-`Caixa` `:versao`
844    // universal-axis SemVer-2 pinned-version scalar into the two
845    // per-`Chart.yaml` version-carrier fields Helm's chart-schema
846    // parser routes per-chart identity through — `Chart.yaml`'s
847    // top-level `version:` (the axis Helm's per-chart resolver keys
848    // per-release reconciliation off, the paired `HelmRelease`
849    // `spec.chart.spec.version` binds through, and every `helm
850    // template <chart>` / `helm install <release> <chart>` /
851    // `helm upgrade <release> <chart> --version` invocation names
852    // through) and `Chart.yaml`'s top-level `appVersion:` (the
853    // axis Helm chart-consumers key per-application-version
854    // documentation / release-note / OCI-tag / operator-side
855    // per-Caixa CR revision off), both routing through the typed
856    // [`caixa_core::Caixa::versao`] accessor's canonical
857    // `to_string()` extension of `&self.versao`. Peer of the
858    // sibling 4a363bf / 54bf2f3 `caixa.nome.clone()` converges
859    // on the outer-Caixa `:nome` `String`-carry axis in caixa-flux
860    // / caixa-mesh — this converges the last unlifted per-Caixa
861    // `.versao.clone()` raw-field `String`-carry axis in
862    // caixa-helm on the same "one typed dispatch per axis"
863    // discipline.
864    let versao = caixa.versao().to_string();
865    ChartYaml {
866        api_version: HELM_CHART_API_VERSION.into(),
867        name: chart_name.into(),
868        description,
869        chart_type: HELM_CHART_TYPE_APPLICATION.into(),
870        version: versao.clone(),
871        app_version: versao,
872        keywords,
873        maintainers,
874        home: caixa.repositorio().map(str::to_owned),
875        dependencies: vec![ChartDependency {
876            name: opts.library_name.clone(),
877            version: opts.library_version.clone(),
878            repository: opts.library_repo.clone(),
879            alias: None,
880        }],
881    }
882}
883
884fn build_values_yaml(
885    caixa: &Caixa,
886    computeunit_yaml: &serde_yaml::Value,
887    opts: &RenderOpts,
888) -> Result<String, Error> {
889    // The library chart consumes its values under the key matching its
890    // Helm chart `dependencies[].name` (Helm's per-dep alias convention
891    // — when no `alias:` is set on the dependency, values are scoped
892    // under the dependency's `name`). This renderer wires both axes
893    // through the same `opts.library_name`: the chart's dep `name:`
894    // (build_chart_yaml at line 277) and this site's values wrap key
895    // both consult one `&str`, so a future fork that overrides
896    // `RenderOpts::library_name` to point at `acme-computeunit` /
897    // `pleme-computeunit-mirror` / the future per-edition library name
898    // reaches both axes by construction. Until this lift landed the
899    // wrap key was hardcoded `"pleme-computeunit"` while the dep name
900    // followed `opts.library_name`, so an override silently emitted
901    // values keyed under one name (the literal) while the rendered
902    // Chart.yaml's dep was declared under another (the override) —
903    // Helm's per-dep values router would route nothing to the
904    // configured dependency at `helm template` / `helm install` time,
905    // and every typed value the values block carries (`enabled`,
906    // `module`, `trigger`, the M2 overlay's `:limits`/`:behavior`/
907    // `:upgrade-from`) would silently no-op at the rendered chart's
908    // landing site. The wrap key now reads from the same `&str` the
909    // dep name reads from, structurally closing the drift footgun
910    // peer with the [`caixa_core::DEFAULT_NAMESPACE`] /
911    // [`caixa_core::DEFAULT_SERVICO_PORT`] lifts on the sibling
912    // canonical-K8s-axis constants (where two production-code call
913    // sites of the same load-bearing value would drift apart on
914    // any rebrand without a shared source of truth).
915    let library_alias = opts.library_name.as_str();
916    let spec = computeunit_yaml
917        .get(KUBE_KEY_SPEC)
918        .ok_or(Error::MissingField(KUBE_KEY_SPEC))?;
919
920    // Prepend a comment header so the file is human-friendly.
921    let header = format!(
922        "# Auto-generated by caixa-helm from caixa.lisp + servicos/{nome}.computeunit.yaml.\n\
923         # Edits to this file are overwritten by `feira chart`.\n\
924         #\n\
925         # `{library_alias}:` is the alias under which the library chart\n\
926         # in pleme-io/helmworks/charts/{library_alias} consumes its values.\n\n",
927        nome = caixa.nome()
928    );
929
930    let mut block = BTreeMap::new();
931    block.insert(
932        HELM_VALUES_KEY_ENABLED.to_string(),
933        serde_yaml::Value::Bool(opts.enabled_default),
934    );
935    // Two-step per-Servico value-block splice — the `spec.*` field
936    // splice (module / trigger / capabilities / config / resources /
937    // serviceAccount) and the M2 typed-slot overlay (limits / behavior
938    // / upgradeFrom, `or_insert` semantics so `spec.*` wins on
939    // collision) now route through the canonical
940    // [`caixa_core::servico_spec_and_m2_overlay_entries`] composition
941    // helper — the two prior inline for-loops chained around
942    // `string_keyed_entries` + `servico_m2_overlay` this call site
943    // (and the peer [`caixa_flux::programs_yaml_entry`] site) each
944    // re-derived collapse onto one canonical composition, so a future
945    // change to the per-Servico splice / overlay shape (the M4 typed
946    // per-edge policy overlay slot addition MESH-COMPOSITION §III.2 #3
947    // acknowledges, a change to the precedence rule once per-Aplicacao
948    // operator overrides land, a canonicalization pass on the merged
949    // key set) reaches both renderers by construction instead of a
950    // coordinated two-file rewrite. See the helper's docstring for the
951    // full lift rationale. The target `BTreeMap` re-sorts by key on
952    // insert, so the final rendered values block stays byte-identical
953    // to the prior inline block's alphabetical shape.
954    for (k, v) in caixa_core::servico_spec_and_m2_overlay_entries(caixa, spec)? {
955        block.entry(k).or_insert(v);
956    }
957
958    let mut wrapped = serde_yaml::Mapping::new();
959    wrapped.insert_str_key(library_alias, serde_yaml::to_value(block)?);
960    let body = serde_yaml::to_string(&serde_yaml::Value::Mapping(wrapped))?;
961    Ok(format!("{header}{body}"))
962}
963
964fn build_readme(caixa: &Caixa, chart_name: &str) -> String {
965    let descricao = caixa
966        .descricao()
967        .map(str::to_owned)
968        .unwrap_or_else(|| format!("caixa Servico {}", caixa.nome()));
969    // Route the per-`README.md` `## Origin` line's `{repo}` interpolation
970    // through the substrate-canonical [`caixa_core::Caixa::canonical_git_url`]
971    // resolved-git-URL composer (124f864) rather than the prior
972    // `caixa.repositorio().unwrap_or(caixa.nome())` two-arm inline fallback.
973    // The prior shape's `:repositorio`-null arm folded to `caixa.nome()`
974    // verbatim, so the rendered README's `## Origin` line emitted the
975    // meaningless `Generated by `caixa-helm` from `<nome>/caixa.lisp``
976    // scalar on every `:repositorio`-null caixa — a bare `<nome>` prefix
977    // that told a downstream reader nothing about where the caixa's
978    // source actually lives, and disagreed on that same axis with the
979    // sibling [`caixa_flux::ClusterBundleOpts::for_caixa`] `git_url`
980    // seed which had already routed through the resolved-URL composer
981    // and emitted the canonical `https://github.com/{DEFAULT_PLEME_GIT_ORG}/<nome>`
982    // pleme-org fallback on the same author-omitted-`:repositorio` case.
983    // The 124f864 commit body explicitly anticipated this converge (the
984    // "same accessor consumed by every renderer" convergence surface it
985    // called out as the live-behavior-correcting fold distinct from that
986    // commit's pure byte-preserving convergence). Post-lift the two
987    // renderers agree byte-for-byte on the resolved-URL substrate
988    // primitive on both arms, and the README's `## Origin` line names a
989    // concrete pleme-org URL on the fallback path.
990    format!(
991        "# {chart_name}\n\
992         \n\
993         {descricao}\n\
994         \n\
995         ## Origin\n\
996         \n\
997         Generated by `caixa-helm` from `{repo}/caixa.lisp` v{versao}.\n\
998         Edits here are overwritten by `feira chart`.\n\
999         \n\
1000         ## Install\n\
1001         \n\
1002         ```bash\n\
1003         helm dependency build\n\
1004         helm template {chart_name} . --values values.yaml\n\
1005         ```\n\
1006         \n\
1007         ## License\n\
1008         \n\
1009         {license}.\n",
1010        chart_name = chart_name,
1011        descricao = descricao,
1012        repo = caixa.canonical_git_url(),
1013        versao = caixa.versao(),
1014        license = caixa.licenca().unwrap_or("MIT"),
1015    )
1016}
1017
1018#[cfg(test)]
1019mod tests {
1020    use super::*;
1021    use caixa_core::{
1022        Caixa, CaixaKind, M2_BEHAVIOR_KEY_ON_CALL, M2_BEHAVIOR_KEY_ON_INIT, M2_KEY_BEHAVIOR,
1023        M2_KEY_LIMITS, M2_KEY_UPGRADE_FROM, M2_LIMITS_KEY_CPU, M2_LIMITS_KEY_FUEL,
1024        M2_LIMITS_KEY_MEMORY, M2_LIMITS_KEY_WALL_CLOCK, find_file_by_path, kube_has, kube_str,
1025        kube_u64, mapping_string_keys, parse_yaml_at_path, parse_yaml_at_path_as,
1026    };
1027    use std::path::PathBuf;
1028
1029    fn sample_caixa() -> Caixa {
1030        Caixa {
1031            nome: "hello-rio".into(),
1032            versao: "0.1.0".into(),
1033            kind: CaixaKind::Servico,
1034            edicao: Some("2026".into()),
1035            descricao: Some("Canonical Rust→wasm32-wasip2 caixa Servico.".into()),
1036            repositorio: Some("github:pleme-io/hello-rio".into()),
1037            licenca: Some("MIT".into()),
1038            autores: vec!["pleme-io".into()],
1039            etiquetas: vec!["hello-world".into(), "wasm".into(), "rust".into()],
1040            deps: vec![],
1041            deps_dev: vec![],
1042            exe: vec![],
1043            bibliotecas: vec![],
1044            servicos: vec!["servicos/hello-rio.computeunit.yaml".into()],
1045            limits: None,
1046            behavior: None,
1047            upgrade_from: vec![],
1048            estrategia: None,
1049            max_restarts: None,
1050            restart_window: None,
1051            children: vec![],
1052            membros: vec![],
1053            contratos: vec![],
1054            politicas: None,
1055            placement: None,
1056            entrada: None,
1057            ci: None,
1058        }
1059    }
1060
1061    fn sample_cu_yaml() -> serde_yaml::Value {
1062        serde_yaml::from_str(
1063            r#"
1064apiVersion: wasm.pleme.io/v1alpha1
1065kind: ComputeUnit
1066metadata:
1067  name: hello-rio
1068spec:
1069  module:
1070    source: oci://ghcr.io/pleme-io/hello-rio:v0.1.0
1071  trigger:
1072    service:
1073      port: 8080
1074      paths: ["/", "/hello", "/healthz"]
1075      breathability:
1076        enabled: true
1077        minReplicas: 0
1078        maxReplicas: 5
1079        cooldownPeriod: 600
1080  capabilities:
1081    - http-in:0.0.0.0:8080
1082    - env
1083"#,
1084        )
1085        .unwrap()
1086    }
1087
1088    #[test]
1089    fn renders_three_files() {
1090        let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
1091        assert_eq!(dir.name, "lareira-hello-rio");
1092        let names: Vec<_> = dir
1093            .files
1094            .iter()
1095            .map(|f| f.path.to_string_lossy().to_string())
1096            .collect();
1097        assert!(names.contains(&HELM_CHART_YAML_FILENAME.to_string()));
1098        assert!(names.contains(&HELM_VALUES_YAML_FILENAME.to_string()));
1099        assert!(names.contains(&HELM_CHART_README_FILENAME.to_string()));
1100    }
1101
1102    #[test]
1103    fn chart_yaml_metadata_propagates() {
1104        let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
1105        let chart: ChartYaml = parse_yaml_at_path_as(&dir.files, HELM_CHART_YAML_FILENAME);
1106        assert_eq!(chart.api_version, "v2");
1107        assert_eq!(chart.name, "lareira-hello-rio");
1108        assert_eq!(chart.version, "0.1.0");
1109        assert_eq!(chart.app_version, "0.1.0");
1110        assert_eq!(chart.dependencies.len(), 1);
1111        assert_eq!(chart.dependencies[0].name, DEFAULT_LIBRARY_NAME);
1112        assert!(chart.keywords.contains(&"caixa-servico".to_string()));
1113        assert!(chart.keywords.contains(&"hello-world".to_string()));
1114        assert_eq!(chart.maintainers[0].name, "pleme-io");
1115    }
1116
1117    #[test]
1118    fn chart_yaml_keywords_union_pins_every_lareira_chart_keywords_entry() {
1119        // Structural pin: `build_chart_yaml`'s substrate-fixed
1120        // chart-keyword union routes through the canonical
1121        // `caixa_core::LAREIRA_CHART_KEYWORDS` array — every rendered
1122        // `lareira-<nome>` chart's emitted `Chart.yaml` `keywords:`
1123        // sequence carries every substrate-fixed entry the array
1124        // declares. A future substrate-fixed keyword addition
1125        // (an `"opentelemetry"` entry once the caixa-otel collector-
1126        // pipeline chart lands, a `"lunatic"` entry once the wasm-
1127        // process-runtime marker lands, a `"gen_server"` entry once
1128        // the OTP-shape callback marker lands per the
1129        // [`caixa_core::behavior`] surface) that lands in the array
1130        // reaches this crate's production emit site by construction
1131        // through the shared `&[&str]` reference — a drift where the
1132        // production emit at `build_chart_yaml` re-inlines the pre-
1133        // lift `["lareira", "wasm", "tatara-lisp", "caixa-servico"]`
1134        // literal set (or drops a per-entry axis on a rebrand
1135        // sweep) fails this pin at caixa-helm build time rather than
1136        // surfacing as a `helm search hub caixa-servico` miss on the
1137        // Artifact Hub keyword-search index at chart-publish time
1138        // downstream. Peer to
1139        // [`chart_yaml_metadata_propagates`] on the
1140        // per-`Chart.yaml`-body-field propagation surface.
1141        let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
1142        let chart: ChartYaml = parse_yaml_at_path_as(&dir.files, HELM_CHART_YAML_FILENAME);
1143        for keyword in caixa_core::LAREIRA_CHART_KEYWORDS {
1144            assert!(
1145                chart.keywords.contains(&(*keyword).to_string()),
1146                "rendered Chart.yaml keywords {:?} must contain the \
1147                 substrate-fixed LAREIRA_CHART_KEYWORDS entry {keyword:?}",
1148                chart.keywords,
1149            );
1150        }
1151    }
1152
1153    #[test]
1154    fn values_yaml_wraps_under_pleme_computeunit_key() {
1155        let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
1156        let parsed = parse_yaml_at_path(&dir.files, HELM_VALUES_YAML_FILENAME);
1157        let cu_block = parsed
1158            .get(DEFAULT_LIBRARY_NAME)
1159            .expect("must wrap under DEFAULT_LIBRARY_NAME");
1160        assert_eq!(
1161            cu_block.get(HELM_VALUES_KEY_ENABLED),
1162            Some(&serde_yaml::Value::Bool(false))
1163        );
1164        assert!(kube_has(cu_block, COMPUTEUNIT_SPEC_KEY_MODULE));
1165        assert!(kube_has(cu_block, COMPUTEUNIT_SPEC_KEY_TRIGGER));
1166        assert!(kube_has(cu_block, COMPUTEUNIT_SPEC_KEY_CAPABILITIES));
1167    }
1168
1169    #[test]
1170    fn values_yaml_wrap_key_follows_library_name_override() {
1171        // Pinning the canonical alignment between the Helm chart's
1172        // `dependencies[].name` axis (build_chart_yaml at line 277) and
1173        // the values block's wrap key (build_values_yaml at the
1174        // `wrapped.insert(...)` site): both consult the same
1175        // `opts.library_name`, so an override on either axis reaches the
1176        // other by construction. Helm's per-dep alias convention — when
1177        // no `alias:` is set on a dependency, values are scoped under
1178        // its `name:` — makes wrap-key drift a silent value-routing
1179        // no-op at `helm template` / `helm install` time, so the
1180        // structural pin is load-bearing.
1181        let opts = RenderOpts {
1182            library_name: "acme-computeunit".into(),
1183            ..RenderOpts::default()
1184        };
1185        let dir = render_chart_for_servico_with(&sample_caixa(), &sample_cu_yaml(), &opts).unwrap();
1186        let parsed = parse_yaml_at_path(&dir.files, HELM_VALUES_YAML_FILENAME);
1187        assert!(
1188            kube_has(&parsed, "acme-computeunit"),
1189            "values wrap key must follow opts.library_name override \
1190             (got top-level keys: {keys:?})",
1191            keys = parsed
1192                .as_mapping()
1193                .map(mapping_string_keys)
1194                .unwrap_or_default()
1195        );
1196        assert!(
1197            !kube_has(&parsed, DEFAULT_LIBRARY_NAME),
1198            "values wrap key must not retain the default `{DEFAULT_LIBRARY_NAME}` literal \
1199             when opts.library_name overrides it"
1200        );
1201        let cu_block = parsed.get("acme-computeunit").unwrap();
1202        assert_eq!(
1203            cu_block.get(HELM_VALUES_KEY_ENABLED),
1204            Some(&serde_yaml::Value::Bool(false))
1205        );
1206        assert!(kube_has(cu_block, COMPUTEUNIT_SPEC_KEY_MODULE));
1207        assert!(kube_has(cu_block, COMPUTEUNIT_SPEC_KEY_TRIGGER));
1208        assert!(kube_has(cu_block, COMPUTEUNIT_SPEC_KEY_CAPABILITIES));
1209    }
1210
1211    #[test]
1212    fn values_yaml_wrap_key_matches_chart_dependency_name() {
1213        // The structural invariant the lift defends: every rendered
1214        // chart's values.yaml wrap key equals its Chart.yaml
1215        // `dependencies[0].name`. Sweeping the canonical default + a
1216        // typed override on the same axis pins the alignment across the
1217        // accepted set of `RenderOpts::library_name` values rather than
1218        // at a single canonical literal.
1219        for library_name in [DEFAULT_LIBRARY_NAME, "acme-computeunit", "fork-pleme-cu"] {
1220            let opts = RenderOpts {
1221                library_name: library_name.into(),
1222                ..RenderOpts::default()
1223            };
1224            let dir =
1225                render_chart_for_servico_with(&sample_caixa(), &sample_cu_yaml(), &opts).unwrap();
1226            let chart: ChartYaml = parse_yaml_at_path_as(&dir.files, HELM_CHART_YAML_FILENAME);
1227            let dep_name = &chart.dependencies[0].name;
1228            let parsed = parse_yaml_at_path(&dir.files, HELM_VALUES_YAML_FILENAME);
1229            assert!(
1230                parsed.get(dep_name.as_str()).is_some(),
1231                "values.yaml wrap key must match Chart.yaml dependencies[0].name {dep_name:?} \
1232                 (library_name = {library_name:?}); Helm's per-dep alias convention scopes \
1233                 values under the dep's `name` when no `alias:` is set, so any drift between \
1234                 the two axes silently routes the values block nowhere"
1235            );
1236        }
1237    }
1238
1239    #[test]
1240    fn values_yaml_header_comment_follows_library_name_override() {
1241        // The human-facing values.yaml header's `<library_alias>:` /
1242        // `pleme-io/helmworks/charts/<library_alias>` references both
1243        // resolve through `opts.library_name`, peer with the wrap key
1244        // itself, so an override leaves the header self-consistent
1245        // with the rendered structure rather than naming a drifted
1246        // default literal.
1247        let opts = RenderOpts {
1248            library_name: "acme-computeunit".into(),
1249            ..RenderOpts::default()
1250        };
1251        let dir = render_chart_for_servico_with(&sample_caixa(), &sample_cu_yaml(), &opts).unwrap();
1252        let values = find_file_by_path(&dir.files, HELM_VALUES_YAML_FILENAME).unwrap();
1253        assert!(
1254            values.contents.contains("`acme-computeunit:`"),
1255            "header must name the overriding library alias verbatim \
1256             (got: {contents:?})",
1257            contents = values.contents
1258        );
1259        assert!(
1260            values
1261                .contents
1262                .contains("pleme-io/helmworks/charts/acme-computeunit"),
1263            "header's helmworks path must follow the overriding library alias \
1264             (got: {contents:?})",
1265            contents = values.contents
1266        );
1267        assert!(
1268            !values.contents.contains("`pleme-computeunit:`"),
1269            "header must not retain the default library alias literal \
1270             when overridden (got: {contents:?})",
1271            contents = values.contents
1272        );
1273    }
1274
1275    #[test]
1276    fn refuses_non_servico() {
1277        let mut c = sample_caixa();
1278        c.kind = CaixaKind::Biblioteca;
1279        c.servicos = vec![];
1280        let err = render_chart_for_servico(&c, &sample_cu_yaml()).unwrap_err();
1281        assert!(matches!(err, Error::NotAServico(_)));
1282    }
1283
1284    #[test]
1285    fn kind_mismatch_error_names_offending_caixa_nome() {
1286        // Pinning the lifted [`caixa_core::KindMismatch`] view's
1287        // load-bearing property: a kind-mismatched caixa surfaces a
1288        // diagnostic that *names the offending caixa* (`hello-rio`),
1289        // not just the rejected kind. Before the lift the renderer
1290        // raised `Error::NotAServico(CaixaKind::Biblioteca)` whose
1291        // Display said "caixa :kind must be Servico for caixa-helm
1292        // rendering, got Biblioteca" — the user had to grep their
1293        // source tree for which caixa.lisp triggered it. After the
1294        // lift the wrapped KindMismatch carries the `:nome`, the
1295        // renderer's `#[error("{0}")]` arm prints it through, and
1296        // the diagnostic is self-locating.
1297        let mut c = sample_caixa();
1298        c.kind = CaixaKind::Biblioteca;
1299        c.servicos = vec![];
1300        let err = render_chart_for_servico(&c, &sample_cu_yaml()).unwrap_err();
1301        let msg = format!("{err}");
1302        assert!(
1303            msg.contains("hello-rio"),
1304            "kind-mismatch diagnostic must name the offending caixa nome \
1305             (got: {msg:?})"
1306        );
1307        assert!(
1308            msg.contains("Servico"),
1309            "diagnostic must name the expected kind (got: {msg:?})"
1310        );
1311        assert!(
1312            msg.contains("Biblioteca"),
1313            "diagnostic must name the actual kind (got: {msg:?})"
1314        );
1315    }
1316
1317    #[test]
1318    fn kind_mismatch_carries_typed_view_via_from_conversion() {
1319        // The renderer's `Error::NotAServico` variant wraps the typed
1320        // [`caixa_core::KindMismatch`] view via `#[from]`, so the `?`
1321        // operator at the call site converts without manual glue.
1322        // Pinning the typed payload (not just the variant) so a
1323        // future refactor can't silently switch the variant to a
1324        // raw-`CaixaKind` payload (which would regress the lift's
1325        // shared-shape contract with caixa-flux + caixa-mesh).
1326        let mut c = sample_caixa();
1327        c.kind = CaixaKind::Aplicacao;
1328        c.servicos = vec![];
1329        let err = render_chart_for_servico(&c, &sample_cu_yaml()).unwrap_err();
1330        match err {
1331            Error::NotAServico(km) => {
1332                assert_eq!(km.nome, "hello-rio");
1333                assert_eq!(km.expected, CaixaKind::Servico);
1334                assert_eq!(km.actual, CaixaKind::Aplicacao);
1335            }
1336            other => panic!("expected Error::NotAServico, got {other:?}"),
1337        }
1338    }
1339
1340    #[test]
1341    fn servico_count_mismatch_carries_typed_view_with_nome() {
1342        // Peer to the [`KindMismatch`]-lift pin above on the V0
1343        // `:servicos`-singularity axis: a Servico-kind caixa whose
1344        // `:servicos` list is non-singleton fails
1345        // [`render_chart_for_servico`] with the renderer's
1346        // `Error::UnsupportedServicoCount` variant wrapping the typed
1347        // [`caixa_core::ServicoCountMismatch`] view (carrying the
1348        // offending caixa's `:nome` + the actual count). Before the
1349        // lift the variant carried only `usize` — the user had to grep
1350        // their source tree for which `caixa.lisp` triggered it; after
1351        // the lift the wrapped typed view names the offending caixa
1352        // verbatim. Pins both the variant routing (via `#[from]`) and
1353        // the typed payload so a future refactor can't silently switch
1354        // back to the raw-`usize` payload (which would regress the
1355        // shared-shape contract with caixa-flux on the peer
1356        // programs.yaml-entry path).
1357        let mut c = sample_caixa();
1358        c.servicos = vec![
1359            "servicos/hello-rio.computeunit.yaml".into(),
1360            "servicos/extra.computeunit.yaml".into(),
1361        ];
1362        let err = render_chart_for_servico(&c, &sample_cu_yaml()).unwrap_err();
1363        match err {
1364            Error::UnsupportedServicoCount(scm) => {
1365                assert_eq!(scm.nome, "hello-rio");
1366                assert_eq!(scm.count, 2);
1367            }
1368            other => panic!("expected Error::UnsupportedServicoCount, got {other:?}"),
1369        }
1370    }
1371
1372    #[test]
1373    fn servico_count_mismatch_diagnostic_names_offending_caixa_nome() {
1374        // The renderer's `#[error("{0}")] UnsupportedServicoCount(
1375        // #[from] ServicoCountMismatch)` arm prints the typed view's
1376        // Display through verbatim, so the offending caixa's `:nome`
1377        // appears in the rendered diagnostic. Pinning the
1378        // self-locating property end-to-end (renderer entry-point →
1379        // typed view's Display → final diagnostic string) so a future
1380        // refactor that re-wraps the variant in a Display impl that
1381        // drops the `:nome` surfaces here as a test failure rather
1382        // than as silent fragmentation of the diagnostic. Peer to the
1383        // `kind_mismatch_error_names_offending_caixa_nome` test above
1384        // on the sibling V0 Servico-shape axis.
1385        let mut c = sample_caixa();
1386        c.servicos = vec![
1387            "servicos/hello-rio.computeunit.yaml".into(),
1388            "servicos/extra.computeunit.yaml".into(),
1389        ];
1390        let err = render_chart_for_servico(&c, &sample_cu_yaml()).unwrap_err();
1391        let msg = format!("{err}");
1392        assert!(
1393            msg.contains("hello-rio"),
1394            ":servicos-count-mismatch diagnostic must name the offending caixa nome \
1395             (got: {msg:?})"
1396        );
1397        assert!(
1398            msg.contains("2"),
1399            "diagnostic must name the actual count (got: {msg:?})"
1400        );
1401        assert!(
1402            msg.contains(":servicos"),
1403            "diagnostic must name the offending field axis (got: {msg:?})"
1404        );
1405    }
1406
1407    #[test]
1408    fn limits_slot_propagates_into_values_block() {
1409        use caixa_core::LimitsSpec;
1410        use std::time::Duration;
1411        let mut c = sample_caixa();
1412        c.limits = Some(LimitsSpec {
1413            memory: Some(64 * 1024 * 1024),
1414            fuel: Some(1_000_000),
1415            wall_clock: Some(Duration::from_secs(30)),
1416            cpu: Some(500),
1417        });
1418        let dir = render_chart_for_servico(&c, &sample_cu_yaml()).unwrap();
1419        let parsed = parse_yaml_at_path(&dir.files, HELM_VALUES_YAML_FILENAME);
1420        let cu_block = parsed.get(DEFAULT_LIBRARY_NAME).unwrap();
1421        let limits = cu_block.get(M2_KEY_LIMITS).expect("limits must propagate");
1422        assert_eq!(kube_str(limits, M2_LIMITS_KEY_MEMORY), Some("64MiB"));
1423        assert_eq!(kube_u64(limits, M2_LIMITS_KEY_FUEL), Some(1_000_000));
1424        assert_eq!(kube_str(limits, M2_LIMITS_KEY_WALL_CLOCK), Some("30s"));
1425        assert_eq!(kube_str(limits, M2_LIMITS_KEY_CPU), Some("500m"));
1426    }
1427
1428    #[test]
1429    fn behavior_slot_propagates_into_values_block() {
1430        use caixa_core::BehaviorSpec;
1431        let mut c = sample_caixa();
1432        c.behavior = Some(BehaviorSpec {
1433            on_init: Some(PathBuf::from("lib/init.lisp")),
1434            on_call: Some(PathBuf::from("lib/handlers.lisp")),
1435            ..Default::default()
1436        });
1437        let dir = render_chart_for_servico(&c, &sample_cu_yaml()).unwrap();
1438        let parsed = parse_yaml_at_path(&dir.files, HELM_VALUES_YAML_FILENAME);
1439        let cu_block = parsed.get(DEFAULT_LIBRARY_NAME).unwrap();
1440        let behavior = cu_block
1441            .get(M2_KEY_BEHAVIOR)
1442            .expect("behavior must propagate");
1443        assert_eq!(
1444            kube_str(behavior, M2_BEHAVIOR_KEY_ON_INIT),
1445            Some("lib/init.lisp")
1446        );
1447        assert_eq!(
1448            kube_str(behavior, M2_BEHAVIOR_KEY_ON_CALL),
1449            Some("lib/handlers.lisp")
1450        );
1451    }
1452
1453    #[test]
1454    fn upgrade_from_slot_propagates_into_values_block() {
1455        use caixa_core::{UpgradeFromEntry, UpgradeInstruction};
1456        let mut c = sample_caixa();
1457        c.upgrade_from = vec![UpgradeFromEntry {
1458            from: "0.0.9".into(),
1459            instructions: vec![UpgradeInstruction::LoadModule {
1460                module: "hello-rio".into(),
1461            }],
1462        }];
1463        let dir = render_chart_for_servico(&c, &sample_cu_yaml()).unwrap();
1464        let parsed = parse_yaml_at_path(&dir.files, HELM_VALUES_YAML_FILENAME);
1465        let cu_block = parsed.get(DEFAULT_LIBRARY_NAME).unwrap();
1466        assert!(kube_has(cu_block, M2_KEY_UPGRADE_FROM));
1467    }
1468
1469    #[test]
1470    fn empty_m2_slots_do_not_appear() {
1471        // Existing caixa with no M2 slots → values.yaml carries no
1472        // limits/behavior/upgradeFrom keys (forward-compat invariant).
1473        let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
1474        let parsed = parse_yaml_at_path(&dir.files, HELM_VALUES_YAML_FILENAME);
1475        let cu_block = parsed.get(DEFAULT_LIBRARY_NAME).unwrap();
1476        assert!(!kube_has(cu_block, M2_KEY_LIMITS));
1477        assert!(!kube_has(cu_block, M2_KEY_BEHAVIOR));
1478        assert!(!kube_has(cu_block, M2_KEY_UPGRADE_FROM));
1479    }
1480
1481    #[test]
1482    fn write_to_creates_files() {
1483        let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
1484        let tmp = tempfile::tempdir().unwrap();
1485        dir.write_to(tmp.path()).unwrap();
1486        let chart_root = tmp.path().join("lareira-hello-rio");
1487        assert!(chart_root.join(HELM_CHART_YAML_FILENAME).exists());
1488        assert!(chart_root.join(HELM_VALUES_YAML_FILENAME).exists());
1489        assert!(chart_root.join(HELM_CHART_README_FILENAME).exists());
1490    }
1491
1492    #[test]
1493    fn default_library_name_re_export_points_at_caixa_core_canonical() {
1494        // The renderer's `pub const DEFAULT_LIBRARY_NAME` was lifted to a
1495        // re-export of [`caixa_core::DEFAULT_LIBRARY_NAME`] so the Helm
1496        // library-chart name lives in exactly one place across every
1497        // caixa renderer (caixa-helm's `RenderOpts::library_name`
1498        // default here + caixa-flux's `cluster_bundle` `helmrelease.yaml`
1499        // wrap key on the sibling deploy-path crate). Pin the equality
1500        // here so any local re-introduction of a sibling `pub const
1501        // DEFAULT_LIBRARY_NAME: &str = "…"` (the canonical drift footgun
1502        // the prior `DEFAULT_NAMESPACE` / `DEFAULT_SERVICO_PORT` lift
1503        // commits' bodies acknowledged as the recurring shape) is a
1504        // build-time test failure naming the offending drift, not a
1505        // silent apply-time wrap-key mismatch routing the per-cluster
1506        // `enabled: true` override nowhere on `helm template` /
1507        // `helm install`. Peer to
1508        // `caixa_flux::tests::default_library_name_re_export_points_at_caixa_core_canonical`
1509        // on the sibling renderer crate.
1510        caixa_core::assert_str_reexport_identity(
1511            "DEFAULT_LIBRARY_NAME",
1512            DEFAULT_LIBRARY_NAME,
1513            caixa_core::DEFAULT_LIBRARY_NAME,
1514        );
1515    }
1516
1517    #[test]
1518    fn kube_key_spec_re_export_points_at_caixa_core_canonical() {
1519        // The renderer's `KUBE_KEY_SPEC` was lifted from the production-
1520        // code inline `"spec"` literal at `build_values_yaml`'s
1521        // `computeunit_yaml.get("spec")` ComputeUnit-side spec read (+
1522        // its matching `Error::MissingField("spec")` diagnostic) to a
1523        // re-export of [`caixa_core::KUBE_KEY_SPEC`] so the canonical
1524        // K8s-CR top-level spec-axis string lives in exactly one place
1525        // across every caixa renderer. Pin the equality + static-data
1526        // identity here so any local re-introduction of a sibling
1527        // `pub const KUBE_KEY_SPEC: &str = "…"` (the canonical drift
1528        // footgun where a sibling local `pub const` could happen to
1529        // carry the same string at the source while pointing at a
1530        // different `&'static` allocation) is a build-time test
1531        // failure naming the offending drift. Peer to
1532        // [`default_library_name_re_export_points_at_caixa_core_canonical`]
1533        // on the sibling re-export axis +
1534        // `caixa_flux::tests::kube_key_spec_re_export_points_at_caixa_core_canonical`
1535        // / `caixa_mesh::tests::kube_key_spec_re_export_points_at_caixa_core_canonical`
1536        // on the sibling renderer crates.
1537        caixa_core::assert_str_reexport_identity(
1538            "KUBE_KEY_SPEC",
1539            KUBE_KEY_SPEC,
1540            caixa_core::KUBE_KEY_SPEC,
1541        );
1542    }
1543
1544    #[test]
1545    fn helm_chart_api_version_re_export_points_at_caixa_core_canonical() {
1546        // The renderer's `HELM_CHART_API_VERSION` was lifted from the
1547        // production-code inline `"v2".into()` literal at
1548        // [`build_chart_yaml`]'s `api_version` field assignment (formerly
1549        // `caixa-helm/src/lib.rs:298`) to a re-export of
1550        // [`caixa_core::HELM_CHART_API_VERSION`] so the Helm 3
1551        // chart-schema apiVersion the rendered Chart.yaml declares lives
1552        // in exactly one place across every caixa renderer. Pin the
1553        // equality + `&'static` static-data identity here so any local
1554        // re-introduction of a sibling `pub const HELM_CHART_API_VERSION:
1555        // &str = "…"` at this crate — the canonical drift footgun where
1556        // a sibling local `pub const` could happen to carry the same
1557        // string at the source while pointing at a different `&'static`
1558        // allocation — is a build-time test failure naming the offending
1559        // drift, not a silent chart-schema-parser reroute at
1560        // `helm template` time far from the drift site. Peer to
1561        // [`kube_key_spec_re_export_points_at_caixa_core_canonical`] /
1562        // [`default_library_name_re_export_points_at_caixa_core_canonical`]
1563        // on the sibling re-export axes.
1564        caixa_core::assert_str_reexport_identity(
1565            "HELM_CHART_API_VERSION",
1566            HELM_CHART_API_VERSION,
1567            caixa_core::HELM_CHART_API_VERSION,
1568        );
1569    }
1570
1571    #[test]
1572    fn chart_yaml_uses_lifted_helm_chart_api_version() {
1573        // Fail-before-pass-after pin on the production-code
1574        // substitution: [`build_chart_yaml`]'s `api_version` field
1575        // consults the lifted [`HELM_CHART_API_VERSION`] re-export at
1576        // its assignment site, so the rendered Chart.yaml's top-level
1577        // `apiVersion` axis is byte-identical to the canonical constant
1578        // by construction. Before the lift the field carried an inline
1579        // `"v2".into()` literal at [`build_chart_yaml`]; a future
1580        // refactor that accidentally reverted the substitution — or
1581        // any parallel per-renderer variant that inlined a stale
1582        // Helm 2 `"v1"` literal — would silently reroute the rendered
1583        // Chart.yaml through the wrong chart-schema parser at
1584        // `helm dependency build` / `helm template` time, so this pin
1585        // trips at caixa-helm build time. Peer to
1586        // `values_yaml_wrap_key_matches_chart_dependency_name` on the
1587        // sibling structural-cross-axis-invariant surface.
1588        let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
1589        let chart: ChartYaml = parse_yaml_at_path_as(&dir.files, HELM_CHART_YAML_FILENAME);
1590        assert_eq!(
1591            chart.api_version, HELM_CHART_API_VERSION,
1592            "rendered Chart.yaml `apiVersion` must equal the lifted \
1593             HELM_CHART_API_VERSION verbatim — a drifted value silently \
1594             reroutes the rendered chart through the wrong Helm chart-schema \
1595             parser at `helm template` time"
1596        );
1597    }
1598
1599    #[test]
1600    fn helm_chart_type_application_re_export_points_at_caixa_core_canonical() {
1601        // The renderer's `HELM_CHART_TYPE_APPLICATION` was lifted from
1602        // the production-code inline `"application".into()` literal at
1603        // [`build_chart_yaml`]'s `chart_type` field assignment (formerly
1604        // `caixa-helm/src/lib.rs:354`) to a re-export of
1605        // [`caixa_core::HELM_CHART_TYPE_APPLICATION`] so the Helm 3
1606        // chart-schema per-chart-kind discriminator scalar-value the
1607        // rendered `lareira-<nome>` chart declares lives in exactly one
1608        // place across every caixa renderer. Pin the equality +
1609        // `&'static` static-data identity here so any local
1610        // re-introduction of a sibling `pub const
1611        // HELM_CHART_TYPE_APPLICATION: &str = "…"` at this crate — the
1612        // canonical drift footgun where a sibling local `pub const`
1613        // could happen to carry the same string at the source while
1614        // pointing at a different `&'static` allocation — is a
1615        // build-time test failure naming the offending drift, not a
1616        // silent per-release install-shape dispatch reroute at
1617        // `helm install` time far from the drift site. Peer to
1618        // [`helm_chart_api_version_re_export_points_at_caixa_core_canonical`]
1619        // on the sibling canonical-Helm-chart-schema-axis re-export
1620        // surface — completes the per-Chart.yaml `(apiVersion, type)`
1621        // canonical-scalar-axis re-export pair every rendered
1622        // `lareira-<nome>` chart declares at its top-level Chart.yaml
1623        // body.
1624        caixa_core::assert_str_reexport_identity(
1625            "HELM_CHART_TYPE_APPLICATION",
1626            HELM_CHART_TYPE_APPLICATION,
1627            caixa_core::HELM_CHART_TYPE_APPLICATION,
1628        );
1629    }
1630
1631    #[test]
1632    fn helm_chart_type_library_re_export_points_at_caixa_core_canonical() {
1633        // Re-export identity pin on the peer closed-set arm the
1634        // renderer's `HELM_CHART_TYPE_LIBRARY` alias resolves to. Peer
1635        // of `helm_chart_type_application_re_export_points_at_caixa_core_canonical`
1636        // on the sibling closed-set arm — the two pins together enshrine
1637        // the two-arm `{"application", "library"}` closed set at the
1638        // caixa-helm re-export surface as byte-identical `&'static`
1639        // static-data views onto the canonical caixa-core lifts, so any
1640        // local re-introduction of a sibling `pub const
1641        // HELM_CHART_TYPE_LIBRARY: &str = "…"` at this crate (the same
1642        // drift footgun the peer pin closes on the sibling arm) is a
1643        // build-time test failure naming the offending drift. The pin
1644        // also structurally forbids the two arms from converging on the
1645        // same `&'static` allocation — a future rebrand that
1646        // accidentally aliased `HELM_CHART_TYPE_LIBRARY` at the
1647        // [`caixa_core::HELM_CHART_TYPE_APPLICATION`] canonical would
1648        // pass this identity check but trip the caixa-core-side
1649        // `helm_chart_type_application_and_library_are_distinct` pin
1650        // paired to the two arms' distinctness contract.
1651        caixa_core::assert_str_reexport_identity(
1652            "HELM_CHART_TYPE_LIBRARY",
1653            HELM_CHART_TYPE_LIBRARY,
1654            caixa_core::HELM_CHART_TYPE_LIBRARY,
1655        );
1656    }
1657
1658    #[test]
1659    fn chart_yaml_uses_lifted_helm_chart_type_application() {
1660        // Fail-before-pass-after pin on the production-code substitution:
1661        // [`build_chart_yaml`]'s `chart_type` field consults the lifted
1662        // [`HELM_CHART_TYPE_APPLICATION`] re-export at its assignment
1663        // site, so the rendered Chart.yaml's top-level `type` axis is
1664        // byte-identical to the canonical constant by construction.
1665        // Before the lift the field carried an inline `"application".into()`
1666        // literal at [`build_chart_yaml`]; a future refactor that
1667        // accidentally reverted the substitution — or any parallel
1668        // per-renderer variant that inlined a `"library"` literal (the
1669        // sibling closed-set value from the Helm chart-schema's
1670        // per-chart-kind enum) — would silently reroute the rendered
1671        // Chart.yaml through the wrong per-release install-shape
1672        // dispatch at `helm install` time (Helm refuses to install a
1673        // `library` chart directly), so this pin trips at caixa-helm
1674        // build time. Peer to `chart_yaml_uses_lifted_helm_chart_api_version`
1675        // on the sibling per-Chart.yaml top-level `(apiVersion, type)`
1676        // canonical-scalar-axis pin pair — extends the per-Chart.yaml
1677        // top-level canonical-scalar-axis production-emit-pin
1678        // discipline from the `apiVersion` half onto the sibling `type`
1679        // half.
1680        let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
1681        let chart: ChartYaml = parse_yaml_at_path_as(&dir.files, HELM_CHART_YAML_FILENAME);
1682        assert_eq!(
1683            chart.chart_type, HELM_CHART_TYPE_APPLICATION,
1684            "rendered Chart.yaml `type` must equal the lifted \
1685             HELM_CHART_TYPE_APPLICATION verbatim — a drifted value \
1686             silently reroutes the rendered chart through the wrong \
1687             per-release install-shape dispatch at `helm install` time \
1688             (Helm refuses to install a `library` chart directly, or \
1689             silently treats an unrecognized value as the default \
1690             `application` shape masking the schema violation)"
1691        );
1692    }
1693
1694    #[test]
1695    fn helm_chart_key_type_re_export_points_at_caixa_core_canonical() {
1696        // The renderer's `HELM_CHART_KEY_TYPE` was lifted from the
1697        // sole production-side inline `"type"` literal at [`ChartYaml`]'s
1698        // `chart_type` field `#[serde(rename = "type")]` attribute
1699        // (formerly `caixa-helm/src/lib.rs:149`) to a re-export of
1700        // [`caixa_core::HELM_CHART_KEY_TYPE`] so the Helm 3 top-level
1701        // per-chart-kind discriminator YAML axis-key lives in exactly
1702        // one place across every caixa renderer. Pin the equality +
1703        // `&'static` static-data identity here so any local
1704        // re-introduction of a sibling `pub const HELM_CHART_KEY_TYPE:
1705        // &str = "…"` at this crate — the canonical drift footgun
1706        // where a sibling local `pub const` could happen to carry the
1707        // same string at the source while pointing at a different
1708        // `&'static` allocation — is a build-time test failure naming
1709        // the offending drift, not a silent Helm-chart-schema-parser
1710        // per-chart-kind-defaulting reroute at `helm dependency build`
1711        // / `helm lint` / `helm template` / `helm install` time far
1712        // from the drift site. Peer to
1713        // [`helm_chart_type_application_re_export_points_at_caixa_core_canonical`]
1714        // / [`helm_chart_type_library_re_export_points_at_caixa_core_canonical`]
1715        // on the sibling per-chart-kind axis-value re-export surface —
1716        // completes the per-Chart.yaml per-chart-kind discriminator
1717        // axis's `(key, value-set)` canonical re-export trio at the
1718        // caixa-helm surface.
1719        caixa_core::assert_str_reexport_identity(
1720            "HELM_CHART_KEY_TYPE",
1721            HELM_CHART_KEY_TYPE,
1722            caixa_core::HELM_CHART_KEY_TYPE,
1723        );
1724    }
1725
1726    #[test]
1727    fn helm_chart_key_app_version_re_export_points_at_caixa_core_canonical() {
1728        // The renderer's `HELM_CHART_KEY_APP_VERSION` was lifted from
1729        // the sole production-side inline `"appVersion"` literal at
1730        // [`ChartYaml`]'s `app_version` field
1731        // `#[serde(rename = "appVersion")]` attribute (formerly
1732        // `caixa-helm/src/lib.rs:152`) to a re-export of
1733        // [`caixa_core::HELM_CHART_KEY_APP_VERSION`] so the Helm 3
1734        // top-level per-chart-app-version YAML axis-key lives in
1735        // exactly one place across every caixa renderer. Pin the
1736        // equality + `&'static` static-data identity here so any
1737        // local re-introduction of a sibling `pub const
1738        // HELM_CHART_KEY_APP_VERSION: &str = "…"` at this crate — the
1739        // canonical drift footgun where a sibling local `pub const`
1740        // could happen to carry the same string at the source while
1741        // pointing at a different `&'static` allocation — is a
1742        // build-time test failure naming the offending drift, not a
1743        // silent Helm-chart-schema-parser field-drop at every
1744        // downstream Artifact Hub / `helm search` chart-consumer far
1745        // from the drift site. Peer to
1746        // [`helm_chart_key_type_re_export_points_at_caixa_core_canonical`]
1747        // on the sibling per-Chart.yaml top-level YAML axis-key
1748        // re-export surface — completes the per-Chart.yaml top-level
1749        // YAML axis-key re-export pair at the caixa-helm surface for
1750        // the two serde-rename-literal-only axes.
1751        caixa_core::assert_str_reexport_identity(
1752            "HELM_CHART_KEY_APP_VERSION",
1753            HELM_CHART_KEY_APP_VERSION,
1754            caixa_core::HELM_CHART_KEY_APP_VERSION,
1755        );
1756    }
1757
1758    #[test]
1759    fn chart_yaml_serializes_type_axis_under_lifted_helm_chart_key_type() {
1760        // Fail-before-pass-after drift-detection pin on the
1761        // `#[serde(rename = "type")]` attribute at [`ChartYaml`]'s
1762        // `chart_type` field. Rust's attribute grammar admits only
1763        // string literals so the lifted [`HELM_CHART_KEY_TYPE`]
1764        // constant cannot substitute for the literal syntactically at
1765        // the attribute-argument site — a future refactor that
1766        // dropped the `#[serde(rename = "type")]` attribute (or
1767        // changed the target key to `"Type"` / `"kind"` /
1768        // `"chartType"`) would silently serialize the field under
1769        // Rust's default snake_case `chart_type:` key, which Helm's
1770        // chart-schema parser silently ignores as an unknown top-
1771        // level key, defaulting the per-chart-kind axis to
1772        // `application` with no process-log signal. This pin closes
1773        // the drift by round-tripping a rendered `Chart.yaml` through
1774        // `serde_yaml::from_str::<serde_yaml::Value>` and asserting
1775        // the top-level `Mapping::get(HELM_CHART_KEY_TYPE)` resolves
1776        // (rather than serializing through the [`ChartYaml`]-typed
1777        // deserializer that would silently absorb the rename drift
1778        // via `#[serde(default)]` fall-through at the struct-side).
1779        // Peer to
1780        // [`chart_yaml_uses_lifted_helm_chart_type_application`] on
1781        // the sibling per-Chart.yaml per-chart-kind axis-value
1782        // production-emit pin — the two pins together enforce the
1783        // full `(key, value)` production-emit pair at the caixa-helm
1784        // surface for the per-Chart.yaml per-chart-kind discriminator
1785        // axis.
1786        let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
1787        let doc = parse_yaml_at_path(&dir.files, HELM_CHART_YAML_FILENAME);
1788        let mapping = doc.as_mapping().expect(
1789            "rendered Chart.yaml must be a top-level YAML mapping per \
1790             the Helm 3 chart-schema shape",
1791        );
1792        assert!(
1793            kube_has(mapping, HELM_CHART_KEY_TYPE),
1794            "rendered Chart.yaml must carry a top-level {HELM_CHART_KEY_TYPE:?} \
1795             axis-key — a drift on the `#[serde(rename = {HELM_CHART_KEY_TYPE:?})]` \
1796             attribute at ChartYaml's `chart_type` field silently reroutes the \
1797             per-chart-kind discriminator axis through Rust's default snake_case \
1798             serialization (`chart_type:`), which Helm's chart-schema parser \
1799             silently ignores as an unknown top-level key (defaulting the \
1800             per-chart-kind axis to `application` with no process-log signal)"
1801        );
1802    }
1803
1804    #[test]
1805    fn chart_yaml_serializes_app_version_axis_under_lifted_helm_chart_key_app_version() {
1806        // Fail-before-pass-after drift-detection pin on the
1807        // `#[serde(rename = "appVersion")]` attribute at [`ChartYaml`]'s
1808        // `app_version` field. Same attribute-literal-only-grammar
1809        // constraint the peer
1810        // [`chart_yaml_serializes_type_axis_under_lifted_helm_chart_key_type`]
1811        // pin closes on the sibling per-Chart.yaml top-level YAML
1812        // axis-key applies here: a future refactor that dropped the
1813        // `#[serde(rename = "appVersion")]` attribute (or changed the
1814        // target key to `"AppVersion"` / `"applicationVersion"` /
1815        // `"appversion"`) would silently serialize the field under
1816        // Rust's default snake_case `app_version:` key, which Helm's
1817        // chart-schema parser silently drops from the parsed
1818        // chart-metadata shape, and every downstream Artifact Hub /
1819        // `helm search` per-chart index falls back to "no application
1820        // version" for the rendered chart. This pin closes the drift
1821        // by round-tripping a rendered `Chart.yaml` through
1822        // `serde_yaml::from_str::<serde_yaml::Value>` (rather than
1823        // through the [`ChartYaml`]-typed deserializer that would
1824        // silently absorb the rename drift). Peer to
1825        // [`chart_yaml_serializes_type_axis_under_lifted_helm_chart_key_type`]
1826        // on the sibling per-Chart.yaml top-level YAML axis-key
1827        // serialization pin surface — completes the per-Chart.yaml
1828        // top-level YAML axis-key production-emit pin pair at the
1829        // caixa-helm surface for the two serde-rename-literal-only
1830        // axes.
1831        let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
1832        let doc = parse_yaml_at_path(&dir.files, HELM_CHART_YAML_FILENAME);
1833        let mapping = doc.as_mapping().expect(
1834            "rendered Chart.yaml must be a top-level YAML mapping per \
1835             the Helm 3 chart-schema shape",
1836        );
1837        assert!(
1838            kube_has(mapping, HELM_CHART_KEY_APP_VERSION),
1839            "rendered Chart.yaml must carry a top-level \
1840             {HELM_CHART_KEY_APP_VERSION:?} axis-key — a drift on the \
1841             `#[serde(rename = {HELM_CHART_KEY_APP_VERSION:?})]` attribute at \
1842             ChartYaml's `app_version` field silently reroutes the per-chart \
1843             underlying-application-version axis through Rust's default \
1844             snake_case serialization (`app_version:`), which Helm's \
1845             chart-schema parser silently drops from the parsed chart-metadata \
1846             shape (every downstream Artifact Hub / `helm search` per-chart \
1847             index falls back to \"no application version\" for the rendered \
1848             chart with no process-log signal at the substrate-side emitter site)"
1849        );
1850    }
1851
1852    #[test]
1853    fn chart_yaml_serializes_api_version_axis_under_lifted_helm_chart_key_api_version() {
1854        // Fail-before-pass-after drift-detection pin on the
1855        // `#[serde(rename = "apiVersion")]` attribute at [`ChartYaml`]'s
1856        // `api_version` field. Same attribute-literal-only-grammar
1857        // constraint the peer
1858        // [`chart_yaml_serializes_type_axis_under_lifted_helm_chart_key_type`]
1859        // / [`chart_yaml_serializes_app_version_axis_under_lifted_helm_chart_key_app_version`]
1860        // pins close on the sibling per-Chart.yaml top-level YAML
1861        // axis-keys applies here: a future refactor that dropped the
1862        // `#[serde(rename = "apiVersion")]` attribute (or changed the
1863        // target key to `"ApiVersion"` / `"apiversion"` /
1864        // `"schemaVersion"`) would silently serialize the field under
1865        // Rust's default snake_case `api_version:` key, which Helm's
1866        // chart-schema parser rejects at `helm lint` / `helm
1867        // dependency build` / `helm template` time with an "apiVersion
1868        // is required" error far from the drift site — every downstream
1869        // `lareira-<nome>` chart consumer drops with no field naming
1870        // the serde-rename-drift root cause. This pin closes the drift
1871        // by round-tripping a rendered `Chart.yaml` through
1872        // `serde_yaml::from_str::<serde_yaml::Value>` (rather than
1873        // through the [`ChartYaml`]-typed deserializer that would
1874        // silently absorb the rename drift via `#[serde(default)]`
1875        // fall-through at the struct-side). Peer to
1876        // [`chart_yaml_serializes_type_axis_under_lifted_helm_chart_key_type`]
1877        // / [`chart_yaml_serializes_app_version_axis_under_lifted_helm_chart_key_app_version`]
1878        // on the sibling per-Chart.yaml top-level YAML axis-key
1879        // serialization pin surface — completes the per-Chart.yaml
1880        // top-level YAML axis-key production-emit pin trio at the
1881        // caixa-helm surface for the three serde-rename-literal-only
1882        // axes.
1883        let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
1884        let doc = parse_yaml_at_path(&dir.files, HELM_CHART_YAML_FILENAME);
1885        let mapping = doc.as_mapping().expect(
1886            "rendered Chart.yaml must be a top-level YAML mapping per \
1887             the Helm 3 chart-schema shape",
1888        );
1889        assert!(
1890            kube_has(mapping, HELM_CHART_KEY_API_VERSION),
1891            "rendered Chart.yaml must carry a top-level \
1892             {HELM_CHART_KEY_API_VERSION:?} axis-key — a drift on the \
1893             `#[serde(rename = {HELM_CHART_KEY_API_VERSION:?})]` attribute at \
1894             ChartYaml's `api_version` field silently reroutes the per-chart \
1895             chart-schema-apiVersion axis through Rust's default snake_case \
1896             serialization (`api_version:`), which Helm's chart-schema parser \
1897             rejects at `helm lint` / `helm template` time with an \"apiVersion \
1898             is required\" error far from the drift site"
1899        );
1900    }
1901
1902    #[test]
1903    fn chart_dependency_serializes_tetrad_under_lifted_helm_chart_dependency_keys() {
1904        // Fail-before-pass-after drift-detection pin on the per-
1905        // `dependencies[]`-entry sub-mapping serde field-name tetrad
1906        // at [`ChartDependency`]. The four fields are identity-mapped
1907        // to their target wire keys today (no `#[serde(rename)]` or
1908        // `#[serde(rename_all)]` attribute on the struct), so a drift
1909        // would surface as one of two shapes: a rename of the Rust
1910        // field (`pub name` → `pub nome`) that silently rebrands the
1911        // wire key, or a `#[serde(rename_all = "camelCase")]` attribute
1912        // addition that stays a no-op on the four lowercase-identity
1913        // fields today but silently activates on a future field
1914        // addition (e.g. an `import_values: Option<Vec<String>>` axis
1915        // matching Helm 3's per-dep `import-values` sub-key). Either
1916        // shape silently reroutes the per-dep sub-mapping through a
1917        // Helm-per-dep-resolver drop at `helm dependency build` time
1918        // far from the drift site (Helm silently drops the drifted
1919        // per-dep sub-mapping field and the per-dep resolver falls
1920        // back to the parsed-shape defaults). This pin closes the
1921        // drift by serializing a fully-populated [`ChartDependency`]
1922        // (`alias` set to a non-`None` value so the
1923        // `#[serde(skip_serializing_if = "Option::is_none")]`
1924        // attribute doesn't elide the axis from the emitted YAML)
1925        // through `serde_yaml::to_value` and asserting each of the
1926        // four per-dep sub-mapping wire keys resolves via
1927        // `Mapping::get(HELM_CHART_DEPENDENCY_KEY_*)`. Peer to
1928        // [`chart_yaml_serializes_type_axis_under_lifted_helm_chart_key_type`]
1929        // / [`chart_yaml_serializes_app_version_axis_under_lifted_helm_chart_key_app_version`]
1930        // / [`chart_yaml_serializes_api_version_axis_under_lifted_helm_chart_key_api_version`]
1931        // on the sibling per-Chart.yaml top-level serde-rename-literal-
1932        // only axis-key drift-detection pin trio (cc44e4b / d29bc23) —
1933        // extends the drift-detection discipline from the per-Chart.yaml
1934        // top-level serde-rename-literal axes onto the per-
1935        // `dependencies[]`-entry sub-mapping serde-field-name tetrad.
1936        let dep = ChartDependency {
1937            name: "pleme-computeunit".into(),
1938            version: "~0.1.0".into(),
1939            repository: "file://../pleme-computeunit".into(),
1940            alias: Some("acme-alias".into()),
1941        };
1942        let doc = serde_yaml::to_value(&dep).unwrap();
1943        let mapping = doc.as_mapping().expect(
1944            "ChartDependency must serialize to a top-level YAML mapping per \
1945             the Helm 3 per-dep sub-mapping shape",
1946        );
1947        for key in [
1948            HELM_CHART_DEPENDENCY_KEY_NAME,
1949            HELM_CHART_DEPENDENCY_KEY_VERSION,
1950            HELM_CHART_DEPENDENCY_KEY_REPOSITORY,
1951            HELM_CHART_DEPENDENCY_KEY_ALIAS,
1952        ] {
1953            assert!(
1954                kube_has(mapping, key),
1955                "serialized ChartDependency must carry a top-level {key:?} \
1956                 axis-key — a drift on the `ChartDependency` struct's serde \
1957                 field-name (a Rust-side rename, an added \
1958                 `#[serde(rename_all)]` attribute, an added `#[serde(rename)]` \
1959                 per-field override) silently reroutes the per-dep sub-mapping \
1960                 through a Helm-per-dep-resolver drop at `helm dependency \
1961                 build` time far from the drift site (Helm silently drops the \
1962                 drifted per-dep sub-mapping field and the per-dep resolver \
1963                 falls back to the parsed-shape defaults); the emitted mapping \
1964                 keys are {keys:?}",
1965                keys = mapping_string_keys(mapping)
1966            );
1967        }
1968    }
1969
1970    #[test]
1971    fn chart_yaml_serializes_dependencies_axis_under_lifted_helm_chart_key_dependencies() {
1972        // Fail-before-pass-after drift-detection pin on the top-level
1973        // per-chart dependency-list YAML axis-key at [`ChartYaml`]'s
1974        // `dependencies` field. The Rust field name and the emitted
1975        // wire key coincide by default today (no `#[serde(rename)]`
1976        // attribute on the field, no `#[serde(rename_all = "…")]`
1977        // attribute on the struct — so serde emits `dependencies:`
1978        // verbatim as the top-level list-container YAML key). A future
1979        // hostile refactor could silently rebrand the wire key in
1980        // three shapes:
1981        //
1982        //   - a rename of the Rust field itself (`pub dependencies:
1983        //     Vec<ChartDependency>` → `pub deps: Vec<ChartDependency>`
1984        //     / `pub chart_dependencies: …`), which serde would then
1985        //     serialize as `deps:` / `chart_dependencies:` verbatim;
1986        //   - an added `#[serde(rename_all = "camelCase")]` /
1987        //     `"snake_case"` / `"kebab-case"` attribute on the struct
1988        //     itself — a no-op on the four identity-mapped top-level
1989        //     lowercase keys (`name` / `description` / `version` /
1990        //     `dependencies`) today but silently activates on a future
1991        //     multi-word field addition (e.g. an `icon_url` axis
1992        //     matching Helm 3's per-chart `icon:` future-schema slot);
1993        //   - an added `#[serde(rename = "deps")]` per-field override
1994        //     at the site of the `dependencies` field.
1995        //
1996        // Under any of the three shapes Helm's chart-schema parser
1997        // silently drops the entire per-chart dep list from the
1998        // parsed chart-metadata (unknown top-level YAML keys silently
1999        // ignored per the Helm 3 chart-schema fallthrough), `helm
2000        // dependency build` finds no chart to vendor, and every
2001        // rendered `lareira-<nome>` chart's install fails with
2002        // `template: no template ... associated with template ...`
2003        // far from the drift site with no field naming the top-level-
2004        // list-key-drift root cause. This pin closes the drift by
2005        // round-tripping a rendered `Chart.yaml` through
2006        // `serde_yaml::from_str::<serde_yaml::Value>` and asserting
2007        // the top-level `Mapping::get(HELM_CHART_KEY_DEPENDENCIES)`
2008        // resolves — rather than through the [`ChartYaml`]-typed
2009        // deserializer that would silently absorb any of the three
2010        // drift shapes via `#[serde(default)]` fall-through at the
2011        // struct-side. Peer to
2012        // [`chart_yaml_serializes_type_axis_under_lifted_helm_chart_key_type`]
2013        // / [`chart_yaml_serializes_app_version_axis_under_lifted_helm_chart_key_app_version`]
2014        // / [`chart_yaml_serializes_api_version_axis_under_lifted_helm_chart_key_api_version`]
2015        // on the sibling per-Chart.yaml top-level YAML axis-key
2016        // serialization pin surface (d29bc23, cc44e4b) — extends the
2017        // per-Chart.yaml top-level YAML axis-key production-emit pin
2018        // trio those closed onto the fourth top-level axis-key, the
2019        // parent list-container the already-lifted per-
2020        // `dependencies[]`-entry sub-mapping tetrad
2021        // [`HELM_CHART_DEPENDENCY_KEY_NAME`] /
2022        // [`HELM_CHART_DEPENDENCY_KEY_VERSION`] /
2023        // [`HELM_CHART_DEPENDENCY_KEY_REPOSITORY`] /
2024        // [`HELM_CHART_DEPENDENCY_KEY_ALIAS`] mounts one level down.
2025        let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
2026        let doc = parse_yaml_at_path(&dir.files, HELM_CHART_YAML_FILENAME);
2027        let mapping = doc.as_mapping().expect(
2028            "rendered Chart.yaml must be a top-level YAML mapping per \
2029             the Helm 3 chart-schema shape",
2030        );
2031        assert!(
2032            kube_has(mapping, HELM_CHART_KEY_DEPENDENCIES),
2033            "rendered Chart.yaml must carry a top-level \
2034             {HELM_CHART_KEY_DEPENDENCIES:?} axis-key — a drift on the \
2035             `ChartYaml.dependencies` field's serde field-name (a Rust-side \
2036             rename to `deps` / `chart_dependencies`, an added \
2037             `#[serde(rename_all)]` attribute on the enclosing struct, an \
2038             added `#[serde(rename)]` per-field override) silently reroutes \
2039             the per-chart dependency-list axis through an unrecognized \
2040             top-level YAML key (`deps:` / `chartDependencies:` / \
2041             `chart_dependencies:`), which Helm's chart-schema parser \
2042             silently drops from the parsed chart-metadata shape (every \
2043             rendered `lareira-<nome>` chart's install fails with \
2044             `template: no template ... associated with template ...` at \
2045             `helm dependency build` / `helm template` time far from the \
2046             drift site with no field naming the top-level-list-key-drift \
2047             root cause); the emitted top-level mapping keys are {keys:?}",
2048            keys = mapping_string_keys(mapping)
2049        );
2050    }
2051
2052    #[test]
2053    fn helm_chart_key_dependencies_re_export_points_at_caixa_core_canonical() {
2054        // The renderer's [`HELM_CHART_KEY_DEPENDENCIES`] was lifted onto
2055        // a re-export of [`caixa_core::HELM_CHART_KEY_DEPENDENCIES`] so
2056        // the Helm 3 per-Chart.yaml top-level dependency-list-container
2057        // YAML axis-key — the parent whose per-`dependencies[]`-entry
2058        // sub-mapping tetrad [`HELM_CHART_DEPENDENCY_KEY_NAME`] /
2059        // [`HELM_CHART_DEPENDENCY_KEY_VERSION`] /
2060        // [`HELM_CHART_DEPENDENCY_KEY_REPOSITORY`] /
2061        // [`HELM_CHART_DEPENDENCY_KEY_ALIAS`] mounts one level down —
2062        // lives in exactly one place across every caixa renderer. Pin
2063        // the equality + `&'static` static-data identity here so any
2064        // local re-introduction of a sibling `pub const
2065        // HELM_CHART_KEY_DEPENDENCIES: &str = "…"` at this crate — the
2066        // canonical drift footgun where a sibling local `pub const`
2067        // could happen to carry the same string at the source while
2068        // pointing at a different `&'static` allocation — is a build-
2069        // time test failure naming the offending drift, not a silent
2070        // per-Chart.yaml top-level-list-key reroute at `helm dependency
2071        // build` / `helm lint` / `helm template` time far from the
2072        // drift site. Peer to
2073        // [`helm_chart_yaml_filename_re_export_points_at_caixa_core_canonical`]
2074        // and every other `helm_chart_*_re_export_points_at_caixa_core_canonical`
2075        // pin on the sibling canonical-Helm-chart-schema-body-axis
2076        // re-export surface — extends the per-Chart.yaml top-level
2077        // YAML axis-key re-export identity discipline onto the fourth
2078        // top-level axis-key at the caixa-helm surface.
2079        caixa_core::assert_str_reexport_identity(
2080            "HELM_CHART_KEY_DEPENDENCIES",
2081            HELM_CHART_KEY_DEPENDENCIES,
2082            caixa_core::HELM_CHART_KEY_DEPENDENCIES,
2083        );
2084    }
2085
2086    #[test]
2087    fn render_opts_default_library_name_follows_lifted_constant() {
2088        // [`RenderOpts::default()`] sets `library_name` from
2089        // [`DEFAULT_LIBRARY_NAME`]; pin that the lift preserves the
2090        // default-knob value bit-for-bit. A future refactor that
2091        // detaches `RenderOpts::default()` from the lifted constant —
2092        // accidentally re-introducing an inline `"pleme-computeunit"`
2093        // literal in the impl — would silently break the shared-shape
2094        // contract with caixa-flux (which uses the same constant
2095        // directly for its `helmrelease.yaml` wrap key); this test
2096        // surfaces the regression at build time rather than at
2097        // apply time as a silent values-routing no-op.
2098        let opts = RenderOpts::default();
2099        assert_eq!(opts.library_name, caixa_core::DEFAULT_LIBRARY_NAME);
2100        assert_eq!(opts.library_name, "pleme-computeunit");
2101    }
2102
2103    #[test]
2104    fn helm_chart_yaml_filename_re_export_points_at_caixa_core_canonical() {
2105        // The renderer's `HELM_CHART_YAML_FILENAME` was lifted from the
2106        // seven production + test-side inline `"Chart.yaml"` /
2107        // `PathBuf::from("Chart.yaml")` / `chart_root.join("Chart.yaml")`
2108        // literals across [`render_chart_for_servico`]'s `ChartDir`
2109        // metadata-file `path` emit site + every test-side round-trip
2110        // navigator that reaches into the rendered `ChartDir` by the
2111        // metadata filename to a re-export of
2112        // [`caixa_core::HELM_CHART_YAML_FILENAME`] so the Helm 3
2113        // per-chart-directory metadata-file filename lives in exactly one
2114        // place across every caixa renderer. Pin the equality +
2115        // `&'static` static-data identity here so any local
2116        // re-introduction of a sibling `pub const
2117        // HELM_CHART_YAML_FILENAME: &str = "…"` at this crate — the
2118        // canonical drift footgun where a sibling local `pub const` could
2119        // happen to carry the same string at the source while pointing
2120        // at a different `&'static` allocation — is a build-time test
2121        // failure naming the offending drift, not a silent
2122        // Helm-chart-schema-parser "Chart.yaml file is missing" reroute
2123        // at `helm dependency build` / `helm lint` / `helm template` /
2124        // `helm install` time far from the drift site. Peer to
2125        // [`helm_chart_api_version_re_export_points_at_caixa_core_canonical`]
2126        // / [`helm_chart_type_application_re_export_points_at_caixa_core_canonical`]
2127        // on the sibling canonical-Helm-chart-schema-body-axis re-export
2128        // surfaces — completes the per-`lareira-<nome>`-chart-directory
2129        // `(filename, apiVersion, type)` canonical-scalar-axis re-export
2130        // triple every rendered chart declares at its top-level metadata
2131        // file.
2132        caixa_core::assert_str_reexport_identity(
2133            "HELM_CHART_YAML_FILENAME",
2134            HELM_CHART_YAML_FILENAME,
2135            caixa_core::HELM_CHART_YAML_FILENAME,
2136        );
2137    }
2138
2139    #[test]
2140    fn helm_values_yaml_filename_re_export_points_at_caixa_core_canonical() {
2141        // The renderer's `HELM_VALUES_YAML_FILENAME` was lifted from the
2142        // twelve production + test-side inline `"values.yaml"` /
2143        // `PathBuf::from("values.yaml")` / `chart_root.join("values.yaml")`
2144        // literals across [`render_chart_for_servico`]'s `ChartDir`
2145        // values-file `path` emit site + every test-side round-trip
2146        // navigator that reaches into the rendered `ChartDir` by the
2147        // values filename to a re-export of
2148        // [`caixa_core::HELM_VALUES_YAML_FILENAME`] so the Helm 3
2149        // per-chart-directory values-file filename lives in exactly one
2150        // place across every caixa renderer. Pin the equality +
2151        // `&'static` static-data identity here so any local
2152        // re-introduction of a sibling `pub const
2153        // HELM_VALUES_YAML_FILENAME: &str = "…"` at this crate — the
2154        // canonical drift footgun where a sibling local `pub const` could
2155        // happen to carry the same string at the source while pointing
2156        // at a different `&'static` allocation — is a build-time test
2157        // failure naming the offending drift, not a silent
2158        // Helm-per-chart-values-loader fall-through to the empty values
2159        // block at `helm template` / `helm install` time far from the
2160        // drift site (where the workload silently comes up under the
2161        // library chart's admission-time defaults with no per-Servico
2162        // M2 overlay applied). Peer to
2163        // [`helm_chart_yaml_filename_re_export_points_at_caixa_core_canonical`]
2164        // on the sibling canonical-Helm-per-chart-directory-metadata-file-
2165        // axis re-export surface — completes the
2166        // per-`lareira-<nome>`-chart-directory `(Chart.yaml, values.yaml)`
2167        // canonical-per-chart-directory-filename-axis re-export pair
2168        // every rendered chart declares as its two schema-load-bearing
2169        // `ChartDir::files` entries.
2170        caixa_core::assert_str_reexport_identity(
2171            "HELM_VALUES_YAML_FILENAME",
2172            HELM_VALUES_YAML_FILENAME,
2173            caixa_core::HELM_VALUES_YAML_FILENAME,
2174        );
2175    }
2176
2177    #[test]
2178    fn helm_chart_readme_filename_re_export_points_at_caixa_core_canonical() {
2179        // The renderer's `HELM_CHART_README_FILENAME` was lifted from the
2180        // three production + test-side inline `"README.md"` literals
2181        // across [`render_chart_for_servico`]'s `ChartDir` readme-file
2182        // `path` emit site + every test-side round-trip navigator that
2183        // reaches into the rendered `ChartDir` by the readme filename
2184        // (the [`renders_three_files`] files-vec-membership pin + the
2185        // [`ChartDir::write_to`] post-write existence pin) to a
2186        // re-export of [`caixa_core::HELM_CHART_README_FILENAME`] so the
2187        // per-`lareira-<nome>` chart-directory human-facing readme
2188        // filename lives in exactly one place across every caixa
2189        // renderer. Pin the equality + `&'static` static-data identity
2190        // here so any local re-introduction of a sibling `pub const
2191        // HELM_CHART_README_FILENAME: &str = "…"` at this crate — the
2192        // canonical drift footgun where a sibling local `pub const`
2193        // could happen to carry the same string at the source while
2194        // pointing at a different `&'static` allocation — is a build-
2195        // time test failure naming the offending drift, not a silent
2196        // GitHub / Artifact Hub / any per-chart README-surfacing UI
2197        // fall-through to "no README available" at chart-consumption
2198        // time far from the drift site. Peer to
2199        // [`helm_chart_yaml_filename_re_export_points_at_caixa_core_canonical`]
2200        // / [`helm_values_yaml_filename_re_export_points_at_caixa_core_canonical`]
2201        // on the sibling canonical-Helm-per-chart-directory-filename
2202        // axis re-export surfaces — completes the
2203        // per-`lareira-<nome>`-chart-directory `(Chart.yaml,
2204        // values.yaml, README.md)` canonical-per-chart-directory-
2205        // filename-axis re-export triple every rendered chart declares
2206        // as its three `ChartDir::files` entries.
2207        caixa_core::assert_str_reexport_identity(
2208            "HELM_CHART_README_FILENAME",
2209            HELM_CHART_README_FILENAME,
2210            caixa_core::HELM_CHART_README_FILENAME,
2211        );
2212    }
2213
2214    #[test]
2215    fn helm_values_key_enabled_re_export_points_at_caixa_core_canonical() {
2216        // The renderer's `HELM_VALUES_KEY_ENABLED` was lifted from the
2217        // production-code inline `"enabled".to_string()` literal at
2218        // [`build_values_yaml`]'s
2219        // `block.insert("enabled".to_string(), Value::Bool(…))` values-
2220        // block-toggle insert (formerly `caixa-helm/src/lib.rs:389`) plus
2221        // its two test-side round-trip navigators
2222        // (`values_yaml_wraps_under_pleme_computeunit_key`,
2223        // `values_yaml_wrap_key_follows_library_name_override`) to a
2224        // re-export of [`caixa_core::HELM_VALUES_KEY_ENABLED`] so the
2225        // canonical `pleme-computeunit` library-chart values-block
2226        // enable-toggle key lives in exactly one place across every
2227        // caixa renderer. Pin the equality + `&'static` static-data
2228        // identity here so any local re-introduction of a sibling
2229        // `pub const HELM_VALUES_KEY_ENABLED: &str = "…"` at this crate
2230        // — the canonical drift footgun where a sibling local
2231        // `pub const` could happen to carry the same string at the
2232        // source while pointing at a different `&'static` allocation —
2233        // is a build-time test failure naming the offending drift, not
2234        // a silent per-values enable-toggle reroute at `helm template` /
2235        // `helm install` time far from the drift site (where the
2236        // workload silently comes up with the library chart's
2237        // admission-time defaults instead of the per-cluster override
2238        // the operator set). Peer to
2239        // [`helm_chart_api_version_re_export_points_at_caixa_core_canonical`]
2240        // / [`kube_key_spec_re_export_points_at_caixa_core_canonical`] /
2241        // [`default_library_name_re_export_points_at_caixa_core_canonical`]
2242        // on the sibling re-export axes +
2243        // `caixa_flux::tests::helm_values_key_enabled_re_export_points_at_caixa_core_canonical`
2244        // on the peer bundle-path renderer crate.
2245        caixa_core::assert_str_reexport_identity(
2246            "HELM_VALUES_KEY_ENABLED",
2247            HELM_VALUES_KEY_ENABLED,
2248            caixa_core::HELM_VALUES_KEY_ENABLED,
2249        );
2250    }
2251
2252    #[test]
2253    fn values_yaml_enable_toggle_key_pins_lifted_helm_values_key_enabled() {
2254        // Fail-before-pass-after pin on the production-code substitution:
2255        // [`build_values_yaml`]'s `block.insert(…, Value::Bool(…))`
2256        // consults the lifted [`HELM_VALUES_KEY_ENABLED`] re-export at
2257        // its insert site, so the rendered `values.yaml`'s per-values
2258        // enable-toggle axis is byte-identical to the canonical constant
2259        // by construction. Before the lift the field carried an inline
2260        // `"enabled".to_string()` literal; a future refactor that
2261        // accidentally reverted the substitution — or any parallel per-
2262        // renderer variant that inlined a stale `"enable"` /
2263        // `"disabled"` literal — would silently emit a values block
2264        // whose per-values enable-toggle lands under one key while
2265        // [`caixa_flux::cluster_bundle`]'s `HelmRelease`
2266        // `spec.values.<library>.enabled` per-cluster override lands
2267        // under another, so this pin trips at caixa-helm build time.
2268        // Peer to `chart_yaml_uses_lifted_helm_chart_api_version` on the
2269        // sibling structural-cross-axis-invariant surface — both close
2270        // the drift between a rendered-value navigator's `.get(…)` /
2271        // struct-field read on the constant and the production-code
2272        // emit site that consumes the same constant.
2273        let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
2274        let parsed = parse_yaml_at_path(&dir.files, HELM_VALUES_YAML_FILENAME);
2275        let cu_block = parsed
2276            .get(DEFAULT_LIBRARY_NAME)
2277            .expect("must wrap under DEFAULT_LIBRARY_NAME");
2278        assert_eq!(
2279            cu_block.get(HELM_VALUES_KEY_ENABLED),
2280            Some(&serde_yaml::Value::Bool(false)),
2281            "rendered values.yaml `{DEFAULT_LIBRARY_NAME}.{HELM_VALUES_KEY_ENABLED}` must \
2282             equal the default-off toggle the lifted HELM_VALUES_KEY_ENABLED axis carries — \
2283             a drifted enable-toggle key silently splits the per-values enable-flip across \
2284             two sibling scalar names on the caixa-helm / caixa-flux consumer split"
2285        );
2286    }
2287
2288    #[test]
2289    fn computeunit_spec_key_module_re_export_points_at_caixa_core_canonical() {
2290        // The renderer's `COMPUTEUNIT_SPEC_KEY_MODULE` was lifted from
2291        // the two inline `"module"` test-side call sites in this crate
2292        // (`values_yaml_wraps_under_pleme_computeunit_key`'s per-values
2293        // module-block present-check + the peer navigator on the
2294        // `library_name`-override wrap-key axis
2295        // `values_yaml_wrap_key_follows_library_name_override`) — every
2296        // per-Servico ComputeUnit CRD `spec.module` sub-block readback
2297        // in this crate now navigates through the same `&'static str`
2298        // re-exported to a re-export of
2299        // [`caixa_core::COMPUTEUNIT_SPEC_KEY_MODULE`] so the canonical
2300        // ComputeUnit-CRD per-`spec.*` wasm-module-reference axis lives
2301        // in exactly one place across every caixa renderer. Pin the
2302        // equality + static-data identity here so any local re-
2303        // introduction of a sibling `pub const COMPUTEUNIT_SPEC_KEY_MODULE:
2304        // &str = "…"` at this crate is a build-time test failure naming
2305        // the offending drift, not a silent per-Servico wasm-runtime-
2306        // binding drop at cluster-apply time. Peer to
2307        // [`helm_values_key_enabled_re_export_points_at_caixa_core_canonical`]
2308        // /
2309        // [`kube_key_spec_re_export_points_at_caixa_core_canonical`]
2310        // on the sibling canonical-Helm-load-bearing-string /
2311        // canonical-K8s-CR-body-key re-export axes +
2312        // `caixa_flux::tests::computeunit_spec_key_module_re_export_points_at_caixa_core_canonical`
2313        // on the peer per-Servico renderer crate.
2314        caixa_core::assert_str_reexport_identity(
2315            "COMPUTEUNIT_SPEC_KEY_MODULE",
2316            COMPUTEUNIT_SPEC_KEY_MODULE,
2317            caixa_core::COMPUTEUNIT_SPEC_KEY_MODULE,
2318        );
2319    }
2320
2321    #[test]
2322    fn computeunit_spec_key_trigger_re_export_points_at_caixa_core_canonical() {
2323        // Peer to
2324        // [`computeunit_spec_key_module_re_export_points_at_caixa_core_canonical`]
2325        // on the same ComputeUnit-CRD per-`spec.*` sub-block re-export
2326        // surface — pins the per-Servico invocation-shape sub-block
2327        // key's identity on the same trajectory.
2328        caixa_core::assert_str_reexport_identity(
2329            "COMPUTEUNIT_SPEC_KEY_TRIGGER",
2330            COMPUTEUNIT_SPEC_KEY_TRIGGER,
2331            caixa_core::COMPUTEUNIT_SPEC_KEY_TRIGGER,
2332        );
2333    }
2334
2335    #[test]
2336    fn computeunit_spec_key_capabilities_re_export_points_at_caixa_core_canonical() {
2337        // Peer to
2338        // [`computeunit_spec_key_module_re_export_points_at_caixa_core_canonical`]
2339        // and
2340        // [`computeunit_spec_key_trigger_re_export_points_at_caixa_core_canonical`]
2341        // on the same ComputeUnit-CRD per-`spec.*` sub-block re-export
2342        // surface — completes the substrate-side ComputeUnit-CRD
2343        // per-`spec.*` sub-block re-export triple in this crate on the
2344        // WASI-capability-token-list axis.
2345        caixa_core::assert_str_reexport_identity(
2346            "COMPUTEUNIT_SPEC_KEY_CAPABILITIES",
2347            COMPUTEUNIT_SPEC_KEY_CAPABILITIES,
2348            caixa_core::COMPUTEUNIT_SPEC_KEY_CAPABILITIES,
2349        );
2350    }
2351
2352    #[test]
2353    fn chart_file_alias_resolves_to_caixa_core_rendered_file() {
2354        // Type-alias identity pin: the [`ChartFile`] alias at this
2355        // crate's boundary resolves to the canonical
2356        // [`caixa_core::RenderedFile`] the substrate-side "one rendered
2357        // leaf artifact" shape lives at. `let _: ChartFile = <a
2358        // RenderedFile>` type-checks *iff* [`ChartFile`] is the aliased
2359        // canonical (not a sibling pub-struct re-declaration that
2360        // happens to carry the same field pair — that would compile
2361        // past the struct-literal navigators below but fail this
2362        // assignment). A drifted local `pub struct ChartFile { pub
2363        // path: PathBuf, pub contents: String }` at this crate — the
2364        // canonical drift footgun that would carry the same field pair
2365        // at the source while pointing at a different struct
2366        // definition — trips this pin at caixa-helm build time rather
2367        // than surfacing as a downstream `caixa_core::RenderedFile`
2368        // consumer refusing a `ChartFile`-shaped value at type-check
2369        // time far from the drift commit. Peer to the sibling
2370        // [`caixa_flux::BundleFile`]-alias-identity pin on the same
2371        // per-target-renderer canonical [`caixa_core::RenderedFile`]
2372        // re-export surface — both crates' per-artifact leaf type now
2373        // resolves through the same canonical struct definition, so a
2374        // future rebrand on the record shape lands at one caixa-core
2375        // edit and reaches both consumers by construction.
2376        let canonical: caixa_core::RenderedFile = caixa_core::RenderedFile {
2377            path: PathBuf::from(HELM_CHART_YAML_FILENAME),
2378            contents: String::new(),
2379        };
2380        let aliased: ChartFile = canonical.clone();
2381        assert_eq!(aliased, canonical);
2382        // Struct-literal construction still resolves through the alias
2383        // — the pre-lift `ChartFile { path, contents }` shape at every
2384        // production emit site (three sites in
2385        // `render_chart_for_servico_with`'s `ChartDir::files` assembly)
2386        // continues to compile, and the derive tuple travels through
2387        // the alias so downstream `ChartDir::files.iter().find(|f|
2388        // f.path == PathBuf::from(HELM_VALUES_YAML_FILENAME))`
2389        // navigators keep matching by `PartialEq` on `PathBuf`.
2390        let via_alias = ChartFile {
2391            path: PathBuf::from(HELM_VALUES_YAML_FILENAME),
2392            contents: format!("{DEFAULT_LIBRARY_NAME}:\n  enabled: false\n"),
2393        };
2394        assert_eq!(via_alias.path.to_string_lossy(), HELM_VALUES_YAML_FILENAME);
2395    }
2396
2397    #[test]
2398    fn chart_file_new_constructor_travels_through_alias_to_canonical() {
2399        // Inherent-method-through-alias pin: the canonical
2400        // [`caixa_core::RenderedFile::new`] `impl Into<PathBuf>` /
2401        // `impl Into<String>` constructor every per-artifact leaf in
2402        // [`render_chart_for_servico_with`] now routes through
2403        // resolves at `ChartFile::new(…)` — Rust inherent methods
2404        // travel through a `pub type ChartFile = caixa_core::RenderedFile`
2405        // alias to the aliased canonical at name resolution, so a
2406        // drifted local `pub struct ChartFile { pub path: PathBuf, pub
2407        // contents: String }` at this crate would carry the field
2408        // pair the sibling type-alias-identity pin above still
2409        // accepts (both records share `pub path` / `pub contents`
2410        // shape) while dropping the constructor — the six sweep sites
2411        // in [`render_chart_for_servico_with`] would stop compiling
2412        // and the failing calls would name `ChartFile` directly,
2413        // making the drift-source unambiguous. This test pins the
2414        // constructor's per-alias reachability + the byte-identical
2415        // record shape against a `HELM_CHART_YAML_FILENAME`-keyed
2416        // probe so the pin fires at caixa-helm build time.
2417        let via_alias_new: ChartFile = ChartFile::new(HELM_CHART_YAML_FILENAME, "apiVersion: v2\n");
2418        let via_canonical_new = caixa_core::RenderedFile::new(
2419            HELM_CHART_YAML_FILENAME,
2420            String::from("apiVersion: v2\n"),
2421        );
2422        assert_eq!(via_alias_new, via_canonical_new);
2423        assert_eq!(via_alias_new.path, PathBuf::from(HELM_CHART_YAML_FILENAME));
2424        assert_eq!(via_alias_new.contents, "apiVersion: v2\n");
2425    }
2426
2427    #[test]
2428    fn render_opts_default_library_version_follows_lifted_constant() {
2429        // Peer of [`render_opts_default_library_name_follows_lifted_constant`]
2430        // (which pins the same alignment on the sibling
2431        // [`RenderOpts::library_name`] / [`DEFAULT_LIBRARY_NAME`] axis). The
2432        // [`RenderOpts::default()`] impl sets `library_version` from
2433        // [`DEFAULT_LIBRARY_VERSION`]; a future refactor that detached the
2434        // default-knob from the lifted constant — accidentally re-inlining
2435        // `"~0.1.0"` in the impl body — would silently split the value the
2436        // default knob threads into every rendered `Chart.yaml`
2437        // `dependencies[0].version` axis from the const the const's callers
2438        // (and this crate's future per-`DEFAULT_LIBRARY_VERSION` drift pins)
2439        // read. The two `Chart.yaml`-dep `(name, version)` scalar-axes now
2440        // share the same "default-knob follows lifted constant, byte for
2441        // byte" pin discipline the peer library-name axis carries.
2442        let opts = RenderOpts::default();
2443        assert_eq!(opts.library_version, DEFAULT_LIBRARY_VERSION);
2444        assert_eq!(opts.library_version, "~0.1.0");
2445    }
2446
2447    #[test]
2448    fn render_opts_default_enabled_default_follows_lifted_constant() {
2449        // Peer of [`render_opts_default_library_name_follows_lifted_constant`]
2450        // /
2451        // [`render_opts_default_library_version_follows_lifted_constant`]
2452        // / [`render_opts_default_library_repo_follows_lifted_constant`] —
2453        // the fourth leg of the [`RenderOpts::default()`]-body
2454        // default-knob-follows-lifted-constant quartet. The
2455        // [`RenderOpts::default()`] impl seeds `enabled_default` from
2456        // [`STANDALONE_LAREIRA_ENABLED_DEFAULT`]; every rendered
2457        // `lareira-<nome>` chart's `values.yaml` under-`<library>.enabled`
2458        // scalar reads through this knob, so a future refactor that
2459        // detached the default-knob from the lifted constant —
2460        // accidentally re-inlining `false` in the impl body — would
2461        // silently split the `bool` the default seed writes from the
2462        // const the drift-detection pin
2463        // [`standalone_lareira_enabled_default_pins_canonical_value`]
2464        // (in caixa-core) reads. The rendered values block would then
2465        // carry one `bool` at the emit site while the const-consuming
2466        // sibling test-fixture navigators (this crate's future per-
2467        // `STANDALONE_LAREIRA_ENABLED_DEFAULT` drift pins) read another,
2468        // and the substrate's chosen mirror-symmetric standalone /
2469        // composition per-values-block child-chart-enablement-toggle-
2470        // scalar-value default pair would silently disagree on the
2471        // standalone-path half. Peer with the sibling
2472        // `caixa_flux::tests::cluster_bundle_lareira_enabled_default_re_export_matches_caixa_core_canonical_value`
2473        // pin on the composition-path half of the same
2474        // [`HELM_VALUES_KEY_ENABLED`] scalar-axis pair.
2475        let opts = RenderOpts::default();
2476        assert_eq!(opts.enabled_default, STANDALONE_LAREIRA_ENABLED_DEFAULT);
2477        assert!(!opts.enabled_default);
2478    }
2479
2480    #[test]
2481    fn standalone_lareira_enabled_default_re_export_matches_caixa_core_canonical_value() {
2482        // The renderer's `STANDALONE_LAREIRA_ENABLED_DEFAULT` was lifted
2483        // from the [`RenderOpts::default()`] impl-body inline `false`
2484        // scalar-value literal at `caixa-helm/src/lib.rs:700` to a
2485        // re-export of [`caixa_core::STANDALONE_LAREIRA_ENABLED_DEFAULT`]
2486        // so the substrate-side default the standalone per-chart path
2487        // seeds under the sibling [`HELM_VALUES_KEY_ENABLED`]
2488        // leaf-scalar-key lives in exactly one place across every caixa
2489        // renderer (this crate's standalone per-chart path + the peer
2490        // `caixa_flux::cluster_bundle`'s composition per-cluster-
2491        // `HelmRelease` values-overlay path, which reads through the
2492        // inverse [`caixa_flux::CLUSTER_BUNDLE_LAREIRA_ENABLED_DEFAULT`]
2493        // re-export). Pin the equality here so any local re-introduction
2494        // of a sibling `pub const STANDALONE_LAREIRA_ENABLED_DEFAULT:
2495        // bool = …` at this crate (the canonical drift footgun the peer
2496        // `CLUSTER_BUNDLE_LAREIRA_ENABLED_DEFAULT` re-export identity
2497        // pin's rationale names as the recurring shape) is a build-time
2498        // test failure naming the offending drift, not a silent
2499        // apply-time toggle-mismatch routing the standalone per-chart
2500        // `values.<library>.enabled` seed onto one substrate-side
2501        // opt-out convention while the peer composition-path override
2502        // routes onto another. Peer to the sibling
2503        // `caixa_flux::tests::cluster_bundle_lareira_enabled_default_re_export_matches_caixa_core_canonical_value`
2504        // on the composition-path half of the same
2505        // [`HELM_VALUES_KEY_ENABLED`] scalar-axis pair — the two
2506        // per-path re-export identity pins together lock the two peer
2507        // scalar-value defaults' per-crate re-exports onto their shared
2508        // caixa-core canonical.
2509        assert_eq!(
2510            STANDALONE_LAREIRA_ENABLED_DEFAULT,
2511            caixa_core::STANDALONE_LAREIRA_ENABLED_DEFAULT,
2512            "STANDALONE_LAREIRA_ENABLED_DEFAULT re-export must remain the \
2513             same `bool` as its caixa-core canonical — a drifted local \
2514             `pub const STANDALONE_LAREIRA_ENABLED_DEFAULT: bool = …` at \
2515             caixa-helm would silently split the substrate's chosen \
2516             standalone per-chart opt-out seed from the peer \
2517             composition-path force-on inversion the caixa-core canonical \
2518             encodes."
2519        );
2520        assert!(
2521            !STANDALONE_LAREIRA_ENABLED_DEFAULT,
2522            "STANDALONE_LAREIRA_ENABLED_DEFAULT must remain `false` — the \
2523             standalone per-chart path is the substrate-side opt-out path \
2524             where cluster operators must opt each caixa in per-cluster, \
2525             inverse of the composition per-cluster-HelmRelease values-\
2526             overlay path's opt-in force-on."
2527        );
2528    }
2529
2530    #[test]
2531    fn render_opts_default_library_repo_follows_lifted_constant() {
2532        // Peer of [`render_opts_default_library_name_follows_lifted_constant`]
2533        // /
2534        // [`render_opts_default_library_version_follows_lifted_constant`] —
2535        // the third leg of the per-`Chart.yaml`-dep
2536        // `(repository, name, version)` default-knob triple. The
2537        // [`RenderOpts::default()`] impl seeds `library_repo` from
2538        // [`DEFAULT_LIBRARY_REPO`]; every rendered `lareira-<nome>` chart's
2539        // `Chart.yaml` `dependencies[0].repository` field reads through
2540        // this knob, so a future refactor that detached the default-knob
2541        // from the lifted constant — re-inlining
2542        // `"file://../pleme-computeunit"` in the impl body — would silently
2543        // split the URL the default seed writes from the const the
2544        // drift-detection pin below
2545        // ([`default_library_repo_ends_with_lifted_default_library_name`])
2546        // reads.
2547        let opts = RenderOpts::default();
2548        assert_eq!(opts.library_repo, DEFAULT_LIBRARY_REPO);
2549        assert_eq!(opts.library_repo, "file://../pleme-computeunit");
2550    }
2551
2552    #[test]
2553    fn default_library_version_parses_as_valid_semver_requirement() {
2554        // Structural pin: [`DEFAULT_LIBRARY_VERSION`] carries a Cargo-shaped
2555        // semver-requirement string that lands verbatim in every rendered
2556        // `lareira-<nome>` chart's `Chart.yaml` `dependencies[0].version`
2557        // field. Helm 3's chart-schema parser (`helm dependency build`,
2558        // `helm lint`, `helm template`, `helm install`) validates the
2559        // scalar against the same `semver::VersionReq` grammar
2560        // [`caixa_core::parse_requirement`] wraps, and rejects a malformed
2561        // shape (`"~0.1.,0"` — paste-from-typography stray comma;
2562        // `"v0.1.0"` — accidental Zig-style publish-tag prefix leaking back
2563        // from [`caixa_core::DEFAULT_PUBLISH_TAG_PREFIX`] into the
2564        // requirement axis; `"0.1"` with a trailing sigil dropped by a
2565        // fat-fingered edit) with the load-bearing `Error: found operator
2566        // …, expected version` diagnostic surfacing at chart-consumption
2567        // time — far from the constant-drift commit's source, with no
2568        // field naming the offending caixa or the drifted default. Routing
2569        // through [`caixa_core::parse_requirement`] here — the same
2570        // requirement-parser entry-point every peer typed `:versao`
2571        // requirement slot (`:deps`, `:deps-dev`, `:membros`, `:children`)
2572        // routes through via
2573        // [`caixa_core::require_valid_versao_requirement`] — closes the
2574        // drift structurally at caixa-helm build time and pins the const's
2575        // accepted set to exactly the set the peer author-facing
2576        // requirement axes accept: any shape a caixa author cannot write
2577        // in `:deps :versao` is a shape the substrate cannot seed as the
2578        // library-chart-dep default. Peer of the sibling
2579        // [`default_library_repo_ends_with_lifted_default_library_name`]
2580        // structural pin on the co-resident `(name, version)` per-Chart.yaml
2581        // dep-scalar pair.
2582        caixa_core::parse_requirement(DEFAULT_LIBRARY_VERSION).unwrap_or_else(|e| {
2583            panic!(
2584                "DEFAULT_LIBRARY_VERSION {DEFAULT_LIBRARY_VERSION:?} must parse as a valid \
2585                 semver::VersionReq — every rendered lareira-<nome> chart's Chart.yaml \
2586                 dependencies[0].version axis lands this scalar verbatim, and Helm 3's \
2587                 chart-schema parser rejects a malformed shape at chart-consumption time \
2588                 far from the constant-drift commit's source: {e}",
2589            )
2590        });
2591    }
2592
2593    #[test]
2594    fn default_library_repo_ends_with_lifted_default_library_name() {
2595        // Structural cross-const coherence pin: [`DEFAULT_LIBRARY_REPO`]
2596        // embeds the [`DEFAULT_LIBRARY_NAME`] byte-string verbatim as its
2597        // trailing directory-name component (the canonical
2598        // `file://../<library-chart-name>` shape every sibling
2599        // `lareira-<nome>` chart's `Chart.yaml` `dependencies[0]` entry
2600        // consults for a two-axis `(name, repository)` per-dep tuple that
2601        // Helm's per-chart-dep resolver `(chart-source-scheme + chart-name)`
2602        // navigator round-trips). The two axes must stay coupled: the
2603        // library-chart-directory on disk (the repo's trailing component)
2604        // and the library-chart's declared `name:` in its own
2605        // [`DEFAULT_LIBRARY_NAME`]-published `Chart.yaml` are the same
2606        // load-bearing chart-name identity. Prior to this pin the two
2607        // consts were independently authored — a future substrate-side
2608        // library-chart rebrand (`pleme-computeunit` → `pleme-cu` on a
2609        // shorter-form migration, `pleme-computeunit` →
2610        // `caixa-computeunit` on a substrate-alignment migration, a
2611        // per-edition library-chart fork the [`DEFAULT_LIBRARY_NAME`]
2612        // docstring names as a trajectory item) on the
2613        // [`caixa_core::DEFAULT_LIBRARY_NAME`] canonical without a
2614        // coordinated edit on this crate's [`DEFAULT_LIBRARY_REPO`] would
2615        // silently emit rendered `Chart.yaml` documents whose
2616        // `dependencies[0].name` names the new chart while
2617        // `dependencies[0].repository` points at the old directory —
2618        // `helm dependency build` would refuse to resolve the dep ("chart
2619        // <new-name> not found in file://../<old-name>") at chart-
2620        // consumption time, far from the constant-rebrand commit's source,
2621        // with no field naming the two-axis coherence drift root cause.
2622        // Pinning the structural `ends_with(DEFAULT_LIBRARY_NAME)` invariant
2623        // here surfaces the drift as a caixa-helm build-time test failure
2624        // and forces the coordinated `(REPO, NAME)` edit to move together.
2625        // Peer of the sibling
2626        // [`default_library_version_parses_as_valid_semver_requirement`]
2627        // structural pin on the co-resident `(name, version)` per-Chart.yaml
2628        // dep-scalar pair — completes the `(repository, name, version)`
2629        // per-Chart.yaml-dep default-triple's structural pin surface.
2630        assert!(
2631            DEFAULT_LIBRARY_REPO.ends_with(DEFAULT_LIBRARY_NAME),
2632            "DEFAULT_LIBRARY_REPO {DEFAULT_LIBRARY_REPO:?} must terminate with the lifted \
2633             DEFAULT_LIBRARY_NAME {DEFAULT_LIBRARY_NAME:?} — the two-axis (repository, name) \
2634             per-Chart.yaml-dep tuple must resolve to the same library-chart identity on \
2635             disk, so a rebrand on either axis must move both",
2636        );
2637    }
2638
2639    #[test]
2640    fn chart_yaml_version_routes_through_caixa_versao_accessor() {
2641        // Fail-before-pass-after pin: the emit-side per-`Chart.yaml`
2642        // top-level `version:` scalar the [`build_chart_yaml`] fn
2643        // writes must derive from the typed
2644        // [`caixa_core::Caixa::versao`] accessor byte-for-byte.
2645        // Before this converge the emit site carried a raw
2646        // `caixa.versao.clone()` field access at
2647        // [`build_chart_yaml`]'s per-`Chart.yaml` version-field
2648        // insert position — one of the two production-code
2649        // `String`-carry sites of `Caixa::versao` on this fn's
2650        // emit path — and a future extension of the accessor
2651        // (a build-metadata canonicalization pass the CAIXA-SDLC
2652        // §I SemVer-2 pin acknowledges, an OCI-tag normalization
2653        // the M4 registry-alignment slot lands, a per-edition
2654        // pre-release-tag overlay the sibling `Caixa::edicao`
2655        // universal-axis 4-digit-ASCII-decimal-year scalar
2656        // dispatches through) that landed on the accessor but
2657        // not on this emit site would silently split the
2658        // per-`Chart.yaml` `version:` axis (the discriminator
2659        // Helm's per-chart resolver keys per-release
2660        // reconciliation off, the paired `HelmRelease`
2661        // `spec.chart.spec.version` binds through, and every
2662        // `helm template <chart>` / `helm install <release>
2663        // <chart>` / `helm upgrade <release> <chart>
2664        // --version` invocation names through) from every peer
2665        // read-side consumer of `Caixa::versao` (the
2666        // README-body `v{versao}` scalar at
2667        // [`build_readme`]:958 the paired `feira chart`
2668        // Nord-themed emit round-trips through, every peer
2669        // per-axis navigator via `Caixa::versao()`, the
2670        // `caixa_flux::programs_yaml_entry` per-entry
2671        // `versao:` scalar at caixa-flux/src/lib.rs:2031, the
2672        // `caixa_feira::cmd::publish` per-tag `caixa {nome} v{versao}`
2673        // git-tag scalar at caixa-feira/src/cmd/publish.rs:72).
2674        // Byte-equal today (the accessor is `&self.versao`);
2675        // the pin catches any future accessor extension whose
2676        // emit-side write regresses to the raw field. Peer to
2677        // [`chart_yaml_app_version_routes_through_caixa_versao_accessor`]
2678        // on the sibling per-`Chart.yaml` `appVersion:` axis.
2679        let caixa = sample_caixa();
2680        let dir = render_chart_for_servico(&caixa, &sample_cu_yaml()).unwrap();
2681        let chart: ChartYaml = parse_yaml_at_path_as(&dir.files, HELM_CHART_YAML_FILENAME);
2682        assert_eq!(
2683            chart.version.as_str(),
2684            caixa.versao(),
2685            "Chart.yaml `version:` must derive from the typed \
2686             `caixa_core::Caixa::versao` accessor byte-for-byte — a regression \
2687             that re-inlines `caixa.versao.clone()` at the emit site silently \
2688             splits the per-`Chart.yaml` `version:` axis from every future \
2689             accessor extension (SemVer-2 build-metadata canonicalization, \
2690             OCI-tag normalization, per-edition pre-release-tag overlay) that \
2691             lands on the accessor",
2692        );
2693    }
2694
2695    #[test]
2696    fn chart_yaml_app_version_routes_through_caixa_versao_accessor() {
2697        // Fail-before-pass-after pin: the emit-side per-`Chart.yaml`
2698        // top-level `appVersion:` scalar the [`build_chart_yaml`] fn
2699        // writes must derive from the typed
2700        // [`caixa_core::Caixa::versao`] accessor byte-for-byte.
2701        // Same single-source `let versao = caixa.versao().to_string()`
2702        // binding as the peer `version:` sibling pin — this test
2703        // pins the derived `Chart.yaml` `appVersion:` axis (the
2704        // axis Helm chart-consumers key per-application-version
2705        // documentation / release-note / OCI-tag / operator-side
2706        // per-Caixa CR revision off). Peer to
2707        // [`chart_yaml_version_routes_through_caixa_versao_accessor`]
2708        // on the sibling per-`Chart.yaml` `version:` axis — the
2709        // two together pin every per-`Chart.yaml` version-carrier
2710        // field on the typed accessor.
2711        let caixa = sample_caixa();
2712        let dir = render_chart_for_servico(&caixa, &sample_cu_yaml()).unwrap();
2713        let chart: ChartYaml = parse_yaml_at_path_as(&dir.files, HELM_CHART_YAML_FILENAME);
2714        assert_eq!(
2715            chart.app_version.as_str(),
2716            caixa.versao(),
2717            "Chart.yaml `appVersion:` must derive from the typed \
2718             `caixa_core::Caixa::versao` accessor byte-for-byte — a regression \
2719             that re-inlines `caixa.versao.clone()` at the emit site silently \
2720             splits the per-`Chart.yaml` `appVersion:` axis from every future \
2721             accessor extension (SemVer-2 build-metadata canonicalization, \
2722             OCI-tag normalization, per-edition pre-release-tag overlay) that \
2723             lands on the accessor",
2724        );
2725    }
2726
2727    #[test]
2728    fn chart_yaml_name_routes_through_caixa_nome_accessor() {
2729        // Emit-path pin: the per-`Chart.yaml` top-level `name:`
2730        // scalar the [`build_chart_yaml`] fn writes must derive
2731        // from the typed [`caixa_core::Caixa::nome`] accessor
2732        // byte-for-byte through the substrate-canonical
2733        // [`caixa_core::lareira_chart_name`] identity composer.
2734        // Before this converge the outer `lareira_chart_name(&caixa.nome)`
2735        // call at [`render_chart_for_servico_with`] carried a raw
2736        // `&caixa.nome` borrow-then-deref of the underlying `String`
2737        // field, bypassing the typed accessor. Peer of the sibling
2738        // eb912de `caixa.versao().to_string()` converge on the
2739        // co-resident `Caixa::versao` `String`-carry axis in this
2740        // crate and the sibling 4a363bf / 54bf2f3 `caixa.nome().to_string()`
2741        // converges on the outer-Caixa `:nome` `String`-carry axis
2742        // in caixa-flux / caixa-mesh — extends the "one typed
2743        // dispatch on the substrate primitive, thin projections at
2744        // each consumer" discipline onto the non-`.clone()` raw-
2745        // field-access axis of `Caixa::nome` in caixa-helm. Byte-
2746        // equal today (the accessor is `&self.nome`); the pin
2747        // catches any future accessor extension (a per-cluster
2748        // alias overlay, an M4 CR-materializer name rewrite, a
2749        // future `:nome-suffix` slot) whose emit-side write
2750        // regresses to the raw `&caixa.nome` field access.
2751        let caixa = sample_caixa();
2752        let dir = render_chart_for_servico(&caixa, &sample_cu_yaml()).unwrap();
2753        let chart: ChartYaml = parse_yaml_at_path_as(&dir.files, HELM_CHART_YAML_FILENAME);
2754        assert_eq!(
2755            chart.name,
2756            caixa_core::lareira_chart_name(caixa.nome()),
2757            "Chart.yaml `name:` must derive from the typed \
2758             `caixa_core::Caixa::nome` accessor through \
2759             `caixa_core::lareira_chart_name` byte-for-byte — a regression \
2760             that re-inlines `lareira_chart_name(&caixa.nome)` at the emit \
2761             site silently splits the per-`Chart.yaml` `name:` axis from \
2762             every future accessor extension (per-cluster alias overlay, \
2763             M4 CR-materializer name rewrite, `:nome-suffix` slot) that \
2764             lands on the accessor",
2765        );
2766    }
2767
2768    #[test]
2769    fn chart_yaml_description_fallback_routes_through_caixa_nome_accessor() {
2770        // Emit-path pin: on a `:descricao`-null caixa the
2771        // [`build_chart_yaml`] `description:` fallback substitutes
2772        // `format!("Generated chart for caixa Servico {}", caixa.nome())`,
2773        // which must derive its terminal identity byte-string from the
2774        // typed [`caixa_core::Caixa::nome`] accessor. Before this
2775        // converge the fallback carried a raw `caixa.nome` Display of
2776        // the underlying `String` field, bypassing the typed accessor.
2777        // Byte-equal today; the pin catches any future accessor
2778        // extension whose fallback emit regresses to the raw field.
2779        let caixa = Caixa {
2780            descricao: None,
2781            ..sample_caixa()
2782        };
2783        let dir = render_chart_for_servico(&caixa, &sample_cu_yaml()).unwrap();
2784        let chart: ChartYaml = parse_yaml_at_path_as(&dir.files, HELM_CHART_YAML_FILENAME);
2785        assert_eq!(
2786            chart.description,
2787            format!("Generated chart for caixa Servico {}", caixa.nome()),
2788            "Chart.yaml `description:` `:descricao`-null fallback must \
2789             derive from the typed `caixa_core::Caixa::nome` accessor \
2790             byte-for-byte — a regression that re-inlines \
2791             `format!(\"Generated chart for caixa Servico {{}}\", caixa.nome)` \
2792             at the emit site silently splits the per-`Chart.yaml` \
2793             `description:` axis from every future accessor extension \
2794             that lands on the accessor",
2795        );
2796    }
2797
2798    #[test]
2799    fn values_yaml_header_nome_routes_through_caixa_nome_accessor() {
2800        // Emit-path pin: the [`build_values_yaml`] `# Auto-generated
2801        // by caixa-helm from caixa.lisp + servicos/{nome}.computeunit.yaml.`
2802        // comment header carries the parent-caixa's `:nome` identity
2803        // byte-string verbatim through the typed
2804        // [`caixa_core::Caixa::nome`] accessor. Before this converge
2805        // the site carried a raw `nome = caixa.nome` Display of the
2806        // underlying `String` field, bypassing the typed accessor.
2807        // Byte-equal today; the pin catches any future accessor
2808        // extension whose header-emit regresses to the raw field.
2809        let caixa = sample_caixa();
2810        let dir = render_chart_for_servico(&caixa, &sample_cu_yaml()).unwrap();
2811        let values_file =
2812            find_file_by_path(&dir.files, HELM_VALUES_YAML_FILENAME).expect("values.yaml present");
2813        let expected = format!("servicos/{}.computeunit.yaml", caixa.nome());
2814        assert!(
2815            values_file.contents.contains(&expected),
2816            "values.yaml header comment must carry the typed \
2817             `caixa_core::Caixa::nome` accessor's byte-string \
2818             ({expected:?}) verbatim — a regression that re-inlines \
2819             `caixa.nome` in the header format silently splits the \
2820             values.yaml provenance-annotation axis from every future \
2821             accessor extension that lands on the accessor. \
2822             Full contents:\n{contents}",
2823            contents = values_file.contents,
2824        );
2825    }
2826
2827    #[test]
2828    fn readme_descricao_fallback_routes_through_caixa_nome_accessor() {
2829        // Emit-path pin: on a `:descricao`-null caixa the
2830        // [`build_readme`] descricao-line fallback substitutes
2831        // `format!("caixa Servico {}", caixa.nome())`, which must
2832        // derive its terminal identity byte-string from the typed
2833        // [`caixa_core::Caixa::nome`] accessor. Before this converge
2834        // the fallback carried a raw `caixa.nome` Display of the
2835        // underlying `String` field, bypassing the typed accessor.
2836        // Byte-equal today; the pin catches any future accessor
2837        // extension whose fallback emit regresses to the raw field.
2838        let caixa = Caixa {
2839            descricao: None,
2840            ..sample_caixa()
2841        };
2842        let dir = render_chart_for_servico(&caixa, &sample_cu_yaml()).unwrap();
2843        let readme_file =
2844            find_file_by_path(&dir.files, HELM_CHART_README_FILENAME).expect("README.md present");
2845        let expected = format!("caixa Servico {}", caixa.nome());
2846        assert!(
2847            readme_file.contents.contains(&expected),
2848            "README.md `:descricao`-null fallback must carry the typed \
2849             `caixa_core::Caixa::nome` accessor's byte-string ({expected:?}) \
2850             verbatim — a regression that re-inlines `format!(\"caixa \
2851             Servico {{}}\", caixa.nome)` at the emit site silently \
2852             splits the README fallback-descricao axis from every future \
2853             accessor extension. Full contents:\n{contents}",
2854            contents = readme_file.contents,
2855        );
2856    }
2857
2858    #[test]
2859    fn readme_body_version_routes_through_caixa_versao_accessor() {
2860        // Emit-path pin: the per-`README.md` `Origin` line the
2861        // [`build_readme`] fn writes carries the terminal
2862        // `v{versao}` scalar the `feira chart` Nord-themed emit
2863        // round-trips through — that scalar must derive from the
2864        // typed [`caixa_core::Caixa::versao`] accessor byte-for-byte.
2865        // Before this converge the emit site carried a raw
2866        // `caixa.versao` `Display` field-access, bypassing the
2867        // typed accessor. Sibling of the 162e2e2 (caixa-flux) /
2868        // 980c059 (caixa-mesh) / 22461ef (caixa-helm) `Caixa::nome`
2869        // Display-axis converges — this closes the co-resident
2870        // `Caixa::versao` Display-axis in caixa-helm the eb912de
2871        // `caixa.versao().to_string()` `String`-carry converge
2872        // left open on the read-only Display-borrow arm. Byte-equal
2873        // today (the accessor is `&self.versao`); the pin catches
2874        // any future accessor extension (SemVer-2 build-metadata
2875        // canonicalization, OCI-tag normalization, per-edition
2876        // pre-release-tag overlay) whose emit-side Display regresses
2877        // to the raw field.
2878        let caixa = sample_caixa();
2879        let dir = render_chart_for_servico(&caixa, &sample_cu_yaml()).unwrap();
2880        let readme_file =
2881            find_file_by_path(&dir.files, HELM_CHART_README_FILENAME).expect("README.md present");
2882        let expected = format!("caixa.lisp` v{}.", caixa.versao());
2883        assert!(
2884            readme_file.contents.contains(&expected),
2885            "README.md `Origin`-line `v{{versao}}` scalar must derive from \
2886             the typed `caixa_core::Caixa::versao` accessor byte-for-byte \
2887             ({expected:?}) — a regression that re-inlines `caixa.versao` \
2888             in the format silently splits the README origin-line version \
2889             axis from every future accessor extension (SemVer-2 \
2890             build-metadata canonicalization, OCI-tag normalization, \
2891             per-edition pre-release-tag overlay) that lands on the \
2892             accessor. Full contents:\n{contents}",
2893            contents = readme_file.contents,
2894        );
2895    }
2896
2897    #[test]
2898    fn contains_key_bare_str_key_byte_equals_value_string_wrapped_form_across_swept_axis_keys() {
2899        // Per-crate fail-before-pass-after equivalence pin covering the
2900        // five test-side `assert!(mapping.contains_key(<KEY>))`
2901        // drift-detection sites that this commit swept off the
2902        // three-token `serde_yaml::Value::String(<KEY>.to_string())`
2903        // wrapped shape onto the shorter bare-`&str` form
2904        // ([`HELM_CHART_KEY_TYPE`] on the per-Chart.yaml top-level
2905        // chart-kind discriminator axis, [`HELM_CHART_KEY_APP_VERSION`]
2906        // on the underlying-application-version axis,
2907        // [`HELM_CHART_KEY_API_VERSION`] on the chart-schema-apiVersion
2908        // axis, the per-`dependencies[]`-entry tetrad iterator
2909        // ([`HELM_CHART_DEPENDENCY_KEY_NAME`] /
2910        // [`HELM_CHART_DEPENDENCY_KEY_VERSION`] /
2911        // [`HELM_CHART_DEPENDENCY_KEY_REPOSITORY`] /
2912        // [`HELM_CHART_DEPENDENCY_KEY_ALIAS`]), and
2913        // [`HELM_CHART_KEY_DEPENDENCIES`] on the top-level
2914        // dependency-list container axis).
2915        //
2916        // The equivalence is a serde_yaml crate-level property (the
2917        // `impl Index for str` and `impl Index for Value` paths both
2918        // route through the same `HashLikeValue(&str)` bucket lookup)
2919        // pinned at the caixa-core-side sibling
2920        // `mapping_get_bare_str_key_byte_equals_value_string_wrapped_form`'s
2921        // trailing `contains_key` parity block for the generic axis
2922        // (0e84fb9). This pin lifts the same equivalence to the
2923        // caixa-helm-side lifted axis-key set every swept site keys
2924        // off, so a future `serde_yaml` upgrade whose `Index for str`
2925        // path diverges from `Index for Value` for the specific
2926        // key-string byte-shape at any of the seven swept keys is a
2927        // caixa-helm-build-time failure naming the offending axis-key,
2928        // not a silent regression that would let the swept
2929        // `assert!(_.contains_key(K))` shape probes drift past the
2930        // emitter's [`MappingExt::insert_str_key`]-promoted key.
2931        //
2932        // Peer to the caixa-core-side
2933        // `mapping_get_bare_str_key_byte_equals_value_string_wrapped_form`
2934        // pin (0e84fb9) on the generic `contains_key` parity axis —
2935        // the two together partition the equivalence-pin surface
2936        // exactly on the axis-key set specificity: caixa-core-side pin
2937        // covers the generic KUBE_KEY_KIND / KUBE_KEY_SPEC probe;
2938        // this caixa-helm-side pin covers the seven Chart.yaml-shape
2939        // axis-keys the swept sites key off.
2940        let mut m = serde_yaml::Mapping::new();
2941        m.insert_string(HELM_CHART_KEY_TYPE, "application");
2942        m.insert_string(HELM_CHART_KEY_APP_VERSION, "1.0.0");
2943        m.insert_string(HELM_CHART_KEY_API_VERSION, "v2");
2944        m.insert_string(HELM_CHART_KEY_DEPENDENCIES, "placeholder");
2945        m.insert_string(HELM_CHART_DEPENDENCY_KEY_NAME, "pleme-computeunit");
2946        m.insert_string(HELM_CHART_DEPENDENCY_KEY_VERSION, "0.1.0");
2947        m.insert_string(
2948            HELM_CHART_DEPENDENCY_KEY_REPOSITORY,
2949            "oci://ghcr.io/pleme-io",
2950        );
2951        m.insert_string(HELM_CHART_DEPENDENCY_KEY_ALIAS, "cu");
2952        for key in [
2953            HELM_CHART_KEY_TYPE,
2954            HELM_CHART_KEY_APP_VERSION,
2955            HELM_CHART_KEY_API_VERSION,
2956            HELM_CHART_KEY_DEPENDENCIES,
2957            HELM_CHART_DEPENDENCY_KEY_NAME,
2958            HELM_CHART_DEPENDENCY_KEY_VERSION,
2959            HELM_CHART_DEPENDENCY_KEY_REPOSITORY,
2960            HELM_CHART_DEPENDENCY_KEY_ALIAS,
2961        ] {
2962            // Present-key path: both forms find the same insertion.
2963            assert_eq!(
2964                m.contains_key(key),
2965                m.contains_key(serde_yaml::Value::String(key.to_string())),
2966                "present-key mapping.contains_key({key:?}) via bare-&str must \
2967                 byte-equal mapping.contains_key(Value::String({key:?}.to_string())) \
2968                 — otherwise the swept `assert!(_.contains_key({key:?}))` sites \
2969                 in this file drift silently past the emitter's `insert_str_key` \
2970                 promotion at every downstream drift-detection pin call site"
2971            );
2972        }
2973        // Absent-key path: both forms return false on a key that was
2974        // never inserted (mirroring the peer caixa-core-side
2975        // KUBE_KEY_SPEC absent-key parity assertion).
2976        let absent = "this_key_never_appears_in_any_chart_yaml_axis";
2977        assert_eq!(
2978            m.contains_key(absent),
2979            m.contains_key(serde_yaml::Value::String(absent.to_string())),
2980            "absent-key mapping.contains_key(<bare-&str>) must byte-equal \
2981             absent-key mapping.contains_key(Value::String(<key>.to_string())) \
2982             — otherwise a future swept drift-detection pin's absent-key arm \
2983             could silently disagree between the two forms"
2984        );
2985    }
2986
2987    #[test]
2988    fn readme_repositorio_null_fallback_routes_through_canonical_git_url_accessor() {
2989        // Emit-path pin: on a `:repositorio`-null caixa the
2990        // [`build_readme`] `## Origin` line's `{repo}` interpolation must
2991        // derive from the substrate-canonical
2992        // [`caixa_core::Caixa::canonical_git_url`] resolved-git-URL
2993        // composer's None-arm fallback (`https://github.com/{DEFAULT_PLEME_GIT_ORG}/<nome>`)
2994        // rather than the prior `caixa.repositorio().unwrap_or(caixa.nome())`
2995        // two-arm inline whose fallback folded to the bare `<nome>` scalar
2996        // and emitted the meaningless `Generated by `caixa-helm` from
2997        // `<nome>/caixa.lisp`` line the 124f864 commit body explicitly
2998        // called out as the live-behavior-correcting converge surface
2999        // waiting on this lift. Fail-before-pass-after: the pre-lift
3000        // shape carried the bare `hello-rio/caixa.lisp` substring and
3001        // this pin asserts the post-lift canonical pleme-org URL substring
3002        // in its place, so a regression that re-inlines the raw two-arm
3003        // fallback at the emit site surfaces at caixa-helm build time on
3004        // this test's failure rather than at a downstream README-consuming
3005        // UI's "where does this caixa live?" broken-URL trail.
3006        let caixa = Caixa {
3007            repositorio: None,
3008            ..sample_caixa()
3009        };
3010        let dir = render_chart_for_servico(&caixa, &sample_cu_yaml()).unwrap();
3011        let readme_file =
3012            find_file_by_path(&dir.files, HELM_CHART_README_FILENAME).expect("README.md present");
3013        let expected = format!(
3014            "Generated by `caixa-helm` from `{}/caixa.lisp`",
3015            caixa.canonical_git_url()
3016        );
3017        assert!(
3018            readme_file.contents.contains(&expected),
3019            "README.md `:repositorio`-null fallback must derive its \
3020             `repo` interpolation from the typed \
3021             `caixa_core::Caixa::canonical_git_url` resolved-git-URL \
3022             composer byte-for-byte ({expected:?}) — a regression that \
3023             re-inlines the prior `caixa.repositorio().unwrap_or(caixa.nome())` \
3024             two-arm fallback at the emit site silently regresses the \
3025             README origin-line repo axis to the meaningless bare `<nome>` \
3026             prefix and splits the caixa-helm README axis from the sibling \
3027             [`caixa_flux::ClusterBundleOpts::for_caixa`] `git_url` seed \
3028             which already resolves through the canonical composer. \
3029             Full contents:\n{contents}",
3030            contents = readme_file.contents,
3031        );
3032    }
3033
3034    #[test]
3035    fn readme_repositorio_null_fallback_no_longer_emits_bare_nome_prefix() {
3036        // Fail-before-pass-after regression guard: pins the negative arm
3037        // the sibling
3038        // [`readme_repositorio_null_fallback_routes_through_canonical_git_url_accessor`]
3039        // pin's positive arm implies — the pre-lift bare `<nome>/caixa.lisp`
3040        // substring (the shape the prior `caixa.repositorio().unwrap_or(caixa.nome())`
3041        // inline two-arm fallback emitted on every `:repositorio`-null
3042        // caixa) must NOT appear in the rendered README's `## Origin` line.
3043        // A regression that re-inlines the two-arm fallback at
3044        // [`build_readme`]'s emit site would satisfy the positive
3045        // canonical-composer substring check on the `Some(<full-URL>)`
3046        // arm and drop the fallback arm back to `<nome>/caixa.lisp`
3047        // silently — so an explicit negative-substring pin closes the
3048        // remaining converge surface the sibling positive pin does not
3049        // catch on its own.
3050        let caixa = Caixa {
3051            repositorio: None,
3052            ..sample_caixa()
3053        };
3054        let dir = render_chart_for_servico(&caixa, &sample_cu_yaml()).unwrap();
3055        let readme_file =
3056            find_file_by_path(&dir.files, HELM_CHART_README_FILENAME).expect("README.md present");
3057        let regressed = format!(
3058            "Generated by `caixa-helm` from `{}/caixa.lisp`",
3059            caixa.nome()
3060        );
3061        assert!(
3062            !readme_file.contents.contains(&regressed),
3063            "README.md `:repositorio`-null fallback must NOT emit the \
3064             pre-lift bare `<nome>/caixa.lisp` prefix ({regressed:?}) — \
3065             the `caixa.repositorio().unwrap_or(caixa.nome())` two-arm \
3066             inline fallback was retired in favor of \
3067             `caixa.canonical_git_url()` so the fallback arm now names \
3068             the substrate-canonical pleme-org URL; a regression that \
3069             re-inlines the two-arm fallback at the emit site would \
3070             silently split this caixa-helm README axis from the sibling \
3071             [`caixa_flux::ClusterBundleOpts::for_caixa`] `git_url` seed. \
3072             Full contents:\n{contents}",
3073            contents = readme_file.contents,
3074        );
3075    }
3076
3077    #[test]
3078    fn readme_repositorio_declared_arm_routes_through_canonical_git_url_accessor() {
3079        // Emit-path pin (peer of the `:repositorio`-null fallback sibling):
3080        // on a `:repositorio`-declared caixa the [`build_readme`] `## Origin`
3081        // line's `{repo}` interpolation must also derive from the substrate-
3082        // canonical [`caixa_core::Caixa::canonical_git_url`] resolved-git-URL
3083        // composer — the Some-arm return byte-string is the author-declared
3084        // `:repositorio` verbatim, so the substring the rendered README
3085        // carries matches the composer output on both arms and the pair
3086        // of pins jointly pins the entire two-arm dispatch through one
3087        // canonical composer. A regression that split the emit site into
3088        // a Some-arm-only bypass through the raw
3089        // [`caixa_core::Caixa::repositorio`] accessor while leaving the
3090        // fallback arm routed through the canonical composer would
3091        // silently drift the two-arm shape and be caught here (the raw
3092        // accessor path bypasses any future extension of the resolved-URL
3093        // composer, e.g. a `github:` → `https://github.com/` canonicalization
3094        // pass or the per-cluster repo-mirror overlay the M4 CR materializer
3095        // will apply).
3096        let caixa = sample_caixa();
3097        assert!(
3098            caixa.repositorio().is_some(),
3099            "sample_caixa() must carry a `Some(...)` `:repositorio` for \
3100             the Some-arm to exercise the canonical composer's non-fallback \
3101             path — a future sample_caixa() edit that drops the field must \
3102             re-establish it here or the pin regresses to a no-op",
3103        );
3104        let dir = render_chart_for_servico(&caixa, &sample_cu_yaml()).unwrap();
3105        let readme_file =
3106            find_file_by_path(&dir.files, HELM_CHART_README_FILENAME).expect("README.md present");
3107        let expected = format!(
3108            "Generated by `caixa-helm` from `{}/caixa.lisp`",
3109            caixa.canonical_git_url()
3110        );
3111        assert!(
3112            readme_file.contents.contains(&expected),
3113            "README.md `:repositorio`-declared arm must derive its `repo` \
3114             interpolation from the typed `caixa_core::Caixa::canonical_git_url` \
3115             resolved-git-URL composer byte-for-byte ({expected:?}) so the \
3116             two-arm dispatch routes through one canonical composer end to \
3117             end. Full contents:\n{contents}",
3118            contents = readme_file.contents,
3119        );
3120    }
3121
3122    #[test]
3123    #[allow(clippy::cmp_owned)] // Deliberate: the pin reproduces the
3124    // prior owning-`PathBuf::from(...)` comparand byte-for-byte to
3125    // guarantee the lift's substitution is behavior-preserving on
3126    // exactly the shape the 26 converged caixa-helm callers previously
3127    // carried.
3128    fn find_file_by_path_matches_prior_inline_iter_find_pathbuf_from_shape() {
3129        // Per-crate byte-equivalence pin on the lifted
3130        // [`caixa_core::find_file_by_path`] navigator: for every leaf
3131        // the `render_chart_for_servico` `lareira-<nome>` chart-
3132        // directory emit writes ([`HELM_CHART_YAML_FILENAME`] /
3133        // [`HELM_VALUES_YAML_FILENAME`] /
3134        // [`HELM_CHART_README_FILENAME`]), the lifted navigator must
3135        // byte-equal the prior three-line inline
3136        // `dir.files.iter().find(|f| f.path ==
3137        // PathBuf::from(<FILENAME_CONST>))` combinator the 26 test-
3138        // side per-artifact readback sites previously carried. Mirrors
3139        // the sibling `caixa-flux`
3140        // `find_file_by_path_matches_prior_inline_iter_find_pathbuf_from_shape`
3141        // pin at the peer caller-crate altitude and the substrate-
3142        // level `find_file_by_path_matches_inline_iter_find_pathbuf_from_shape`
3143        // pin at the primitive definition — the three-arm closure
3144        // that closes the discipline the sibling `sequence_str_values`
3145        // / `kube_has` sweeps established.
3146        let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
3147        for filename in [
3148            HELM_CHART_YAML_FILENAME,
3149            HELM_VALUES_YAML_FILENAME,
3150            HELM_CHART_README_FILENAME,
3151        ] {
3152            let via_helper = find_file_by_path(&dir.files, filename);
3153            let via_inline = dir.files.iter().find(|f| f.path == PathBuf::from(filename));
3154            assert_eq!(
3155                via_helper, via_inline,
3156                "find_file_by_path(&dir.files, {filename:?}) must \
3157                 byte-equal the prior three-line `dir.files.iter()\
3158                 .find(|f| f.path == PathBuf::from({filename:?}))` \
3159                 combinator on every leaf the `lareira-<nome>` chart-\
3160                 directory emit writes — otherwise the 26 routed per-\
3161                 artifact readback sites regress silently on the \
3162                 leaf-path axis",
3163            );
3164        }
3165    }
3166
3167    #[test]
3168    fn parse_yaml_at_path_matches_prior_inline_find_then_from_str_shape() {
3169        // Per-crate byte-equivalence pin on the lifted
3170        // [`caixa_core::parse_yaml_at_path`] composed navigator: for
3171        // every YAML leaf the `render_chart_for_servico` `lareira-
3172        // <nome>` chart-directory emit writes that the test-side
3173        // routed callers parse to a [`serde_yaml::Value`]
3174        // ([`HELM_CHART_YAML_FILENAME`] as `doc` /
3175        // [`HELM_VALUES_YAML_FILENAME`] as `parsed`), the lifted helper
3176        // must return a `Value` byte-equal to the prior two-step
3177        //
3178        //   let f = find_file_by_path(&dir.files, <FILENAME>).unwrap();
3179        //   let parsed: serde_yaml::Value =
3180        //       serde_yaml::from_str(&f.contents).unwrap();
3181        //
3182        // the 12 test-side per-artifact YAML readback sites previously
3183        // carried. Mirrors the sibling `caixa-flux`
3184        // `parse_yaml_at_path_matches_prior_inline_find_then_from_str_shape`
3185        // pin at the peer caller-crate altitude and the substrate-
3186        // level `parse_yaml_at_path_matches_prior_inline_two_step_shape`
3187        // pin at the primitive definition — the three-arm closure that
3188        // closes the same discipline the sibling `find_file_by_path`
3189        // sweep established. [`HELM_CHART_README_FILENAME`] is
3190        // deliberately not swept: the routed callers only assert on its
3191        // raw-byte content, never parse it to YAML.
3192        let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
3193        for filename in [HELM_CHART_YAML_FILENAME, HELM_VALUES_YAML_FILENAME] {
3194            let via_helper = parse_yaml_at_path(&dir.files, filename);
3195            let via_inline_file = find_file_by_path(&dir.files, filename).unwrap();
3196            let via_inline: serde_yaml::Value =
3197                serde_yaml::from_str(&via_inline_file.contents).unwrap();
3198            assert_eq!(
3199                via_helper, via_inline,
3200                "parse_yaml_at_path(&dir.files, {filename:?}) must \
3201                 byte-equal the prior two-step `find_file_by_path(&dir.files, \
3202                 {filename:?}).unwrap() + serde_yaml::from_str(&_.contents).unwrap()` \
3203                 combinator on every YAML leaf the `lareira-<nome>` chart-\
3204                 directory emit writes — otherwise the 12 routed per-\
3205                 artifact YAML readback sites regress silently on the \
3206                 parse-target axis",
3207            );
3208        }
3209    }
3210
3211    #[test]
3212    fn parse_yaml_at_path_as_matches_prior_inline_find_then_from_str_typed_shape() {
3213        // Per-crate byte-equivalence pin on the lifted
3214        // [`caixa_core::parse_yaml_at_path_as`] caller-typed composed
3215        // navigator: for the `Chart.yaml` YAML leaf the
3216        // `render_chart_for_servico` `lareira-<nome>` chart-directory
3217        // emit writes that the test-side routed callers parse to a
3218        // [`ChartYaml`]-typed body, the lifted helper must return a
3219        // `ChartYaml` byte-equal to the prior two-step
3220        //
3221        //   let chart_file = find_file_by_path(&dir.files, HELM_CHART_YAML_FILENAME)
3222        //       .unwrap(); // or .expect("Chart.yaml present")
3223        //   let chart: ChartYaml =
3224        //       serde_yaml::from_str(&chart_file.contents).unwrap();
3225        //
3226        // the 9 test-side per-`Chart.yaml`-typed-parse readback sites
3227        // previously carried. Mirrors the sibling
3228        // [`parse_yaml_at_path_matches_prior_inline_find_then_from_str_shape`]
3229        // untyped-Value-arity pin in this crate at the caller-typed-
3230        // arity peer, and the substrate-level
3231        // `parse_yaml_at_path_as_matches_prior_inline_typed_two_step_shape`
3232        // pin at the primitive definition — the three-arm closure
3233        // that closes the same discipline the sibling
3234        // [`parse_yaml_at_path`] sweep established. Only
3235        // [`HELM_CHART_YAML_FILENAME`] is swept: the routed
3236        // caller-typed-parse sites in this crate reach exclusively
3237        // through the `ChartYaml`-typed body at the `Chart.yaml`
3238        // leaf; [`HELM_VALUES_YAML_FILENAME`] carries no typed-mirror
3239        // struct in this crate (the routed callers navigate its
3240        // parsed [`serde_yaml::Value`] through `kube_*` / `mapping_*`
3241        // primitives), and [`HELM_CHART_README_FILENAME`] is not
3242        // parsed as YAML at all.
3243        let dir = render_chart_for_servico(&sample_caixa(), &sample_cu_yaml()).unwrap();
3244        let filename = HELM_CHART_YAML_FILENAME;
3245        let via_helper: ChartYaml = parse_yaml_at_path_as(&dir.files, filename);
3246        let via_inline_file = find_file_by_path(&dir.files, filename).unwrap();
3247        let via_inline: ChartYaml = serde_yaml::from_str(&via_inline_file.contents).unwrap();
3248        assert_eq!(
3249            via_helper, via_inline,
3250            "parse_yaml_at_path_as::<ChartYaml>(&dir.files, {filename:?}) \
3251             must byte-equal the prior two-step \
3252             `find_file_by_path(&dir.files, {filename:?}).unwrap() + \
3253             serde_yaml::from_str::<ChartYaml>(&_.contents).unwrap()` \
3254             combinator on the `Chart.yaml` YAML leaf — otherwise \
3255             the 9 routed per-artifact typed-parse readback sites \
3256             regress silently on the parse-target axis",
3257        );
3258    }
3259}