Skip to main content

caixa_core/
manifest.rs

1use std::path::{Path, PathBuf};
2
3use serde::{Deserialize, Serialize};
4use tatara_lisp::DeriveTataraDomain;
5
6use thiserror::Error;
7
8use crate::{
9    CaixaKind, Dep,
10    behavior::BehaviorSpec,
11    dep::DepError,
12    limits::LimitsSpec,
13    render::{
14        PathShapeViolation, is_computeunit_yaml_extension, is_git_repo_url, is_lisp_extension,
15        is_sandboxed_relative_path,
16    },
17    supervisor::SupervisorSpec,
18    upgrade::UpgradeFromEntry,
19};
20
21/// Top-level manifest for a caixa (a tatara-lisp package).
22///
23/// Authored as `caixa.lisp`:
24///
25/// ```lisp
26/// (defcaixa
27///   :nome        "pangea-tatara-aws"
28///   :versao      "0.1.0"
29///   :kind        Biblioteca
30///   :edicao      "2026"
31///   :descricao   "AWS provider caixa for tatara-lisp"
32///   :repositorio "github:pleme-io/pangea-tatara-aws"
33///   :licenca     "MIT"
34///   :autores     ("pleme-io")
35///   :etiquetas   ("iac" "aws" "pangea")
36///   :deps        ((:nome "caixa-teia"    :versao "^0.1")
37///                 (:nome "iac-forge-ir"  :versao "^0.5"))
38///   :deps-dev    ((:nome "tatara-check"  :versao "*"))
39///   :bibliotecas ("lib/pangea-tatara-aws.lisp"))
40/// ```
41///
42/// Because `Caixa` derives [`tatara_lisp::domain::TataraDomain`], the manifest
43/// is parsed directly by the tatara-lisp compiler — an ill-formed manifest is
44/// a compile error, not a runtime error.
45#[derive(DeriveTataraDomain, Serialize, Deserialize, Debug, Clone, PartialEq)]
46#[serde(rename_all = "camelCase")]
47#[tatara(keyword = "defcaixa")]
48pub struct Caixa {
49    /// Package name — the canonical string used in `:deps`, the registry, and
50    /// the default lib/exe entry names.
51    pub nome: String,
52
53    /// Package version — a semver literal like `"0.1.0"`. Parsed lazily via
54    /// [`crate::CaixaVersion::parse`].
55    pub versao: String,
56
57    /// What this caixa produces. See [`CaixaKind`].
58    pub kind: CaixaKind,
59
60    /// Language edition — determines macro surface + compatibility flags.
61    #[serde(default, skip_serializing_if = "Option::is_none")]
62    pub edicao: Option<String>,
63
64    /// Free-form description shown in the registry listing.
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub descricao: Option<String>,
67
68    /// Homepage or repo URL.
69    #[serde(default, skip_serializing_if = "Option::is_none")]
70    pub repositorio: Option<String>,
71
72    /// SPDX license expression — `"MIT"`, `"Apache-2.0 OR MIT"`, etc.
73    #[serde(default, skip_serializing_if = "Option::is_none")]
74    pub licenca: Option<String>,
75
76    /// Authors — free-form strings.
77    #[serde(default)]
78    pub autores: Vec<String>,
79
80    /// Topical tags used for registry search.
81    #[serde(default)]
82    pub etiquetas: Vec<String>,
83
84    /// Runtime dependencies.
85    #[serde(default)]
86    pub deps: Vec<Dep>,
87
88    /// Development-only dependencies (tests, lint, bench).
89    #[serde(default)]
90    pub deps_dev: Vec<Dep>,
91
92    /// Paths to executable entry points (relative to the package root).
93    /// Required when `:kind Binario`.
94    #[serde(default)]
95    pub exe: Vec<String>,
96
97    /// Paths to library entry points (relative to the package root).
98    /// First entry is the canonical `lib/<nome>.lisp`; when omitted under
99    /// `:kind Biblioteca`, the layout check expects `lib/<nome>.lisp`.
100    #[serde(default)]
101    pub bibliotecas: Vec<String>,
102
103    /// Paths to service manifests (relative to the package root).
104    /// Required when `:kind Servico`.
105    #[serde(default)]
106    pub servicos: Vec<String>,
107
108    // ── M2 typed-substrate extensions per theory/ABSORPTION-ROADMAP.md ──
109    //
110    // All four are optional + default to "absent"; existing caixas
111    // round-trip unchanged. Each maps onto a prior-art primitive named
112    // in theory/INSPIRATIONS.md:
113    //
114    //   :limits        — Lunatic per-process limits (§III.1)
115    //   :behavior      — OTP gen_server callbacks  (§II.3)
116    //   :upgrade-from  — OTP appup migration       (§II.4)
117    //   :estrategia    — OTP supervisor strategy   (§II.2 + §III.2)
118    //   :children      — OTP supervisor children    (§II.2 + §III.2)
119    //
120    // The supervisor slots are flat on Caixa (vs nested under a
121    // SupervisorSpec sub-form) to keep tatara-lisp authoring at one
122    // level of nesting; SupervisorSpec exists for validation +
123    // composition convenience (`Caixa::supervisor_view()`).
124    /// Lunatic-style per-process resource limits. None = unbounded.
125    #[serde(default, skip_serializing_if = "Option::is_none")]
126    pub limits: Option<LimitsSpec>,
127
128    /// OTP-shaped behavior callbacks for Servico-kind caixas.
129    /// Authored as `(:on-init "..." :on-call "..." …)`.
130    #[serde(default, skip_serializing_if = "Option::is_none")]
131    pub behavior: Option<BehaviorSpec>,
132
133    /// OTP appup — declarative upgrade instructions per prior version.
134    /// Empty list = no hot-upgrade path declared (caller falls back to
135    /// `:Restart` strategy).
136    #[serde(default)]
137    pub upgrade_from: Vec<UpgradeFromEntry>,
138
139    /// OTP supervisor strategy. Required when `:kind Supervisor`;
140    /// ignored otherwise.
141    #[serde(default, skip_serializing_if = "Option::is_none")]
142    pub estrategia: Option<crate::supervisor::RestartStrategy>,
143
144    /// Max restarts before the supervisor itself fails. Defaults via
145    /// SupervisorSpec at validation time.
146    #[serde(default, skip_serializing_if = "Option::is_none")]
147    pub max_restarts: Option<u32>,
148
149    /// Sliding window for `max_restarts`. Authored as a duration
150    /// string (`"60s"`, `"5m"`).
151    #[serde(default, skip_serializing_if = "Option::is_none")]
152    pub restart_window: Option<String>,
153
154    /// Static children of a supervisor. Required for OneForOne /
155    /// OneForAll / RestForOne; must be empty for SimpleOneForOne.
156    #[serde(default)]
157    pub children: Vec<crate::supervisor::ChildSpec>,
158
159    // ── M3 Aplicacao slots (theory/MESH-COMPOSITION.md) ─────────────────
160    //
161    // Required when :kind Aplicacao; ignored otherwise.
162    // Composed into a typed AplicacaoSpec via Caixa::aplicacao_view().
163    /// Member Servicos that make up this Aplicacao. Each is a
164    /// caixa-name + version-constraint pair. Required for Aplicacao.
165    #[serde(default)]
166    pub membros: Vec<crate::aplicacao::Membro>,
167
168    /// WIT-typed inter-Servico contracts. Each `:de` and `:para`
169    /// must reference a name in `:membros`.
170    #[serde(default)]
171    pub contratos: Vec<crate::aplicacao::WitContract>,
172
173    /// Mesh-level policies (timeout, retries, circuit-breaker, mTLS,
174    /// rate-limit). Apply to every contrato unless overridden per-edge
175    /// in M4.
176    #[serde(default, skip_serializing_if = "Option::is_none")]
177    pub politicas: Option<crate::aplicacao::MeshPolicy>,
178
179    /// Placement strategy across the cluster fleet
180    /// (single-node | replicated | sharded).
181    #[serde(default, skip_serializing_if = "Option::is_none")]
182    pub placement: Option<crate::aplicacao::Placement>,
183
184    /// External entry point — gateway / ingress shape. Optional;
185    /// only for public Aplicacaos.
186    #[serde(default, skip_serializing_if = "Option::is_none")]
187    pub entrada: Option<crate::aplicacao::Entrada>,
188
189    // ── Acao slot (CANTEIRO §7.1-C) ──────────────────────────────────────
190    //
191    // Required when :kind Acao; ignored otherwise (mirrors the M2/
192    // supervisor-tree/M3 slot triads above — a declared-but-foreign `:ci`
193    // is a `LayoutError::CiOnNonAcao` build error, not a silent drop).
194    /// Typed CI run — a repo's CI run as a set of typed nodes + their
195    /// dependency edges. Required for `:kind Acao`; validated (not
196    /// rendered) by the `caixa-actions` renderer via
197    /// `canteiro_types::decompose`. See `caixa-actions`' crate docs for
198    /// the M0 validate-only contract.
199    #[serde(default, skip_serializing_if = "Option::is_none")]
200    pub ci: Option<canteiro_types::CiRun>,
201}
202
203/// Why reading a manifest into a [`Caixa`] failed.
204///
205/// Split from [`ManifestError`] (which reports a *parsed* manifest that is
206/// semantically wrong) because the two answer different questions, and the
207/// distinction is the whole point of this type: `ManifestError` means "your
208/// caixa is wrong", `LeituraError::DialetoEstrangeiro` means "this file is not
209/// a caixa".
210#[derive(Debug, thiserror::Error)]
211pub enum LeituraError {
212    /// The source is not readable as a `(defcaixa …)` package manifest — bad
213    /// syntax, a wrong head symbol, an unknown or mistyped slot.
214    ///
215    /// `#[source]`, not `#[error(transparent)]`. Transparent delegates
216    /// `source()` past the inner error to ITS source, which drops the
217    /// `LispError` off the cause chain — and `feira`'s
218    /// `load_caixa_parse_error_preserves_underlying_lisp_error_on_chain`
219    /// pins that a caller can `downcast_ref::<tatara_lisp::LispError>()`
220    /// through an anyhow context to read the typed payload. That pin caught
221    /// this exact regression when the variant first landed transparent.
222    #[error("{0}")]
223    Leitura(
224        #[source]
225        #[from]
226        tatara_lisp::LispError,
227    ),
228
229    /// The source IS a well-formed `(defcaixa …)` form, but of a different
230    /// declaration than this crate's.
231    ///
232    /// The variant that did not exist before, and whose absence is the defect.
233    /// A `(defcaixa :name "x" :ecosystem :go …)` used to reach the derive's
234    /// `parse_kwargs_strict` and come back as an unknown-keyword rejection —
235    /// byte-identical in shape to a typo in a real manifest. Measured over the
236    /// org checkout on 2026-07-31, that shape is the MAJORITY of the corpus, so
237    /// the confusing error was also the common one.
238    ///
239    /// Carrying the dialect means a consumer can branch on "not mine" without
240    /// re-parsing, and a census can count it. Every user-facing byte-string
241    /// (canonical keyword, one-line description, consuming crate) is a
242    /// projection of [`crate::dialeto::CaixaDialeto`] — the variant stores the
243    /// typed dialect and the `#[error]` template calls
244    /// [`CaixaDialeto::palavra_canonica`] /
245    /// [`CaixaDialeto::descricao`] / [`CaixaDialeto::consumidor`] on it, so
246    /// the three axes cannot silently diverge from the classification. Prior
247    /// to this closure the variant carried each accessor's return value as a
248    /// stored `&'static str` snapshot alongside `dialeto`, and the sole
249    /// constructor at [`Caixa::from_lisp`] filled all four fields — a caller
250    /// could construct `DialetoEstrangeiro { dialeto: Molde,
251    /// palavra_canonica: "defcaixa", … }` and every downstream consumer
252    /// (Display, ad-hoc audit, future JSON serialization) would silently
253    /// disagree with `dialeto.palavra_canonica() == "defmolde"`. The typed
254    /// enum owns the projections; the variant only carries the axis.
255    #[error(
256        "this is a `{palavra}` declaration ({desc}), read by \
257         {cons} — not a caixa-core package manifest. `defcaixa` is the \
258         tatara-lisp package manifest (`:nome :versao :kind :deps …`); the two \
259         are different declarations that shared one keyword until 2026-07-31",
260        palavra = dialeto.palavra_canonica(),
261        desc = dialeto.descricao(),
262        cons = dialeto.consumidor()
263    )]
264    DialetoEstrangeiro {
265        /// Which declaration this actually is. Sole authoritative axis;
266        /// every user-facing projection routes through
267        /// [`crate::dialeto::CaixaDialeto`]'s typed accessors so the four
268        /// axes cannot silently disagree.
269        dialeto: crate::dialeto::CaixaDialeto,
270    },
271
272    /// Not a manifest declaration at all.
273    #[error(transparent)]
274    Dialeto(#[from] crate::dialeto::DialetoError),
275}
276
277impl Caixa {
278    /// Parse a `caixa.lisp` source string to a typed `Caixa`.
279    ///
280    /// Classifies the dialect **before** parsing. A `(defcaixa …)` of another
281    /// declaration is [`LeituraError::DialetoEstrangeiro`], naming what it is
282    /// and who reads it, instead of an unknown-keyword rejection that reads as
283    /// "your manifest is broken".
284    ///
285    /// The ordering is load-bearing. Handing a foreign dialect to the derive
286    /// first and interpreting the failure afterwards would mean guessing from
287    /// an error message, and the guess would be wrong for every file whose
288    /// first unknown slot happens to be one both schemas could plausibly carry.
289    pub fn from_lisp(src: &str) -> Result<Self, LeituraError> {
290        use tatara_lisp::domain::TataraDomain;
291        let forms = tatara_lisp::read(src).map_err(LeituraError::Leitura)?;
292        let first = forms.first().ok_or(crate::dialeto::DialetoError::Vazio)?;
293
294        // Route the foreign-dialect rejection gate through the lifted
295        // [`crate::dialeto::CaixaDialeto::is_molde_family`] (e9d2315)
296        // typed predicate rather than the pre-lift hand-rolled three-arm
297        // `match { Pacote => {}, Desconhecido => {}, foreign => Err(…) }`
298        // literal — the `defmolde` declaration-family partition (the two-
299        // arity closure of [`crate::dialeto::CaixaDialeto::Molde`] and
300        // [`crate::dialeto::CaixaDialeto::MoldePosicional`], the two arms
301        // whose sibling [`crate::dialeto::CaixaDialeto::palavra_canonica`]
302        // projection already collapses onto `"defmolde"` and whose sibling
303        // [`crate::dialeto::CaixaDialeto::consumidor`] projection already
304        // collapses onto `"pleme-doc-gen"`) resolves through one dispatch
305        // on the substrate primitive. `Pacote` (the tatara-lisp package
306        // manifest this derive can parse) and `Desconhecido` (deliberately
307        // falls through to the derive rather than short-circuiting: a
308        // `(defcaixa …)` matching neither schema is most likely a genuine
309        // package manifest with a typo in `:nome`, and the derive's
310        // diagnostic — which names the offending keyword and suggests the
311        // nearest slot — is far better than anything this classifier
312        // could say) both return `false` from `is_molde_family()` and fall
313        // through to the derive. Only the typed dialect flows into the
314        // error — the three user-facing projections (canonical keyword,
315        // description, consumer) are read at Display time through
316        // [`crate::dialeto::CaixaDialeto`]'s own accessors, so the
317        // variant cannot carry a snapshot that drifts from
318        // [`crate::dialeto::CaixaDialeto::palavra_canonica`] /
319        // `descricao` / `consumidor`. A future fifth dialect the
320        // [`crate::dialeto`] module doc's "third dialect" hazard
321        // actualises that belongs to the `defmolde` family lands one
322        // match arm at [`crate::dialeto::CaixaDialeto::is_molde_family`]
323        // and this gate picks up the new arm by construction — the pre-
324        // lift wildcard `foreign =>` was compile-time-anonymous and would
325        // silently absorb any hypothetical fifth `defcaixa`-family arm as
326        // foreign; routing the partition through the typed predicate
327        // closes both drift surfaces.
328        let dialeto = crate::dialeto::classify_form(first)?;
329        if dialeto.is_molde_family() {
330            return Err(LeituraError::DialetoEstrangeiro { dialeto });
331        }
332
333        Self::compile_from_sexp(first).map_err(LeituraError::Leitura)
334    }
335
336    /// Register `Caixa` with the global tatara-lisp domain registry so
337    /// `defcaixa` is dispatchable from any tatara-lisp binary that seeds
338    /// the registry (e.g. `tatara-check`).
339    ///
340    /// Returns the typed [`tatara_lisp::KeywordCollision`] on the second
341    /// (and every subsequent) call in the same process — one keyword,
342    /// one type, per process is a hard invariant of the upstream
343    /// registry, and a caller that hits it must fix its crate graph
344    /// rather than swallowing the error. Peer of the sibling per-crate
345    /// `register()` entry points at `caixa-flake/src/flake.rs`,
346    /// `caixa-fmt/src/lisp_config.rs`, `caixa-lacre/src/lock.rs`,
347    /// `caixa-lint/src/lisp_config.rs`, `caixa-resolver/src/lisp_config.rs`
348    /// — every substrate crate that owns a tatara-lisp keyword now
349    /// propagates the same typed error verbatim, so a downstream binary
350    /// that seeds the registry (`tatara-check`, the future LSP) reaches
351    /// for one shape at every call site.
352    ///
353    /// # Errors
354    ///
355    /// [`tatara_lisp::KeywordCollision`] when a peer type has already
356    /// claimed the `defcaixa` keyword in this process.
357    pub fn register() -> Result<(), tatara_lisp::KeywordCollision> {
358        tatara_lisp::domain::register::<Self>()
359    }
360
361    /// Substrate-canonical per-`Caixa` `:licenca` SPDX-expression scalar
362    /// accessor every consumer of the top-level manifest's license axis
363    /// keys off — returns the author-declared `:licenca` byte-string
364    /// verbatim as an `Option<&str>`, borrowed from the typed slot's own
365    /// `Option<String>` storage. `None` when the slot is absent (the
366    /// canonical "omit to defer to the caixa-helm renderer's `MIT`
367    /// fallback" shape [`Self::validate_licenca`] documents at
368    /// caixa-core/src/manifest.rs:1560; the peer [`caixa-helm`]
369    /// `build_readme` fold at caixa-helm/src/lib.rs:962 reads this
370    /// predicate too, so an authored-but-unset `:licenca` round-trips to
371    /// a rendered `lareira-<nome>` chart's `README.md` `## License`
372    /// section structurally identical to one that omits the slot).
373    ///
374    /// The `:licenca` slot carries the universal-axis SPDX-expression
375    /// license identifier every kind of caixa emits under (CAIXA-SDLC
376    /// §I — the author-facing surface every `defcaixa` form supplies) —
377    /// the typed slot's `Option<String>` accept-set (empty-string
378    /// rejected through [`ManifestError::LicencaEmpty`], SPDX-alphabet-
379    /// invalid rejected through [`ManifestError::LicencaInvalid`]) maps
380    /// onto the `lareira-<nome>` Helm chart's `README.md` `## License`
381    /// section (caixa-helm/src/lib.rs:962) and (through future
382    /// tightening documented at [`Self::validate_licenca`]) the
383    /// Chart.yaml `annotations["artifacthub.io/license"]` axis every
384    /// registry-facing chart carries. Every downstream consumer that
385    /// reads the license byte-string keys off this scalar (the
386    /// [`Self::validate_licenca`] empty-arm + SPDX-shape gate that
387    /// routes through `self.licenca.as_deref()`, the caixa-helm
388    /// `build_readme` `unwrap_or_else(|| "MIT".into())` fold that keys
389    /// the fallback off the `Option::is_none()` arm, every future
390    /// per-`Caixa` registry-facing renderer the CAIXA-SDLC §I roadmap
391    /// acknowledges).
392    ///
393    /// Prior to this lift the `.licenca` field was accessed inline at
394    /// two production sites — [`Self::validate_licenca`]'s
395    /// `self.licenca.as_deref()` empty-and-shape gate binding and the
396    /// caixa-helm `build_readme` `caixa.licenca.clone().unwrap_or_else(||
397    /// "MIT".into())` `README.md` `## License` fold — two open-coded
398    /// field-accesses that expressed no compile-time link back to the
399    /// typed slot. A future extension of the `:licenca` axis to a
400    /// richer author surface — a per-`:licenca` structured SPDX
401    /// expression parser + license-id allowlist (the future tightening
402    /// [`Self::validate_licenca`]'s docstring acknowledges), a
403    /// per-cluster license-default overlay the M4 CR materializer
404    /// resolves per-CR (the "cluster policy pins `Apache-2.0` for every
405    /// unlisted caixa" arm), a promotion of the plain
406    /// `Option<String>` byte-string to a richer `SpdxExpression` enum
407    /// once the SPDX-expression parser lands — would have had to be
408    /// threaded through both open-coded copies in lockstep or the
409    /// validate gate and the caixa-helm emit path would silently
410    /// disagree on which license a given [`Caixa`] resolves to (an
411    /// author's `:licenca "MIT OR Apache-2.0"` would satisfy validate
412    /// while the emit path silently rendered a stale `MIT` fallback,
413    /// or vice versa). Lifting the resolution to a typed method on the
414    /// substrate primitive means every downstream consumer of the
415    /// caixa's per-`Caixa` license surface reaches for exactly one
416    /// typed dispatch — the resolver's accept-set migrates as a unit
417    /// on any future axis addition.
418    ///
419    /// First `Option<&str>`-return top-level [`Caixa`] scalar accessor —
420    /// opens the "outer [`Caixa`] `Option<&str>` scalar" projection
421    /// pattern the sibling per-`Caixa` `:descricao` / `:repositorio` /
422    /// `:edicao` future lifts fold on. Same "one typed dispatch on the
423    /// substrate primitive, thin projections at each consumer"
424    /// discipline the peer per-`:placement` [`crate::aplicacao::Placement::shard_key`]
425    /// (7cd2a28) / [`crate::aplicacao::Placement::affinity`] (74ec2d3)
426    /// / per-`:contratos` [`crate::aplicacao::WitContract::endpoint`]
427    /// (7020470) / [`crate::aplicacao::WitContract::subject`] (90de675)
428    /// / [`crate::aplicacao::WitContract::slot`] (ed22b66)
429    /// `Option<&str>`-return accessors carry on the sibling M2 / M3
430    /// typed-slot atom axes, extended here to the outer top-level
431    /// `Caixa` universal-axis surface. Named `licenca()` to match the
432    /// storage field's name; the accessor's identity maps onto the
433    /// canonical CAIXA-SDLC §I vocabulary the slot's docstring already
434    /// carries.
435    #[must_use]
436    pub fn licenca(&self) -> Option<&str> {
437        self.licenca.as_deref()
438    }
439
440    /// Substrate-canonical per-`Caixa` `:repositorio` git-repo-URL scalar
441    /// accessor every consumer of the top-level manifest's homepage /
442    /// source-of-truth axis keys off — returns the author-declared
443    /// `:repositorio` byte-string verbatim as an `Option<&str>`, borrowed
444    /// from the typed slot's own `Option<String>` storage. `None` when
445    /// the slot is absent (the canonical "omit to defer to the renderer's
446    /// per-target placeholder" shape — [`caixa-helm`]'s `ChartYaml.home`
447    /// carries the `Option<String>` through verbatim so an author-omitted
448    /// `:repositorio` renders a `Chart.yaml` without a `home:` field
449    /// (`skip_serializing_if = "Option::is_none"`), while [`caixa-flux`]'s
450    /// `ClusterBundleOpts::for_caixa` folds the omitted slot through a
451    /// `format!("https://github.com/{DEFAULT_PLEME_GIT_ORG}/{nome}")`
452    /// fallback derived from `caixa.nome`).
453    ///
454    /// The `:repositorio` slot carries the universal-axis git-repo-URL
455    /// homepage identifier every kind of caixa emits under (CAIXA-SDLC
456    /// §I — the author-facing surface every `defcaixa` form supplies) —
457    /// the typed slot's `Option<String>` accept-set (empty-string
458    /// rejected through [`ManifestError::RepositorioEmpty`], git-repo-URL-
459    /// shape-invalid rejected through [`ManifestError::RepositorioInvalid`]
460    /// past the shared [`crate::render::is_git_repo_url`] predicate the
461    /// peer per-`:deps :fonte :repo` axis also routes through) maps onto
462    /// four load-bearing downstream consumers:
463    ///
464    ///   - [`Self::validate_repositorio`]'s empty-arm + shape-predicate
465    ///     gate binding at caixa-core/src/manifest.rs:1456 — the
466    ///     universal-axis identity gate wired at caixa-build time.
467    ///   - [`caixa-helm`]'s `build_chart_yaml` `ChartYaml.home` fold at
468    ///     caixa-helm/src/lib.rs:840 — the rendered `lareira-<nome>`
469    ///     Helm chart's `Chart.yaml` `home:` field, which every registry
470    ///     that ingests the chart (ArtifactHub, chartmuseum,
471    ///     `helm search repo`) surfaces as the chart's canonical source-
472    ///     of-truth link.
473    ///   - [`caixa-helm`]'s `build_readme` `## Source` fold at
474    ///     caixa-helm/src/lib.rs:957 — the rendered `lareira-<nome>`
475    ///     chart's `README.md` header link back to the source repo,
476    ///     which every author who inspects the rendered chart bundle
477    ///     lands at.
478    ///   - [`caixa-flux`]'s `ClusterBundleOpts::for_caixa`
479    ///     `GitRepository.spec.url` fold at caixa-flux/src/lib.rs:2006 —
480    ///     the rendered `GitRepository` CR's `spec.url` field, which
481    ///     FluxCD's `source-controller` polls to reconcile the caixa's
482    ///     manifest bundle from git.
483    ///
484    /// Prior to this lift the `.repositorio` field was accessed inline
485    /// at four production sites — [`Self::validate_repositorio`]'s
486    /// `self.repositorio.as_deref()` empty-and-shape gate binding, the
487    /// caixa-helm `build_chart_yaml` `caixa.repositorio.clone()`
488    /// `Chart.yaml` `home:` field fold, the caixa-helm `build_readme`
489    /// `caixa.repositorio.clone().unwrap_or_else(|| caixa.nome.clone())`
490    /// `README.md` `## Source` fold, and the caixa-flux
491    /// `ClusterBundleOpts::for_caixa`
492    /// `caixa.repositorio.clone().unwrap_or_else(|| format!(...))`
493    /// `GitRepository.spec.url` fold — four open-coded field-accesses
494    /// that expressed no compile-time link back to the typed slot. A
495    /// future extension of the `:repositorio` axis to a richer author
496    /// surface — a per-`:repositorio` structured
497    /// [`crate::render::GitRepoUrl`]-shaped scheme+host+path parse
498    /// (the future tightening [`Self::validate_repositorio`]'s
499    /// docstring anticipates alongside the peer per-`:deps :fonte
500    /// :repo` axis), a per-cluster repo-mirror overlay the M4 CR
501    /// materializer resolves per-CR (the "cluster policy rewrites
502    /// `github:pleme-io/...` to `git.internal/mirror/pleme-io/...`"
503    /// arm the private-registry story acknowledges), a promotion of
504    /// the plain `Option<String>` byte-string to a richer
505    /// `RepoUrl` enum discriminated on scheme — would have had to be
506    /// threaded through all four open-coded copies in lockstep or the
507    /// validate gate and the three emit paths would silently disagree
508    /// on which URL a given [`Caixa`] resolves to (an author's
509    /// `:repositorio "github:pleme-io/checkout"` would satisfy validate
510    /// while one of the emit paths silently rendered a stale URL, or
511    /// vice versa). Lifting the resolution to a typed method on the
512    /// substrate primitive means every downstream consumer of the
513    /// caixa's per-`Caixa` repo-URL surface reaches for exactly one
514    /// typed dispatch — the resolver's accept-set migrates as a unit on
515    /// any future axis addition.
516    ///
517    /// Second outer top-level [`Caixa`] `Option<&str>`-return scalar
518    /// accessor — sibling of [`Self::licenca`] (6d5bc28), the accessor
519    /// that opened the "outer [`Caixa`] `Option<&str>` scalar"
520    /// projection pattern this lift folds on. Same "one typed dispatch
521    /// on the substrate primitive, thin projections at each consumer"
522    /// discipline the peer per-`:placement`
523    /// [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
524    /// [`crate::aplicacao::Placement::affinity`] (74ec2d3) /
525    /// per-`:contratos` [`crate::aplicacao::WitContract::endpoint`]
526    /// (7020470) / [`crate::aplicacao::WitContract::subject`] (90de675)
527    /// / [`crate::aplicacao::WitContract::slot`] (ed22b66)
528    /// `Option<&str>`-return accessors carry on the sibling M2 / M3
529    /// typed-slot atom axes, extended here to the second outer top-level
530    /// `Caixa` universal-axis surface. Named `repositorio()` to match
531    /// the storage field's name; the accessor's identity maps onto the
532    /// canonical CAIXA-SDLC §I vocabulary the slot's docstring already
533    /// carries.
534    #[must_use]
535    pub fn repositorio(&self) -> Option<&str> {
536        self.repositorio.as_deref()
537    }
538
539    /// Substrate-canonical per-`Caixa` **resolved-git-repo-URL** composer —
540    /// returns the caixa's canonical git-source-of-truth URL as an owned
541    /// [`String`], author-declared `:repositorio` byte-string verbatim on
542    /// the `Some` arm and the substrate's canonical pleme-org github URL
543    /// fallback ([`crate::DEFAULT_PLEME_GIT_ORG`] and [`Self::nome`]
544    /// interpolated into `https://github.com/<org>/<nome>`) on the
545    /// `None` arm. Every substrate-side consumer that resolves
546    /// "which git URL does this caixa's source live at?" reaches for
547    /// exactly one typed dispatch on the substrate primitive — the raw
548    /// `caixa.repositorio().map(str::to_owned).unwrap_or_else(|| format!(
549    /// "https://github.com/{org}/{nome}", org = DEFAULT_PLEME_GIT_ORG,
550    /// nome = caixa.nome()))` open-coded composition every prior caller
551    /// re-derived collapses onto one canonical arm.
552    ///
553    /// Distinct from [`Self::repositorio`] (`Option<&str>`, exposes the
554    /// author-omitted / author-declared partition to the caller) — this
555    /// accessor is the **resolved** URL surface, folding the fallback in
556    /// at the substrate-primitive boundary. Every consumer that keys off
557    /// the `Option::is_none()` discriminator (a [`Chart.yaml`] `home:`
558    /// field emit that must omit the field entirely on an author-omitted
559    /// `:repositorio`, per the [`Self::repositorio`] docstring's
560    /// documented four-consumer list) reaches through the raw
561    /// [`Self::repositorio`] `Option<&str>` accessor by construction — the
562    /// resolved-URL composer sits alongside it as the second projection
563    /// on the same underlying `:repositorio` slot rather than replacing
564    /// the raw accessor.
565    ///
566    /// The fallback branch is the exact byte-image of the prior inline
567    /// [`caixa-flux::ClusterBundleOpts::for_caixa`] `git_url` composer at
568    /// caixa-flux/src/lib.rs:2080 — pinned by the sibling caixa-flux
569    /// byte-parity test
570    /// `cluster_bundle_opts_for_caixa_git_url_routes_through_canonical_git_url_accessor`
571    /// against a future implementation of this method that reordered the
572    /// `format!` template arguments, migrated the `<org>` segment to a
573    /// different constant (the [`crate::DEFAULT_PLEME_GIT_ORG`] axis a
574    /// future substrate-side git-org migration may split off), or
575    /// silently absorbed the empty-string arm (a hypothetical
576    /// `Some("") → fallback` collapse the raw [`Self::repositorio`]
577    /// accessor's docstring explicitly rejects on the sibling raw
578    /// accessor).
579    ///
580    /// Peer of the sibling per-`&Caixa`-axis composed helpers
581    /// [`caixa-flux::cluster_bundle_for_caixa`] (06d52d7) on the sibling
582    /// substrate-side renderer surface — same "close the composed
583    /// substrate-primitive at one canonical arm on the single-`&Caixa`
584    /// dispatch, converge every prior open-coded caller onto the arm"
585    /// discipline extended onto the resolved-git-URL projection of the
586    /// per-`Caixa` `:repositorio` axis. Owns per-call [`String`]
587    /// allocation on both arms (the `Some` arm's `str::to_owned` and the
588    /// `None` arm's `format!`) — the by-value return matches every
589    /// downstream consumer's field-fill shape (the caixa-flux
590    /// `ClusterBundleOpts::git_url: String` field, every future
591    /// `Chart.yaml` `home:` fold's `Option<String>` field-fill on the
592    /// `Some` arm).
593    #[must_use]
594    pub fn canonical_git_url(&self) -> String {
595        self.repositorio().map_or_else(
596            || {
597                format!(
598                    "https://github.com/{org}/{nome}",
599                    org = crate::DEFAULT_PLEME_GIT_ORG,
600                    nome = self.nome(),
601                )
602            },
603            str::to_owned,
604        )
605    }
606
607    /// Substrate-canonical per-`Caixa` **resolved-publish-tag** composer —
608    /// returns the caixa's canonical Zig-style git-publish-tag as an owned
609    /// [`String`], derived by concatenating
610    /// [`crate::DEFAULT_PUBLISH_TAG_PREFIX`] with the typed
611    /// [`Self::versao`] byte-string on a single `format!` template.
612    /// Every substrate-side consumer that resolves "which git tag does this
613    /// caixa publish under?" reaches for exactly one typed dispatch on the
614    /// substrate primitive — the raw `format!("{prefix}{versao}", prefix =
615    /// caixa_core::DEFAULT_PUBLISH_TAG_PREFIX, versao = caixa.versao())`
616    /// open-coded composition every prior caller re-derived collapses onto
617    /// one canonical arm.
618    ///
619    /// Peer of the sibling [`Self::canonical_git_url`] (124f864) resolved-
620    /// git-URL composer on the paired per-`Caixa` git-remote axis — same
621    /// "close the composed substrate-primitive at one canonical arm on the
622    /// single-`&Caixa` dispatch, converge every prior open-coded caller
623    /// onto the arm" discipline extended from the resolved-URL projection
624    /// of the per-`Caixa` `:repositorio` axis onto the resolved-tag
625    /// projection of the per-`Caixa` `:versao` axis. The two accessors
626    /// jointly close the pair of scalars every `FluxCD` `GitRepository` CR
627    /// keys off (`spec.url` via [`Self::canonical_git_url`],
628    /// `spec.ref.tag` via [`Self::publish_tag`]) at the substrate primitive
629    /// — a downstream consumer that reaches through both accessors reads
630    /// the complete published-git-identity of a caixa through two typed
631    /// dispatches, not four open-coded field accesses.
632    ///
633    /// The reader-side (`caixa-flux::cluster_bundle` /
634    /// `ClusterBundleOpts::for_caixa`'s `git_ref` field, every future
635    /// per-cluster snapshot bundle emitter, the future M4
636    /// `mesh.pleme.io/v1alpha1/Aplicacao` CR materializer's tag-carrier
637    /// slot on the tatara `Process` intent) always resolves the tag under
638    /// the canonical [`crate::DEFAULT_PUBLISH_TAG_PREFIX`] prefix — this
639    /// method encodes that reader-side convention. The writer-side
640    /// (`caixa-feira`'s `feira publish` `--prefix` clap flag) allows the
641    /// operator to override the prefix at publish time; the two surfaces
642    /// intentionally sit on the "canonical default + operator override"
643    /// pair the sibling [`crate::DEFAULT_PUBLISH_TAG_PREFIX`] constant's
644    /// own docstring documents — a `feira publish --prefix release/`
645    /// override is the operator's explicit opt-out from the substrate
646    /// default, not a supported drift axis.
647    ///
648    /// The composition body is the exact byte-image of the prior inline
649    /// [`caixa-flux::ClusterBundleOpts::for_caixa`] `git_ref` composer at
650    /// caixa-flux/src/lib.rs:2105 — pinned by the sibling caixa-flux
651    /// byte-parity test
652    /// `cluster_bundle_opts_for_caixa_git_ref_routes_through_publish_tag_accessor`
653    /// against a future implementation of this method that reordered the
654    /// `format!` template arguments, migrated the `<prefix>` segment to a
655    /// different constant (the [`crate::DEFAULT_PUBLISH_TAG_PREFIX`] axis
656    /// a future Zig-style-tag rebrand may split off — the constant's own
657    /// docstring anticipates a substrate-side move to `release/<versao>`
658    /// or bare `<versao>` shapes once a sibling forge convention adopts a
659    /// slash-namespaced or bare-scalar form), interposed a canonicalization
660    /// pass on the `:versao` axis (a SemVer-2 build-metadata strip an OCI-
661    /// tag normalizer might apply once the M4 registry-alignment slot
662    /// lands), or silently absorbed an empty `:versao` arm (which cannot
663    /// occur past the [`Self::validate_versao`] gate but which a
664    /// hypothetical bypass on the accessor path must not silently paper
665    /// over).
666    ///
667    /// Owns per-call [`String`] allocation via the single `format!`
668    /// invocation — the by-value return matches every downstream
669    /// consumer's field-fill shape (the caixa-flux `GitRefSpec::Tag(String)`
670    /// variant's owned payload, every future `intent.aplicacao.tag: String`
671    /// field-fill on the M4 CR materializer's tag-carrier slot).
672    #[must_use]
673    pub fn publish_tag(&self) -> String {
674        format!(
675            "{prefix}{versao}",
676            prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
677            versao = self.versao(),
678        )
679    }
680
681    /// Substrate-canonical per-`Caixa` `:descricao` free-form-prose
682    /// chart-description scalar accessor every consumer of the top-level
683    /// manifest's Chart.yaml `description:` axis keys off — returns the
684    /// author-declared `:descricao` byte-string verbatim as an
685    /// `Option<&str>`, borrowed from the typed slot's own
686    /// `Option<String>` storage. `None` when the slot is absent (the
687    /// canonical "omit to defer to the per-renderer `caixa.nome`-derived
688    /// fallback" shape — [`caixa-helm`]'s `build_chart_yaml` folds the
689    /// omitted slot through a `format!("Generated chart for caixa Servico
690    /// {}", caixa.nome)` fallback, [`caixa-helm`]'s `build_readme` folds
691    /// it through a `format!("caixa Servico {}", caixa.nome)` fallback,
692    /// and [`caixa-feira`]'s `render_flake` folds it through a
693    /// `format!("caixa {}", c.nome)` `flake.nix` `description = ""`
694    /// fallback — each derived from `caixa.nome` on the null-carrier arm).
695    ///
696    /// The `:descricao` slot carries the universal-axis free-form-prose
697    /// chart-description identifier every kind of caixa emits under
698    /// (CAIXA-SDLC §I — the author-facing surface every `defcaixa` form
699    /// supplies) — the typed slot's `Option<String>` accept-set
700    /// (empty-string rejected through [`ManifestError::DescricaoEmpty`],
701    /// chart-description-shape-invalid rejected through
702    /// [`ManifestError::DescricaoInvalid`] past the shared
703    /// [`crate::render::is_chart_description_shape`] predicate the peer
704    /// per-`Caixa` `:descricao` axis also routes through) maps onto four
705    /// load-bearing downstream consumers:
706    ///
707    ///   - [`Self::validate_descricao`]'s empty-arm + shape-predicate
708    ///     gate binding — the universal-axis identity gate wired at
709    ///     caixa-build time.
710    ///   - [`caixa-helm`]'s `build_chart_yaml` `ChartYaml.description`
711    ///     `Chart.yaml` field fold — the rendered `lareira-<nome>` Helm
712    ///     chart's `Chart.yaml` `description:` field, which
713    ///     `apiVersion: v2` charts require non-empty (`helm lint` fires
714    ///     `WARNING [chart.metadata.description]: description is required`
715    ///     when absent) and which every registry that ingests the chart
716    ///     (ArtifactHub, chartmuseum, `helm search repo`) surfaces as the
717    ///     chart's canonical one-line prose descriptor.
718    ///   - [`caixa-helm`]'s `build_readme` chart-`README.md` header fold
719    ///     — the rendered `lareira-<nome>` chart's `README.md` prose
720    ///     header directly beneath the `# <chart-name>` title, which
721    ///     every author who inspects the rendered chart bundle lands at.
722    ///   - [`caixa-feira`]'s `render_flake` `flake.nix` `description = ""`
723    ///     top-level fold — the emitted `flake.nix`'s `description`
724    ///     field, which every Nix consumer (`nix flake show`,
725    ///     `nix flake metadata`, downstream flake-registry ingestors)
726    ///     surfaces as the flake's canonical descriptor.
727    ///
728    /// Prior to this lift the `.descricao` field was accessed inline at
729    /// four production sites — [`Self::validate_descricao`]'s
730    /// `self.descricao.as_deref()` empty-and-shape gate binding, the
731    /// caixa-helm `build_chart_yaml`
732    /// `caixa.descricao.clone().unwrap_or_else(|| format!(...))`
733    /// `Chart.yaml` `description:` fold, the caixa-helm `build_readme`
734    /// `caixa.descricao.clone().unwrap_or_else(|| format!(...))`
735    /// `README.md` header fold, and the caixa-feira `render_flake`
736    /// `c.descricao.clone().unwrap_or_else(|| format!(...))` `flake.nix`
737    /// `description = ""` fold — four open-coded field-accesses that
738    /// expressed no compile-time link back to the typed slot. A future
739    /// extension of the `:descricao` axis to a richer author surface —
740    /// a per-`:descricao` locale-tagged multi-language descriptor map
741    /// (the "one caixa, N language-tagged prose descriptions" arm
742    /// author-tooling internationalization anticipates), a
743    /// per-registry-target length-and-shape overlay the M4 CR
744    /// materializer resolves per-CR (the "ArtifactHub caps description
745    /// at 512 bytes but the internal registry caps at 256" arm), a
746    /// promotion of the plain `Option<String>` byte-string to a richer
747    /// `ChartDescription` newtype guaranteeing the
748    /// `is_chart_description_shape` predicate at the type level — would
749    /// have had to be threaded through all four open-coded copies in
750    /// lockstep or the validate gate and the three emit paths would
751    /// silently disagree on which prose string a given [`Caixa`]
752    /// resolves to (an author's
753    /// `:descricao "Checkout flow orchestration."` would satisfy
754    /// validate while one of the emit paths silently rendered a stale
755    /// `caixa.nome`-derived fallback, or vice versa). Lifting the
756    /// resolution to a typed method on the substrate primitive means
757    /// every downstream consumer of the caixa's per-`Caixa`
758    /// chart-description surface reaches for exactly one typed dispatch
759    /// — the resolver's accept-set migrates as a unit on any future
760    /// axis addition.
761    ///
762    /// Third outer top-level [`Caixa`] `Option<&str>`-return scalar
763    /// accessor — sibling of [`Self::licenca`] (6d5bc28) and
764    /// [`Self::repositorio`] (cc7332d), the accessors that opened the
765    /// "outer [`Caixa`] `Option<&str>` scalar" projection pattern this
766    /// lift folds on. Same "one typed dispatch on the substrate
767    /// primitive, thin projections at each consumer" discipline the
768    /// peer per-`:placement`
769    /// [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
770    /// [`crate::aplicacao::Placement::affinity`] (74ec2d3) /
771    /// per-`:contratos` [`crate::aplicacao::WitContract::endpoint`]
772    /// (7020470) / [`crate::aplicacao::WitContract::subject`] (90de675)
773    /// / [`crate::aplicacao::WitContract::slot`] (ed22b66)
774    /// `Option<&str>`-return accessors carry on the sibling M2 / M3
775    /// typed-slot atom axes, extended here to the third outer top-level
776    /// `Caixa` universal-axis surface. Named `descricao()` to match the
777    /// storage field's name; the accessor's identity maps onto the
778    /// canonical CAIXA-SDLC §I vocabulary the slot's docstring already
779    /// carries. The one remaining universal `Option<String>` slot
780    /// (`:edicao`) folds on this pattern next.
781    #[must_use]
782    pub fn descricao(&self) -> Option<&str> {
783        self.descricao.as_deref()
784    }
785
786    /// Substrate-canonical per-`Caixa` `:edicao` language-edition scalar
787    /// accessor every consumer of the top-level manifest's tatara-lisp
788    /// edition-selector axis keys off — returns the author-declared
789    /// `:edicao` byte-string verbatim as an `Option<&str>`, borrowed from
790    /// the typed slot's own `Option<String>` storage. `None` when the
791    /// slot is absent (the canonical "omit the slot to defer to the
792    /// substrate's default edition" shape every existing
793    /// [`caixa-resolver`] integration test fixture carries via
794    /// `edicao: None` — see `caixa-resolver/tests/git_integration.rs`;
795    /// the peer [`Self::validate_edicao`] gate is a no-op on the omitted
796    /// arm by construction, so an author-omitted `:edicao` round-trips
797    /// to a build without triggering the year-shape predicate).
798    ///
799    /// The `:edicao` slot carries the universal-axis 4-digit-ASCII-
800    /// decimal-year language-edition identifier every kind of caixa
801    /// emits under (CAIXA-SDLC §I — the author-facing surface every
802    /// `defcaixa` form supplies) — the typed slot's `Option<String>`
803    /// accept-set (empty-string rejected through
804    /// [`ManifestError::EdicaoEmpty`], year-shape-invalid rejected
805    /// through [`ManifestError::EdicaoInvalid`] past the 4-digit-ASCII-
806    /// decimal-year predicate [`Self::validate_edicao`] enforces) maps
807    /// onto one load-bearing downstream consumer today
808    /// ([`Self::validate_edicao`]'s empty-arm + year-shape-predicate
809    /// gate binding at caixa-core/src/manifest.rs:1959) plus every
810    /// future edition-aware substrate consumer the CAIXA-SDLC §I
811    /// roadmap anticipates (the tatara-lisp compiler's macro-surface
812    /// selector every edition-aware build step keys off, the future
813    /// per-edition compatibility-flag overlay the M4 CR materializer
814    /// resolves per-CR, the peer [`Caixa::template`] canonical
815    /// `:edicao "2026"` scaffold every `feira init` emits verbatim,
816    /// and the renderer-side fixtures at `caixa-helm/src/lib.rs:978` /
817    /// `caixa-flux/src/lib.rs:2319` / `caixa-mesh/src/lib.rs:3208` that
818    /// carry `edicao: Some("2026".into())` by construction).
819    ///
820    /// Prior to this lift the `.edicao` field was accessed inline at
821    /// one production site — [`Self::validate_edicao`]'s
822    /// `self.edicao.as_deref()` empty-and-shape gate binding — one
823    /// open-coded field-access that expressed no compile-time link
824    /// back to the typed slot. A future extension of the `:edicao`
825    /// axis to a richer author surface — a per-`:edicao` known-
826    /// edition allowlist (the future tightening
827    /// [`Self::validate_edicao`]'s docstring acknowledges past the
828    /// structural year-shape floor, rejecting year-shaped values that
829    /// don't name a tatara-lisp edition the substrate actually
830    /// understands — `"1999"` is year-shaped but no `1999` edition
831    /// exists), a per-edition compatibility-flag overlay the M4 CR
832    /// materializer resolves per-CR (the "edition `"2026"` enables
833    /// macro-surface features the sibling `"2018"` gates behind a
834    /// feature flag" arm the edition-selector story anticipates), a
835    /// promotion of the plain `Option<String>` byte-string to a
836    /// richer `CaixaEdition` enum discriminated on year once a sibling
837    /// edition to `"2026"` lands — would have had to be threaded
838    /// through the open-coded copy in lockstep with every future
839    /// edition-aware consumer, or the validate gate and the future
840    /// edition-aware consumer path would silently disagree on which
841    /// edition a given [`Caixa`] resolves to (an author's
842    /// `:edicao "2026"` would satisfy validate while a future
843    /// edition-aware consumer silently defaulted to a stale edition,
844    /// or vice versa). Lifting the resolution to a typed method on
845    /// the substrate primitive means every downstream consumer of the
846    /// caixa's per-`Caixa` edition surface reaches for exactly one
847    /// typed dispatch — the resolver's accept-set migrates as a unit
848    /// on any future axis addition.
849    ///
850    /// Fourth and final outer top-level [`Caixa`] `Option<&str>`-return
851    /// scalar accessor — sibling of [`Self::licenca`] (6d5bc28),
852    /// [`Self::repositorio`] (cc7332d), and [`Self::descricao`]
853    /// (3f16e2f), the accessors that opened the "outer [`Caixa`]
854    /// `Option<&str>` scalar" projection pattern this lift folds on.
855    /// Same "one typed dispatch on the substrate primitive, thin
856    /// projections at each consumer" discipline the peer per-`:placement`
857    /// [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
858    /// [`crate::aplicacao::Placement::affinity`] (74ec2d3) /
859    /// per-`:contratos` [`crate::aplicacao::WitContract::endpoint`]
860    /// (7020470) / [`crate::aplicacao::WitContract::subject`] (90de675)
861    /// / [`crate::aplicacao::WitContract::slot`] (ed22b66)
862    /// `Option<&str>`-return accessors carry on the sibling M2 / M3
863    /// typed-slot atom axes, extended here to close the outer top-level
864    /// `Caixa` universal-axis surface's last unlifted `Option<String>`
865    /// slot. Named `edicao()` to match the storage field's name; the
866    /// accessor's identity maps onto the canonical CAIXA-SDLC §I
867    /// vocabulary the slot's docstring already carries.
868    #[must_use]
869    pub fn edicao(&self) -> Option<&str> {
870        self.edicao.as_deref()
871    }
872
873    /// Substrate-canonical per-`Caixa` `:nome` universal-axis DNS-1123-
874    /// label caixa-identity scalar accessor every consumer of the top-
875    /// level manifest's identity axis keys off — returns the author-
876    /// declared `:nome` byte-string verbatim as an `&str`, borrowed from
877    /// the typed slot's own `String` storage. Non-optional (`:nome` is
878    /// a required-axis scalar every `defcaixa` form must supply; the
879    /// [`Self::from_lisp`] derive rejects an omitted / non-string
880    /// `:nome` at parse time, so a `Caixa` past parse definitionally
881    /// carries a non-`None` `:nome`).
882    ///
883    /// The `:nome` slot carries the universal-axis DNS-1123-label
884    /// caixa-identity every kind of caixa emits under (CAIXA-SDLC §I —
885    /// the primary identity axis every `defcaixa` form supplies
886    /// alongside `:versao` / `:kind`; the substrate-wide identity every
887    /// other typed surface that names a caixa reaches through — `:deps`
888    /// entries, `:membros` entries, `:children` entries, the
889    /// `lareira-<nome>` Helm chart name every per-Servico renderer
890    /// derives, the `pleme-program-<nome>` label every per-Aplicacao
891    /// renderer emits) — the typed slot's `String` accept-set (empty
892    /// rejected through [`ManifestError::NomeEmpty`], DNS-1123-shape-
893    /// invalid rejected through [`ManifestError::NomeInvalid`] past
894    /// the shared [`crate::render::require_valid_dns_1123_label`] gate
895    /// the peer name axes each land on, joint-length-with-`lareira-`-
896    /// prefix rejected through
897    /// [`ManifestError::NomeChartNameBudgetExceeded`] past
898    /// [`crate::render::is_lareira_chart_name_shape`]) maps onto every
899    /// load-bearing downstream consumer the substrate carries — the
900    /// two universal-axis validate gates at caixa-build time
901    /// ([`Self::validate_nome`] + [`Self::validate_nome_chart_name_budget`]),
902    /// [`crate::lareira_chart_name`]'s `lareira-<nome>` Helm chart-name
903    /// derivation every per-Servico renderer keys off, the caixa-helm
904    /// `Chart.yaml`'s `name:` axis, caixa-flux's `programs.yaml` entry
905    /// `name:` axis, caixa-mesh's Cilium `CiliumNetworkPolicy` /
906    /// `HTTPRoute` per-Aplicacao name axes at
907    /// caixa-mesh/src/lib.rs:{2650, 2797, 2919, 2925},
908    /// [`crate::pleme_program_selector`] /
909    /// [`crate::pleme_program_in_aplicacao_selector`] label-selector
910    /// derivations, and every future substrate renderer that emits an
911    /// artifact keyed by the caixa's identity.
912    ///
913    /// Prior to this lift the `.nome` field was accessed inline at a
914    /// dozen production sites across `caixa-core` (the two universal-
915    /// axis validate gates + [`Dep::validate`]-adjacent duplicate
916    /// tracking), `caixa-helm` (the `lareira_chart_name` fold, the
917    /// `ChartYaml.name` / `ChartYaml.description` / `Chart.yaml`
918    /// `keywords` fallback), `caixa-flux` (the `programs.yaml`
919    /// entry `name:` fold, the `flux_kustomization_source_subtree`
920    /// per-cluster subpath derivation), and `caixa-mesh` (the
921    /// `pleme_program_in_aplicacao_selector` label-selector fold, the
922    /// `cilium_network_policy_name` / `gateway_api_http_route_name`
923    /// per-CR name derivations, the `LABEL_APLICACAO` labels-map
924    /// insert) — a dozen open-coded field-accesses that expressed no
925    /// compile-time link back to the typed slot. A future extension of
926    /// the `:nome` axis to a richer author surface — a per-`:nome`
927    /// structured `CaixaIdentity` newtype that carries the joint-
928    /// length-with-prefix invariant [`Self::validate_nome_chart_name_budget`]
929    /// enforces at the type level (rather than as a validate-time
930    /// gate), a per-registry `:nome` namespacing overlay the M4 CR
931    /// materializer resolves per-CR (the "`pleme-io/checkout` vs
932    /// `partner-org/checkout` collision" arm the multi-tenant-registry
933    /// story acknowledges), a promotion of the plain `String` byte-
934    /// string to a richer `CaixaNome` newtype discriminated on
935    /// namespace prefix — would have had to be threaded through every
936    /// open-coded copy in lockstep or the two validate gates and the
937    /// dozen emit paths would silently disagree on which identity a
938    /// given [`Caixa`] resolves to (an author's `:nome "checkout"`
939    /// would satisfy validate while one of the emit paths silently
940    /// rendered a drifted other identity, or vice versa). Lifting the
941    /// resolution to a typed method on the substrate primitive means
942    /// every downstream consumer of the caixa's per-`Caixa` identity
943    /// surface reaches for exactly one typed dispatch — the resolver's
944    /// accept-set migrates as a unit on any future axis addition.
945    ///
946    /// First outer top-level [`Caixa`] `&str`-return required-scalar
947    /// accessor — opens the "outer [`Caixa`] `&str` required-scalar"
948    /// projection pattern the sibling per-`Caixa` `:versao` future lift
949    /// folds on. Sibling in shape to the peer per-`:membros`
950    /// [`crate::aplicacao::Membro::nome`] (4a32abf) / per-`:contratos`
951    /// [`crate::aplicacao::WitContract::source`] /
952    /// [`crate::aplicacao::WitContract::destination`] (7f0fd43),
953    /// [`crate::aplicacao::WitContract::world_ref`] (0804823),
954    /// [`crate::aplicacao::Membro::versao_requirement`] (a40b0e3),
955    /// [`crate::aplicacao::Entrada::destination`] (6db982c),
956    /// [`crate::aplicacao::CircuitBreaker::max_failures`] (3a74062),
957    /// per-sub-struct required-axis accessors carry on the sibling M3
958    /// mesh-slot-atom scalar-value axes, extended here to open the
959    /// outer top-level [`Caixa`] `&str`-return required-scalar surface.
960    /// Named `nome()` to match the storage field's name; the accessor's
961    /// identity maps onto the canonical CAIXA-SDLC §I vocabulary the
962    /// slot's docstring already carries.
963    #[must_use]
964    pub fn nome(&self) -> &str {
965        &self.nome
966    }
967
968    /// Substrate-canonical per-`Caixa` `:versao` universal-axis SemVer-2
969    /// pinned-version scalar accessor every consumer of the top-level
970    /// manifest's version axis keys off — returns the author-declared
971    /// `:versao` byte-string verbatim as an `&str`, borrowed from the
972    /// typed slot's own `String` storage. Non-optional (`:versao` is a
973    /// required-axis scalar every `defcaixa` form must supply alongside
974    /// `:nome` / `:kind`; the [`Self::from_lisp`] derive rejects an
975    /// omitted / non-string `:versao` at parse time, so a `Caixa` past
976    /// parse definitionally carries a non-`None` `:versao`).
977    ///
978    /// The `:versao` slot carries the universal-axis SemVer-2
979    /// concrete-version body every kind of caixa emits under
980    /// (CAIXA-SDLC §I — the required-scalar every `defcaixa` form
981    /// supplies alongside `:nome` / `:kind`; the substrate-wide
982    /// pinned-version every downstream artifact-emitting consumer
983    /// composes under — the `lareira-<nome>` Helm chart's `Chart.yaml`
984    /// `version:` + `appVersion:` axes, the `feira publish` Zig-style
985    /// `v<versao>` git tag the [`crate::DEFAULT_PUBLISH_TAG_PREFIX`]
986    /// prefix composes on top of, the programs.yaml entry's `versao:`
987    /// value the `lareira-fleet-programs` aggregator carries onto each
988    /// rendered `ComputeUnit`, the OCI image's `:v<versao>` / `:latest`
989    /// tags every substrate-side `skopeo push` writes, the lacre
990    /// closure's pinned `concrete_versao`, and the `:upgrade-from :from`
991    /// prior-version references peers in the exact same SemVer-2 shape).
992    /// The typed slot's `String` accept-set (empty rejected through
993    /// [`ManifestError::VersaoEmpty`], SemVer-2-shape-invalid rejected
994    /// through [`ManifestError::VersaoInvalid`] past
995    /// [`semver::Version::parse`]) maps onto every load-bearing
996    /// downstream consumer the substrate carries — the [`Self::validate_versao`]
997    /// universal-axis validate gate at caixa-build time, the
998    /// [`crate::CaixaVersion::parse`] typed-wrapper resolver,
999    /// [`caixa-helm`]'s `Chart.yaml` `version:` / `appVersion:` fold,
1000    /// [`caixa-flux`]'s `programs.yaml` entry `versao:` fold + the
1001    /// `cluster_bundle` `GitRepository` `ref: { tag: v<versao> }`
1002    /// derivation, [`caixa-mesh`]'s per-Aplicacao `programs.yaml` fan-
1003    /// out entry `versao:` fold, [`caixa-feira`]'s `feira publish` git-
1004    /// tag derivation (`format!("{prefix}{versao}")`), and every future
1005    /// substrate renderer that emits an artifact keyed by the caixa's
1006    /// pinned version.
1007    ///
1008    /// Prior to this lift the `.versao` field was accessed inline at a
1009    /// dozen production sites across `caixa-core` (the universal-axis
1010    /// [`Self::validate_versao`] gate + [`Dep::validate`]-adjacent
1011    /// version-shape gates), `caixa-helm` (the `ChartYaml.version` /
1012    /// `ChartYaml.app_version` folds), `caixa-flux` (the `programs.yaml`
1013    /// entry `versao:` fold, the `cluster_bundle` `GitRepository` `ref:
1014    /// { tag: v<versao> }` derivation), `caixa-mesh` (the per-Aplicacao
1015    /// `programs.yaml` fan-out entry `versao:` fold), and `caixa-feira`
1016    /// (the `feira publish` git-tag derivation + the `feira app graph` /
1017    /// `feira app deploy` diagnostic renderers) — a dozen open-coded
1018    /// field-accesses that expressed no compile-time link back to the
1019    /// typed slot. A future extension of the `:versao` axis to a richer
1020    /// author surface — a per-`:versao` structured `CaixaVersion` at the
1021    /// storage layer (the substrate already carries a `CaixaVersion`
1022    /// newtype at [`crate::version::CaixaVersion`], deferred until the
1023    /// serde-transparent-newtype-through-DeriveTataraDomain path lands),
1024    /// a per-registry `:versao` immutability overlay the M4 CR
1025    /// materializer enforces per-CR, a promotion of the plain `String`
1026    /// byte-string to a richer `PinnedVersao` newtype discriminated on
1027    /// SemVer-2 pre-release / build-metadata presence — would have had
1028    /// to be threaded through every open-coded copy in lockstep or the
1029    /// validate gate and the dozen emit paths would silently disagree
1030    /// on which version a given [`Caixa`] resolves to (an author's
1031    /// `:versao "0.1.0"` would satisfy validate while one of the emit
1032    /// paths silently rendered a drifted other version, or vice versa).
1033    /// Lifting the resolution to a typed method on the substrate
1034    /// primitive means every downstream consumer of the caixa's
1035    /// per-`Caixa` pinned-version surface reaches for exactly one typed
1036    /// dispatch — the resolver's accept-set migrates as a unit on any
1037    /// future axis addition.
1038    ///
1039    /// Second outer top-level [`Caixa`] `&str`-return required-scalar
1040    /// accessor — folds on the "outer [`Caixa`] `&str` required-scalar"
1041    /// projection pattern the sibling per-`Caixa` [`Self::nome`]
1042    /// (e6b7d97) opened. Sibling in shape to the peer per-`:membros`
1043    /// [`crate::aplicacao::Membro::versao_requirement`] (4127bb6) /
1044    /// per-`:children` [`crate::supervisor::ChildSpec::versao_requirement`]
1045    /// (2c053c8) / per-`:upgrade-from` [`crate::UpgradeFromEntry::prior_versao`]
1046    /// (75d27a8) per-sub-struct `:versao`-shaped `&str`-return accessors
1047    /// on the sibling per-typed-slot version-carrier axes, extended here
1048    /// to close the second outer top-level [`Caixa`] required-`&str`-
1049    /// carrying axis so the two universal-axis identity-carrying
1050    /// scalars every `defcaixa` form supplies (`:nome` + `:versao`)
1051    /// share the same "one typed dispatch per axis" discipline. Named
1052    /// `versao()` to match the storage field's name; the accessor's
1053    /// identity maps onto the canonical CAIXA-SDLC §I vocabulary the
1054    /// slot's docstring already carries.
1055    #[must_use]
1056    pub fn versao(&self) -> &str {
1057        &self.versao
1058    }
1059
1060    /// Substrate-canonical per-`Caixa` `:kind` universal-axis
1061    /// closed-set-enum discriminant accessor every consumer of the top-
1062    /// level manifest's kind axis keys off — returns the author-declared
1063    /// `:kind` variant verbatim as a [`CaixaKind`], `Copy`-projected
1064    /// from the typed slot's own [`CaixaKind`] storage. Non-optional
1065    /// (`:kind` is a required-axis discriminant every `defcaixa` form
1066    /// must supply alongside `:nome` / `:versao`; the [`Self::from_lisp`]
1067    /// derive rejects an omitted / non-symbol `:kind` at parse time, so
1068    /// a `Caixa` past parse definitionally carries a valid [`CaixaKind`]
1069    /// variant).
1070    ///
1071    /// The `:kind` slot carries the universal-axis closed-set typed-
1072    /// discriminant every substrate-side dispatch keys off (CAIXA-SDLC
1073    /// §I — the primary shape gate every renderer / verifier /
1074    /// operator branches on; the five variants `Biblioteca` /
1075    /// `Binario` / `Servico` / `Supervisor` / `Aplicacao` partition
1076    /// the caixa surface into disjoint runtime contracts) — the typed
1077    /// slot's [`CaixaKind`] accept-set (parse-time-rejected non-symbol
1078    /// values through the derive-macro's symbol-arm gate, exhaustively
1079    /// matched at every downstream dispatch site) maps onto every
1080    /// load-bearing downstream consumer the substrate carries:
1081    ///
1082    ///   - [`crate::render::require_kind`]'s per-renderer entry-gate
1083    ///     predicate — the canonical two-line
1084    ///     `require_kind(caixa, Servico)?` prelude every per-Servico
1085    ///     renderer (`caixa-helm`, `caixa-flux`, the future `caixa-otel`
1086    ///     / per-Servico OCI packager / M4 `wasm.pleme.io/v1alpha1/
1087    ///     ComputeUnit` CR materializer) runs at its entry-point,
1088    ///     alongside the [`crate::render::KindMismatch`] error carrier's
1089    ///     `actual:` field the diagnostic surfaces to name the offending
1090    ///     caixa's variant.
1091    ///   - [`Self::aplicacao_view`]'s + [`Self::supervisor_view`]'s
1092    ///     per-view kind-gate binding — the two `Option<TypedSpec>`
1093    ///     `_view` composers that fold the flat mesh-slot / supervisor-
1094    ///     slot columns into their typed sub-spec only when the kind
1095    ///     matches (returns `None` otherwise); the future per-Servico
1096    ///     M2-view composer (`servico_view`) will follow the same shape.
1097    ///   - [`Self::declared_foreign_code_slots`]'s per-slot kind-
1098    ///     coherence gate — the `!self.kind.requires_exe()` /
1099    ///     `!self.kind.requires_servicos()` predicates that fence
1100    ///     each code-surface slot from the wrong owning kind.
1101    ///   - [`crate::LayoutInvariants::verify`]'s kind ↔ code-surface
1102    ///     coherence gates — the six `caixa.kind == CaixaKind::X` /
1103    ///     `caixa.kind != CaixaKind::X` predicates and the four kind-
1104    ///     coherence error carriers (`SupervisorOwnsCode` /
1105    ///     `AplicacaoOwnsCode` / `MeshSlotsOnNonAplicacao` /
1106    ///     `SupervisorSlotsOnNonSupervisor` / `ServicoSlotsOnNonServico`
1107    ///     / `ForeignCodeSlot`) which each name the offending caixa's
1108    ///     variant in their `kind:` field.
1109    ///
1110    /// Prior to this lift the `.kind` field was accessed inline at
1111    /// twenty-plus production sites across `caixa-core` (the
1112    /// [`crate::render::require_kind`] entry-gate predicate + the
1113    /// [`crate::render::KindMismatch`] `actual:` field, the two `_view`
1114    /// composers, the `declared_foreign_code_slots` per-slot kind-
1115    /// coherence gate, and the six [`crate::LayoutInvariants::verify`]
1116    /// kind ↔ code-surface predicates + four error carriers) — a score
1117    /// of open-coded field-accesses that expressed no compile-time link
1118    /// back to the typed slot. A future extension of the `:kind` axis
1119    /// to a richer author surface — a per-`:kind` sub-variant discriminant
1120    /// (e.g. `Servico(ServicoRuntime)` splitting the current single
1121    /// variant across the wasm-component / legacy-container / native-
1122    /// binary runtime axes the M5 roadmap acknowledges), a per-cluster
1123    /// kind-overlay the M4 CR materializer resolves per-CR (the
1124    /// "cluster policy demotes `Aplicacao` to `Servico` on a single-
1125    /// tenant cluster" arm), a promotion of the plain [`CaixaKind`]
1126    /// enum to a richer `KindWithRuntime` discriminated on the
1127    /// component-model world axis — would have had to be threaded
1128    /// through every open-coded copy in lockstep or the entry gate,
1129    /// the view composers, and the layout invariants would silently
1130    /// disagree on which kind a given [`Caixa`] resolves to. Lifting
1131    /// the resolution to a typed method on the substrate primitive
1132    /// means every downstream consumer of the caixa's per-`Caixa`
1133    /// kind surface reaches for exactly one typed dispatch — the
1134    /// resolver's accept-set migrates as a unit on any future axis
1135    /// addition.
1136    ///
1137    /// First outer top-level [`Caixa`] `Copy`-return required-enum-
1138    /// discriminant accessor — opens the "outer [`Caixa`] `Copy`-return
1139    /// required-discriminant" projection pattern. Sibling in shape to
1140    /// the peer per-`:supervisor` [`crate::supervisor::SupervisorSpec::estrategia`]
1141    /// (eafb619), per-`:placement` [`crate::aplicacao::Placement::estrategia`]
1142    /// (921fe1b), and per-`:children` [`crate::supervisor::ChildSpec::restart`]
1143    /// (dfb4a81) `Copy`-return closed-set-enum discriminant accessors
1144    /// on the sibling nested-spec typed-slot discriminator axes,
1145    /// extended here to the outer top-level [`Caixa`] universal-axis
1146    /// surface. Named `kind()` to match the storage field's name;
1147    /// the accessor's identity maps onto the canonical CAIXA-SDLC §I
1148    /// vocabulary the slot's docstring already carries.
1149    #[must_use]
1150    pub fn kind(&self) -> CaixaKind {
1151        self.kind
1152    }
1153
1154    /// Substrate-canonical per-`Caixa` `:autores` universal-axis
1155    /// maintainer-name-list slice-accessor every consumer of the top-
1156    /// level manifest's maintainer axis keys off — returns the author-
1157    /// declared `:autores` list verbatim as a `&[String]` slice-view over
1158    /// the same backing buffer the raw `self.autores.as_slice()` field
1159    /// access borrows from. Empty-list-carrying (`:autores` is a default-
1160    /// empty axis every `defcaixa` form supplies with an empty `()` when
1161    /// unset; the [`Self::from_lisp`] derive folds an omitted `:autores`
1162    /// through `#[serde(default)]` to `Vec::new()`, so a `Caixa` past
1163    /// parse definitionally carries a `Vec<String>` slot — possibly
1164    /// empty — and the returned `&[String]` degenerates to an empty
1165    /// slice on that arm without any silent `None` collapse).
1166    ///
1167    /// The `:autores` slot carries the universal-axis maintainer-name
1168    /// list every kind of caixa emits under (CAIXA-SDLC §I — the author-
1169    /// facing surface every `defcaixa` form supplies alongside `:nome` /
1170    /// `:versao` / `:kind`; the substrate-wide contact-carrying axis
1171    /// every downstream registry-facing artifact emits under) — the
1172    /// typed slot's `Vec<String>` accept-set (empty-per-entry rejected
1173    /// through [`ManifestError::AutorEmpty`], non-chart-maintainer-shape
1174    /// rejected through [`ManifestError::AutorInvalid`], cross-entry
1175    /// duplicate rejected through [`ManifestError::AutorDuplicate`]) maps
1176    /// onto every load-bearing downstream consumer the substrate carries
1177    /// — the [`Self::validate_autores`] universal-axis empty-per-entry +
1178    /// shape + duplicate gate at caixa-core/src/manifest.rs, the
1179    /// caixa-helm `build_chart_yaml` `maintainers:` fold at
1180    /// caixa-helm/src/lib.rs that walks each entry into a `Maintainer {
1181    /// name, email: None }` record, every future per-`Caixa` registry-
1182    /// facing renderer the CAIXA-SDLC §I roadmap acknowledges (the
1183    /// future `artifacthub.io/maintainers` `Chart.yaml` annotation the
1184    /// caixa-helm docstring alludes to at [`Self::validate_licenca`],
1185    /// the future per-cluster author-notification overlay the M4 CR
1186    /// materializer resolves per-CR).
1187    ///
1188    /// Prior to this lift the `.autores` field was accessed inline at
1189    /// two production sites — [`Self::validate_autores`]'s `for autor
1190    /// in &self.autores` walk that gates every entry through
1191    /// [`ManifestError::AutorEmpty`] / `AutorInvalid` / `AutorDuplicate`,
1192    /// and the caixa-helm `build_chart_yaml` `caixa.autores.iter().map(|a|
1193    /// Maintainer { name: a.clone(), email: None }).collect()` fold that
1194    /// materializes every entry into a `Chart.yaml` `maintainers:` row —
1195    /// two open-coded field-accesses that expressed no compile-time link
1196    /// back to the typed slot. A future extension of the `:autores` axis
1197    /// to a richer author surface — a per-`:autores` structured
1198    /// `Maintainer { name, email, url }` at the storage layer once the
1199    /// substrate absorbs `artifacthub.io/maintainers`' name+email+url
1200    /// tuple, a per-registry `:autores` allowlist the M4 CR materializer
1201    /// enforces per-CR (the "cluster policy demands every author declare
1202    /// an on-file `mailto:` contact" arm), a promotion of the plain
1203    /// `Vec<String>` byte-string list to a richer
1204    /// `Vec<ChartMaintainer>` newtype discriminated on the RFC-5322
1205    /// `<name> [<email>]` grammar the `is_chart_maintainer_name_shape`
1206    /// predicate already resolves through — would have had to be
1207    /// threaded through both open-coded copies in lockstep or the
1208    /// validate gate and the caixa-helm emit path would silently
1209    /// disagree on which authors a given [`Caixa`] resolves to (an
1210    /// author's `:autores ("alice" "bob")` would satisfy validate while
1211    /// the caixa-helm emit path silently rendered a drifted other
1212    /// maintainer list, or vice versa). Lifting the resolution to a
1213    /// typed method on the substrate primitive means every downstream
1214    /// consumer of the caixa's per-`Caixa` maintainer surface reaches
1215    /// for exactly one typed dispatch — the resolver's accept-set
1216    /// migrates as a unit on any future axis addition.
1217    ///
1218    /// First outer top-level [`Caixa`] `&[T]`-return slice-accessor —
1219    /// opens the "outer [`Caixa`] `&[T]` slice" projection pattern the
1220    /// sibling per-`Caixa` `:etiquetas` / `:deps` / `:deps-dev` / `:exe`
1221    /// / `:bibliotecas` / `:servicos` / `:upgrade-from` / `:children`
1222    /// future lifts fold on. Sibling in shape to the peer per-`:supervisor`
1223    /// [`crate::supervisor::SupervisorSpec::children`] (bc92bce), per-`:placement`
1224    /// [`crate::aplicacao::Placement::clusters`] (a6e18d7), per-`:membros`
1225    /// [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36), per-`:contratos`
1226    /// [`crate::aplicacao::AplicacaoSpec::contratos`] (0dcc926), and
1227    /// per-`:upgrade-from :instructions` [`crate::upgrade::UpgradeFromEntry::instructions`]
1228    /// (0137e5a) `&[T]`-return slice accessors on the sibling per-M2 /
1229    /// per-M3 typed-slot list axes, extended here to the outer top-level
1230    /// [`Caixa`] universal-axis surface. Returns `&[String]` (not
1231    /// `&Vec<String>`) because every downstream consumer of the author
1232    /// list treats it as a read-only sequence — the slice-view is the
1233    /// narrowest borrow that supports every present + roadmapped consumer
1234    /// (`.iter()`, `.len()`, `.is_empty()`) without leaking the backing
1235    /// `Vec`'s grow/push/reserve surface no consumer of the typed view
1236    /// reaches for (the storage-side `Vec` remains reachable through the
1237    /// `pub autores` field for the mutation-carrying serde round-trip and
1238    /// per-test fixture-mutation paths). Named `autores()` to match the
1239    /// storage field's name; the accessor's identity maps onto the
1240    /// canonical CAIXA-SDLC §I vocabulary the slot's docstring already
1241    /// carries.
1242    #[must_use]
1243    pub fn autores(&self) -> &[String] {
1244        self.autores.as_slice()
1245    }
1246
1247    /// Substrate-canonical per-`Caixa` `:etiquetas` universal-axis
1248    /// registry-search-tag-list slice-accessor every consumer of the
1249    /// top-level manifest's topical-tag axis keys off — returns the
1250    /// author-declared `:etiquetas` list verbatim as a `&[String]`
1251    /// slice-view over the same backing buffer the raw
1252    /// `self.etiquetas.as_slice()` field access borrows from. Empty-
1253    /// list-carrying (`:etiquetas` is a default-empty axis every
1254    /// `defcaixa` form supplies with an empty `()` when unset; the
1255    /// [`Self::from_lisp`] derive folds an omitted `:etiquetas` through
1256    /// `#[serde(default)]` to `Vec::new()`, so a `Caixa` past parse
1257    /// definitionally carries a `Vec<String>` slot — possibly empty —
1258    /// and the returned `&[String]` degenerates to an empty slice on
1259    /// that arm without any silent `None` collapse).
1260    ///
1261    /// The `:etiquetas` slot carries the universal-axis topical-tag
1262    /// list every kind of caixa emits under (CAIXA-SDLC §I — the
1263    /// author-facing surface every `defcaixa` form supplies alongside
1264    /// `:nome` / `:versao` / `:kind`; the substrate-wide registry-
1265    /// search-facing axis every downstream registry-facing artifact
1266    /// emits under) — the typed slot's `Vec<String>` accept-set
1267    /// (empty-per-entry rejected through [`ManifestError::EtiquetaEmpty`],
1268    /// non-chart-keyword-shape rejected through
1269    /// [`ManifestError::EtiquetaInvalid`], cross-entry duplicate
1270    /// rejected through [`ManifestError::EtiquetaDuplicate`]) maps onto
1271    /// every load-bearing downstream consumer the substrate carries —
1272    /// the [`Self::validate_etiquetas`] universal-axis empty-per-entry
1273    /// + shape + duplicate gate at caixa-core/src/manifest.rs, the
1274    /// caixa-helm `build_chart_yaml` `keywords:` fold at
1275    /// caixa-helm/src/lib.rs that walks each entry into the rendered
1276    /// `Chart.yaml` `keywords:` array (chained with the
1277    /// [`crate::LAREIRA_CHART_KEYWORDS`] substrate-wide floor set and
1278    /// dedup'd through a `BTreeSet` at emit time), every future per-
1279    /// `Caixa` registry-facing renderer the CAIXA-SDLC §I roadmap
1280    /// acknowledges (the future `artifacthub.io/keywords` `Chart.yaml`
1281    /// annotation, the future per-cluster tag-notification overlay the
1282    /// M4 CR materializer resolves per-CR).
1283    ///
1284    /// Prior to this lift the `.etiquetas` field was accessed inline at
1285    /// two production sites — [`Self::validate_etiquetas`]'s `for
1286    /// etiqueta in &self.etiquetas` walk that gates every entry through
1287    /// [`ManifestError::EtiquetaEmpty`] / `EtiquetaInvalid` /
1288    /// `EtiquetaDuplicate`, and the caixa-helm `build_chart_yaml`
1289    /// `caixa.etiquetas.iter().cloned().chain(...)` fold that
1290    /// materializes every entry into a `Chart.yaml` `keywords:` row —
1291    /// two open-coded field-accesses that expressed no compile-time
1292    /// link back to the typed slot. A future extension of the
1293    /// `:etiquetas` axis to a richer tag surface — a per-`:etiquetas`
1294    /// structured `ChartKeyword { name, uri, category }` at the storage
1295    /// layer once the substrate absorbs `artifacthub.io/keywords`
1296    /// richer tag tuple, a per-registry `:etiquetas` allowlist the M4
1297    /// CR materializer enforces per-CR (the "cluster policy demands
1298    /// every tag come from a substrate-approved taxonomy" arm), a
1299    /// promotion of the plain `Vec<String>` byte-string list to a
1300    /// richer `Vec<ChartKeyword>` newtype discriminated on the DNS-
1301    /// 1123-label-shaped grammar the `is_chart_keyword_shape` predicate
1302    /// already resolves through — would have had to be threaded through
1303    /// both open-coded copies in lockstep or the validate gate and the
1304    /// caixa-helm emit path would silently disagree on which tags a
1305    /// given [`Caixa`] resolves to (an author's `:etiquetas ("demo"
1306    /// "aplicacao")` would satisfy validate while the caixa-helm emit
1307    /// path silently rendered a drifted other keyword list, or vice
1308    /// versa). Lifting the resolution to a typed method on the
1309    /// substrate primitive means every downstream consumer of the
1310    /// caixa's per-`Caixa` topical-tag surface reaches for exactly one
1311    /// typed dispatch — the resolver's accept-set migrates as a unit
1312    /// on any future axis addition.
1313    ///
1314    /// Second outer top-level [`Caixa`] `&[T]`-return slice-accessor —
1315    /// folds on the "outer [`Caixa`] `&[T]` slice" projection pattern
1316    /// [`Self::autores`] (b5d813f) opened, sibling in shape and
1317    /// idiom. The remaining unlifted outer-`Caixa` slice-carrying axes
1318    /// (`:deps` / `:deps-dev` / `:exe` / `:bibliotecas` / `:servicos`
1319    /// / `:upgrade-from` / `:children` / `:membros` / `:contratos`)
1320    /// fold onto the same pattern in future lifts. Sibling in shape to
1321    /// the peer per-`:supervisor`
1322    /// [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
1323    /// per-`:placement` [`crate::aplicacao::Placement::clusters`]
1324    /// (a6e18d7), per-`:membros`
1325    /// [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
1326    /// per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
1327    /// (0dcc926), and per-`:upgrade-from :instructions`
1328    /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
1329    /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
1330    /// typed-slot list axes, extended here to the outer top-level
1331    /// [`Caixa`] universal-axis surface. Returns `&[String]` (not
1332    /// `&Vec<String>`) because every downstream consumer of the tag
1333    /// list treats it as a read-only sequence — the slice-view is the
1334    /// narrowest borrow that supports every present + roadmapped
1335    /// consumer (`.iter()`, `.len()`, `.is_empty()`) without leaking
1336    /// the backing `Vec`'s grow/push/reserve surface no consumer of
1337    /// the typed view reaches for (the storage-side `Vec` remains
1338    /// reachable through the `pub etiquetas` field for the mutation-
1339    /// carrying serde round-trip and per-test fixture-mutation paths).
1340    /// Named `etiquetas()` to match the storage field's name; the
1341    /// accessor's identity maps onto the canonical CAIXA-SDLC §I
1342    /// vocabulary the slot's docstring already carries.
1343    #[must_use]
1344    pub fn etiquetas(&self) -> &[String] {
1345        self.etiquetas.as_slice()
1346    }
1347
1348    /// Substrate-canonical per-`Caixa` `:bibliotecas` universal-axis
1349    /// library-source-path-list slice-accessor every consumer of the
1350    /// top-level manifest's Biblioteca-source axis keys off — returns
1351    /// the author-declared `:bibliotecas` list verbatim as a
1352    /// `&[String]` slice-view over the same backing buffer the raw
1353    /// `self.bibliotecas.as_slice()` field access borrows from. Empty-
1354    /// list-carrying (`:bibliotecas` is a default-empty axis every
1355    /// `defcaixa` form supplies with an empty `()` when unset; the
1356    /// [`Self::from_lisp`] derive folds an omitted `:bibliotecas`
1357    /// through `#[serde(default)]` to `Vec::new()`, so a `Caixa` past
1358    /// parse definitionally carries a `Vec<String>` slot — possibly
1359    /// empty — and the returned `&[String]` degenerates to an empty
1360    /// slice on that arm without any silent `None` collapse).
1361    ///
1362    /// The `:bibliotecas` slot carries the universal-axis lisp-library
1363    /// entry-path list every `:kind Biblioteca` caixa emits under
1364    /// (CAIXA-SDLC §I — the author-facing surface every `defcaixa`
1365    /// form supplies alongside `:nome` / `:versao` / `:kind`; the
1366    /// substrate-wide library-carrier axis every downstream
1367    /// authoring-facing consumer keys off) — the typed slot's
1368    /// `Vec<String>` accept-set (empty-per-entry rejected through
1369    /// [`ManifestError::CodePathEmpty { slot: ":bibliotecas" }`],
1370    /// non-sandboxed-relative-shape rejected through
1371    /// [`ManifestError::CodePathShape`], non-`.lisp`-extension rejected
1372    /// through [`ManifestError::CodePathNonLispExtension`], cross-entry
1373    /// duplicate rejected through [`ManifestError::CodePathDuplicate`])
1374    /// maps onto every load-bearing downstream consumer the substrate
1375    /// carries — the [`crate::LayoutInvariants`] Biblioteca-arm
1376    /// empty-check + per-entry file-exists loop at
1377    /// caixa-core/src/layout.rs that gates each entry through
1378    /// [`crate::LayoutError::MissingLib`] / `MissingEntry`, the
1379    /// [`Self::validate_code_paths`] per-slot shape gate at
1380    /// caixa-core/src/manifest.rs that walks each entry through the
1381    /// sandbox-relative / `.lisp`-extension / cross-entry duplicate
1382    /// gates, the `feira build` per-entry `tatara_lisp::read` parse
1383    /// walk at caixa-feira/src/cmd/build.rs that phase-1-checks each
1384    /// declared library file for lexical / structural errors before
1385    /// downstream `importar` resolution, every future per-`Caixa`
1386    /// library-facing renderer the CAIXA-SDLC §I roadmap acknowledges
1387    /// (the future `tatara-lispc` compilation entry the docstring at
1388    /// caixa-feira/src/cmd/build.rs alludes to, the future per-cluster
1389    /// bytecode-caching overlay the M4 CR materializer resolves per-CR,
1390    /// the future `caixa-lsp` per-library semantic-token stream the
1391    /// caixa-lsp docstring roadmaps).
1392    ///
1393    /// Prior to this lift the `.bibliotecas` field was accessed inline
1394    /// at three production sites — [`crate::LayoutInvariants`]'s
1395    /// `caixa.bibliotecas.is_empty()` `MissingLib`-arm gate + `for p
1396    /// in &caixa.bibliotecas` `MissingEntry` walk that gates each
1397    /// declared library path through the on-disk-existence check,
1398    /// the compound-code-path `has_code = !caixa.bibliotecas.is_empty()
1399    /// || !caixa.exe.is_empty() || !caixa.servicos.is_empty()` OR-fold
1400    /// on the [`crate::LayoutError::SupervisorOwnsCode`] /
1401    /// `AplicacaoOwnsCode` kind-coherence gate, and the `feira build`
1402    /// per-entry `for entry in &caixa.bibliotecas` + `caixa.bibliotecas.
1403    /// len()` phase-1 tatara-lispc-precursor parse walk — three open-
1404    /// coded field-accesses that expressed no compile-time link back
1405    /// to the typed slot. A future extension of the `:bibliotecas`
1406    /// axis to a richer library surface — a per-`:bibliotecas`
1407    /// structured `BibliotecaEntry { path, edition, exports }` at the
1408    /// storage layer once the substrate absorbs the per-library
1409    /// language-edition + explicit-exports tuple the tatara-lisp
1410    /// module-system roadmap acknowledges, a per-registry
1411    /// `:bibliotecas` allowlist the M4 CR materializer enforces
1412    /// per-CR (the "cluster policy demands every biblioteca declare
1413    /// its own :edicao" arm), a promotion of the plain `Vec<String>`
1414    /// byte-string list to a richer `Vec<LibraryPath>` newtype
1415    /// discriminated on the `lib/<nome>.lisp`-shape grammar the
1416    /// [`crate::render::is_sandboxed_relative_path`] +
1417    /// [`crate::render::is_lisp_extension`] predicates already resolve
1418    /// through — would have had to be threaded through all three
1419    /// open-coded copies in lockstep or the layout gate, the shape
1420    /// validator, and the `feira build` phase-1 parse walk would
1421    /// silently disagree on which library paths a given [`Caixa`]
1422    /// resolves to (an author's `:bibliotecas ("lib/foo.lisp"
1423    /// "lib/bar.lisp")` would satisfy layout while `feira build`
1424    /// silently parsed a drifted other list, or vice versa). Lifting
1425    /// the resolution to a typed method on the substrate primitive
1426    /// means every downstream consumer of the caixa's per-`Caixa`
1427    /// library-source surface reaches for exactly one typed dispatch
1428    /// — the resolver's accept-set migrates as a unit on any future
1429    /// axis addition.
1430    ///
1431    /// Third outer top-level [`Caixa`] `&[T]`-return slice-accessor —
1432    /// folds on the "outer [`Caixa`] `&[T]` slice" projection pattern
1433    /// [`Self::autores`] (b5d813f) opened and [`Self::etiquetas`]
1434    /// (78c7d3c) folded on, sibling in shape and idiom. The remaining
1435    /// unlifted outer-`Caixa` slice-carrying axes (`:deps` /
1436    /// `:deps-dev` / `:exe` / `:servicos` / `:upgrade-from` /
1437    /// `:children` / `:membros` / `:contratos`) fold onto the same
1438    /// pattern in future lifts. Sibling in shape to the peer
1439    /// per-`:supervisor`
1440    /// [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
1441    /// per-`:placement` [`crate::aplicacao::Placement::clusters`]
1442    /// (a6e18d7), per-`:membros`
1443    /// [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
1444    /// per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
1445    /// (0dcc926), and per-`:upgrade-from :instructions`
1446    /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
1447    /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
1448    /// typed-slot list axes, extended here to the outer top-level
1449    /// [`Caixa`] universal-axis surface. Returns `&[String]` (not
1450    /// `&Vec<String>`) because every downstream consumer of the
1451    /// library-source list treats it as a read-only sequence — the
1452    /// slice-view is the narrowest borrow that supports every
1453    /// present + roadmapped consumer (`.iter()`, `.len()`,
1454    /// `.is_empty()`) without leaking the backing `Vec`'s
1455    /// grow/push/reserve surface no consumer of the typed view
1456    /// reaches for (the storage-side `Vec` remains reachable through
1457    /// the `pub bibliotecas` field for the mutation-carrying serde
1458    /// round-trip and per-test fixture-mutation paths). Named
1459    /// `bibliotecas()` to match the storage field's name; the
1460    /// accessor's identity maps onto the canonical CAIXA-SDLC §I
1461    /// vocabulary the slot's docstring already carries.
1462    #[must_use]
1463    pub fn bibliotecas(&self) -> &[String] {
1464        self.bibliotecas.as_slice()
1465    }
1466
1467    /// Substrate-canonical per-`Caixa` `:exe` universal-axis
1468    /// nix-built-executable-entry-path-list slice-accessor every consumer
1469    /// of the top-level manifest's Binario-executable axis keys off —
1470    /// returns the author-declared `:exe` list verbatim as a `&[String]`
1471    /// slice-view over the same backing buffer the raw
1472    /// `self.exe.as_slice()` field access borrows from. Empty-list-
1473    /// carrying (`:exe` is a default-empty axis every `defcaixa` form
1474    /// supplies with an empty `()` when unset; the [`Self::from_lisp`]
1475    /// derive folds an omitted `:exe` through `#[serde(default)]` to
1476    /// `Vec::new()`, so a `Caixa` past parse definitionally carries a
1477    /// `Vec<String>` slot — possibly empty — and the returned `&[String]`
1478    /// degenerates to an empty slice on that arm without any silent
1479    /// `None` collapse).
1480    ///
1481    /// The `:exe` slot carries the universal-axis nix-built executable
1482    /// entry-path list every `:kind Binario` caixa emits under
1483    /// (CAIXA-SDLC §I — the author-facing surface every `defcaixa`
1484    /// form supplies alongside `:nome` / `:versao` / `:kind`; the
1485    /// substrate-wide `exe/`-directory-fenced entry-carrier axis every
1486    /// downstream flake-build-facing consumer keys off) — the typed
1487    /// slot's `Vec<String>` accept-set (empty-per-entry rejected
1488    /// through [`ManifestError::CodePathEmpty { slot: ":exe" }`],
1489    /// non-sandboxed-relative-shape rejected through
1490    /// [`ManifestError::CodePathShape`], cross-entry duplicate rejected
1491    /// through [`ManifestError::CodePathDuplicate`], out-of-`exe/`-
1492    /// directory paths rejected past the layout's
1493    /// [`crate::LayoutError::ExeOutsideDir`] `starts_with` fence) maps
1494    /// onto every load-bearing downstream consumer the substrate carries
1495    /// — the [`crate::LayoutInvariants`] Binario-arm empty-check +
1496    /// per-entry file-exists + `exe/`-directory-fence loop at
1497    /// caixa-core/src/layout.rs that gates each entry through
1498    /// [`crate::LayoutError::BinarioWithoutExe`] / `MissingEntry` /
1499    /// `ExeOutsideDir`, the compound `has_code` OR-fold on the
1500    /// [`crate::LayoutError::SupervisorOwnsCode`] /
1501    /// [`crate::LayoutError::AplicacaoOwnsCode`] kind-coherence gate
1502    /// that fences code-surface slots off from the two no-code kinds,
1503    /// [`Self::declared_foreign_code_slots`]'s `!self.exe.is_empty()`
1504    /// arm on the [`crate::LayoutError::ForeignCodeSlot`] gate that
1505    /// fences the `:exe` code surface off from every non-Binario code-
1506    /// running kind, [`Self::validate_code_paths`]'s per-slot shape gate
1507    /// that walks each entry through the sandbox-relative / cross-entry
1508    /// duplicate gates, every future per-`Caixa` executable-facing
1509    /// renderer the CAIXA-SDLC §I roadmap acknowledges (the future
1510    /// `caixa-flake` per-Binario `packages.<system>.<nome>` derivation
1511    /// entry the caixa-flake docstring roadmaps, the future per-cluster
1512    /// `nix-store` overlay the M4 CR materializer resolves per-CR, the
1513    /// future `feira nix` per-executable Binario-target emit path).
1514    ///
1515    /// Prior to this lift the `.exe` field was accessed inline at three
1516    /// production sites — the compound-code-path `has_code =
1517    /// !caixa.bibliotecas().is_empty() || !caixa.exe.is_empty() ||
1518    /// !caixa.servicos.is_empty()` OR-fold on the
1519    /// [`crate::LayoutError::SupervisorOwnsCode`] /
1520    /// `AplicacaoOwnsCode` kind-coherence gate, the Binario-arm
1521    /// `caixa.exe.is_empty()` [`crate::LayoutError::BinarioWithoutExe`]
1522    /// gate, the per-entry `for p in &caixa.exe`
1523    /// `MissingEntry`/`ExeOutsideDir` walk, and the
1524    /// [`Self::declared_foreign_code_slots`]'s
1525    /// `!self.exe.is_empty()` arm on the `ForeignCodeSlot` gate — four
1526    /// open-coded field-accesses that expressed no compile-time link
1527    /// back to the typed slot. A future extension of the `:exe` axis
1528    /// to a richer executable surface — a per-`:exe` structured
1529    /// `BinarioEntry { path, wrapper, capabilities }` at the storage
1530    /// layer once the substrate absorbs the per-executable
1531    /// nix-wrapper + linux-capabilities tuple the CAIXA-SDLC §I
1532    /// executable roadmap acknowledges, a per-registry `:exe` allowlist
1533    /// the M4 CR materializer enforces per-CR (the "cluster policy
1534    /// demands every Binario declare an explicit `:wrapper`" arm), a
1535    /// promotion of the plain `Vec<String>` byte-string list to a
1536    /// richer `Vec<ExecutablePath>` newtype discriminated on the
1537    /// `exe/<nome>`-shape grammar the layout's `starts_with(exe_dir)`
1538    /// fence already resolves through — would have had to be threaded
1539    /// through all four open-coded copies in lockstep or the layout
1540    /// gate, the shape validator, and the `feira nix` emit path would
1541    /// silently disagree on which executable paths a given [`Caixa`]
1542    /// resolves to (an author's `:exe ("exe/cli" "exe/serve")` would
1543    /// satisfy layout while `feira nix` silently packaged a drifted
1544    /// other list, or vice versa). Lifting the resolution to a typed
1545    /// method on the substrate primitive means every downstream
1546    /// consumer of the caixa's per-`Caixa` executable-source surface
1547    /// reaches for exactly one typed dispatch — the resolver's accept-
1548    /// set migrates as a unit on any future axis addition.
1549    ///
1550    /// Fourth outer top-level [`Caixa`] `&[T]`-return slice-accessor —
1551    /// folds on the "outer [`Caixa`] `&[T]` slice" projection pattern
1552    /// [`Self::autores`] (b5d813f) opened, [`Self::etiquetas`]
1553    /// (78c7d3c) folded on, and [`Self::bibliotecas`] (8a36c23) closed
1554    /// the universal-axis text-tag family of. Opens the outer-`Caixa`
1555    /// foreign-code-slot `&[T]` sub-family the sibling `:servicos`
1556    /// future lift closes onto (per the trio of code-surface list slots
1557    /// the [`Self::validate_code_paths`] per-slot dispatch tuple
1558    /// already carries — `:bibliotecas` + `:exe` + `:servicos`, of which
1559    /// `:bibliotecas` landed at 8a36c23 and `:servicos` remains as the
1560    /// last unlifted code-surface slot). Sibling in shape to the peer
1561    /// per-`:supervisor` [`crate::supervisor::SupervisorSpec::children`]
1562    /// (bc92bce), per-`:placement`
1563    /// [`crate::aplicacao::Placement::clusters`] (a6e18d7),
1564    /// per-`:membros` [`crate::aplicacao::AplicacaoSpec::membros`]
1565    /// (6c77e36), per-`:contratos`
1566    /// [`crate::aplicacao::AplicacaoSpec::contratos`] (0dcc926), and
1567    /// per-`:upgrade-from :instructions`
1568    /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
1569    /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
1570    /// typed-slot list axes, extended here to the outer top-level
1571    /// [`Caixa`] universal-axis surface. Returns `&[String]` (not
1572    /// `&Vec<String>`) because every downstream consumer of the
1573    /// executable-source list treats it as a read-only sequence — the
1574    /// slice-view is the narrowest borrow that supports every
1575    /// present + roadmapped consumer (`.iter()`, `.len()`,
1576    /// `.is_empty()`) without leaking the backing `Vec`'s
1577    /// grow/push/reserve surface no consumer of the typed view
1578    /// reaches for (the storage-side `Vec` remains reachable through
1579    /// the `pub exe` field for the mutation-carrying serde
1580    /// round-trip and per-test fixture-mutation paths). Named `exe()`
1581    /// to match the storage field's name; the accessor's identity
1582    /// maps onto the canonical CAIXA-SDLC §I vocabulary the slot's
1583    /// docstring already carries.
1584    #[must_use]
1585    pub fn exe(&self) -> &[String] {
1586        self.exe.as_slice()
1587    }
1588
1589    /// Substrate-canonical per-`Caixa` `:servicos` universal-axis
1590    /// ComputeUnit-CR-YAML-entry-path-list slice-accessor every consumer
1591    /// of the top-level manifest's Servico-component axis keys off —
1592    /// returns the author-declared `:servicos` list verbatim as a
1593    /// `&[String]` slice-view over the same backing buffer the raw
1594    /// `self.servicos.as_slice()` field access borrows from. Empty-list-
1595    /// carrying (`:servicos` is a default-empty axis every `defcaixa`
1596    /// form supplies with an empty `()` when unset; the
1597    /// [`Self::from_lisp`] derive folds an omitted `:servicos` through
1598    /// `#[serde(default)]` to `Vec::new()`, so a `Caixa` past parse
1599    /// definitionally carries a `Vec<String>` slot — possibly empty —
1600    /// and the returned `&[String]` degenerates to an empty slice on
1601    /// that arm without any silent `None` collapse).
1602    ///
1603    /// The `:servicos` slot carries the universal-axis
1604    /// `.computeunit.yaml` ComputeUnit-CR entry-path list every
1605    /// `:kind Servico` caixa emits under (CAIXA-SDLC §I — the
1606    /// author-facing surface every `defcaixa` form supplies alongside
1607    /// `:nome` / `:versao` / `:kind`; the substrate-wide
1608    /// `servicos/`-directory-fenced entry-carrier axis every downstream
1609    /// Servico-facing renderer keys off) — the typed slot's
1610    /// `Vec<String>` accept-set (empty-per-entry rejected through
1611    /// [`ManifestError::CodePathEmpty { slot: ":servicos" }`],
1612    /// non-sandboxed-relative-shape rejected through
1613    /// [`ManifestError::CodePathShape`], non-`.computeunit.yaml`
1614    /// extension rejected through
1615    /// [`ManifestError::CodePathNonComputeUnitYamlExtension`], cross-
1616    /// entry duplicate rejected through
1617    /// [`ManifestError::CodePathDuplicate`], `len != 1` rejected by the
1618    /// V0 [`crate::ServicoCountMismatch`] gate on the per-Servico
1619    /// renderer entry-points, out-of-`servicos/`-directory paths
1620    /// rejected past the layout's [`crate::LayoutError::ServicoOutsideDir`]
1621    /// `starts_with` fence) maps onto every load-bearing downstream
1622    /// consumer the substrate carries — the [`crate::LayoutInvariants`]
1623    /// Servico-arm empty-check + per-entry file-exists + `servicos/`-
1624    /// directory-fence loop at caixa-core/src/layout.rs that gates each
1625    /// entry through [`crate::LayoutError::ServicoWithoutServicos`] /
1626    /// `MissingEntry` / `ServicoOutsideDir`, the compound `has_code`
1627    /// OR-fold on the [`crate::LayoutError::SupervisorOwnsCode`] /
1628    /// [`crate::LayoutError::AplicacaoOwnsCode`] kind-coherence gate
1629    /// that fences code-surface slots off from the two no-code kinds,
1630    /// [`Self::declared_foreign_code_slots`]'s
1631    /// `!self.servicos.is_empty()` arm on the
1632    /// [`crate::LayoutError::ForeignCodeSlot`] gate that fences the
1633    /// `:servicos` code surface off from every non-Servico code-running
1634    /// kind, [`Self::validate_code_paths`]'s per-slot shape gate that
1635    /// walks each entry through the sandbox-relative / `.computeunit.
1636    /// yaml`-extension / cross-entry duplicate gates, the
1637    /// [`crate::require_single_servico`] V0 singularity gate every
1638    /// per-Servico renderer entry-point runs through
1639    /// [`crate::require_v0_servico_shape`], the `feira chart` /
1640    /// `feira deploy` per-verb `first_servico_path` walk at
1641    /// caixa-feira/src/cmd/chart.rs that resolves the singleton
1642    /// ComputeUnit-CR file, every future per-`Caixa` Servico-facing
1643    /// renderer the CAIXA-SDLC §I roadmap acknowledges (the future
1644    /// per-Servico OCI packager, the future M4
1645    /// `wasm.pleme.io/v1alpha1/ComputeUnit` CR materializer, the future
1646    /// per-Servico OTel collector-config emit).
1647    ///
1648    /// Prior to this lift the `.servicos` field was accessed inline at
1649    /// five production sites — the compound-code-path `has_code =
1650    /// !caixa.bibliotecas().is_empty() || !caixa.exe().is_empty() ||
1651    /// !caixa.servicos.is_empty()` OR-fold on the
1652    /// [`crate::LayoutError::SupervisorOwnsCode`] /
1653    /// `AplicacaoOwnsCode` kind-coherence gate, the Servico-arm
1654    /// `caixa.servicos.is_empty()`
1655    /// [`crate::LayoutError::ServicoWithoutServicos`] gate, the
1656    /// per-entry `for p in &caixa.servicos`
1657    /// `MissingEntry`/`ServicoOutsideDir` walk, the
1658    /// [`Self::declared_foreign_code_slots`]'s
1659    /// `!self.servicos.is_empty()` arm on the `ForeignCodeSlot` gate,
1660    /// and the [`crate::require_single_servico`] V0 count gate's
1661    /// `caixa.servicos.len() == 1` / `caixa.servicos.len()` count
1662    /// projection (both the accept-arm predicate and the
1663    /// diagnostic-carrying `ServicoCountMismatch { count }`
1664    /// projection) — five open-coded field-accesses across three
1665    /// crates that expressed no compile-time link back to the typed
1666    /// slot. A future extension of the `:servicos` axis to a richer
1667    /// component surface — a per-`:servicos` structured
1668    /// `ServicoEntry { path, world, capabilities }` at the storage
1669    /// layer once the substrate absorbs the per-component WIT-world +
1670    /// capability-set tuple the CAIXA-SDLC §I Servico roadmap
1671    /// acknowledges, a per-registry `:servicos` allowlist the M4 CR
1672    /// materializer enforces per-CR (the "cluster policy demands every
1673    /// Servico declare an explicit `:world`" arm), a promotion of the
1674    /// plain `Vec<String>` byte-string list to a richer
1675    /// `Vec<ComputeUnitPath>` newtype discriminated on the
1676    /// `servicos/<nome>.computeunit.yaml`-shape grammar the layout's
1677    /// `starts_with(servicos_dir)` fence and the
1678    /// [`crate::render::is_computeunit_yaml_extension`] predicate
1679    /// already resolve through, a promotion of the V0 singleton
1680    /// contract to a multi-component `Vec<ComputeUnitPath>` past the M5
1681    /// component-model multi-world boundary — would have had to be
1682    /// threaded through all five open-coded copies in lockstep or the
1683    /// layout gate, the shape validator, the V0 count gate, and the
1684    /// `feira chart` / `feira deploy` entry-point walks would silently
1685    /// disagree on which ComputeUnit-CR paths a given [`Caixa`]
1686    /// resolves to (an author's `:servicos ("servicos/foo.computeunit.
1687    /// yaml")` would satisfy layout while `feira chart` silently
1688    /// packaged a drifted other list, or vice versa). Lifting the
1689    /// resolution to a typed method on the substrate primitive means
1690    /// every downstream consumer of the caixa's per-`Caixa`
1691    /// ComputeUnit-CR-source surface reaches for exactly one typed
1692    /// dispatch — the resolver's accept-set migrates as a unit on any
1693    /// future axis addition.
1694    ///
1695    /// Fifth and final outer top-level [`Caixa`] `&[T]`-return slice-
1696    /// accessor — folds on the "outer [`Caixa`] `&[T]` slice"
1697    /// projection pattern [`Self::autores`] (b5d813f) opened,
1698    /// [`Self::etiquetas`] (78c7d3c) folded on, [`Self::bibliotecas`]
1699    /// (8a36c23) closed the universal-axis text-tag family of, and
1700    /// [`Self::exe`] (65d9527) opened the foreign-code-slot sub-family
1701    /// of. Closes the outer-`Caixa` foreign-code-slot `&[T]` sub-family
1702    /// — with `:bibliotecas`, `:exe`, and `:servicos` now each carrying
1703    /// a substrate-canonical slice accessor, the trio of code-surface
1704    /// list slots the [`Self::validate_code_paths`] per-slot dispatch
1705    /// tuple carries is complete on the typed dispatch surface (the
1706    /// internal `[(":bibliotecas", &self.bibliotecas, ..), (":exe",
1707    /// &self.exe, ..), (":servicos", &self.servicos, ..)]` per-slot
1708    /// dispatch tuple's homogeneous `&Vec<String>`-typed shape blocks a
1709    /// per-element accessor swap in isolation — a future companion lift
1710    /// promotes the tuple's element type to `&[String]` and threads the
1711    /// triple of typed dispatches through as a unit). Sibling in shape
1712    /// to the peer per-`:supervisor`
1713    /// [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
1714    /// per-`:placement` [`crate::aplicacao::Placement::clusters`]
1715    /// (a6e18d7), per-`:membros`
1716    /// [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
1717    /// per-`:contratos`
1718    /// [`crate::aplicacao::AplicacaoSpec::contratos`] (0dcc926), and
1719    /// per-`:upgrade-from :instructions`
1720    /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
1721    /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
1722    /// typed-slot list axes, extended here to the outer top-level
1723    /// [`Caixa`] universal-axis surface. Returns `&[String]` (not
1724    /// `&Vec<String>`) because every downstream consumer of the
1725    /// ComputeUnit-CR-source list treats it as a read-only sequence —
1726    /// the slice-view is the narrowest borrow that supports every
1727    /// present + roadmapped consumer (`.iter()`, `.len()`,
1728    /// `.is_empty()`, `.first()`) without leaking the backing `Vec`'s
1729    /// grow/push/reserve surface no consumer of the typed view reaches
1730    /// for (the storage-side `Vec` remains reachable through the
1731    /// `pub servicos` field for the mutation-carrying serde round-trip
1732    /// and per-test fixture-mutation paths, and for the
1733    /// [`Self::validate_code_paths`] per-slot dispatch tuple whose
1734    /// homogeneous-element-type shape carries the raw field access
1735    /// until the trio-closure lift promotes the tuple as a unit).
1736    /// Named `servicos()` to match the storage field's name; the
1737    /// accessor's identity maps onto the canonical CAIXA-SDLC §I
1738    /// vocabulary the slot's docstring already carries.
1739    #[must_use]
1740    pub fn servicos(&self) -> &[String] {
1741        self.servicos.as_slice()
1742    }
1743
1744    /// Substrate-canonical per-`Caixa` `:deps` universal-axis
1745    /// runtime-dependency-declaration-list slice-accessor every consumer
1746    /// of the top-level manifest's runtime-dep-graph axis keys off —
1747    /// returns the author-declared `:deps` list verbatim as a `&[Dep]`
1748    /// slice-view over the same backing buffer the raw
1749    /// `self.deps.as_slice()` field access borrows from. Empty-list-
1750    /// carrying (`:deps` is a default-empty axis every `defcaixa` form
1751    /// supplies with an empty `()` when unset; the [`Self::from_lisp`]
1752    /// derive folds an omitted `:deps` through `#[serde(default)]` to
1753    /// `Vec::new()`, so a `Caixa` past parse definitionally carries a
1754    /// `Vec<Dep>` slot — possibly empty — and the returned `&[Dep]`
1755    /// degenerates to an empty slice on that arm without any silent
1756    /// `None` collapse).
1757    ///
1758    /// The `:deps` slot carries the universal-axis runtime dependency
1759    /// list every kind of caixa emits under (CAIXA-SDLC §I — the author-
1760    /// facing surface every `defcaixa` form supplies alongside `:nome` /
1761    /// `:versao` / `:kind`; the substrate-wide runtime-closure-input axis
1762    /// every downstream resolver-facing artifact emits under) — the
1763    /// typed slot's `Vec<Dep>` accept-set (empty-`:nome` rejected through
1764    /// [`DepError::NomeEmpty`], non-DNS-1123-label `:nome` rejected
1765    /// through [`DepError::NomeInvalid`], malformed `:versao` rejected
1766    /// through [`DepError::VersaoInvalid`], empty `:fonte.repo` rejected
1767    /// through [`DepError::FonteRepoEmpty`], within-list duplicate `:nome`
1768    /// rejected through [`DepError::DuplicateNome { list: ":deps" }`])
1769    /// maps onto every load-bearing downstream consumer the substrate
1770    /// carries — the [`Self::validate_deps`] per-entry
1771    /// [`Dep::validate`] + within-list dedup walk at
1772    /// caixa-core/src/manifest.rs, the [`crate::dep::validate_no_self_dep`]
1773    /// cross-list self-reference gate at caixa-core/src/layout.rs that
1774    /// checks each entry against the caixa's own `:nome`, the
1775    /// caixa-resolver `for dep in &root.deps` closure walk at
1776    /// caixa-resolver/src/resolve.rs that seeds every git-clone target
1777    /// through the resolver's [`crate::Dep`]-keyed pipeline, the
1778    /// caixa-crd `caixa.deps.iter().map(dep_into_ref).collect()` fold at
1779    /// caixa-crd/src/conversion.rs that materializes each entry into the
1780    /// K8s `Caixa` CR's `spec.deps` field, every future per-`Caixa`
1781    /// resolver-facing renderer the CAIXA-SDLC §I roadmap acknowledges
1782    /// (the future per-cluster runtime-closure-audit overlay the M4 CR
1783    /// materializer resolves per-CR, the future `lacre.lisp` BLAKE3-
1784    /// closure emit walk the caixa-resolver docstring roadmaps).
1785    ///
1786    /// First outer top-level [`Caixa`] `&[Dep]`-return slice-accessor —
1787    /// opens the outer-`Caixa` dependency-slot `&[Dep]` sub-family the
1788    /// sibling `:deps-dev` future lift closes on. Peer of the closed
1789    /// outer-`Caixa` foreign-code-slot `&[String]` sub-family
1790    /// ([`Self::bibliotecas`] 8a36c23, [`Self::exe`] 65d9527,
1791    /// [`Self::servicos`] 611f78b) and the outer-`Caixa` universal-axis
1792    /// text-tag family ([`Self::autores`] b5d813f, [`Self::etiquetas`]
1793    /// 78c7d3c) — extends the "outer [`Caixa`] `&[T]` slice" projection
1794    /// pattern onto a novel element-type axis (`Dep` composite vs the
1795    /// prior sibling family's `String` scalar). Sibling in shape to the
1796    /// peer per-`:supervisor`
1797    /// [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
1798    /// per-`:placement` [`crate::aplicacao::Placement::clusters`]
1799    /// (a6e18d7), per-`:membros`
1800    /// [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
1801    /// per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
1802    /// (0dcc926), and per-`:upgrade-from :instructions`
1803    /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
1804    /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
1805    /// typed-slot list axes, extended here to the outer top-level
1806    /// [`Caixa`] universal-axis dep-graph surface. Returns `&[Dep]`
1807    /// (not `&Vec<Dep>`) because every downstream consumer of the
1808    /// runtime-dep list treats it as a read-only sequence — the slice-
1809    /// view is the narrowest borrow that supports every present +
1810    /// roadmapped consumer (`.iter()`, `.len()`, `.is_empty()`) without
1811    /// leaking the backing `Vec`'s grow/push/reserve surface no consumer
1812    /// of the typed view reaches for (the storage-side `Vec` remains
1813    /// reachable through the `pub deps` field for the mutation-carrying
1814    /// serde round-trip and per-test fixture-mutation paths). Named
1815    /// `deps()` to match the storage field's name; the accessor's
1816    /// identity maps onto the canonical CAIXA-SDLC §I vocabulary the
1817    /// slot's docstring already carries.
1818    #[must_use]
1819    pub fn deps(&self) -> &[Dep] {
1820        self.deps.as_slice()
1821    }
1822
1823    /// Substrate-canonical per-`Caixa` `:deps-dev` universal-axis
1824    /// development-only-dependency-declaration-list slice-accessor every
1825    /// consumer of the top-level manifest's dev-dep-graph axis keys off —
1826    /// returns the author-declared `:deps-dev` list verbatim as a `&[Dep]`
1827    /// slice-view over the same backing buffer the raw
1828    /// `self.deps_dev.as_slice()` field access borrows from. Empty-list-
1829    /// carrying (`:deps-dev` is a default-empty axis every `defcaixa`
1830    /// form supplies with an empty `()` when unset; the
1831    /// [`Self::from_lisp`] derive folds an omitted `:deps-dev` through
1832    /// `#[serde(default)]` to `Vec::new()`, so a `Caixa` past parse
1833    /// definitionally carries a `Vec<Dep>` slot — possibly empty — and
1834    /// the returned `&[Dep]` degenerates to an empty slice on that arm
1835    /// without any silent `None` collapse).
1836    ///
1837    /// The `:deps-dev` slot carries the universal-axis dev-only
1838    /// dependency list every kind of caixa emits under (CAIXA-SDLC §I —
1839    /// the author-facing sibling of `:deps` that every `defcaixa` form
1840    /// supplies to declare tests / lint / bench closures the runtime
1841    /// `:deps` axis does not carry; the substrate-wide dev-closure-input
1842    /// axis every downstream test-facing artifact emits under, matching
1843    /// Cargo's `[dev-dependencies]` table's dev-time-only visibility
1844    /// contract) — the typed slot's `Vec<Dep>` accept-set (empty-`:nome`
1845    /// rejected through [`DepError::NomeEmpty`], non-DNS-1123-label
1846    /// `:nome` rejected through [`DepError::NomeInvalid`], malformed
1847    /// `:versao` rejected through [`DepError::VersaoInvalid`], empty
1848    /// `:fonte.repo` rejected through [`DepError::FonteRepoEmpty`],
1849    /// within-list duplicate `:nome` rejected through
1850    /// [`DepError::DuplicateNome { list: ":deps-dev" }`]) maps onto every
1851    /// load-bearing downstream consumer the substrate carries — the
1852    /// [`Self::validate_deps`] per-entry [`Dep::validate`] + within-list
1853    /// dedup walk at caixa-core/src/manifest.rs, the
1854    /// [`crate::dep::validate_no_self_dep`] cross-list self-reference
1855    /// gate at caixa-core/src/layout.rs that checks each entry against
1856    /// the caixa's own `:nome`, the caixa-resolver
1857    /// `for dep in &root.deps_dev` closure walk at
1858    /// caixa-resolver/src/resolve.rs that seeds every dev-only git-clone
1859    /// target through the resolver's [`crate::Dep`]-keyed pipeline, and
1860    /// every future per-`Caixa` resolver-facing renderer the CAIXA-SDLC
1861    /// §I roadmap acknowledges (the future per-cluster dev-closure-audit
1862    /// overlay the M4 CR materializer resolves per-CR, the future
1863    /// `lacre.lisp` BLAKE3-closure emit walk the caixa-resolver docstring
1864    /// roadmaps).
1865    ///
1866    /// Second outer top-level [`Caixa`] `&[Dep]`-return slice-accessor —
1867    /// closes the outer-`Caixa` dependency-slot `&[Dep]` sub-family the
1868    /// sibling [`Self::deps`] (ad34b4e) opened on. The two accessors
1869    /// jointly close the two-list dep-graph surface every downstream
1870    /// resolver-facing consumer keys off (runtime `:deps` +
1871    /// dev-only `:deps-dev`, the canonical Cargo-shaped dependency-table
1872    /// pair the [`Self::validate_deps`] gate already walks in canonical
1873    /// order). Peer of the closed outer-`Caixa` foreign-code-slot
1874    /// `&[String]` sub-family ([`Self::bibliotecas`] 8a36c23,
1875    /// [`Self::exe`] 65d9527, [`Self::servicos`] 611f78b) and the outer-
1876    /// `Caixa` universal-axis text-tag family ([`Self::autores`]
1877    /// b5d813f, [`Self::etiquetas`] 78c7d3c) — folds the "outer
1878    /// [`Caixa`] `&[T]` slice" projection pattern onto the sibling
1879    /// dev-dep composite-element axis (`Dep` composite, matching the
1880    /// [`Self::deps`] element type). Sibling in shape to the peer
1881    /// per-`:supervisor` [`crate::supervisor::SupervisorSpec::children`]
1882    /// (bc92bce), per-`:placement`
1883    /// [`crate::aplicacao::Placement::clusters`] (a6e18d7),
1884    /// per-`:membros` [`crate::aplicacao::AplicacaoSpec::membros`]
1885    /// (6c77e36), per-`:contratos`
1886    /// [`crate::aplicacao::AplicacaoSpec::contratos`] (0dcc926), and
1887    /// per-`:upgrade-from :instructions`
1888    /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
1889    /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
1890    /// typed-slot list axes, folded here to the outer top-level
1891    /// [`Caixa`] universal-axis dev-dep-graph surface. Returns `&[Dep]`
1892    /// (not `&Vec<Dep>`) because every downstream consumer of the
1893    /// dev-dep list treats it as a read-only sequence — the slice-view
1894    /// is the narrowest borrow that supports every present +
1895    /// roadmapped consumer (`.iter()`, `.len()`, `.is_empty()`) without
1896    /// leaking the backing `Vec`'s grow/push/reserve surface no consumer
1897    /// of the typed view reaches for (the storage-side `Vec` remains
1898    /// reachable through the `pub deps_dev` field for the mutation-
1899    /// carrying serde round-trip and per-test fixture-mutation paths).
1900    /// Named `deps_dev()` to match the storage field's `snake_case` name;
1901    /// the kebab-case author-surface tag `:deps-dev` is the same axis
1902    /// after tatara-lisp's kebab↔snake fold and the accessor's identity
1903    /// maps onto the canonical CAIXA-SDLC §I vocabulary the slot's
1904    /// docstring already carries.
1905    #[must_use]
1906    pub fn deps_dev(&self) -> &[Dep] {
1907        self.deps_dev.as_slice()
1908    }
1909
1910    /// Substrate-canonical per-[`Caixa`] typed-dispatch read accessor
1911    /// every consumer that walks one of the two dep-list axes keyed on a
1912    /// [`crate::dep::DepList`] discriminant reaches for — routes the
1913    /// `(list: DepList) -> &[Dep]` projection through one typed method on
1914    /// the substrate primitive rather than the prior open-coded
1915    /// `match list { Prod => caixa.deps(), Dev => caixa.deps_dev() }`
1916    /// inline dispatch every per-axis walker would otherwise carry.
1917    /// Returns the author-declared per-list `Vec<Dep>` verbatim as a
1918    /// `&[Dep]` slice-view over the same backing buffer the sibling
1919    /// [`Self::deps`] (`Prod`) / [`Self::deps_dev`] (`Dev`) per-slot
1920    /// accessors borrow from, preserving the empty-list-carrying invariant
1921    /// each per-slot accessor already establishes (`:deps` / `:deps-dev`
1922    /// are default-empty axes every `defcaixa` form supplies with an empty
1923    /// `()` when unset; the [`Self::from_lisp`] derive folds an omitted
1924    /// list through `#[serde(default)]` to `Vec::new()`, so both arms
1925    /// definitionally carry a `Vec<Dep>` slot — possibly empty — and the
1926    /// returned `&[Dep]` degenerates to an empty slice on either arm
1927    /// without any silent `None` collapse).
1928    ///
1929    /// The [`crate::dep::DepList`] closed-set typed enum is the
1930    /// substrate's canonical discriminator for the "runtime-closure
1931    /// `:deps` vs dev-only-closure `:deps-dev`" axis every dep-list
1932    /// consumer dispatches on — the compiler-checked exhaustiveness on
1933    /// the enum's `match` arms is the build-time guarantee that no future
1934    /// per-list read-site regresses to a bare-`bool`-flag inline dispatch
1935    /// that a future third dep-list axis (a `:deps-build` build-only
1936    /// closure once the substrate grows cross-artifact heterogeneous
1937    /// dep-graphs, per CAIXA-SDLC §I) would silently split at every
1938    /// consumer. Prior to this the read side carried two per-slot
1939    /// accessors ([`Self::deps`] ad34b4e, [`Self::deps_dev`]) and no
1940    /// typed dispatch that a per-axis walker could parametrise on, so
1941    /// every per-list walker (the [`Self::validate_deps`] per-list
1942    /// [`crate::render::insert_first_seen`] dedup walk, a future
1943    /// `feira app graph` per-list dep summary, a future M4 per-cluster
1944    /// dev-closure-audit overlay the CR materializer resolves per-CR)
1945    /// open-coded the same two-block "run over `:deps`, then run over
1946    /// `:deps-dev`" pattern — a silent duplication that a future third
1947    /// dep-list axis would have had to grow a third block at every site.
1948    ///
1949    /// Peer of the sibling [`Self::push_dep`] typed-mutation dispatch
1950    /// (359fba5) — closes the two-side dispatch symmetry on the outer
1951    /// [`Caixa`] two-list dep-graph surface: `push_dep` on the mutation
1952    /// side, `deps_of` on the read side, both keyed on the same
1953    /// [`crate::dep::DepList`] discriminator. Same "one typed dispatch on
1954    /// the substrate primitive, thin projections at each consumer"
1955    /// discipline the sibling per-slot read accessors ([`Self::nome`]
1956    /// e6b7d97, [`Self::versao`], [`Self::kind`]) carry — extended onto
1957    /// the outer-[`Caixa`] typed-dispatch read surface.
1958    #[must_use]
1959    pub fn deps_of(&self, list: crate::dep::DepList) -> &[Dep] {
1960        match list {
1961            crate::dep::DepList::Prod => self.deps(),
1962            crate::dep::DepList::Dev => self.deps_dev(),
1963        }
1964    }
1965
1966    /// Substrate-canonical per-[`Caixa`] typed-mutation dispatch every
1967    /// consumer that appends to one of the two dep-list axes keys off
1968    /// — routes the `(list: DepList, dep: Dep)` tuple through one typed
1969    /// method on the substrate primitive rather than the prior
1970    /// `feira add`-side open-coded `if self.dev { &mut caixa.deps_dev }
1971    /// else { &mut caixa.deps }` inline dispatch + open-coded
1972    /// `.iter().any(|d| d.nome == …)` dup-check cascade. Refuses the
1973    /// mutation with the canonical typed [`DepError::DuplicateNome`] on
1974    /// a within-list name collision — the same `list: &'static str`
1975    /// diagnostic shape [`Self::validate_deps`]'s per-list
1976    /// [`crate::render::insert_first_seen`] walk raises on the peer
1977    /// parse-time within-list dedup axis, so a future author reading a
1978    /// `feira add` refusal and a `feira build` refusal reaches for the
1979    /// same corrective surface without switching diagnostic idioms.
1980    ///
1981    /// The two-arm [`crate::dep::DepList`] enum is the substrate's
1982    /// closed-set typed carrier for the "runtime-closure `:deps` vs
1983    /// dev-only-closure `:deps-dev`" axis every dep-list consumer
1984    /// dispatches on — the compiler-checked exhaustiveness on the
1985    /// enum's `match` arms is the build-time guarantee that no future
1986    /// per-list mutation-site regresses to a bare-`bool`-flag
1987    /// (`is_dev: bool`) inline dispatch that a future third
1988    /// dep-list axis (a `:deps-build` build-only closure once the
1989    /// substrate grows cross-artifact heterogeneous dep-graphs, per
1990    /// CAIXA-SDLC §I) would silently split at every consumer.
1991    ///
1992    /// Same "one typed dispatch on the substrate primitive, thin
1993    /// projections at each consumer" discipline the sibling per-slot
1994    /// read accessors ([`Self::deps`] ad34b4e, [`Self::deps_dev`],
1995    /// [`Self::nome`] e6b7d97, [`Self::versao`], [`Self::kind`])
1996    /// carry — extended onto the outer-[`Caixa`] typed-mutation surface,
1997    /// the substrate's first typed-mutation dispatch on the top-level
1998    /// manifest. The prior `feira add` open-coded `&mut caixa.deps` /
1999    /// `&mut caixa.deps_dev` inline field-access + `bail!` string-
2000    /// diagnostic path routed no through-line back to the typed slot,
2001    /// so a future extension of either dep-list axis to a richer author
2002    /// surface (a per-cluster override the operator pins through a
2003    /// future `:placement`-scoped dep-list slot the CAIXA-SDLC §I
2004    /// roadmap acknowledges, an M4
2005    /// `mesh.pleme.io/v1alpha1/Caixa` CR materializer's per-CR
2006    /// admission-webhook that normalized the list at admission time)
2007    /// would have had to be threaded through the `feira add` mutation
2008    /// site in lockstep with every read consumer or one path would
2009    /// silently disagree with the other on which list a given dep lands
2010    /// in. Lifting the resolution rule to a typed method on the
2011    /// substrate primitive means every downstream dep-list-mutating
2012    /// consumer of the top-level manifest reaches for exactly one typed
2013    /// dispatch — the resolver's accept-set migrates as a unit on any
2014    /// future axis addition.
2015    ///
2016    /// # Errors
2017    ///
2018    /// Returns [`DepError::DuplicateNome`] with `list = list.as_str()`
2019    /// when another entry in the same list already carries the same
2020    /// `:nome` — the mutation is refused and the caller can surface the
2021    /// typed diagnostic to the author (the `feira add` verb routes the
2022    /// error through `anyhow::Error::from`, which preserves the
2023    /// canonical `#[error(...)]`-templated diagnostic body).
2024    pub fn push_dep(&mut self, list: crate::dep::DepList, dep: Dep) -> Result<(), DepError> {
2025        let target = match list {
2026            crate::dep::DepList::Prod => &mut self.deps,
2027            crate::dep::DepList::Dev => &mut self.deps_dev,
2028        };
2029        if target.iter().any(|d| d.nome() == dep.nome()) {
2030            return Err(DepError::DuplicateNome {
2031                nome: dep.nome().to_string(),
2032                list: list.as_str(),
2033            });
2034        }
2035        target.push(dep);
2036        Ok(())
2037    }
2038
2039    /// Substrate-canonical per-`Caixa` `:limits` M2 typed-slot outer-
2040    /// composite Lunatic-per-process wasm32-sandboxing-composite optional-
2041    /// composite-reference accessor every consumer of the top-level
2042    /// manifest's per-Servico [`LimitsSpec`] outer-composite reader keys
2043    /// off — returns the author-declared `:limits` typed composite
2044    /// verbatim as an `Option<&LimitsSpec>` reference over the same
2045    /// backing storage the raw `self.limits.as_ref()` field access
2046    /// borrows from, with `None` naming the "no `:limits` block
2047    /// authored — every per-axis Lunatic-sandbox cap defers to the
2048    /// wasm-engine-default arm named on the per-axis
2049    /// [`LimitsSpec::memory`] / [`LimitsSpec::fuel`] /
2050    /// [`LimitsSpec::wall_clock`] / [`LimitsSpec::cpu`] scalar-accessor
2051    /// docstrings" partition every downstream Servico-M2-overlay
2052    /// emitter treats as "emit nothing" and the sibling
2053    /// [`crate::StandardLayout::verify`] per-`:limits` shape gate
2054    /// treats as "skip the per-axis
2055    /// [`crate::LimitsError::MemoryZero`] / `MemoryBelowWasm32Page` /
2056    /// `FuelZero` / `WallClockZero` / `CpuZero` refusal cascade".
2057    ///
2058    /// The outer `:limits` slot carries the M2 Servico-runtime typed
2059    /// composite — the load-bearing container of every Lunatic-shaped
2060    /// per-process wasm32-sandbox cap axis every long-running wasm
2061    /// component's runtime dispatches on (INSPIRATIONS §III.1 —
2062    /// Lunatic per-process linear-memory / fuel / wall-clock /
2063    /// millicore cap primitives translated onto pleme-io's typed
2064    /// `:limits :memory` / `:limits :fuel` / `:limits :wall-clock` /
2065    /// `:limits :cpu` sub-slot axes; CAIXA-SDLC §II — the typed-M2
2066    /// slot algebra the wasm-engine + `pleme-computeunit` Helm-library
2067    /// chart both fan on). Every per-`:limits` axis threads through a
2068    /// lifted per-slot accessor on the [`LimitsSpec`] type: the
2069    /// [`LimitsSpec::memory`] wasm32 linear-memory byte-cap scalar
2070    /// accessor, the [`LimitsSpec::fuel`] wasmtime fuel-cap scalar
2071    /// accessor, the [`LimitsSpec::wall_clock`] per-call wall-clock
2072    /// deadline scalar accessor, and the [`LimitsSpec::cpu`]
2073    /// K8s-millicore soft-CPU-share scalar accessor. Every downstream
2074    /// consumer that reaches for a limits axis first passes through
2075    /// this outer accessor onto the composite and then dispatches
2076    /// onto the per-axis accessor — the two-level dispatch means
2077    /// every per-`:limits` reader now routes through a typed dispatch
2078    /// on the substrate primitive at both altitudes.
2079    ///
2080    /// Prior to this lift the `.limits` `Option<LimitsSpec>` composite
2081    /// was accessed inline at three production sites — the
2082    /// [`crate::StandardLayout::verify`] per-`:limits` shape gate's
2083    /// `if let Some(l) = &caixa.limits { … }` traversal head
2084    /// (caixa-core/src/layout.rs:882, which drives the per-axis
2085    /// refusal cascade on the composite: the `LimitsError::MemoryZero`
2086    /// / `MemoryBelowWasm32Page` / `MemoryExceedsWasm32Max` /
2087    /// `FuelZero` / `FuelExceedsMax` / `WallClockZero` /
2088    /// `WallClockExceedsMax` / `CpuZero` / `CpuExceedsMax` refusals
2089    /// [`LimitsSpec::validate`] fans onto), the
2090    /// [`crate::render::servico_m2_overlay`] per-Servico M2 overlay
2091    /// emitter's `if let Some(limits) = &caixa.limits { … }` traversal
2092    /// head (caixa-core/src/render.rs:18504, which drives the
2093    /// `M2_KEY_LIMITS`-keyed `limits.is_empty()`-gated `serde_yaml`
2094    /// projection every `caixa-helm` / `caixa-flux` Servico values-
2095    /// block emitter fans on), and the
2096    /// [`Self::declared_servico_slots`] per-Servico M2 declared-slot-
2097    /// set enumerator's `self.limits.is_some()` presence probe
2098    /// (caixa-core/src/manifest.rs:1788, which drives the
2099    /// `M2_AUTHOR_KEY_LIMITS` kebab-case author-label push every
2100    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
2101    /// gate reads) — three open-coded outer-field accesses that
2102    /// expressed no compile-time link back to the typed slot at the
2103    /// [`Caixa`] altitude. A future extension of the `:limits` outer
2104    /// axis to a richer author surface (a multi-`:limits` list the M4
2105    /// CR materializer resolves per-CR at admission time so a Servico
2106    /// can expose a compute-heavy + IO-heavy limits pair, a per-
2107    /// cluster `:limits-overrides` slot the operator pins so a
2108    /// cluster-specific policy can tighten a caixa-declared cap
2109    /// without re-authoring the `caixa.lisp`, a promotion of the
2110    /// plain `Option<LimitsSpec>` to a richer
2111    /// `{static, dynamic}` partition once the wasm-engine's runtime-
2112    /// resolved dynamic-cap surface lands) would have had to be
2113    /// threaded through all three open-coded copies in lockstep or
2114    /// one consumer would silently disagree with the peers on which
2115    /// limits composite a given Caixa resolves to — the layout gate's
2116    /// per-axis bracket-dispatch seed reading the raw slot while the
2117    /// peer `servico_m2_overlay` emitter read an operator-resolved
2118    /// slot would silently split the build-time sandbox-shape gate
2119    /// from the runtime `ComputeUnit` CR emission gate, a three-
2120    /// consumer split at the layout gate, the M2 overlay emitter, and
2121    /// the declared-slot enumerator far from the source `caixa.lisp`
2122    /// with no field naming the limits-drift root cause. Lifting the
2123    /// resolution rule to a typed method on the substrate primitive
2124    /// means every downstream consumer of the caixa's per-`Caixa`
2125    /// Lunatic-sandboxing outer-composite surface reaches for exactly
2126    /// one typed dispatch — the resolver's accept-set migrates as a
2127    /// unit on any future axis addition.
2128    ///
2129    /// First outer top-level [`Caixa`] `Option<&Composite>`-return
2130    /// composite-reference accessor — opens the outer-`Caixa`
2131    /// `Option<&Composite>` composite-reference projection pattern the
2132    /// sibling per-`Caixa` `:behavior` [`crate::BehaviorSpec`] /
2133    /// `:politicas` [`crate::aplicacao::MeshPolicy`] / `:placement`
2134    /// [`crate::aplicacao::Placement`] / `:entrada`
2135    /// [`crate::aplicacao::Entrada`] future outer-composite lifts
2136    /// fold on. Peer of the M3 mesh-slot outer-composite family the
2137    /// sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
2138    /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2139    /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2140    /// accessors already close on the outer [`crate::AplicacaoSpec`]
2141    /// altitude — extends that "one typed dispatch on the substrate
2142    /// primitive, thin projections at each consumer" discipline onto
2143    /// the outer top-level [`Caixa`] altitude, opening the M2 Servico-
2144    /// runtime slot family's outer-composite axis. Returns
2145    /// `Option<&LimitsSpec>` (not the owning composite by copy or
2146    /// clone) because every downstream consumer of the limits
2147    /// composite treats it as a read-only per-axis dispatch source —
2148    /// the reference-view is the narrowest borrow that supports every
2149    /// present + roadmapped consumer (per-axis accessor dispatch,
2150    /// `.is_empty()`-gated overlay projection, presence-probe early
2151    /// return on the "author-omitted `:limits` ⇒ engine-default
2152    /// applies" partition) without cloning the composite through
2153    /// every consumer's fast path. The `Option` half of the return-
2154    /// type preserves the load-bearing "author-omitted `:limits` ⇒
2155    /// engine-default applies" partition (not a default composite the
2156    /// downstream must reject on emptiness) — the accessor projects
2157    /// the raw `Option<LimitsSpec>` slot's presence bit through the
2158    /// reference-return unchanged. Named `limits()` to match the
2159    /// storage field's name verbatim and the tatara-lisp author-
2160    /// surface term (`:limits`) the field's own docstring already
2161    /// carries.
2162    #[must_use]
2163    pub fn limits(&self) -> Option<&LimitsSpec> {
2164        self.limits.as_ref()
2165    }
2166
2167    /// Substrate-canonical per-`Caixa` `:behavior` M2 typed-slot outer-
2168    /// composite OTP-`gen_server`-shaped callback-table optional-
2169    /// composite-reference accessor every consumer of the top-level
2170    /// manifest's per-Servico [`BehaviorSpec`] outer-composite reader
2171    /// keys off — returns the author-declared `:behavior` typed
2172    /// composite verbatim as an `Option<&BehaviorSpec>` reference over
2173    /// the same backing storage the raw `self.behavior.as_ref()` field
2174    /// access borrows from, with `None` naming the "no `:behavior`
2175    /// block authored — every per-callback OTP-shaped hook defers to
2176    /// the wasm-engine's runtime default arm named on the per-axis
2177    /// [`BehaviorSpec::on_init`] / [`BehaviorSpec::on_call`] /
2178    /// [`BehaviorSpec::on_cast`] / [`BehaviorSpec::on_info`] /
2179    /// [`BehaviorSpec::on_state_change`] /
2180    /// [`BehaviorSpec::on_terminate`] scalar-accessor docstrings"
2181    /// partition every downstream Servico-M2-overlay emitter treats as
2182    /// "emit nothing" and the sibling [`crate::StandardLayout::verify`]
2183    /// per-`:behavior` shape gate treats as "skip the per-arm
2184    /// [`crate::behavior::BehaviorError`] refusal cascade + the
2185    /// per-callback on-disk `MissingEntry` existence check".
2186    ///
2187    /// The outer `:behavior` slot carries the M2 Servico-runtime typed
2188    /// composite — the load-bearing container of every OTP-shaped
2189    /// per-Servico lifecycle-callback path axis every long-running wasm
2190    /// component's runtime dispatches on (INSPIRATIONS §II.3 — Erlang/
2191    /// OTP `gen_server:init/1` / `handle_call/3` / `handle_cast/2` /
2192    /// `handle_info/2` / `code_change/3` / `terminate/2` primitives
2193    /// translated onto pleme-io's typed `:behavior :on-init` /
2194    /// `:on-call` / `:on-cast` / `:on-info` / `:on-state-change` /
2195    /// `:on-terminate` sub-slot axes; CAIXA-SDLC §II — the typed-M2
2196    /// slot algebra the wasm-engine + `pleme-computeunit` Helm-library
2197    /// chart both fan on). Every per-`:behavior` axis threads through a
2198    /// lifted per-callback accessor on the [`BehaviorSpec`] type
2199    /// (9b4ecde / d66c702 / 156ddbe / 99616ac / 4846cef / 701add7).
2200    /// Every downstream consumer that reaches for a behavior axis
2201    /// first passes through this outer accessor onto the composite
2202    /// and then dispatches onto the per-callback accessor — the
2203    /// two-level dispatch means every per-`:behavior` reader now
2204    /// routes through a typed dispatch on the substrate primitive at
2205    /// both altitudes.
2206    ///
2207    /// Composes cross-slot with the M2 `:upgrade-from` gate: the
2208    /// [`crate::upgrade::validate_upgrade_from_against_behavior`]
2209    /// cross-slot composition gate at [`crate::StandardLayout::verify`]
2210    /// keys the "per-version `:state-change` instruction must have a
2211    /// `:on-state-change` callback" precondition off this accessor's
2212    /// composite (the callback-side counterpart to the
2213    /// `:upgrade-from :instructions :state-change :script` refusal at
2214    /// the appup-side). Threading that gate's traversal input through
2215    /// this accessor closes the cross-slot invariant on the substrate
2216    /// primitive, not on the raw field.
2217    ///
2218    /// Prior to this lift the `.behavior` `Option<BehaviorSpec>`
2219    /// composite was accessed inline at four production sites — the
2220    /// [`crate::StandardLayout::verify`] per-`:behavior` shape gate's
2221    /// `if let Some(b) = &caixa.behavior { … }` traversal head
2222    /// (caixa-core/src/layout.rs:896, which drives the per-arm
2223    /// `BehaviorError` refusal cascade + the per-callback on-disk
2224    /// [`crate::LayoutError::MissingEntry`] existence check under
2225    /// [`crate::render::LAYOUT_MISSING_ENTRY_KIND_BEHAVIOR_CALLBACK`]),
2226    /// the [`crate::upgrade::validate_upgrade_from_against_behavior`]
2227    /// cross-slot composition gate's `caixa.behavior.as_ref()`
2228    /// traversal-input feed (caixa-core/src/layout.rs:1008, which
2229    /// drives the `:state-change` ↔ `:on-state-change` precondition
2230    /// refusal), the [`crate::render::servico_m2_overlay`] per-Servico
2231    /// M2 overlay emitter's `if let Some(behavior) = &caixa.behavior
2232    /// { … }` traversal head (caixa-core/src/render.rs:18513, which
2233    /// drives the `M2_KEY_BEHAVIOR`-keyed `behavior.is_empty()`-gated
2234    /// `serde_yaml` projection every `caixa-helm` / `caixa-flux`
2235    /// Servico values-block emitter fans on), and the
2236    /// [`Self::declared_servico_slots`] per-Servico M2 declared-slot-
2237    /// set enumerator's `self.behavior.is_some()` presence probe
2238    /// (caixa-core/src/manifest.rs:1919, which drives the
2239    /// `M2_AUTHOR_KEY_BEHAVIOR` kebab-case author-label push every
2240    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
2241    /// gate reads) — four open-coded outer-field accesses that
2242    /// expressed no compile-time link back to the typed slot at the
2243    /// [`Caixa`] altitude. A future extension of the `:behavior`
2244    /// outer axis to a richer author surface (a per-callback overlay
2245    /// resolver the operator materializes at admission time so a
2246    /// cluster-specific policy can inject a per-callback tracing
2247    /// interceptor without re-authoring the `caixa.lisp`, a promotion
2248    /// of the plain `Option<BehaviorSpec>` to a richer `{static,
2249    /// dynamic}` partition once a runtime-resolved behavior-swap
2250    /// surface lands, the M4 per-callback middleware chain the
2251    /// caixa-operator's per-Servico admission webhook keys off) would
2252    /// have had to be threaded through all four open-coded copies in
2253    /// lockstep or one consumer would silently disagree with the
2254    /// peers on which behavior composite a given Caixa resolves to —
2255    /// the layout gate's per-callback existence-check seed reading
2256    /// the raw slot while the peer `servico_m2_overlay` emitter read
2257    /// an operator-resolved slot would silently split the build-time
2258    /// callback-shape gate from the runtime `ComputeUnit` CR emission
2259    /// gate from the cross-slot `:state-change` composition gate from
2260    /// the M2 declared-slot enumerator, a four-consumer split far
2261    /// from the source `caixa.lisp` with no field naming the
2262    /// behavior-drift root cause. Lifting the resolution rule to a
2263    /// typed method on the substrate primitive means every downstream
2264    /// consumer of the caixa's per-`Caixa` OTP-callback-table outer-
2265    /// composite surface reaches for exactly one typed dispatch — the
2266    /// resolver's accept-set migrates as a unit on any future axis
2267    /// addition.
2268    ///
2269    /// Second outer top-level [`Caixa`] `Option<&Composite>`-return
2270    /// composite-reference accessor — sibling to the opening
2271    /// [`Self::limits`] (b2bd9d7) accessor on the outer-`Caixa`
2272    /// `Option<&Composite>` composite-reference sub-family, extends
2273    /// the "one typed dispatch on the substrate primitive, thin
2274    /// projections at each consumer" discipline onto the second of
2275    /// the three M2 Servico-runtime slots. The remaining
2276    /// `Option<&Composite>` axes at the outer top-level [`Caixa`]
2277    /// altitude — the M3 mesh-slot family (`:politicas`,
2278    /// `:placement`, `:entrada` — already closed on the inner
2279    /// [`crate::AplicacaoSpec`] altitude via 534dc21 / 9abb8f0 /
2280    /// d32111c) — remain the future sibling lifts on the outer
2281    /// top-level projection. Returns `Option<&BehaviorSpec>` (not
2282    /// the owning composite by copy or clone) because every
2283    /// downstream consumer of the behavior composite treats it as a
2284    /// read-only per-callback dispatch source — the reference-view is
2285    /// the narrowest borrow that supports every present + roadmapped
2286    /// consumer (per-callback accessor dispatch, `.is_empty()`-gated
2287    /// overlay projection, presence-probe early return on the
2288    /// "author-omitted `:behavior` ⇒ runtime-default applies"
2289    /// partition, cross-slot `:state-change` composition input)
2290    /// without cloning the composite through every consumer's fast
2291    /// path. The `Option` half of the return-type preserves the
2292    /// load-bearing "author-omitted `:behavior` ⇒ runtime-default
2293    /// applies" partition (not a default composite the downstream
2294    /// must reject on emptiness) — the accessor projects the raw
2295    /// `Option<BehaviorSpec>` slot's presence bit through the
2296    /// reference-return unchanged. Named `behavior()` to match the
2297    /// storage field's name verbatim and the tatara-lisp author-
2298    /// surface term (`:behavior`) the field's own docstring already
2299    /// carries.
2300    #[must_use]
2301    pub fn behavior(&self) -> Option<&crate::BehaviorSpec> {
2302        self.behavior.as_ref()
2303    }
2304
2305    /// Substrate-canonical per-`Caixa` `:politicas` M3 mesh-slot outer-
2306    /// composite MESH-COMPOSITION-shaped mesh-policy optional-composite-
2307    /// reference accessor every consumer of the top-level manifest's
2308    /// per-Aplicacao [`crate::aplicacao::MeshPolicy`] outer-composite
2309    /// reader keys off — returns the author-declared `:politicas` typed
2310    /// composite verbatim as an `Option<&MeshPolicy>` reference over the
2311    /// same backing storage the raw `self.politicas.as_ref()` field
2312    /// access borrows from, with `None` naming the "no `:politicas`
2313    /// block authored — every per-axis mesh-policy scalar defers to the
2314    /// cluster-default arm named on the per-axis
2315    /// [`crate::aplicacao::MeshPolicy::timeout`] /
2316    /// [`crate::aplicacao::MeshPolicy::retries`] /
2317    /// [`crate::aplicacao::MeshPolicy::circuit_breaker`] /
2318    /// [`crate::aplicacao::MeshPolicy::mtls_required`] /
2319    /// [`crate::aplicacao::MeshPolicy::rate_limit`] scalar-accessor
2320    /// docstrings" partition every downstream caixa-mesh /
2321    /// caixa-flux / caixa-helm Aplicacao-artifact emitter treats as
2322    /// "emit no per-`:politicas` overlay" and the sibling
2323    /// [`Self::aplicacao_view`] Aplicacao-composition seed folds through
2324    /// the [`crate::aplicacao::MeshPolicy::default`] cluster-default
2325    /// arm.
2326    ///
2327    /// The outer `:politicas` slot carries the M3 mesh-slot per-
2328    /// Aplicacao typed composite — the load-bearing container of every
2329    /// mesh-level policy axis every Cilium NetworkPolicy / Gateway API
2330    /// v1.x HTTPRoute / future M4 per-edge policy overlay emitter fans
2331    /// on (MESH-COMPOSITION §III.2 — the Aplicacao's typed mesh-policy
2332    /// composite; §V — the "no infinite blocking" per-call deadline +
2333    /// "sandboxing-by-default" mTLS-enforcement CSE invariants; §III.3
2334    /// — the typed inter-Servico contrato-edge overlay the per-`(:de,
2335    /// :para)` mesh renderer keys off). Every per-`:politicas` axis
2336    /// threads through a lifted per-slot accessor on the
2337    /// [`crate::aplicacao::MeshPolicy`] type: the
2338    /// [`crate::aplicacao::MeshPolicy::mtls_required`] (c0110f1) Cilium
2339    /// mTLS-enforcement toggle, the
2340    /// [`crate::aplicacao::MeshPolicy::retries`] (bdfb399) transient-
2341    /// failure retry budget, the [`crate::aplicacao::MeshPolicy::timeout`]
2342    /// (7073d0f) Gateway-API per-call deadline, the
2343    /// [`crate::aplicacao::MeshPolicy::circuit_breaker`] (b0e741a)
2344    /// Envoy-outlier-detection composite. Every downstream consumer
2345    /// that reaches for a mesh-policy axis first passes through this
2346    /// outer accessor onto the composite and then dispatches onto the
2347    /// per-axis accessor — the two-level dispatch means every per-
2348    /// `:politicas` reader now routes through a typed dispatch on the
2349    /// substrate primitive at both altitudes.
2350    ///
2351    /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2352    /// seed: the Aplicacao-view builder folds the outer `Option`'s
2353    /// author-omitted arm onto the [`crate::aplicacao::MeshPolicy::default`]
2354    /// cluster-default, so the peer inner [`crate::AplicacaoSpec::politicas`]
2355    /// (534dc21) `&MeshPolicy`-return accessor observes a typed
2356    /// composite whether or not the author declared the outer slot.
2357    /// The outer accessor preserves the "author-omitted vs authored-
2358    /// empty" partition the inner accessor's `is_empty()`-gated
2359    /// renderer overlay collapses — routing the presence bit through
2360    /// this accessor keeps the [`Self::declared_mesh_slots`] M3 kind-
2361    /// coherence enumerator's `M3_AUTHOR_KEY_POLITICAS` push separate
2362    /// from the inner `MeshPolicy::is_empty()`-gated overlay elision.
2363    ///
2364    /// Prior to this lift the `.politicas` `Option<MeshPolicy>`
2365    /// composite was accessed inline at two production sites — the
2366    /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2367    /// `self.politicas.clone().unwrap_or_default()` traversal head
2368    /// (caixa-core/src/manifest.rs:1899, which drives the fold onto
2369    /// the [`crate::aplicacao::MeshPolicy::default`] cluster-default
2370    /// arm the inner [`crate::AplicacaoSpec::politicas`] accessor
2371    /// then observes), and the [`Self::declared_mesh_slots`] M3
2372    /// declared-slot-set enumerator's `self.politicas.is_some()`
2373    /// presence probe (caixa-core/src/manifest.rs:1961, which drives
2374    /// the `M3_AUTHOR_KEY_POLITICAS` kebab-case author-label push
2375    /// every [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
2376    /// coherence gate reads) — two open-coded outer-field accesses
2377    /// that expressed no compile-time link back to the typed slot at
2378    /// the [`Caixa`] altitude. A future extension of the `:politicas`
2379    /// outer axis to a richer author surface (a per-cluster
2380    /// `:politicas-overrides` slot the operator materializes at
2381    /// admission time so a cluster-specific policy can tighten the
2382    /// caixa-declared bound without re-authoring the `caixa.lisp`, a
2383    /// promotion of the plain `Option<MeshPolicy>` to a richer
2384    /// `{static, dynamic}` partition once the M4 per-edge
2385    /// contrato-scoped policy-override surface lands, the M5 traffic-
2386    /// shaping composition the caixa-operator's per-Aplicacao mesh
2387    /// admission webhook keys off) would have had to be threaded
2388    /// through both open-coded copies in lockstep or the Aplicacao-
2389    /// composition seed's default-fold arm would silently disagree
2390    /// with the M3 declared-slot enumerator on which policy composite
2391    /// a given Caixa resolves to — the seed reading an operator-
2392    /// resolved slot while the enumerator's presence probe read the
2393    /// raw slot would silently split the build-time mesh-artifact
2394    /// emission gate from the M3 declared-slot enumerator's kind-
2395    /// coherence gate, a two-consumer split far from the source
2396    /// `caixa.lisp` with no field naming the policy-drift root cause.
2397    /// Lifting the resolution rule to a typed method on the substrate
2398    /// primitive means every downstream consumer of the caixa's per-
2399    /// `Caixa` MESH-COMPOSITION mesh-policy outer-composite surface
2400    /// reaches for exactly one typed dispatch — the resolver's
2401    /// accept-set migrates as a unit on any future axis addition.
2402    ///
2403    /// Third outer top-level [`Caixa`] `Option<&Composite>`-return
2404    /// composite-reference accessor — sibling to the opening
2405    /// [`Self::limits`] (b2bd9d7) and [`Self::behavior`] (35d8b52)
2406    /// accessors on the outer-`Caixa` `Option<&Composite>` composite-
2407    /// reference sub-family, extends the "one typed dispatch on the
2408    /// substrate primitive, thin projections at each consumer"
2409    /// discipline onto the first of the three M3 mesh-slot axes.
2410    /// Peer of the closed inner mesh-slot outer-composite family the
2411    /// sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
2412    /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2413    /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2414    /// accessor pins already close on the inner [`crate::AplicacaoSpec`]
2415    /// altitude — opens the outer top-level [`Caixa`] altitude's M3
2416    /// mesh-slot arm of the composite-reference family the remaining
2417    /// two axes (`:placement`, `:entrada`) fold onto in future
2418    /// sibling lifts. Returns `Option<&MeshPolicy>` (not the owning
2419    /// composite by copy or clone) because every downstream consumer
2420    /// of the mesh-policy composite treats it as a read-only per-axis
2421    /// dispatch source — the reference-view is the narrowest borrow
2422    /// that supports every present + roadmapped consumer (per-axis
2423    /// accessor dispatch, `.is_empty()`-gated overlay projection,
2424    /// presence-probe early return on the "author-omitted `:politicas`
2425    /// ⇒ cluster-default applies" partition, `Aplicacao`-composition
2426    /// seed's default-fold arm) without cloning the composite through
2427    /// every consumer's fast path. The `Option` half of the return-
2428    /// type preserves the load-bearing "author-omitted `:politicas` ⇒
2429    /// cluster-default applies" partition (not a default composite
2430    /// the downstream must reject on emptiness) — the accessor
2431    /// projects the raw `Option<MeshPolicy>` slot's presence bit
2432    /// through the reference-return unchanged. Named `politicas()` to
2433    /// match the storage field's name verbatim and the tatara-lisp
2434    /// author-surface term (`:politicas`) the field's own docstring
2435    /// already carries.
2436    #[must_use]
2437    pub fn politicas(&self) -> Option<&crate::aplicacao::MeshPolicy> {
2438        self.politicas.as_ref()
2439    }
2440
2441    /// Substrate-canonical per-`Caixa` `:placement` M3 mesh-slot outer-
2442    /// composite MESH-COMPOSITION-shaped distribution optional-composite-
2443    /// reference accessor every consumer of the top-level manifest's
2444    /// per-Aplicacao [`crate::aplicacao::Placement`] outer-composite
2445    /// reader keys off — returns the author-declared `:placement` typed
2446    /// composite verbatim as an `Option<&Placement>` reference over the
2447    /// same backing storage the raw `self.placement.as_ref()` field
2448    /// access borrows from, with `None` naming the "no `:placement`
2449    /// block authored — every per-axis placement scalar defers to the
2450    /// cluster-default arm named on the per-axis
2451    /// [`crate::aplicacao::Placement::estrategia`] /
2452    /// [`crate::aplicacao::Placement::clusters`] /
2453    /// [`crate::aplicacao::Placement::affinity`] /
2454    /// [`crate::aplicacao::Placement::shard_key`] scalar-accessor
2455    /// docstrings" partition every downstream caixa-mesh /
2456    /// caixa-flux / caixa-helm Aplicacao-artifact emitter treats as
2457    /// "emit no per-`:placement` overlay" and the sibling
2458    /// [`Self::aplicacao_view`] Aplicacao-composition seed folds through
2459    /// the [`crate::aplicacao::Placement::default`] cluster-default arm.
2460    ///
2461    /// The outer `:placement` slot carries the M3 mesh-slot per-
2462    /// Aplicacao typed distribution composite — the load-bearing
2463    /// container of every where-does-this-Aplicacao-run axis every
2464    /// caixa-mesh programs.yaml per-cluster distribution overlay /
2465    /// caixa-flux per-Aplicacao GitRepository/HelmRelease fan-out /
2466    /// future M4 per-Aplicacao Akka-style cluster-sharding entity-id
2467    /// resolver emitter fans on (MESH-COMPOSITION §II.4 — the
2468    /// Aplicacao's typed distribution composite; §V CSE invariants —
2469    /// "distribution is a first-class typed composite, not a runtime
2470    /// scheduler hint" the per-axis scalars enforce; §III.3 — the
2471    /// typed inter-Servico contrato-edge overlay the per-cluster
2472    /// mesh renderer keys off). Every per-`:placement` axis threads
2473    /// through a lifted per-slot accessor on the
2474    /// [`crate::aplicacao::Placement`] type: the
2475    /// [`crate::aplicacao::Placement::estrategia`] (921fe1b)
2476    /// MESH-COMPOSITION distribution-strategy scalar, the
2477    /// [`crate::aplicacao::Placement::clusters`] (a6e18d7) per-cluster
2478    /// distribution-target slice, the [`crate::aplicacao::Placement::affinity`]
2479    /// M3-Adaptive-compression-hint optional-scalar, and the
2480    /// [`crate::aplicacao::Placement::shard_key`] (7cd2a28) Akka-cluster-
2481    /// sharding extractor-expression optional-scalar. Every downstream
2482    /// consumer that reaches for a placement axis first passes through
2483    /// this outer accessor onto the composite and then dispatches onto
2484    /// the per-axis accessor — the two-level dispatch means every per-
2485    /// `:placement` reader now routes through a typed dispatch on the
2486    /// substrate primitive at both altitudes.
2487    ///
2488    /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2489    /// seed: the Aplicacao-view builder folds the outer `Option`'s
2490    /// author-omitted arm onto the [`crate::aplicacao::Placement::default`]
2491    /// cluster-default, so the peer inner [`crate::AplicacaoSpec::placement`]
2492    /// (9abb8f0) `&Placement`-return accessor observes a typed composite
2493    /// whether or not the author declared the outer slot. The outer
2494    /// accessor preserves the "author-omitted vs authored-empty" partition
2495    /// the inner accessor collapses at the cluster-default fold —
2496    /// routing the presence bit through this accessor keeps the
2497    /// [`Self::declared_mesh_slots`] M3 kind-coherence enumerator's
2498    /// `M3_AUTHOR_KEY_PLACEMENT` push separate from the inner
2499    /// [`crate::AplicacaoSpec::validate_placement`]-gated overlay
2500    /// dispatch.
2501    ///
2502    /// Prior to this lift the `.placement` `Option<Placement>`
2503    /// composite was accessed inline at two production sites — the
2504    /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2505    /// `self.placement.clone().unwrap_or_default()` traversal head
2506    /// (caixa-core/src/manifest.rs:2036, which drives the fold onto
2507    /// the [`crate::aplicacao::Placement::default`] cluster-default
2508    /// arm the inner [`crate::AplicacaoSpec::placement`] accessor
2509    /// then observes), and the [`Self::declared_mesh_slots`] M3
2510    /// declared-slot-set enumerator's `self.placement.is_some()`
2511    /// presence probe (caixa-core/src/manifest.rs:2100, which drives
2512    /// the `M3_AUTHOR_KEY_PLACEMENT` kebab-case author-label push
2513    /// every [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
2514    /// coherence gate reads) — two open-coded outer-field accesses
2515    /// that expressed no compile-time link back to the typed slot at
2516    /// the [`Caixa`] altitude. A future extension of the `:placement`
2517    /// outer axis to a richer author surface (a per-cluster
2518    /// `:placement-overrides` slot the operator materializes at
2519    /// admission time so a cluster-specific placement can tighten the
2520    /// caixa-declared bound without re-authoring the `caixa.lisp`, a
2521    /// per-tenant placement-alias table the M4
2522    /// `mesh.pleme.io/v1alpha1/Aplicacao` CR materializer resolves
2523    /// per-CR at admission time, a promotion of the plain
2524    /// `Option<Placement>` to a richer `{static, dynamic}` partition
2525    /// once Orleans-style virtual-actor dynamic placement comes into
2526    /// typed scope) would have had to be threaded through both open-
2527    /// coded copies in lockstep or the Aplicacao-composition seed's
2528    /// default-fold arm would silently disagree with the M3 declared-
2529    /// slot enumerator on which distribution composite a given Caixa
2530    /// resolves to — the seed reading an operator-resolved slot while
2531    /// the enumerator's presence probe read the raw slot would
2532    /// silently split the build-time distribution-artifact emission
2533    /// gate from the M3 declared-slot enumerator's kind-coherence
2534    /// gate, a two-consumer split far from the source `caixa.lisp`
2535    /// with no field naming the distribution-drift root cause.
2536    /// Lifting the resolution rule to a typed method on the substrate
2537    /// primitive means every downstream consumer of the caixa's per-
2538    /// `Caixa` MESH-COMPOSITION distribution outer-composite surface
2539    /// reaches for exactly one typed dispatch — the resolver's
2540    /// accept-set migrates as a unit on any future axis addition.
2541    ///
2542    /// Fourth outer top-level [`Caixa`] `Option<&Composite>`-return
2543    /// composite-reference accessor — sibling to the opening
2544    /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) M2-
2545    /// Servico-runtime pair and the peer [`Self::politicas`] (5d23d29)
2546    /// M3-mesh-slot arm on the outer-`Caixa` `Option<&Composite>`
2547    /// composite-reference sub-family, folds on the "one typed
2548    /// dispatch on the substrate primitive, thin projections at each
2549    /// consumer" discipline extended onto the second of the three M3
2550    /// mesh-slot axes. Peer of the closed inner mesh-slot outer-
2551    /// composite family the sibling
2552    /// [`crate::AplicacaoSpec::politicas`] (534dc21) /
2553    /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2554    /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2555    /// accessor pins already close on the inner
2556    /// [`crate::AplicacaoSpec`] altitude — folds on the outer top-
2557    /// level [`Caixa`] altitude's M3 mesh-slot arm the sibling
2558    /// [`Self::politicas`] opened, extending the discipline onto the
2559    /// second of the three M3 mesh-slot axes. The remaining M3
2560    /// mesh-slot axis (`:entrada`) folds onto this accessor's
2561    /// discipline in the final sibling lift, closing the outer top-
2562    /// level [`Caixa`] `Option<&Composite>` M3 mesh-slot sub-family.
2563    /// Returns `Option<&Placement>` (not the owning composite by copy
2564    /// or clone) because every downstream consumer of the placement
2565    /// composite treats it as a read-only per-axis dispatch source —
2566    /// the reference-view is the narrowest borrow that supports every
2567    /// present + roadmapped consumer (per-axis accessor dispatch,
2568    /// serde composite-serialization on the programs.yaml overlay,
2569    /// presence-probe early return on the "author-omitted `:placement`
2570    /// ⇒ cluster-default applies" partition, `Aplicacao`-composition
2571    /// seed's default-fold arm) without cloning the composite through
2572    /// every consumer's fast path. The `Option` half of the return-
2573    /// type preserves the load-bearing "author-omitted `:placement` ⇒
2574    /// cluster-default applies" partition (not a default composite
2575    /// the downstream must reject on emptiness) — the accessor
2576    /// projects the raw `Option<Placement>` slot's presence bit
2577    /// through the reference-return unchanged. Named `placement()` to
2578    /// match the storage field's name verbatim and the tatara-lisp
2579    /// author-surface term (`:placement`) the field's own docstring
2580    /// already carries.
2581    #[must_use]
2582    pub fn placement(&self) -> Option<&crate::aplicacao::Placement> {
2583        self.placement.as_ref()
2584    }
2585
2586    /// Substrate-canonical per-`Caixa` `:entrada` M3 mesh-slot outer-
2587    /// composite MESH-COMPOSITION-shaped external-gateway optional-
2588    /// composite-reference accessor every consumer of the top-level
2589    /// manifest's per-Aplicacao [`crate::aplicacao::Entrada`] outer-
2590    /// composite reader keys off — returns the author-declared
2591    /// `:entrada` typed composite verbatim as an `Option<&Entrada>`
2592    /// reference over the same backing storage the raw
2593    /// `self.entrada.as_ref()` field access borrows from, with `None`
2594    /// naming the "no `:entrada` block authored — this Aplicacao is
2595    /// cluster-internal, no `Gateway`/`HTTPRoute` fan-out emitted"
2596    /// partition every downstream caixa-mesh Gateway-API artifact
2597    /// emitter treats as "emit no gateway-listener + no `HTTPRoute`
2598    /// backend for this Aplicacao" and the sibling
2599    /// [`Self::aplicacao_view`] Aplicacao-composition seed forwards
2600    /// verbatim (unlike the peer `:politicas` / `:placement` arms,
2601    /// `:entrada` has no cluster-default fold — an omitted `:entrada`
2602    /// stays `None` on the projected [`crate::AplicacaoSpec`] and the
2603    /// peer inner [`crate::AplicacaoSpec::entrada`] accessor observes
2604    /// the same `Option<&Entrada>` presence bit unchanged).
2605    ///
2606    /// The outer `:entrada` slot carries the M3 mesh-slot per-
2607    /// Aplicacao typed external-gateway composite — the load-bearing
2608    /// container of every how-does-the-outside-world-reach-this-
2609    /// Aplicacao axis every caixa-mesh `Gateway`/`HTTPRoute` fan-out
2610    /// emitter fans on (MESH-COMPOSITION §II.5 — the Aplicacao's typed
2611    /// external-entry composite; §V CSE invariants — "the external
2612    /// gateway is a first-class typed composite, not a per-Servico
2613    /// ingress annotation" the per-axis scalars enforce; §III.4 — the
2614    /// typed hostname + backend-Servico pair the per-cluster Gateway-
2615    /// API renderer keys off). Every per-`:entrada` axis threads
2616    /// through a lifted per-slot accessor on the
2617    /// [`crate::aplicacao::Entrada`] type: the
2618    /// [`crate::aplicacao::Entrada::host`] Gateway-API `Listener.hostname`
2619    /// scalar, the [`crate::aplicacao::Entrada::para`] backend-Servico
2620    /// caixa-name scalar, the [`crate::aplicacao::Entrada::paths`]
2621    /// per-rule `HTTPPathMatch` list, the [`crate::aplicacao::Entrada::port`]
2622    /// backend `trigger.service.port` scalar, and the
2623    /// [`crate::aplicacao::Entrada::resolved_paths`] URL-path fallback
2624    /// resolver every HTTPRoute-aware renderer consumes. Every
2625    /// downstream consumer that reaches for an entry axis first passes
2626    /// through this outer accessor onto the composite and then
2627    /// dispatches onto the per-axis accessor — the two-level dispatch
2628    /// means every per-`:entrada` reader now routes through a typed
2629    /// dispatch on the substrate primitive at both altitudes.
2630    ///
2631    /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2632    /// seed: the Aplicacao-view builder forwards the outer `Option`
2633    /// arm verbatim (no default fold — `:entrada` is inherently
2634    /// optional; a cluster-internal Aplicacao has no external gateway
2635    /// at all, not "an external gateway that defaults to nothing"), so
2636    /// the peer inner [`crate::AplicacaoSpec::entrada`] (d32111c)
2637    /// `Option<&Entrada>`-return accessor observes the same presence
2638    /// bit whether or not the author declared the outer slot. Routing
2639    /// the presence bit through this accessor keeps the
2640    /// [`Self::declared_mesh_slots`] M3 kind-coherence enumerator's
2641    /// `M3_AUTHOR_KEY_ENTRADA` push separate from the inner
2642    /// [`crate::AplicacaoSpec::validate_entrada`]-gated
2643    /// hostname/backend/path emission dispatch.
2644    ///
2645    /// Prior to this lift the `.entrada` `Option<Entrada>` composite
2646    /// was accessed inline at two production sites — the
2647    /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2648    /// `self.entrada.clone()` traversal head (caixa-core/src/manifest.rs:2182,
2649    /// which drives the forward onto the peer inner
2650    /// [`crate::AplicacaoSpec::entrada`] accessor the caixa-mesh
2651    /// Gateway-API fan-out then observes), and the
2652    /// [`Self::declared_mesh_slots`] M3 declared-slot-set enumerator's
2653    /// `self.entrada.is_some()` presence probe (caixa-core/src/manifest.rs:2248,
2654    /// which drives the `M3_AUTHOR_KEY_ENTRADA` kebab-case author-
2655    /// label push every [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
2656    /// kind-coherence gate reads) — two open-coded outer-field
2657    /// accesses that expressed no compile-time link back to the typed
2658    /// slot at the [`Caixa`] altitude. A future extension of the
2659    /// `:entrada` outer axis to a richer author surface (a per-cluster
2660    /// `:entrada-overrides` slot the operator materializes at admission
2661    /// time so a cluster-specific hostname can pin the caixa-declared
2662    /// bound without re-authoring the `caixa.lisp`, a per-tenant
2663    /// gateway-alias table the M4 `mesh.pleme.io/v1alpha1/Aplicacao`
2664    /// CR materializer resolves per-CR at admission time, a promotion
2665    /// of the plain `Option<Entrada>` to a richer
2666    /// `{public, private, internal}` partition once Cilium-identity-
2667    /// scoped internal gateways come into typed scope) would have had
2668    /// to be threaded through both open-coded copies in lockstep or the
2669    /// Aplicacao-composition seed's forward arm would silently
2670    /// disagree with the M3 declared-slot enumerator on which external-
2671    /// gateway composite a given Caixa resolves to — the seed reading
2672    /// an operator-resolved slot while the enumerator's presence probe
2673    /// read the raw slot would silently split the build-time gateway-
2674    /// artifact emission gate from the M3 declared-slot enumerator's
2675    /// kind-coherence gate, a two-consumer split far from the source
2676    /// `caixa.lisp` with no field naming the entry-drift root cause.
2677    /// Lifting the resolution rule to a typed method on the substrate
2678    /// primitive means every downstream consumer of the caixa's per-
2679    /// `Caixa` MESH-COMPOSITION external-gateway outer-composite
2680    /// surface reaches for exactly one typed dispatch — the resolver's
2681    /// accept-set migrates as a unit on any future axis addition.
2682    ///
2683    /// Fifth and final outer top-level [`Caixa`] `Option<&Composite>`-
2684    /// return composite-reference accessor — closes the outer-`Caixa`
2685    /// `Option<&Composite>` composite-reference sub-family opened by
2686    /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) on the
2687    /// M2 Servico-runtime arm and extended onto the M3 mesh-slot arm
2688    /// by [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074),
2689    /// folds on the "one typed dispatch on the substrate primitive,
2690    /// thin projections at each consumer" discipline extended onto the
2691    /// third and final M3 mesh-slot axis. Peer of the closed inner
2692    /// mesh-slot outer-composite family the sibling
2693    /// [`crate::AplicacaoSpec::politicas`] (534dc21) /
2694    /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2695    /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2696    /// accessor pins already close on the inner
2697    /// [`crate::AplicacaoSpec`] altitude — this lift closes the mirror
2698    /// sub-family on the outer top-level [`Caixa`] altitude, so both
2699    /// altitudes of the outer-composite reference-return discipline
2700    /// (per-`Caixa` outer-slot presence + per-`AplicacaoSpec` inner-
2701    /// slot presence) now carry the full five-arm accept-set behind a
2702    /// typed dispatch on the substrate primitive. Returns
2703    /// `Option<&Entrada>` (not the owning composite by copy or clone)
2704    /// because every downstream consumer of the entrada composite
2705    /// treats it as a read-only per-axis dispatch source — the
2706    /// reference-view is the narrowest borrow that supports every
2707    /// present + roadmapped consumer (per-axis accessor dispatch,
2708    /// serde composite-serialization on the programs.yaml overlay,
2709    /// presence-probe early return on the "author-omitted `:entrada`
2710    /// ⇒ cluster-internal Aplicacao" partition, `Aplicacao`-composition
2711    /// seed's forward arm) without cloning the composite through every
2712    /// consumer's fast path. The `Option` half of the return-type
2713    /// preserves the load-bearing "author-omitted `:entrada` ⇒
2714    /// cluster-internal Aplicacao" partition (not a default composite
2715    /// the downstream must reject on emptiness — a cluster-internal
2716    /// Aplicacao has no external gateway at all, not "a default gateway
2717    /// that emits nothing"); the accessor projects the raw
2718    /// `Option<Entrada>` slot's presence bit through the reference-
2719    /// return unchanged. Named `entrada()` to match the storage field's
2720    /// name verbatim and the tatara-lisp author-surface term
2721    /// (`:entrada`) the field's own docstring already carries.
2722    #[must_use]
2723    pub fn entrada(&self) -> Option<&crate::aplicacao::Entrada> {
2724        self.entrada.as_ref()
2725    }
2726
2727    /// Substrate-canonical per-`Caixa` `:ci` slot accessor — returns the
2728    /// author-declared typed CI run (`canteiro_types::CiRun`) verbatim as
2729    /// an `Option<&CiRun>`, borrowed from the typed slot's own
2730    /// `Option<CiRun>` storage. `None` when the slot is absent (every
2731    /// non-`Acao` kind, and an `Acao` caixa that hasn't declared `:ci`
2732    /// yet — the latter is caught by [`crate::LayoutError::MissingCi`],
2733    /// not silently accepted).
2734    ///
2735    /// Named `ci()` to match the storage field's name and the
2736    /// tatara-lisp author surface (`:ci`); mirrors the sibling
2737    /// `Option<&Composite>` accessors on this same `Caixa` altitude
2738    /// ([`Self::limits`], [`Self::behavior`], [`Self::politicas`],
2739    /// [`Self::placement`], [`Self::entrada`]) — one typed dispatch on
2740    /// the substrate primitive rather than an open-coded `self.ci.as_ref()`
2741    /// at every consumer.
2742    #[must_use]
2743    pub fn ci(&self) -> Option<&canteiro_types::CiRun> {
2744        self.ci.as_ref()
2745    }
2746
2747    /// Substrate-canonical per-`Caixa` `:estrategia` M2 supervisor-tree-
2748    /// slot flat-spread OTP-shaped sibling-restart-strategy discriminant
2749    /// accessor every consumer of the top-level manifest's per-Supervisor
2750    /// restart-strategy axis keys off — returns the author-declared
2751    /// `:estrategia` variant verbatim as an `Option<RestartStrategy>`,
2752    /// `Copy`-projected from the typed slot's own
2753    /// `Option<crate::supervisor::RestartStrategy>` storage. Optional
2754    /// (`:estrategia` is a flat-spread supervisor-only slot every
2755    /// non-`Supervisor`-kind `defcaixa` carries as `None` by
2756    /// `#[serde(default)]`, and every `Supervisor`-kind `defcaixa` may
2757    /// still omit to defer to [`RestartStrategy::default`] —
2758    /// [`RestartStrategy::OneForOne`] — through the [`Self::supervisor_view`]
2759    /// `unwrap_or_default()` fold; a returned `None` degenerates to the
2760    /// [`SupervisorSpec::default`]-inherited strategy without any silent
2761    /// promotion to a fresh explicit variant at the accessor boundary).
2762    ///
2763    /// The `:estrategia` slot carries the M2 typed OTP-shaped sibling-
2764    /// restart-strategy discriminant every substrate-side per-Supervisor
2765    /// dispatch fans on (INSPIRATIONS §II.2 — OTP `supervisor:strategy`
2766    /// closed-set `one_for_one | one_for_all | rest_for_one |
2767    /// simple_one_for_one` algebra translated onto pleme-io's typed
2768    /// [`RestartStrategy`] enum; CAIXA-SDLC §II — the M2 supervisor-tree
2769    /// slot algebra the operator's hierarchical reconciliation scheduler
2770    /// fans on). The slot is *flat-spread* on the outer top-level `Caixa`
2771    /// (per the field-shape docstring at caixa-core/src/manifest.rs — "The
2772    /// supervisor slots are flat on Caixa (vs nested under a
2773    /// `SupervisorSpec` sub-form) to keep tatara-lisp authoring at one
2774    /// level of nesting"), so the accessor's altitude is the outer
2775    /// [`Caixa`] surface rather than the composed [`SupervisorSpec`]
2776    /// altitude the sibling [`crate::supervisor::SupervisorSpec::estrategia`]
2777    /// (eafb619) accessor keys off. The two typed axes — the outer
2778    /// author-surface `Option<RestartStrategy>` on the [`Caixa`] altitude
2779    /// (author-omitted arm carried as `None`) and the inner post-
2780    /// composition `RestartStrategy` on the [`SupervisorSpec`] altitude
2781    /// (`Option` collapsed through the [`Self::supervisor_view`]
2782    /// `unwrap_or_default()` fold) — now share one accessor discipline for
2783    /// the shared substrate concept "the author-declared OTP-shaped
2784    /// sibling-restart-strategy variant that partitions the downstream
2785    /// per-Supervisor renderer's per-arm fan-out"; the outer-altitude
2786    /// `None` arm is the pre-composition presence bit every declared-slot
2787    /// enumerator ([`Self::declared_supervisor_slots`]) reads, and the
2788    /// inner-altitude non-`Option` `RestartStrategy` is the post-
2789    /// composition partition-dispatch input every strategy-arm consumer
2790    /// ([`SupervisorSpec::validate`], the future wasm-operator's per-
2791    /// Supervisor sibling-restart branch, the future M4
2792    /// `mesh.pleme.io/v1alpha1/Supervisor` CR materializer's admission
2793    /// webhook) fans on.
2794    ///
2795    /// Prior to this lift the `.estrategia` field was accessed inline at
2796    /// two production sites in `caixa-core/src/manifest.rs` — the
2797    /// [`Self::declared_supervisor_slots`] `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA`
2798    /// presence-probe arm at `if self.estrategia.is_some()` (which drives
2799    /// the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
2800    /// coherence gate's per-slot label push) and the [`Self::supervisor_view`]
2801    /// `SupervisorSpec` construction site at `estrategia:
2802    /// self.estrategia.unwrap_or_default()` (which composes the flat-
2803    /// spread outer author-surface `Option<RestartStrategy>` onto the
2804    /// inner post-composition [`SupervisorSpec`] `RestartStrategy` field
2805    /// the [`SupervisorSpec::estrategia`] accessor keys off) — two open-
2806    /// coded field-accesses that expressed no compile-time link back to
2807    /// the typed slot. A future extension of the outer `:estrategia` axis
2808    /// to a richer author surface (a per-cluster strategy override the
2809    /// operator pins through a future `:estrategia-overrides` overlay the
2810    /// MESH-COMPOSITION §III.2 supervision-canary roadmap acknowledges,
2811    /// a per-tenant strategy-alias table the M4 CR materializer resolves
2812    /// per-CR, a per-Supervisor dynamic strategy derivation the future
2813    /// adaptive-supervision engine computes from child-failure-history
2814    /// topology, a per-child-cohort strategy split the future
2815    /// `RestForCohort` extension the INSPIRATIONS.md §II.2 Erlang/OTP
2816    /// absorption roadmap acknowledges, a promotion of the plain
2817    /// `Option<RestartStrategy>` to a richer
2818    /// `AuthorDeclaredStrategy { declared, overlay }` newtype once the
2819    /// operator-resolved overlay lands) would have had to be threaded
2820    /// through both open-coded copies in lockstep or the enumerator's
2821    /// presence probe and the composition site's `unwrap_or_default()`
2822    /// fold would silently disagree on which strategy a given [`Caixa`]
2823    /// resolves to (an author's `:estrategia OneForAll` would satisfy
2824    /// the enumerator's presence probe while the composition site
2825    /// silently rendered a stale `OneForOne`, or vice versa). Lifting
2826    /// the resolution rule to a typed method on the substrate primitive
2827    /// means every downstream consumer of the caixa's per-`Caixa` outer-
2828    /// altitude sibling-restart-strategy surface reaches for exactly one
2829    /// typed dispatch — the resolver's accept-set migrates as a unit on
2830    /// any future axis addition.
2831    ///
2832    /// First outer top-level [`Caixa`] `Option<Copy>`-return supervisor-
2833    /// tree-slot flat-spread accessor for M2 supervisor-slot Copy-carry
2834    /// axes — opens the outer-`Caixa` `Option<Copy>` flat-spread
2835    /// projection pattern the sibling per-`Caixa` `:max-restarts`
2836    /// `Option<u32>` and (through the future duration-newtype landing)
2837    /// `:restart-window` `Option<Duration>` future outer-scalar lifts
2838    /// fold on. Peer of the inner-altitude [`crate::supervisor::SupervisorSpec::estrategia`]
2839    /// (eafb619) `Copy`-return sibling-restart-strategy scalar accessor on
2840    /// the post-composition [`SupervisorSpec`] altitude — same "one
2841    /// typed dispatch on the substrate primitive, thin projections at
2842    /// each consumer" discipline extended onto the pre-composition outer
2843    /// author-surface [`Caixa`] altitude for the same OTP-shaped
2844    /// sibling-restart-strategy axis. Peer of the closed outer-`Caixa`
2845    /// `Option<&Composite>` composite-reference family the sibling
2846    /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) /
2847    /// [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074) /
2848    /// [`Self::entrada`] (e4128e4) accessor pins already carry on the
2849    /// outer `Option<&Composite>` altitude — extends the outer-`Caixa`
2850    /// typed-slot accessor discipline onto the flat-spread M2 supervisor-
2851    /// tree `Option<Copy>`-discriminant sub-family the sibling M3
2852    /// [`crate::aplicacao::Placement::estrategia`] (921fe1b)
2853    /// `PlacementStrategy` `Copy`-composite-enum scalar accessor already
2854    /// pins on the inner-altitude per-`:placement` composite. Named
2855    /// `estrategia()` to match the storage field's name and the
2856    /// per-[`SupervisorSpec`] peer [`crate::supervisor::SupervisorSpec::estrategia`]
2857    /// / per-[`crate::aplicacao::Placement`] peer
2858    /// [`crate::aplicacao::Placement::estrategia`] method-name discipline
2859    /// verbatim; the accessor's identity name maps onto the canonical
2860    /// OTP-shape supervision vocabulary the [`RestartStrategy`] enum's
2861    /// docstring already carries.
2862    #[must_use]
2863    pub fn estrategia(&self) -> Option<crate::supervisor::RestartStrategy> {
2864        self.estrategia
2865    }
2866
2867    /// Substrate-canonical per-`Caixa` `:max-restarts` M2 supervisor-tree-
2868    /// slot flat-spread OTP-`MaxIntensity`-shaped restart-budget-count
2869    /// scalar accessor every consumer of the top-level manifest's per-
2870    /// Supervisor `:max-restarts` restart-budget-count axis keys off —
2871    /// returns the author-declared `:max-restarts` typed `Option<u32>`
2872    /// verbatim, `Copy`-projected from the typed slot's own `Option<u32>`
2873    /// storage (`u32` is `Copy`, so `Option<u32>` is `Copy` and the
2874    /// accessor returns by value; no borrow of `&self` past the call).
2875    /// Optional (`:max-restarts` is a flat-spread supervisor-only slot
2876    /// every non-`Supervisor`-kind `defcaixa` carries as `None` by
2877    /// `#[serde(default)]`, and every `Supervisor`-kind `defcaixa` may
2878    /// still omit to defer to the [`Self::supervisor_view`]
2879    /// `unwrap_or(5)` fold's OTP-canonical `{intensity, 5, 60}` default).
2880    ///
2881    /// The `:max-restarts` slot carries the M2 typed Erlang/OTP-shaped
2882    /// `MaxIntensity` restart-budget count that pairs with the sibling
2883    /// `:restart-window` `Period` to form the `MaxIntensity / Period`
2884    /// restart-intensity ratio the supervisor trips its own escalation on
2885    /// (INSPIRATIONS §II.2 — Erlang/OTP `supervisor` `{intensity, 5, 60}`
2886    /// worker-supervisor default; RUNTIME-PATTERNS §II.2; CAIXA-SDLC §II
2887    /// — the M2 supervisor-tree slot algebra the operator's hierarchical
2888    /// reconciliation scheduler fans on). The slot is *flat-spread* on
2889    /// the outer top-level `Caixa` (per the field-shape docstring at
2890    /// caixa-core/src/manifest.rs — "The supervisor slots are flat on
2891    /// Caixa (vs nested under a `SupervisorSpec` sub-form)"), so the
2892    /// accessor's altitude is the outer [`Caixa`] surface rather than the
2893    /// composed [`SupervisorSpec`] altitude the sibling
2894    /// [`crate::supervisor::SupervisorSpec::max_restarts`] accessor keys
2895    /// off. The two typed axes — the outer author-surface `Option<u32>`
2896    /// on the [`Caixa`] altitude (author-omitted arm carried as `None`)
2897    /// and the inner post-composition `u32` on the [`SupervisorSpec`]
2898    /// altitude (`Option` collapsed through the [`Self::supervisor_view`]
2899    /// `unwrap_or(5)` fold) — now share one accessor discipline for the
2900    /// shared substrate concept "the author-declared OTP-shaped
2901    /// restart-budget count every downstream per-Supervisor consumer's
2902    /// restart-intensity budget-vs-count comparator fans on".
2903    ///
2904    /// Prior to this lift the `.max_restarts` field was accessed inline
2905    /// at two production sites in `caixa-core/src/manifest.rs` — the
2906    /// [`Self::declared_supervisor_slots`] `SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS`
2907    /// presence-probe arm at `if self.max_restarts.is_some()` (which
2908    /// drives the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
2909    /// kind-coherence gate's per-slot label push) and the
2910    /// [`Self::supervisor_view`] `SupervisorSpec` construction site at
2911    /// `max_restarts: self.max_restarts.unwrap_or(5)` (which composes the
2912    /// flat-spread outer author-surface `Option<u32>` onto the inner
2913    /// post-composition [`SupervisorSpec`] `u32` field the
2914    /// [`SupervisorSpec::max_restarts`] accessor keys off) — two open-
2915    /// coded field-accesses that expressed no compile-time link back to
2916    /// the typed slot. A future extension of the outer `:max-restarts`
2917    /// axis to a richer author surface (a per-cluster restart-budget
2918    /// override the operator pins through a future `:max-restarts-overrides`
2919    /// overlay the MESH-COMPOSITION §III.2 supervision-canary roadmap
2920    /// acknowledges, a per-tenant restart-budget-alias table the M4 CR
2921    /// materializer resolves per-CR, a per-Supervisor dynamic restart-
2922    /// budget derivation the future adaptive-supervision engine computes
2923    /// from child-failure-history topology, a promotion of the plain
2924    /// `Option<u32>` count to a richer `{MaxR, MaxT}` per-child-cohort
2925    /// restart-budget-partition once the INSPIRATIONS §II.2 Erlang/OTP
2926    /// per-child-cohort roadmap lands) would have had to be threaded
2927    /// through both open-coded copies in lockstep or the enumerator's
2928    /// presence probe and the composition site's `unwrap_or(5)` fold
2929    /// would silently disagree on which restart-budget a given [`Caixa`]
2930    /// resolves to (an author's `:max-restarts 10` would satisfy the
2931    /// enumerator's presence probe while the composition site silently
2932    /// composed the OTP-canonical `5`, or vice versa). Lifting the
2933    /// resolution rule to a typed method on the substrate primitive means
2934    /// every downstream consumer of the caixa's per-`Caixa` outer-altitude
2935    /// restart-budget-count surface reaches for exactly one typed dispatch
2936    /// — the resolver's accept-set migrates as a unit on any future axis
2937    /// addition.
2938    ///
2939    /// Second outer top-level [`Caixa`] `Option<Copy>`-return supervisor-
2940    /// tree-slot flat-spread accessor for M2 supervisor-slot Copy-carry
2941    /// axes — folds on the outer-`Caixa` `Option<Copy>` flat-spread
2942    /// projection pattern the sibling per-`Caixa`
2943    /// [`Self::estrategia`] (ed04d3c) accessor opened, extends the
2944    /// sub-family onto the sibling `Option<u32>` restart-budget-count arm.
2945    /// Peer of the inner-altitude
2946    /// [`crate::supervisor::SupervisorSpec::max_restarts`] `u32` accessor
2947    /// on the post-composition [`SupervisorSpec`] altitude — same "one
2948    /// typed dispatch on the substrate primitive, thin projections at
2949    /// each consumer" discipline extended onto the pre-composition outer
2950    /// author-surface [`Caixa`] altitude for the same OTP-`MaxIntensity`-
2951    /// shaped restart-budget-count axis. Named `max_restarts()` to match
2952    /// the storage field's name and the per-[`SupervisorSpec`] peer
2953    /// [`crate::supervisor::SupervisorSpec::max_restarts`] method-name
2954    /// discipline verbatim; the accessor's identity maps onto the
2955    /// canonical OTP-shape supervision vocabulary the `:max-restarts`
2956    /// field's docstring already carries.
2957    #[must_use]
2958    pub const fn max_restarts(&self) -> Option<u32> {
2959        self.max_restarts
2960    }
2961
2962    /// Substrate-canonical per-`Caixa` `:restart-window` M2 supervisor-
2963    /// tree-slot flat-spread OTP-`Period`-shaped restart-intensity-
2964    /// denominator raw-duration-string scalar accessor every consumer of
2965    /// the top-level manifest's per-Supervisor `:restart-window` sliding-
2966    /// window axis keys off — returns the author-declared `:restart-window`
2967    /// typed `Option<String>` verbatim as an `Option<&str>`, borrowed
2968    /// from the typed slot's own `Option<String>` storage. `None` when
2969    /// the slot is absent (the canonical "never reset — every restart
2970    /// across the supervisor's lifetime counts against the sibling
2971    /// `:max-restarts` budget" sentinel every non-`Supervisor`-kind
2972    /// `defcaixa` carries by `#[serde(default)]` and every
2973    /// `Supervisor`-kind `defcaixa` may still omit to defer to the
2974    /// [`Self::supervisor_view`] `restart_window: None` composition
2975    /// through the [`crate::supervisor::duration_codec::parse`] soft-
2976    /// swallow `.and_then(|s| … .ok())` fold).
2977    ///
2978    /// The `:restart-window` slot carries the raw M2 typed Erlang/OTP-
2979    /// shaped `Period` sliding-observation-interval duration string that
2980    /// pairs with the sibling `:max-restarts` `MaxIntensity` restart-
2981    /// budget count to form the `MaxIntensity / Period` restart-intensity
2982    /// ratio the supervisor trips its own escalation on (INSPIRATIONS
2983    /// §II.2 — Erlang/OTP `supervisor` `{intensity, 5, 60}` worker-
2984    /// supervisor default; RUNTIME-PATTERNS §II.2). The outer-`Caixa`
2985    /// slot stores the raw duration string (`"60s"`, `"5m"`, `"500ms"`)
2986    /// authored under `:restart-window` — the typed [`SupervisorSpec`]
2987    /// holds an `Option<Duration>` routed through the shared
2988    /// [`crate::supervisor::duration_codec`] via `with = "duration_codec"`
2989    /// — so the outer altitude's accessor returns `Option<&str>` (raw
2990    /// authoring surface) while the inner altitude's
2991    /// [`crate::supervisor::SupervisorSpec::restart_window`] returns
2992    /// `Option<Duration>` (parsed typed surface). The parse-refusal arm
2993    /// is closed by the sibling [`Self::validate_restart_window`] gate
2994    /// that surfaces [`ManifestError::RestartWindowMalformed`] naming
2995    /// the offending value; the view-construction path
2996    /// [`Self::supervisor_view`] soft-swallows the same parse error to
2997    /// `None` to keep the view best-effort.
2998    ///
2999    /// Prior to this lift the `.restart_window` field was accessed inline
3000    /// at three production sites in `caixa-core/src/manifest.rs` — the
3001    /// [`Self::declared_supervisor_slots`]
3002    /// `SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW` presence-probe arm at
3003    /// `if self.restart_window.is_some()` (which drives the
3004    /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
3005    /// coherence gate's per-slot label push), the
3006    /// [`Self::validate_restart_window`] `let Some(s) =
3007    /// self.restart_window.as_deref()` empty-and-shape gate binding
3008    /// (which folds the raw string through the shared
3009    /// [`crate::supervisor::duration_codec::parse`] to surface
3010    /// [`ManifestError::RestartWindowMalformed`] naming the offending
3011    /// value), and the [`Self::supervisor_view`] `self.restart_window
3012    /// .as_deref().and_then(…)` view-construction fold (which composes
3013    /// the flat-spread outer author-surface `Option<String>` onto the
3014    /// inner post-composition [`SupervisorSpec`] `Option<Duration>`
3015    /// field the [`SupervisorSpec::restart_window`] accessor keys off) —
3016    /// three open-coded field-accesses that expressed no compile-time
3017    /// link back to the typed slot. A future extension of the outer
3018    /// `:restart-window` axis to a richer author surface (a per-cluster
3019    /// window override, a per-tenant window-alias table, a per-Supervisor
3020    /// dynamic window derivation the future adaptive-supervision engine
3021    /// computes from child-failure-history topology, a promotion of the
3022    /// plain `Option<String>` raw duration to a typed `Option<Duration>`
3023    /// once the future author-surface parser lands at the [`Caixa`]
3024    /// altitude and the raw-string form is retired) would have had to be
3025    /// threaded through every open-coded copy in lockstep or the three
3026    /// consumers would silently disagree on which raw string a given
3027    /// [`Caixa`] resolves to. Lifting the resolution rule to a typed
3028    /// method on the substrate primitive means every downstream consumer
3029    /// of the caixa's per-`Caixa` outer-altitude restart-window raw-
3030    /// string surface reaches for exactly one typed dispatch — the
3031    /// resolver's accept-set migrates as a unit on any future axis
3032    /// addition.
3033    ///
3034    /// Third outer top-level [`Caixa`] supervisor-tree-slot flat-spread
3035    /// accessor — folds on the outer-`Caixa` M2 supervisor-tree flat-
3036    /// spread projection pattern the sibling per-`Caixa`
3037    /// [`Self::estrategia`] (ed04d3c) `Option<Copy>` and
3038    /// [`Self::max_restarts`] `Option<Copy>` accessors opened, extends
3039    /// the sub-family onto the sibling `Option<&str>` raw-duration-
3040    /// string arm (the outer altitude's raw-string form; the inner
3041    /// altitude's parsed [`Duration`] form is the peer
3042    /// [`crate::supervisor::SupervisorSpec::restart_window`] accessor).
3043    /// Peer of the sibling per-`Caixa` `Option<&str>`-return scalar
3044    /// accessors ([`Self::licenca`] / [`Self::repositorio`] /
3045    /// [`Self::descricao`] / [`Self::edicao`]) on the universal-axis
3046    /// outer scalar-projection family the outer-`Caixa` `Option<&str>`
3047    /// sub-family already carries — same "one typed dispatch on the
3048    /// substrate primitive, thin projections at each consumer"
3049    /// discipline extended onto the M2 supervisor-tree flat-spread
3050    /// `Option<&str>` raw-duration-string arm. Named `restart_window()`
3051    /// to match the storage field's name and the per-[`SupervisorSpec`]
3052    /// peer [`crate::supervisor::SupervisorSpec::restart_window`]
3053    /// method-name discipline verbatim; the accessor's identity maps
3054    /// onto the canonical OTP-shape supervision vocabulary the
3055    /// `:restart-window` field's docstring already carries.
3056    #[must_use]
3057    pub fn restart_window(&self) -> Option<&str> {
3058        self.restart_window.as_deref()
3059    }
3060
3061    /// Substrate-canonical per-`Caixa` `:upgrade-from` M2 typed-slot
3062    /// outer-composite OTP-appup-shaped per-prior-version migration-
3063    /// entry-list slice accessor every consumer of the top-level
3064    /// manifest's per-Servico hot-upgrade-block `&[UpgradeFromEntry]`
3065    /// slice-view keys off — returns the author-declared `:upgrade-from`
3066    /// typed `Vec<UpgradeFromEntry>` verbatim as a
3067    /// `&[UpgradeFromEntry]` slice-view over the same backing buffer
3068    /// the raw `self.upgrade_from.as_slice()` field access borrows
3069    /// from. Empty-slice-carrying (the "no hot-upgrade path declared"
3070    /// arm every `defcaixa` without an `:upgrade-from` block carries;
3071    /// the [`Self::from_lisp`] derive folds an omitted `:upgrade-from`
3072    /// through `#[serde(default)]` to `Vec::new()`, so a `Caixa` past
3073    /// parse definitionally carries a `Vec<UpgradeFromEntry>` slot —
3074    /// possibly empty — and the returned `&[UpgradeFromEntry]`
3075    /// degenerates to an empty slice on that arm without any silent
3076    /// `None` collapse).
3077    ///
3078    /// The outer `:upgrade-from` slot carries the M2 typed OTP-appup
3079    /// migration block — the load-bearing container of every per-
3080    /// prior-`:versao` migration-instruction list the wasm-operator
3081    /// dispatches on at hot-upgrade time (INSPIRATIONS §II.4 — OTP
3082    /// `.appup` per-prior-version `LoadModule | StateChange |
3083    /// SoftPurge | Purge | Restart` instruction algebra translated
3084    /// onto pleme-io's typed `:upgrade-from :from` + `:instructions`
3085    /// entry list; CAIXA-SDLC §II — the typed-M2 slot algebra the
3086    /// operator's hot-upgrade dispatch fans on). Every per-entry axis
3087    /// threads through a lifted per-entry accessor on the
3088    /// [`UpgradeFromEntry`] type: the
3089    /// [`UpgradeFromEntry::prior_versao`] SemVer-shaped previous-
3090    /// version scalar accessor and the
3091    /// [`UpgradeFromEntry::instructions`] `&[UpgradeInstruction]`-
3092    /// return per-entry instruction-list accessor (0137e5a). Every
3093    /// downstream consumer of the hot-upgrade path first passes
3094    /// through this outer accessor onto the slice and then dispatches
3095    /// per-entry through the inner accessors — the two-level dispatch
3096    /// means every per-`:upgrade-from` reader now routes through a
3097    /// typed dispatch on the substrate primitive at both altitudes.
3098    ///
3099    /// Prior to this lift the `.upgrade_from` `Vec<UpgradeFromEntry>`
3100    /// slot was accessed inline at production sites across three
3101    /// files — the [`Self::declared_servico_slots`] M2 declared-slot
3102    /// enumerator's `self.upgrade_from.is_empty()` presence probe
3103    /// (caixa-core/src/manifest.rs, which drives the
3104    /// `M2_AUTHOR_KEY_UPGRADE_FROM` kebab-case author-label push every
3105    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
3106    /// gate reads), the [`crate::StandardLayout::verify`] per-
3107    /// `:upgrade-from` three-stage validation pass (caixa-core/src/
3108    /// layout.rs, which fans onto the
3109    /// [`crate::upgrade::validate_upgrade_from`] per-entry shape +
3110    /// cross-entry duplicate gate, the
3111    /// [`crate::upgrade::validate_upgrade_from_against_versao`]
3112    /// SemVer-precedence cross-slot gate, the
3113    /// [`crate::upgrade::validate_upgrade_from_against_behavior`]
3114    /// `:state-change` ↔ `:on-state-change` cross-slot composition
3115    /// gate, and the per-instruction script-path existence-probe walk
3116    /// that reads each entry's [`UpgradeFromEntry::instructions`] to
3117    /// resolve every declared migration script against the layout
3118    /// root), and the [`crate::render::servico_m2_overlay`] per-
3119    /// Servico M2 overlay emitter's `!caixa.upgrade_from.is_empty()`
3120    /// presence gate + `serde_yaml::to_value(&caixa.upgrade_from)`
3121    /// projection (caixa-core/src/render.rs, which drives the
3122    /// `M2_KEY_UPGRADE_FROM`-keyed `serde_yaml` projection every
3123    /// `caixa-helm` / `caixa-flux` Servico values-block emitter fans
3124    /// on and lands as the ComputeUnit CR's `spec.upgradeFrom` field).
3125    /// A future extension of the outer `:upgrade-from` axis (a per-
3126    /// cluster `:upgrade-overrides` overlay the wasm-engine operator
3127    /// resolves at admission time so a cluster-specific migration
3128    /// policy can tighten a caixa-declared step without re-authoring
3129    /// the `caixa.lisp`, promotion of the plain
3130    /// `Vec<UpgradeFromEntry>` to a richer `{static, dynamic}`
3131    /// partition once runtime-resolved hot-upgrade instructions land,
3132    /// per-entry priority annotation once multi-strategy fan-out
3133    /// lands) would have had to be threaded through all six open-
3134    /// coded copies in lockstep or one consumer would silently
3135    /// disagree with the peers on which upgrade slice a given Caixa
3136    /// resolves to — a six-consumer split at the enumerator, the
3137    /// three-stage validate pass, the script-path probe walk, and the
3138    /// M2 overlay emitter, far from the source `caixa.lisp` with no
3139    /// field naming the upgrade-drift root cause. Lifting the
3140    /// resolution rule to a typed method on the substrate primitive
3141    /// means every downstream consumer of the caixa's per-`Caixa`
3142    /// OTP-appup outer-slice surface reaches for exactly one typed
3143    /// dispatch — the resolver's accept-set migrates as a unit on any
3144    /// future axis addition.
3145    ///
3146    /// First outer top-level [`Caixa`] `&[Composite]`-return slice
3147    /// accessor for M2 / M3 typed-slot vec-carry axes — opens the
3148    /// outer-`Caixa` `&[Composite]` composite-slice projection
3149    /// pattern the sibling `:children`
3150    /// [`crate::supervisor::ChildSpec`] / `:membros`
3151    /// [`crate::aplicacao::Membro`] / `:contratos`
3152    /// [`crate::aplicacao::WitContract`] future outer-composite-slice
3153    /// lifts fold on. Peer of the closed outer-`Caixa` scalar
3154    /// `Option<&Composite>` composite-reference family the sibling
3155    /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) /
3156    /// [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074) /
3157    /// [`Self::entrada`] (e4128e4) accessors closed on the outer
3158    /// `Option<&Composite>` altitude, extended here to the outer-
3159    /// `Caixa` `&[Composite]` vec-carry altitude. Peer at the inner
3160    /// altitude of [`crate::upgrade::UpgradeFromEntry::instructions`]
3161    /// (0137e5a) — same "one typed dispatch on the substrate
3162    /// primitive, thin projections at each consumer" discipline
3163    /// folded onto the outer top-level [`Caixa`] altitude, opening the
3164    /// M2 vec-carry slot family's outer-composite-slice axis. Sibling
3165    /// in shape to the peer outer-`Caixa` `&[Dep]`-return
3166    /// [`Self::deps`] (ad34b4e) / [`Self::deps_dev`] (f7fd81e) and
3167    /// `&[String]`-return [`Self::autores`] (b5d813f) /
3168    /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`]
3169    /// (8a36c23) / [`Self::exe`] (65d9527) / [`Self::servicos`]
3170    /// (611f78b) slice-accessors on the sibling outer-`Caixa` scalar-
3171    /// element vec-carry axes — folds the "outer [`Caixa`] `&[T]`
3172    /// slice" projection pattern onto the sibling M2 typed-composite-
3173    /// element axis (`UpgradeFromEntry` composite, matching the
3174    /// per-inner [`UpgradeFromEntry::instructions`] element type at a
3175    /// different altitude).
3176    ///
3177    /// Returns `&[UpgradeFromEntry]` (not `&Vec<UpgradeFromEntry>`)
3178    /// because every downstream consumer of the hot-upgrade list
3179    /// treats it as a read-only sequence — the slice-view is the
3180    /// narrowest borrow that supports every present + roadmapped
3181    /// consumer (`.iter()`, `.len()`, `.is_empty()`, `serde` slice-
3182    /// serialization through
3183    /// `serde_yaml::to_value(&[UpgradeFromEntry])`) without leaking
3184    /// the backing `Vec`'s grow/push/reserve surface no consumer of
3185    /// the typed view reaches for (the storage-side `Vec` remains
3186    /// reachable through the `pub upgrade_from` field for the
3187    /// mutation-carrying serde round-trip and per-test fixture-
3188    /// mutation paths). Named `upgrade_from()` to match the storage
3189    /// field's `snake_case` name; the kebab-case author-surface tag
3190    /// `:upgrade-from` is the same axis after tatara-lisp's
3191    /// kebab↔snake fold and the accessor's identity maps onto the
3192    /// canonical CAIXA-SDLC §II vocabulary the slot's docstring
3193    /// already carries.
3194    #[must_use]
3195    pub fn upgrade_from(&self) -> &[UpgradeFromEntry] {
3196        self.upgrade_from.as_slice()
3197    }
3198
3199    /// Substrate-canonical per-`Caixa` `:children` M2 supervisor-tree-
3200    /// slot outer-composite OTP-shaped per-supervisor static-child-list
3201    /// slice accessor every consumer of the top-level manifest's per-
3202    /// Supervisor `&[ChildSpec]` slice-view keys off — returns the
3203    /// author-declared `:children` typed `Vec<crate::supervisor::ChildSpec>`
3204    /// verbatim as a `&[crate::supervisor::ChildSpec]` slice-view over
3205    /// the same backing buffer the raw `self.children.as_slice()` field
3206    /// access borrows from. Empty-slice-carrying (the "no static children
3207    /// declared" arm every non-`Supervisor`-kind `defcaixa` carries by
3208    /// #[serde(default)] and every `SimpleOneForOne` supervisor carries
3209    /// by [`crate::supervisor::SupervisorError::SimpleOneForOneWithStaticChildren`]
3210    /// gate; the returned `&[ChildSpec]` degenerates to an empty slice
3211    /// on those arms without any silent `None` collapse).
3212    ///
3213    /// The outer `:children` slot carries the M2 typed OTP-supervisor
3214    /// static-child list — the load-bearing container of every per-
3215    /// child `{caixa, versao, restart}` triple the wasm-operator's
3216    /// hierarchical reconciler dispatches on at supervisor-tree
3217    /// materialization time (INSPIRATIONS §II.2 — OTP `supervisor:init/1`
3218    /// static-child list translated onto pleme-io's typed
3219    /// [`crate::supervisor::ChildSpec`] entry list; CAIXA-SDLC §II —
3220    /// the typed-M2 slot algebra the operator's per-supervisor fan-out
3221    /// dispatch fans on). Every per-child axis threads through a lifted
3222    /// per-entry accessor on the [`crate::supervisor::ChildSpec`] type:
3223    /// the [`crate::supervisor::ChildSpec::nome`] DNS-1123-label
3224    /// child-caixa-identity scalar accessor, the peer versao SemVer-2
3225    /// version-requirement scalar accessor, and the
3226    /// [`crate::supervisor::ChildSpec::restart`] `Copy`-composite-enum
3227    /// per-child post-exit restart-decision-policy discriminant
3228    /// accessor (dfb4a81). Every downstream consumer of the supervisor-
3229    /// tree path first passes through this outer accessor onto the
3230    /// slice and then dispatches per-child through the inner accessors
3231    /// — the two-level dispatch means every per-`:children` reader now
3232    /// routes through a typed dispatch on the substrate primitive at
3233    /// both altitudes.
3234    ///
3235    /// Prior to this lift the `.children` `Vec<ChildSpec>` slot was
3236    /// accessed inline at three production sites across two files —
3237    /// the [`Self::declared_supervisor_slots`] supervisor-tree
3238    /// declared-slot enumerator's `!self.children.is_empty()` presence
3239    /// probe (caixa-core/src/manifest.rs, which drives the
3240    /// `SUPERVISOR_AUTHOR_KEY_CHILDREN` kebab-case author-label push
3241    /// every [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
3242    /// kind-coherence gate reads), the [`Self::supervisor_view`]
3243    /// per-supervisor typed-view composer's `self.children.clone()`
3244    /// per-child fold-in path (caixa-core/src/manifest.rs, which
3245    /// materializes the typed [`crate::supervisor::SupervisorSpec`]
3246    /// view every [`crate::StandardLayout::verify`] Supervisor-arm gate
3247    /// dispatches on), and the [`crate::StandardLayout::verify`] per-
3248    /// `:children :caixa` self-parent refusal probe's
3249    /// `&caixa.children`-borrowed
3250    /// [`crate::supervisor::validate_no_self_supervision`] input
3251    /// (caixa-core/src/layout.rs, which pins the "no child names the
3252    /// supervisor's own `:nome`" cross-slot coherence gate). A future
3253    /// extension of the outer `:children` axis (a per-cluster
3254    /// `:children-overrides` overlay the wasm-engine operator resolves
3255    /// at admission time so a cluster-specific child-set can tighten
3256    /// a caixa-declared list without re-authoring the `caixa.lisp`,
3257    /// promotion of the plain `Vec<ChildSpec>` to a richer
3258    /// `{static, dynamic}` partition once Erlang/OTP's
3259    /// `simple_one_for_one`-shaped dynamic-child slot lands as a typed
3260    /// axis, per-child priority annotation once multi-strategy fan-out
3261    /// lands) would have had to be threaded through all three open-
3262    /// coded copies in lockstep or one consumer would silently
3263    /// disagree with the peers on which child slice a given Caixa
3264    /// resolves to — the enumerator's presence probe reading the raw
3265    /// slot while the peer view-composer's fold-in path read an
3266    /// operator-resolved slot would silently split the paired
3267    /// declared-slot enumerator and typed-view composition, and the
3268    /// [`crate::supervisor::validate_no_self_supervision`] self-parent
3269    /// refusal probe reading a third borrow would silently drift the
3270    /// cross-slot coherence gate's traversal input from the two peers,
3271    /// a three-consumer split at the enumerator, the view composer,
3272    /// and the self-parent gate far from the source `caixa.lisp` with
3273    /// no field naming the child-set-drift root cause. Lifting the
3274    /// resolution rule to a typed method on the substrate primitive
3275    /// means every downstream consumer of the caixa's per-`Caixa`
3276    /// OTP-supervisor outer-slice surface reaches for exactly one
3277    /// typed dispatch — the resolver's accept-set migrates as a unit
3278    /// on any future axis addition.
3279    ///
3280    /// Second outer top-level [`Caixa`] `&[Composite]`-return slice
3281    /// accessor for M2 / M3 typed-slot vec-carry axes — folds on the
3282    /// outer-`Caixa` `&[Composite]` composite-slice sub-family the
3283    /// sibling [`Self::upgrade_from`] (2a1f907) accessor opened, peer
3284    /// at the outer altitude of the closed inner-`SupervisorSpec`
3285    /// [`crate::SupervisorSpec::children`] (bc92bce) accessor on the
3286    /// same OTP-supervisor static-child-list axis — same "byte-equal,
3287    /// borrow-shared" outer-accessor discipline extended onto the
3288    /// second outer-`Caixa` `&[Composite]` vec-carry axis. Sibling in
3289    /// shape to the peer outer-`Caixa` `&[Dep]`-return [`Self::deps`]
3290    /// (ad34b4e) / [`Self::deps_dev`] (f7fd81e) and `&[String]`-return
3291    /// [`Self::autores`] (b5d813f) / [`Self::etiquetas`] (78c7d3c) /
3292    /// [`Self::bibliotecas`] (8a36c23) / [`Self::exe`] (65d9527) /
3293    /// [`Self::servicos`] (611f78b) slice-accessors on the sibling
3294    /// outer-`Caixa` scalar-element vec-carry axes — folds the "outer
3295    /// [`Caixa`] `&[T]` slice" projection pattern onto the sibling
3296    /// M2 typed-composite-element axis
3297    /// ([`crate::supervisor::ChildSpec`] composite, matching the
3298    /// per-inner [`crate::SupervisorSpec::children`] element type at a
3299    /// different altitude).
3300    ///
3301    /// Returns `&[crate::supervisor::ChildSpec]` (not
3302    /// `&Vec<ChildSpec>`) because every downstream consumer of the
3303    /// child list treats it as a read-only sequence — the slice-view
3304    /// is the narrowest borrow that supports every present +
3305    /// roadmapped consumer (`.iter()`, `.len()`, `.is_empty()`, the
3306    /// [`crate::supervisor::validate_no_self_supervision`] `&[ChildSpec]`
3307    /// input, `serde` slice-serialization) without leaking the backing
3308    /// `Vec`'s grow/push/reserve surface no consumer of the typed view
3309    /// reaches for (the storage-side `Vec` remains reachable through
3310    /// the `pub children` field for the mutation-carrying serde round-
3311    /// trip and per-test fixture-mutation paths, including the
3312    /// [`Self::supervisor_view`] fold-in path that clones the slot
3313    /// into the typed view). Named `children()` to match the storage
3314    /// field's name verbatim and the tatara-lisp author-surface term
3315    /// (`:children`) the field's own docstring already carries; the
3316    /// accessor's identity maps onto the canonical OTP supervision
3317    /// vocabulary the [`Caixa::children`] field's docstring already
3318    /// reaches for ("Static children of a supervisor").
3319    #[must_use]
3320    pub fn children(&self) -> &[crate::supervisor::ChildSpec] {
3321        self.children.as_slice()
3322    }
3323
3324    /// Substrate-canonical per-`Caixa` `:membros` M3 mesh-slot outer-
3325    /// composite MESH-COMPOSITION-shaped per-Aplicacao member-list slice
3326    /// accessor every consumer of the top-level manifest's per-Aplicacao
3327    /// `&[crate::aplicacao::Membro]` slice-view keys off — returns the
3328    /// author-declared `:membros` typed `Vec<crate::aplicacao::Membro>`
3329    /// verbatim as a `&[crate::aplicacao::Membro]` slice-view over the
3330    /// same backing buffer the raw `self.membros.as_slice()` field access
3331    /// borrows from. Empty-slice-carrying (the "no members declared" arm
3332    /// every non-`Aplicacao`-kind `defcaixa` carries by `#[serde(default)]`
3333    /// and every partially-authored Aplicacao carries before the
3334    /// [`crate::AplicacaoError::MembrosEmpty`] gate fires; the returned
3335    /// `&[Membro]` degenerates to an empty slice on those arms without any
3336    /// silent `None` collapse).
3337    ///
3338    /// The outer `:membros` slot carries the M3 typed MESH-COMPOSITION
3339    /// per-Aplicacao member list — the load-bearing container of every
3340    /// per-member `{caixa, versao}` pair the caixa-mesh renderer's
3341    /// per-Aplicacao program-emission dispatch fans on at mesh-artifact
3342    /// materialization time (MESH-COMPOSITION §III.1 — the typed graph's
3343    /// vertex set the `:contratos` `:de`/`:para` edges resolve against and
3344    /// the `:entrada :para` external-gateway destination validates
3345    /// against; CAIXA-SDLC §II — the typed-M3 slot algebra the operator's
3346    /// per-Aplicacao fan-out dispatch fans on). Every per-member axis
3347    /// threads through a lifted per-entry accessor on the
3348    /// [`crate::aplicacao::Membro`] type: the
3349    /// [`crate::aplicacao::Membro::nome`] DNS-1123-label member-caixa-
3350    /// identity scalar accessor (4a32abf) and the peer
3351    /// [`crate::aplicacao::Membro::versao_requirement`] SemVer-2
3352    /// version-requirement scalar accessor (a40b0e3). Every downstream
3353    /// consumer of the mesh-graph path first passes through this outer
3354    /// accessor onto the slice and then dispatches per-member through
3355    /// the inner accessors — the two-level dispatch means every per-
3356    /// `:membros` reader now routes through a typed dispatch on the
3357    /// substrate primitive at both altitudes.
3358    ///
3359    /// Prior to this lift the `.membros` `Vec<Membro>` slot was accessed
3360    /// inline at three production sites across two files — the
3361    /// [`Self::declared_mesh_slots`] mesh-slot declared-slot
3362    /// enumerator's `!self.membros.is_empty()` presence probe
3363    /// (caixa-core/src/manifest.rs, which drives the
3364    /// `M3_AUTHOR_KEY_MEMBROS` kebab-case author-label push every
3365    /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-coherence
3366    /// gate reads), the [`Self::aplicacao_view`] per-Aplicacao typed-view
3367    /// composer's `self.membros.clone()` per-member fold-in path
3368    /// (caixa-core/src/manifest.rs, which materializes the typed
3369    /// [`crate::aplicacao::AplicacaoSpec`] view every
3370    /// [`crate::StandardLayout::verify`] Aplicacao-arm gate dispatches
3371    /// on), and the [`crate::StandardLayout::verify`] per-`:membros
3372    /// :caixa` self-membership refusal probe's `&caixa.membros`-borrowed
3373    /// [`crate::aplicacao::validate_no_self_membership`] input
3374    /// (caixa-core/src/layout.rs, which pins the "no member names the
3375    /// Aplicacao's own `:nome`" cross-slot coherence gate). A future
3376    /// extension of the outer `:membros` axis (a per-cluster
3377    /// `:membros-overrides` overlay the wasm-engine operator resolves at
3378    /// admission time so a cluster-specific member-set can tighten a
3379    /// caixa-declared list without re-authoring the `caixa.lisp`,
3380    /// promotion of the plain `Vec<Membro>` to a richer
3381    /// `{static, dynamic}` partition once runtime-resolved Aplicacao
3382    /// members land as a typed axis, per-member priority annotation once
3383    /// multi-strategy fan-out lands) would have had to be threaded
3384    /// through all three open-coded copies in lockstep or one consumer
3385    /// would silently disagree with the peers on which member slice a
3386    /// given Caixa resolves to — the enumerator's presence probe reading
3387    /// the raw slot while the peer view-composer's fold-in path read an
3388    /// operator-resolved slot would silently split the paired
3389    /// declared-slot enumerator and typed-view composition, and the
3390    /// [`crate::aplicacao::validate_no_self_membership`] self-membership
3391    /// refusal probe reading a third borrow would silently drift the
3392    /// cross-slot coherence gate's traversal input from the two peers, a
3393    /// three-consumer split at the enumerator, the view composer, and
3394    /// the self-membership gate far from the source `caixa.lisp` with no
3395    /// field naming the member-set-drift root cause. Lifting the
3396    /// resolution rule to a typed method on the substrate primitive
3397    /// means every downstream consumer of the caixa's per-`Caixa`
3398    /// MESH-COMPOSITION outer-slice surface reaches for exactly one
3399    /// typed dispatch — the resolver's accept-set migrates as a unit on
3400    /// any future axis addition.
3401    ///
3402    /// Third outer top-level [`Caixa`] `&[Composite]`-return slice
3403    /// accessor for M2 / M3 typed-slot vec-carry axes — opens the outer-
3404    /// `Caixa` M3 mesh-slot arm of the `&[Composite]` composite-slice
3405    /// sub-family the sibling M2 [`Self::upgrade_from`] (2a1f907) /
3406    /// [`Self::children`] (c17b51e) accessors opened for the M2 vec-carry
3407    /// altitude. Peer at the outer altitude of the closed inner-
3408    /// [`crate::AplicacaoSpec::membros`] (6c77e36) accessor on the same
3409    /// MESH-COMPOSITION per-Aplicacao member-list axis — the two
3410    /// altitudes now share the same "byte-equal, borrow-shared" outer-
3411    /// accessor discipline. Sibling in shape to the peer outer-`Caixa`
3412    /// `&[Dep]`-return [`Self::deps`] (ad34b4e) / [`Self::deps_dev`]
3413    /// (f7fd81e) and `&[String]`-return [`Self::autores`] (b5d813f) /
3414    /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`] (8a36c23) /
3415    /// [`Self::exe`] (65d9527) / [`Self::servicos`] (611f78b) slice-
3416    /// accessors on the sibling outer-`Caixa` scalar-element vec-carry
3417    /// axes — folds the "outer [`Caixa`] `&[T]` slice" projection
3418    /// pattern onto the sibling M3 typed-composite-element axis
3419    /// ([`crate::aplicacao::Membro`] composite, matching the per-inner
3420    /// [`crate::AplicacaoSpec::membros`] element type at a different
3421    /// altitude).
3422    ///
3423    /// Returns `&[crate::aplicacao::Membro]` (not `&Vec<Membro>`)
3424    /// because every downstream consumer of the member list treats it
3425    /// as a read-only sequence — the slice-view is the narrowest borrow
3426    /// that supports every present + roadmapped consumer (`.iter()`,
3427    /// `.len()`, `.is_empty()`, the
3428    /// [`crate::aplicacao::validate_no_self_membership`] `&[Membro]`
3429    /// input, `serde` slice-serialization) without leaking the backing
3430    /// `Vec`'s grow/push/reserve surface no consumer of the typed view
3431    /// reaches for (the storage-side `Vec` remains reachable through the
3432    /// `pub membros` field for the mutation-carrying serde round-trip
3433    /// and per-test fixture-mutation paths, including the
3434    /// [`Self::aplicacao_view`] fold-in path that clones the slot into
3435    /// the typed view). Named `membros()` to match the storage field's
3436    /// name verbatim and the tatara-lisp author-surface term
3437    /// (`:membros`) the field's own docstring already carries; the
3438    /// accessor's identity maps onto the canonical MESH-COMPOSITION
3439    /// vocabulary the [`Caixa::membros`] field's docstring already
3440    /// reaches for ("Member Servicos that make up this Aplicacao").
3441    #[must_use]
3442    pub fn membros(&self) -> &[crate::aplicacao::Membro] {
3443        self.membros.as_slice()
3444    }
3445
3446    /// Substrate-canonical per-`Caixa` `:contratos` M3 mesh-slot outer-
3447    /// composite MESH-COMPOSITION-shaped per-Aplicacao WIT-typed
3448    /// inter-Servico contract-list slice accessor every consumer of the
3449    /// top-level manifest's per-Aplicacao `&[crate::aplicacao::WitContract]`
3450    /// slice-view keys off — returns the author-declared `:contratos`
3451    /// typed `Vec<crate::aplicacao::WitContract>` verbatim as a
3452    /// `&[crate::aplicacao::WitContract]` slice-view over the same
3453    /// backing buffer the raw `self.contratos.as_slice()` field access
3454    /// borrows from. Empty-slice-carrying (the "no contracts declared"
3455    /// arm every non-`Aplicacao`-kind `defcaixa` carries by
3456    /// `#[serde(default)]` and every leaf Aplicacao carrying only a
3457    /// single member with no inter-Servico edge carries; the returned
3458    /// `&[WitContract]` degenerates to an empty slice on those arms
3459    /// without any silent `None` collapse).
3460    ///
3461    /// The outer `:contratos` slot carries the M3 typed MESH-COMPOSITION
3462    /// per-Aplicacao WIT-typed inter-Servico edge list — the load-bearing
3463    /// container of every per-edge `{de, para, wit, endpoint | subject |
3464    /// slot}` quadruple the caixa-mesh renderer's per-Aplicacao
3465    /// `CiliumNetworkPolicy` fan-out (one L7 policy per edge —
3466    /// MESH-COMPOSITION §III.2 point 2) and per-`(:de, :para)`
3467    /// adjacency-list seed dispatch on at mesh-artifact materialization
3468    /// time (MESH-COMPOSITION §III.1 — the typed graph's edge set the
3469    /// `:membros` vertex set resolves against, closed by the
3470    /// [`crate::AplicacaoError::ContractoUnknownMember`] / cycle-refusal
3471    /// gates in §III.3; CAIXA-SDLC §II — the typed-M3 slot algebra the
3472    /// operator's per-Aplicacao fan-out dispatch fans on). Every
3473    /// per-edge axis threads through a lifted per-entry accessor on the
3474    /// [`crate::aplicacao::WitContract`] type: the peer `de` / `para`
3475    /// DNS-1123-label member-caixa-name endpoint scalar accessors, the
3476    /// [`crate::aplicacao::WitContract::endpoint`] (7020470) HTTP-shape
3477    /// / [`crate::aplicacao::WitContract::subject`] (90de675)
3478    /// NATS-pub-sub-shape / [`crate::aplicacao::WitContract::slot`]
3479    /// (ed22b66) `wasi:keyvalue/store`-shape payload-carrier accessors,
3480    /// and the WIT-world discriminant. Every downstream consumer of the
3481    /// mesh-graph edge path first passes through this outer accessor
3482    /// onto the slice and then dispatches per-contract through the
3483    /// inner accessors — the two-level dispatch means every
3484    /// per-`:contratos` reader now routes through a typed dispatch on
3485    /// the substrate primitive at both altitudes.
3486    ///
3487    /// Prior to this lift the `.contratos` `Vec<WitContract>` slot was
3488    /// accessed inline at two production sites in
3489    /// caixa-core/src/manifest.rs — the [`Self::declared_mesh_slots`]
3490    /// mesh-slot declared-slot enumerator's
3491    /// `!self.contratos.is_empty()` presence probe (which drives the
3492    /// `M3_AUTHOR_KEY_CONTRATOS` kebab-case author-label push every
3493    /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-coherence
3494    /// gate reads) and the [`Self::aplicacao_view`] per-Aplicacao
3495    /// typed-view composer's `self.contratos.clone()` per-contract
3496    /// fold-in path (which materializes the typed
3497    /// [`crate::aplicacao::AplicacaoSpec`] view every
3498    /// [`crate::StandardLayout::verify`] Aplicacao-arm gate and every
3499    /// downstream `caixa-mesh` renderer dispatches on). A future
3500    /// extension of the outer `:contratos` axis (a per-cluster
3501    /// `:contratos-overrides` overlay the wasm-engine operator resolves
3502    /// at admission time so a cluster-specific edge-set can tighten a
3503    /// caixa-declared list without re-authoring the `caixa.lisp`,
3504    /// promotion of the plain `Vec<WitContract>` to a richer
3505    /// `{static, dynamic}` partition once runtime-resolved contract
3506    /// edges land, per-edge policy annotation once the M4 per-edge
3507    /// policy overlay axis lands) would have had to be threaded through
3508    /// both open-coded copies in lockstep or one consumer would
3509    /// silently disagree with the peer on which edge slice a given
3510    /// Caixa resolves to — the enumerator's presence probe reading the
3511    /// raw slot while the peer view-composer's fold-in path read an
3512    /// operator-resolved slot would silently split the paired
3513    /// declared-slot enumerator and typed-view composition, a
3514    /// two-consumer split at the enumerator and the view composer far
3515    /// from the source `caixa.lisp` with no field naming the edge-set-
3516    /// drift root cause. Lifting the resolution rule to a typed method
3517    /// on the substrate primitive means every downstream consumer of
3518    /// the caixa's per-`Caixa` MESH-COMPOSITION outer-slice surface
3519    /// reaches for exactly one typed dispatch — the resolver's
3520    /// accept-set migrates as a unit on any future axis addition.
3521    ///
3522    /// Fourth and final outer top-level [`Caixa`] `&[Composite]`-return
3523    /// slice accessor for M2 / M3 typed-slot vec-carry axes — closes
3524    /// the outer-`Caixa` `&[Composite]` composite-slice sub-family the
3525    /// sibling M2 [`Self::upgrade_from`] (2a1f907) / [`Self::children`]
3526    /// (c17b51e) accessors opened and the M3 [`Self::membros`]
3527    /// (0f26987) accessor folded on, and closes the outer-`Caixa` M3
3528    /// mesh-slot arm of the composite-slice sub-family the sibling
3529    /// [`Self::membros`] accessor opened for the M3 vec-carry altitude.
3530    /// Peer at the outer altitude of the closed inner-
3531    /// [`crate::AplicacaoSpec::contratos`] (0dcc926) accessor on the
3532    /// same MESH-COMPOSITION per-Aplicacao contract-list axis — the two
3533    /// altitudes now share the same "byte-equal, borrow-shared" outer-
3534    /// accessor discipline. Sibling in shape to the peer outer-`Caixa`
3535    /// `&[Dep]`-return [`Self::deps`] (ad34b4e) / [`Self::deps_dev`]
3536    /// (f7fd81e) and `&[String]`-return [`Self::autores`] (b5d813f) /
3537    /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`] (8a36c23) /
3538    /// [`Self::exe`] (65d9527) / [`Self::servicos`] (611f78b) slice-
3539    /// accessors on the sibling outer-`Caixa` scalar-element vec-carry
3540    /// axes — folds the "outer [`Caixa`] `&[T]` slice" projection
3541    /// pattern onto the sibling M3 typed-composite-element axis
3542    /// ([`crate::aplicacao::WitContract`] composite, matching the
3543    /// per-inner [`crate::AplicacaoSpec::contratos`] element type at a
3544    /// different altitude).
3545    ///
3546    /// Returns `&[crate::aplicacao::WitContract]` (not
3547    /// `&Vec<WitContract>`) because every downstream consumer of the
3548    /// contract list treats it as a read-only sequence — the slice-view
3549    /// is the narrowest borrow that supports every present + roadmapped
3550    /// consumer (`.iter()`, `.len()`, `.is_empty()`, per-edge WIT-world
3551    /// discriminant dispatch, `serde` slice-serialization) without
3552    /// leaking the backing `Vec`'s grow/push/reserve surface no
3553    /// consumer of the typed view reaches for (the storage-side `Vec`
3554    /// remains reachable through the `pub contratos` field for the
3555    /// mutation-carrying serde round-trip and per-test fixture-mutation
3556    /// paths, including the [`Self::aplicacao_view`] fold-in path that
3557    /// clones the slot into the typed view). Named `contratos()` to
3558    /// match the storage field's name verbatim and the tatara-lisp
3559    /// author-surface term (`:contratos`) the field's own docstring
3560    /// already carries; the accessor's identity maps onto the canonical
3561    /// MESH-COMPOSITION vocabulary the [`Caixa::contratos`] field's
3562    /// docstring already reaches for ("WIT-typed inter-Servico
3563    /// contracts").
3564    #[must_use]
3565    pub fn contratos(&self) -> &[crate::aplicacao::WitContract] {
3566        self.contratos.as_slice()
3567    }
3568
3569    /// Compose the Aplicacao-related flat slots into a single typed
3570    /// [`crate::aplicacao::AplicacaoSpec`] for validation +
3571    /// downstream renderer consumption. Returns `None` when the
3572    /// caixa isn't a `:kind Aplicacao`.
3573    #[must_use]
3574    pub fn aplicacao_view(&self) -> Option<crate::aplicacao::AplicacaoSpec> {
3575        if !self.kind().is_aplicacao() {
3576            return None;
3577        }
3578        Some(crate::aplicacao::AplicacaoSpec {
3579            membros: self.membros().to_vec(),
3580            contratos: self.contratos().to_vec(),
3581            politicas: self.politicas().cloned().unwrap_or_default(),
3582            placement: self.placement().cloned().unwrap_or_default(),
3583            entrada: self.entrada().cloned(),
3584        })
3585    }
3586
3587    /// The kebab-case `:slot` tags of every M3 mesh slot this caixa
3588    /// *declares* a value on, in canonical declaration order
3589    /// (`:membros` → `:contratos` → `:politicas` → `:placement` →
3590    /// `:entrada`). A slot counts as declared when its backing field
3591    /// carries a value — a non-empty `Vec`, or a `Some(...)`.
3592    ///
3593    /// The M3 mesh slots compose the typed graph of a `:kind Aplicacao`
3594    /// (MESH-COMPOSITION §III.1). [`Self::aplicacao_view`] only folds
3595    /// them into a validatable [`crate::aplicacao::AplicacaoSpec`] when
3596    /// the kind matches (returns `None` otherwise), and the caixa-mesh /
3597    /// caixa-flux / caixa-helm renderers only emit them for an
3598    /// Aplicacao. On any *other* kind a declared mesh slot is the
3599    /// manifest field's documented "ignored otherwise" (see the
3600    /// `:membros` … `:entrada` field docs): it silently passes
3601    /// [`Caixa::from_lisp`] and then vanishes — never validated, never
3602    /// rendered — far from the source caixa.lisp.
3603    /// [`crate::StandardLayout::verify`] consults this to reject that
3604    /// silent-drop at caixa-build time
3605    /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`]), mirroring the
3606    /// `SupervisorOwnsCode` / `AplicacaoOwnsCode` kind-coherence gates:
3607    /// a slot foreign to the kind is a build error, not a silent drop.
3608    ///
3609    /// Lifted as a typed method (rather than an inline disjunction at
3610    /// the verify call site) so the mesh-slot set lives in one place —
3611    /// a future M4 axis added to the Aplicacao surface (per-edge policy
3612    /// overlay, distributed-app takeover config) is one push here, and
3613    /// every consumer reaching for "which mesh slots are set" (the
3614    /// verify gate, a future `feira lint` kind-coherence advisory)
3615    /// inherits the canonical order without rolling its own.
3616    ///
3617    /// Each per-arm kebab-case label is routed through the peer
3618    /// [`crate::M3_AUTHOR_KEY_MEMBROS`] /
3619    /// [`crate::M3_AUTHOR_KEY_CONTRATOS`] /
3620    /// [`crate::M3_AUTHOR_KEY_POLITICAS`] /
3621    /// [`crate::M3_AUTHOR_KEY_PLACEMENT`] /
3622    /// [`crate::M3_AUTHOR_KEY_ENTRADA`] consts declared next to the
3623    /// [`crate::M3_KEY_PLACEMENT`] renderer-side wire-key peer, so both
3624    /// halves of every M3 top-level mesh slot's dual axis (author-facing
3625    /// kebab-case label + renderer-side artifact key) route through one
3626    /// canonical declaration per arm — same discipline the peer
3627    /// [`crate::M2_AUTHOR_KEY_LIMITS`] / [`crate::M2_AUTHOR_KEY_BEHAVIOR`]
3628    /// / [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] top-level M2 slot consts
3629    /// (f49c8b0) establish on the sibling per-Servico M2 top-level slot
3630    /// axis, extended here to close the M3 mesh-slot author-facing-label
3631    /// axis so both altitudes of the typed-slot algebra
3632    /// (per-Servico M2 + per-Aplicacao M3) share the same
3633    /// "one canonical byte-string per arm, next to the axis" discipline.
3634    #[must_use]
3635    pub fn declared_mesh_slots(&self) -> Vec<&'static str> {
3636        let mut slots = Vec::new();
3637        if !self.membros().is_empty() {
3638            slots.push(crate::render::M3_AUTHOR_KEY_MEMBROS);
3639        }
3640        if !self.contratos().is_empty() {
3641            slots.push(crate::render::M3_AUTHOR_KEY_CONTRATOS);
3642        }
3643        if self.politicas().is_some() {
3644            slots.push(crate::render::M3_AUTHOR_KEY_POLITICAS);
3645        }
3646        if self.placement().is_some() {
3647            slots.push(crate::render::M3_AUTHOR_KEY_PLACEMENT);
3648        }
3649        if self.entrada().is_some() {
3650            slots.push(crate::render::M3_AUTHOR_KEY_ENTRADA);
3651        }
3652        slots
3653    }
3654
3655    /// The kebab-case `:slot` tags of every supervisor-tree slot this
3656    /// caixa *declares* a value on, in canonical declaration order
3657    /// (`:estrategia` → `:max-restarts` → `:restart-window` →
3658    /// `:children`). A slot counts as declared when its backing field
3659    /// carries a value — a `Some(...)`, or a non-empty `Vec`.
3660    ///
3661    /// The supervisor-tree slots compose the typed OTP supervisor of a
3662    /// `:kind Supervisor` (INSPIRATIONS §II.2; the `:estrategia` +
3663    /// `:children` field docs above). [`Self::supervisor_view`] only
3664    /// folds them into a validatable [`SupervisorSpec`] when the kind
3665    /// matches (returns `None` otherwise), and the wasm-operator's
3666    /// hierarchical reconciler only consumes them for a Supervisor. On
3667    /// any *other* kind a declared supervisor slot is the manifest
3668    /// field's documented "ignored otherwise" (see the `:estrategia` …
3669    /// `:children` field docs): it silently passes [`Caixa::from_lisp`]
3670    /// and then vanishes — never validated, never reconciled — far from
3671    /// the source caixa.lisp. [`crate::StandardLayout::verify`] consults
3672    /// this to reject that silent-drop at caixa-build time
3673    /// ([`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]), the
3674    /// exact mirror of the [`Self::declared_mesh_slots`] /
3675    /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] gate on the
3676    /// Aplicacao-only slot set: a slot foreign to the kind is a build
3677    /// error, not a silent drop.
3678    #[must_use]
3679    pub fn declared_supervisor_slots(&self) -> Vec<&'static str> {
3680        let mut slots = Vec::new();
3681        if self.estrategia().is_some() {
3682            slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA);
3683        }
3684        if self.max_restarts().is_some() {
3685            slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS);
3686        }
3687        if self.restart_window().is_some() {
3688            slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW);
3689        }
3690        if !self.children().is_empty() {
3691            slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN);
3692        }
3693        slots
3694    }
3695
3696    /// The kebab-case `:slot` tags of every M2 Servico-runtime slot this
3697    /// caixa *declares* a value on, in canonical declaration order
3698    /// (`:limits` → `:behavior` → `:upgrade-from`). A slot counts as
3699    /// declared when its backing field carries a value — a `Some(...)`,
3700    /// or a non-empty `Vec`.
3701    ///
3702    /// The M2 slots configure the runtime of a long-running wasm
3703    /// component, i.e. a `:kind Servico`: `:limits` is Lunatic
3704    /// per-process sandboxing (INSPIRATIONS §III.1), `:behavior` is the
3705    /// OTP `gen_server` callback set (§II.3), `:upgrade-from` is the OTP
3706    /// appup hot-code-reload table (§II.4). The caixa-helm / caixa-flux
3707    /// renderers gate on [`crate::require_kind`]`(_, Servico)` and only
3708    /// emit these slots for a Servico; on any *other* kind a declared M2
3709    /// slot is the manifest field's documented "ignored otherwise": its
3710    /// well-formedness is checked by [`crate::StandardLayout::verify`]
3711    /// but the value is never rendered into a chart / programs.yaml entry
3712    /// — it silently passes [`Caixa::from_lisp`] + `feira build` and then
3713    /// vanishes, far from the source caixa.lisp.
3714    /// [`crate::StandardLayout::verify`] consults this to reject that
3715    /// silent-drop at caixa-build time
3716    /// ([`crate::LayoutError::ServicoSlotsOnNonServico`]), the exact
3717    /// mirror of the [`Self::declared_mesh_slots`] /
3718    /// [`Self::declared_supervisor_slots`] gates on the peer
3719    /// kind-exclusive slot sets: a slot foreign to the kind is a build
3720    /// error, not a silent drop.
3721    ///
3722    /// Each per-arm kebab-case label is routed through the peer
3723    /// [`crate::M2_AUTHOR_KEY_LIMITS`] / [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
3724    /// [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] consts declared next to the
3725    /// [`crate::M2_KEY_LIMITS`] / [`crate::M2_KEY_BEHAVIOR`] /
3726    /// [`crate::M2_KEY_UPGRADE_FROM`] renderer-side wire-key peers, so
3727    /// both halves of the M2 top-level slot's dual axis (author-facing
3728    /// kebab-case label + renderer-side camelCase overlay-container wire
3729    /// key) route through one canonical declaration per arm — same
3730    /// discipline the peer [`crate::M2_BEHAVIOR_AUTHOR_KEY_ON_*`] sub-slot
3731    /// author-label consts (889dc18) establish on the sibling
3732    /// per-callback axis inside the `:behavior` overlay block.
3733    #[must_use]
3734    pub fn declared_servico_slots(&self) -> Vec<&'static str> {
3735        let mut slots = Vec::new();
3736        if self.limits().is_some() {
3737            slots.push(crate::render::M2_AUTHOR_KEY_LIMITS);
3738        }
3739        if self.behavior().is_some() {
3740            slots.push(crate::render::M2_AUTHOR_KEY_BEHAVIOR);
3741        }
3742        if !self.upgrade_from().is_empty() {
3743            slots.push(crate::render::M2_AUTHOR_KEY_UPGRADE_FROM);
3744        }
3745        slots
3746    }
3747
3748    /// The kebab-case `:slot` tags of every code-surface slot this caixa
3749    /// declares a value on that its [`CaixaKind`] doesn't natively own,
3750    /// in canonical declaration order (`:exe` → `:servicos`). A
3751    /// code-surface slot is owned by exactly one kind: `:exe` by
3752    /// [`CaixaKind::Binario`] (the nix-built executable surface), and
3753    /// `:servicos` by [`CaixaKind::Servico`] (the wasm component +
3754    /// `ComputeUnit` daemon surface).
3755    ///
3756    /// Each is silently ignored when declared on the wrong kind: the
3757    /// caixa-helm / caixa-flux / caixa-flake renderers gate on
3758    /// [`crate::require_kind`]`(_, <owning-kind>)`, so on any *other*
3759    /// code-running kind a declared `:exe` / `:servicos` is the manifest
3760    /// field's documented "ignored otherwise" — its path is checked for
3761    /// existence by the layout's `bibliotecas`/`exe`/`servicos` loops
3762    /// (which run after [`Caixa::from_lisp`]), but the value is never
3763    /// rendered into a build target or programs.yaml entry. It silently
3764    /// passes [`Caixa::from_lisp`] + `feira build`, far from the source
3765    /// caixa.lisp, with no field naming which slot is foreign.
3766    ///
3767    /// [`crate::StandardLayout::verify`] consults this to reject that
3768    /// silent-drop at caixa-build time
3769    /// ([`crate::LayoutError::ForeignCodeSlot`]), beside the M2
3770    /// servico-runtime, supervisor-tree, and M3 mesh kind-coherence
3771    /// gates ([`Self::declared_servico_slots`] /
3772    /// [`Self::declared_supervisor_slots`] /
3773    /// [`Self::declared_mesh_slots`]): the fourth kind ↔ slot algebra
3774    /// axis to be closed on the typed surface. The Supervisor /
3775    /// Aplicacao "no code at all" cases ([`crate::LayoutError::SupervisorOwnsCode`]
3776    /// / [`crate::LayoutError::AplicacaoOwnsCode`]) keep their dedicated
3777    /// diagnostics — they fire ahead of this gate on the same `verify`
3778    /// pass, so for Supervisor / Aplicacao the `OwnCode` arm always wins
3779    /// and this method is moot. For Biblioteca / Binario / Servico, this
3780    /// gate fires when a code-running kind declares another code-running
3781    /// kind's exclusive code surface.
3782    ///
3783    /// `:bibliotecas` is deliberately excluded — a Binario or Servico
3784    /// may legitimately ship a `lib/` helper that the underlying
3785    /// substrate (the nix flake for Binario, the wasm component build
3786    /// for Servico) bundles into its build, so the slot's
3787    /// declared-on-wrong-kind cardinality isn't a structural error on
3788    /// either code-running kind. A Biblioteca declaring `:bibliotecas`
3789    /// is the native case (the slot's owning kind). Supervisor /
3790    /// Aplicacao declaring `:bibliotecas` is gated upstream by
3791    /// [`crate::LayoutError::SupervisorOwnsCode`] /
3792    /// [`crate::LayoutError::AplicacaoOwnsCode`].
3793    ///
3794    /// Lifted as a typed method (rather than an inline disjunction at
3795    /// the verify call site) so the foreign-code-slot set lives in one
3796    /// place — a future kind that gains its own code-surface slot is
3797    /// one push here, and every consumer reaching for "which code
3798    /// surfaces are foreign to this kind" (the verify gate, a future
3799    /// `feira lint` kind-coherence advisory, the future `app-operator`'s
3800    /// per-caixa build-target classifier) inherits the canonical order
3801    /// without rolling its own.
3802    #[must_use]
3803    pub fn declared_foreign_code_slots(&self) -> Vec<&'static str> {
3804        let mut slots = Vec::new();
3805        if !self.exe().is_empty() && !self.kind().requires_exe() {
3806            slots.push(":exe");
3807        }
3808        if !self.servicos().is_empty() && !self.kind().requires_servicos() {
3809            slots.push(":servicos");
3810        }
3811        slots
3812    }
3813
3814    /// Validate every entry of `:deps` and `:deps-dev` through
3815    /// [`Dep::validate`] — closing the parity loop with the per-axis
3816    /// `:versao` gates already wired into the typed-graph
3817    /// ([`crate::AplicacaoSpec::validate_membros`] for `:membros`,
3818    /// 9888b13) and typed supervisor tree
3819    /// ([`crate::SupervisorSpec::validate`] for `:children`, b38ff3a).
3820    ///
3821    /// Until this gate landed `:deps :versao` and `:deps-dev :versao`
3822    /// were the only `:versao` axes still untyped past
3823    /// [`Caixa::from_lisp`]: the derive macro stored the requirement
3824    /// as a String without parsing it, so a malformed-but-non-empty
3825    /// requirement (`"^bad-version"`, `"^^0.1"`, `"v0.1"`, `"not-a-req"`)
3826    /// silently passed parse and the `semver::Error` surfaced at
3827    /// lacre-resolve time, far from the source caixa.lisp, with no
3828    /// field naming which `:deps` entry carried the typo. Lifting the
3829    /// gate here makes the four `:versao` typed surfaces (`:deps`,
3830    /// `:deps-dev`, `:membros`, `:children`) structurally equivalent —
3831    /// every requirement string past `validate_deps` is round-trippable
3832    /// through [`crate::parse_requirement`] without re-checking at the
3833    /// resolver layer.
3834    ///
3835    /// Both lists run through the same per-entry validator so a typo
3836    /// in `:deps-dev` surfaces with the same diagnostic as one in
3837    /// `:deps` — neither axis is a second-class citizen of the typed
3838    /// surface.
3839    ///
3840    /// Within each list, [`DepError::DuplicateNome`] closes the
3841    /// set-not-multiset discipline on the `:nome` axis: two entries
3842    /// naming the same caixa carry two `:versao` / `:fonte` / feature
3843    /// triples that the caixa-resolver's lacre pipeline collapses to one
3844    /// via its `HashMap`-keyed-by-`:nome` consumption — the second entry
3845    /// silently overwrites the first at `concrete_versao`-resolve time
3846    /// (the same "second wins / one silently overwrites the other"
3847    /// shape the peer typed-graph duplicate gates already close on every
3848    /// other Vec-shaped authoring surface that keys by name). The
3849    /// duplicate check fires per-list and runs *after* each per-entry
3850    /// [`Dep::validate`] call so a malformed-and-duplicated entry
3851    /// surfaces its narrower per-entry diagnostic
3852    /// ([`DepError::NomeInvalid`], [`DepError::VersaoInvalid`],
3853    /// [`DepError::FonteRepoEmpty`], …) before the cross-entry duplicate
3854    /// diagnostic — the canonical "per-entry shape before cross-entry
3855    /// uniqueness" precedence the peer `:children :caixa`
3856    /// ([`crate::SupervisorSpec::validate`]), `:membros :caixa`
3857    /// ([`crate::AplicacaoSpec::validate_membros`]), `:contratos`
3858    /// ([`crate::AplicacaoSpec::validate`]), `:placement :clusters`
3859    /// ([`crate::AplicacaoSpec::validate_placement`]),
3860    /// `:entrada :paths` ([`crate::AplicacaoSpec::validate`]),
3861    /// `:upgrade-from :from` ([`crate::upgrade::validate_upgrade_from`]),
3862    /// and the within-`:upgrade-from`-entry per-instruction-class
3863    /// singularity gates ([`crate::UpgradeError::DuplicateLoadModule`],
3864    /// [`crate::UpgradeError::DuplicateStateChange`],
3865    /// [`crate::UpgradeError::DuplicateCleanup`]) all establish.
3866    ///
3867    /// Cross-list (`:deps` ↔ `:deps-dev`) coincidence is *not* gated
3868    /// here: Cargo's `[dependencies]` + `[dev-dependencies]` accept the
3869    /// same name in both tables (the dev table's pin overrides the
3870    /// runtime table's pin in test/dev contexts), and caixa's surface
3871    /// mirrors that convention until a deliberate choice retires the
3872    /// override pattern. Only within-list duplicates are structurally
3873    /// incoherent — those are what this gate closes.
3874    pub fn validate_deps(&self) -> Result<(), DepError> {
3875        for &list in crate::dep::DepList::ALL {
3876            let mut seen = std::collections::HashSet::new();
3877            for dep in self.deps_of(list) {
3878                dep.validate()?;
3879                crate::render::insert_first_seen(&mut seen, dep.nome(), || {
3880                    DepError::DuplicateNome {
3881                        nome: dep.nome().to_string(),
3882                        list: list.as_str(),
3883                    }
3884                })?;
3885            }
3886        }
3887        Ok(())
3888    }
3889
3890    /// Reject `:nome` values the K8s apiserver would refuse at admission
3891    /// time. The top-level Caixa identity flows directly into every
3892    /// substrate-side artifact's `metadata.name` axis: the
3893    /// `lareira-<nome>` Helm chart name ([`caixa-helm::lib::chart_name`]),
3894    /// the programs.yaml `name:` entry the `lareira-fleet-programs`
3895    /// aggregator keys ComputeUnit derivation off
3896    /// ([`caixa-flux::lib::programs_yaml_entry`]), the
3897    /// `LABEL_APLICACAO` label value carried on every Aplicacao-owned
3898    /// pod and the per-`:contratos` CiliumNetworkPolicy `metadata.name`
3899    /// (`<aplicacao>-<de>-to-<para>`) and the per-`:entrada`
3900    /// `<aplicacao>-<para>` HTTPRoute `metadata.name`
3901    /// ([`caixa-mesh::lib::cilium_network_policies`],
3902    /// [`caixa-mesh::lib::gateway_routes`]), and the default
3903    /// `lib/<nome>.lisp` / `exe/<nome>` layout paths
3904    /// ([`crate::StandardLayout::verify`]). Each K8s apiserver-side
3905    /// schema enforces the DNS-1123 label rule on admission; a
3906    /// structurally invalid `:nome` (`"MyApp"` — the canonical
3907    /// "I copied the display name verbatim" footgun, `"my_app"` — the
3908    /// Python-/Postgres-leak, `"team.app"` — `:nome` is a single label
3909    /// not a subdomain, `"-app"` / `"app-"` — DNS-1123 boundary
3910    /// violations, `"my app"` — the paste-from-doc footgun, `"café"` —
3911    /// IDN must be pre-encoded as Punycode, the 64-byte UUID-shaped
3912    /// over-cap slug) silently passed [`Caixa::from_lisp`] and the
3913    /// failure surfaced at `kubectl apply` time as a `metadata.name:
3914    /// Invalid value` rejection on whichever derived artifact admitted
3915    /// first, far from the source `caixa.lisp` and without any field
3916    /// naming the offending `:nome`.
3917    ///
3918    /// Thin wrapper around [`crate::render::is_dns_1123_label`] (the
3919    /// substrate-side predicate the per-axis name gates already share:
3920    /// `:membros :caixa` 3f9d7a0, `:placement :clusters` 6cbb900,
3921    /// `:children :caixa` 31bfa43) that maps the shared parser-shaped
3922    /// reason into the [`ManifestError::NomeInvalid`] variant, so the
3923    /// diagnostic is self-locating (the offending `:nome` is named
3924    /// verbatim) and the author can grep their `caixa.lisp` for
3925    /// `:nome "<value>"` and fix it in one edit. Same diagnostic shape
3926    /// every per-axis sibling gate already exposes
3927    /// ([`crate::AplicacaoError::MembroCaixaInvalid`],
3928    /// [`crate::AplicacaoError::PlacementClusterInvalid`],
3929    /// [`crate::SupervisorError::ChildCaixaInvalid`]).
3930    ///
3931    /// Empty `:nome` (which [`Caixa::from_lisp`] does not reject — the
3932    /// derive macro stores the raw String) is gated by the narrower
3933    /// [`ManifestError::NomeEmpty`] arm before the predicate is
3934    /// consulted, mirroring the empty-first cascade every per-axis
3935    /// name gate already uses (e.g. `MembroCaixaEmpty` before
3936    /// `MembroCaixaInvalid`, `EmptyChildName` before `ChildCaixaInvalid`).
3937    pub fn validate_nome(&self) -> Result<(), ManifestError> {
3938        // Routes through the shared
3939        // [`crate::render::require_valid_dns_1123_label`] gate the peer
3940        // name axes each land on so drift between the eight axes'
3941        // accepted DNS-1123-label sets is structurally impossible.
3942        let nome = self.nome();
3943        crate::render::require_valid_dns_1123_label(
3944            nome,
3945            || ManifestError::NomeEmpty,
3946            |reason| ManifestError::NomeInvalid {
3947                nome: nome.to_string(),
3948                reason,
3949            },
3950        )
3951    }
3952
3953    /// Reject `:nome` values whose joint length with the canonical
3954    /// [`crate::LAREIRA_CHART_NAME_PREFIX`] (`"lareira-"`) overflows
3955    /// the K8s DNS-1123 label cap [`crate::DNS_1123_LABEL_MAX_LEN`]
3956    /// (63 bytes). Every per-Servico / per-Aplicacao renderer the
3957    /// substrate carries materializes the caixa's `:nome` through the
3958    /// canonical [`crate::lareira_chart_name`] helper (f7320d7) into a
3959    /// `lareira-<nome>` artifact that lands as a K8s `metadata.name` /
3960    /// Helm chart name / `HelmRelease` `release_name`: `caixa-helm`'s
3961    /// `ChartDir.name` + `Chart.yaml::name`
3962    /// (caixa-helm/src/lib.rs:207), `caixa-flux`'s `cluster_bundle`
3963    /// `HelmRelease` `chart:` slot (caixa-flux/src/lib.rs:329),
3964    /// `caixa-tatara`'s `process_for_aplicacao` `release_name` +
3965    /// `oci://<registry>/lareira-<nome>` chart ref
3966    /// (caixa-tatara/src/lib.rs:124,178). Helm's own `Chart.yaml::name`
3967    /// admission rule strict-parses against DNS-1123-label, the Helm
3968    /// operator's tracking-secret name is derived from `release_name`
3969    /// and is itself DNS-1123-label-bounded, and the rendered chart's
3970    /// K8s object `metadata.name` axes embed the chart name as a
3971    /// prefix — every one fails admission on a > 63-byte chart name.
3972    ///
3973    /// The per-axis [`Self::validate_nome`] gate (6c992f8) already
3974    /// caps `:nome` itself at 63 bytes via [`is_dns_1123_label`], so a
3975    /// `:nome` of 56–63 bytes silently passed validate (the inner
3976    /// DNS-1123 check accepts the bare `:nome`) but produced a
3977    /// `lareira-<nome>` of 64–71 bytes that the apiserver / `helm lint`
3978    /// rejected at admission — far from the source `caixa.lisp`, with
3979    /// no field naming the overflow root cause. The
3980    /// [`lareira_chart_name`] helper's own doc comment
3981    /// (caixa-core/src/render.rs:3198) explicitly deferred the fix:
3982    /// "the M4 admission webhook will pin the joint-length invariant
3983    /// when it lands". This gate lands the invariant at the
3984    /// manifest-validate layer rather than waiting for the apiserver
3985    /// — the same fail-at-the-source posture every peer per-axis
3986    /// value-shape gate (DNS-1123 on `:nome`, SemVer-2 on `:versao`,
3987    /// SPDX-expression-shape on `:licenca`, 4-digit decimal year on
3988    /// `:edicao`, etc.) takes.
3989    ///
3990    /// Thin wrapper around
3991    /// [`crate::render::is_lareira_chart_name_shape`] (the
3992    /// substrate-side predicate that composes [`lareira_chart_name`] +
3993    /// [`is_dns_1123_label`] via the lifted
3994    /// [`crate::LAREIRA_CHART_NAME_NOME_MAX_LEN`] budget); maps the
3995    /// shared parser-shaped reason into the
3996    /// [`ManifestError::NomeChartNameBudgetExceeded`] variant so the
3997    /// diagnostic is self-locating (the offending `:nome` is named
3998    /// verbatim alongside the rendered chart name and the budget) and
3999    /// the author can shorten in one edit. The gate runs across every
4000    /// `:kind` — `:nome` is the substrate-wide identity axis any
4001    /// future renderer the substrate adds can derive a
4002    /// `lareira-<nome>` artifact from, and uniform enforcement closes
4003    /// the drift footgun where a future kind grows a chart-emitting
4004    /// render path while the validate cascade doesn't catch it.
4005    ///
4006    /// Runs *after* [`Self::validate_nome`] so the narrower
4007    /// `NomeEmpty` / `NomeInvalid` shape diagnostics fire first — a
4008    /// structurally-malformed `:nome` (empty, uppercase, underscore,
4009    /// dot, leading/trailing hyphen, Unicode, > 63 bytes) surfaces its
4010    /// specific shape error rather than the chart-name-budget error,
4011    /// preserving the legitimate "well-shaped `:nome` that happens to
4012    /// overflow the joint cap" arm for this gate.
4013    pub fn validate_nome_chart_name_budget(&self) -> Result<(), ManifestError> {
4014        let nome = self.nome();
4015        crate::render::is_lareira_chart_name_shape(nome).map_err(|reason| {
4016            ManifestError::NomeChartNameBudgetExceeded {
4017                nome: nome.to_string(),
4018                reason,
4019            }
4020        })
4021    }
4022
4023    /// Reject `:versao` values that don't parse as [`semver::Version`].
4024    /// The top-level Caixa version flows directly into every
4025    /// substrate-side artifact that carries a "this is which version of
4026    /// the caixa" axis: the `lareira-<nome>` Helm chart's `Chart.yaml`
4027    /// `version:` + `appVersion:` axes ([`caixa-helm::lib`] —
4028    /// SemVer-2-strict at `helm template` / `helm install` time per
4029    /// https://helm.sh/docs/topics/charts/#charts-and-versioning), the
4030    /// `feira publish` Zig-style `v<versao>` git tag
4031    /// ([`caixa-flux::lib::programs_yaml_entry`] / the
4032    /// `caixa-publish.yml` reusable workflow), the programs.yaml entry's
4033    /// `versao:` value the `lareira-fleet-programs` aggregator carries
4034    /// onto each rendered ComputeUnit, the OCI image's `:v<versao>` /
4035    /// `:latest` tags the substrate's `wasi-service-flake` builds with
4036    /// `skopeo push`, the lacre closure's pinned versions
4037    /// ([`caixa-resolver`] keys `concrete_versao`), and the
4038    /// `:upgrade-from :from` references peers in this exact `versao`
4039    /// shape (`semver::Version`, not `VersionReq`). Each consumer
4040    /// expects a strict three-part `MAJOR.MINOR.PATCH` (optionally
4041    /// `-prerelease` and/or `+build`); a structurally invalid `:versao`
4042    /// (`"0.1"` — missing patch, the canonical "I shortened it" footgun;
4043    /// `"v0.1.0"` — the git-tag-shape-leaking-into-versao typo;
4044    /// `"latest"` / `"main"` — the "I confused it with a docker tag"
4045    /// footgun; `"^0.1"` / `"~0.1.2"` — the requirement-shape leaking
4046    /// into the version field a peer `:deps :versao` accepts;
4047    /// `"0.1.0.0"` — the four-part Java/Microsoft convention DNS
4048    /// SemVer-2 forbids) silently passed [`Caixa::from_lisp`] (the
4049    /// derive macro stores the raw String) and the failure surfaced at
4050    /// the *first* downstream consumer that strict-parses it: at
4051    /// `helm install` time as a chart-version rejection, at
4052    /// `feira publish` time as a malformed git tag, at lacre-resolve
4053    /// time as a `semver::Error` not naming the offending caixa, at
4054    /// `feira upgrade --to <versao>` time as an unresolvable
4055    /// `:upgrade-from :from` match — far from the source `caixa.lisp`
4056    /// and without any field naming the offending `:versao`.
4057    ///
4058    /// Thin wrapper around [`semver::Version::parse`] — the same parser
4059    /// [`crate::CaixaVersion::parse`] (the typed `:versao` accessor)
4060    /// and [`crate::UpgradeFromEntry::validate`] (the peer
4061    /// `:upgrade-from :from` axis, 26da2c7) consume. Maps the
4062    /// `semver::Error` reason into the [`ManifestError::VersaoInvalid`]
4063    /// variant, carrying the offending `:versao` verbatim + a
4064    /// parser-shaped reason naming the specific violation, so the
4065    /// diagnostic is self-locating (the author can grep their
4066    /// `caixa.lisp` for `:versao "<value>"` and fix it in one edit).
4067    /// Same diagnostic shape as [`ManifestError::NomeInvalid`]
4068    /// (6c992f8) and [`crate::UpgradeError::FromInvalid`]
4069    /// (b0c8389) on the peer axes. With this gate, the typed `:versao`
4070    /// surfaces — top-level `:versao`, `:upgrade-from :from` — are
4071    /// now structurally equivalent (every value past validate is
4072    /// round-trippable through [`semver::Version::parse`] without
4073    /// re-checking at the renderer, resolver, or operator hot-upgrade
4074    /// layer), peer with the four `:versao` requirement axes (`:deps`,
4075    /// `:deps-dev`, `:membros`, `:children`) the prior commits
4076    /// (2420c44, 9888b13, b38ff3a) wired through `parse_requirement`.
4077    ///
4078    /// Empty `:versao` (which [`Caixa::from_lisp`] does not reject —
4079    /// the derive macro stores the raw String) is gated by the
4080    /// narrower [`ManifestError::VersaoEmpty`] arm before the parser is
4081    /// consulted, mirroring the empty-first cascade every per-axis
4082    /// version gate already uses (e.g. `MembroVersaoEmpty` before
4083    /// `MembroVersaoInvalid`, `EmptyChildVersion` before
4084    /// `ChildVersaoInvalid`, `NomeEmpty` before `NomeInvalid`).
4085    pub fn validate_versao(&self) -> Result<(), ManifestError> {
4086        let versao = self.versao();
4087        if versao.is_empty() {
4088            return Err(ManifestError::VersaoEmpty);
4089        }
4090        semver::Version::parse(versao).map_err(|e| ManifestError::VersaoInvalid {
4091            versao: versao.to_string(),
4092            reason: e.to_string(),
4093        })?;
4094        Ok(())
4095    }
4096
4097    /// Reject `:restart-window` values the shared
4098    /// [`crate::supervisor::duration_codec::parse`] refuses. The flat
4099    /// `restart_window: Option<String>` slot on [`Caixa`] is stored
4100    /// raw by the derive macro (the typed [`SupervisorSpec`] holds an
4101    /// `Option<Duration>` routed through the shared codec via `with =
4102    /// "duration_codec"`); the inline `Caixa → SupervisorSpec`
4103    /// view-construction path ([`Self::supervisor_view`]) folds the
4104    /// raw string through the same shared codec and soft-swallows the
4105    /// parse error as `None` to keep the view best-effort. Without
4106    /// this gate a malformed `:restart-window` (`"1.5s"` — the
4107    /// fractional-seconds drift class; `"1.0s"` — the decimal-shaped
4108    /// integer drift; `"0.5m"` — the unit-fraction drift; `"+30s"` /
4109    /// `"-30s"` — the leading-sign drift; `"30x"` — the unknown-unit
4110    /// footgun; `"abc"` — pure garbage; `""` — the empty-after-trim
4111    /// edge case) silently produced a `SupervisorSpec` with
4112    /// `restart_window: None`, indistinguishable from the canonical
4113    /// "omit the slot to express no reset" authoring shape — Erlang/OTP's
4114    /// `MaxIntensity / Period` invariant turns into a never-reset
4115    /// supervisor far from the source `caixa.lisp`, with no field
4116    /// naming the offending `:restart-window`. Lifting the gate to a
4117    /// Caixa-level validator mirrors the trajectory of the peer
4118    /// per-axis identity gates ([`Self::validate_nome`] 6c992f8,
4119    /// [`Self::validate_versao`] 1fdaa02, [`Self::validate_deps`]
4120    /// a7f0d8c) and the ABSORPTION-ROADMAP.md M2.2 test pin
4121    /// (line 196: "reject invalid `:restart-window` (non-duration)").
4122    ///
4123    /// Thin wrapper around [`crate::supervisor::duration_codec::parse`]
4124    /// (the shared codec backing `:supervisor :restart-window` as
4125    /// serde-routed on [`SupervisorSpec`], `:politicas :timeout`, and
4126    /// `:politicas :circuit-breaker :window` — all three covered by
4127    /// the integer-magnitude gate 1c55a2a). Maps the codec's parse
4128    /// error verbatim into the [`ManifestError::RestartWindowMalformed`]
4129    /// variant, carrying the offending raw string + a parser-shaped
4130    /// reason naming the canonical authoring form, so the diagnostic
4131    /// is self-locating (the author can grep their `caixa.lisp` for
4132    /// `:restart-window "<value>"` and fix it in one edit) and
4133    /// uniform with every other manifest-level validate diagnostic.
4134    /// With this gate the four `:restart-window`-shaped surfaces (the
4135    /// flat raw string on [`Caixa`], the typed `Option<Duration>` on
4136    /// [`SupervisorSpec`], the two `MeshPolicy` peer durations) are
4137    /// now structurally equivalent — every value past the codec is in
4138    /// one accepted set, by construction.
4139    ///
4140    /// `None` (the canonical "omit the slot to express no reset"
4141    /// shape) is accepted trivially — the gate is a no-op when the
4142    /// author didn't author a window. The empty string is rejected by
4143    /// the shared codec (its digit-only gate refuses an empty
4144    /// magnitude), surfacing the same `RestartWindowMalformed`
4145    /// diagnostic as every other rejected non-canonical shape.
4146    pub fn validate_restart_window(&self) -> Result<(), ManifestError> {
4147        let Some(s) = self.restart_window() else {
4148            return Ok(());
4149        };
4150        crate::supervisor::duration_codec::parse(s)
4151            .map(|_| ())
4152            .map_err(|reason| ManifestError::RestartWindowMalformed {
4153                restart_window: s.to_string(),
4154                reason,
4155            })
4156    }
4157
4158    /// Reject per-entry values on the three Caixa-level code-surface
4159    /// path lists (`:bibliotecas`, `:exe`, `:servicos`) that the
4160    /// layout checker's `root.join(p)` sandbox would silently subvert.
4161    /// Same three structural footguns the peer
4162    /// [`BehaviorSpec::validate`] (b0c8389) and
4163    /// [`crate::UpgradeInstruction::validate`] `StateChange` arm
4164    /// (26da2c7) already close on the M2 `:behavior :on-*` and
4165    /// `:upgrade-from :state-change :script` axes, here lifted onto
4166    /// the three top-level code-path axes through the shared
4167    /// [`is_sandboxed_relative_path`] predicate:
4168    ///
4169    ///   - empty entry (`(:bibliotecas (""))` / `(:exe (""))` /
4170    ///     `(:servicos (""))`): `PathBuf::new()` round-trips through
4171    ///     [`Path::join`] as the base itself — `root.join("")` ==
4172    ///     `root`, so the existence check (`self.exists(&root)`)
4173    ///     trivially passes (the project root exists), and the layout
4174    ///     silently treats the project root as a biblioteca / exe /
4175    ///     servico entry. The `:bibliotecas` loop then hands the root
4176    ///     to `tatara_lisp::read` at `feira build` time as if the root
4177    ///     directory itself were a Lisp source file — a parse error
4178    ///     far from the source `caixa.lisp` with no field naming the
4179    ///     offending entry.
4180    ///   - absolute path (`(:bibliotecas ("/etc/passwd"))`):
4181    ///     [`Path::join`] *replaces* the base when the right-hand side
4182    ///     is absolute, so `root.join("/etc/passwd")` resolves to
4183    ///     `"/etc/passwd"` and escapes the project sandbox entirely.
4184    ///     The existence check then silently consults whatever the
4185    ///     escaped path resolves to — for `:bibliotecas`, the layout
4186    ///     has no `starts_with`-fence (only `:exe` is fenced under
4187    ///     `exe/` and `:servicos` under `servicos/`), so an absolute
4188    ///     `:bibliotecas` entry that happens to resolve on disk
4189    ///     silently passes. For `:exe` / `:servicos` the fence catches
4190    ///     the absolute case downstream as `ExeOutsideDir` /
4191    ///     `ServicoOutsideDir` (or `MissingEntry` if the absolute path
4192    ///     doesn't exist), but with a downstream-shaped diagnostic
4193    ///     that names the resolved escape path rather than the
4194    ///     authoring footgun at the source.
4195    ///   - parent-escape (`(:bibliotecas ("../sibling/x.lisp"))` /
4196    ///     `(:exe ("exe/../../escape.lisp"))`): a [`PathBuf`] with any
4197    ///     [`std::path::Component::ParentDir`] anywhere round-trips
4198    ///     through [`Path::join`] as a traversal above the caixa root.
4199    ///     The `:exe` / `:servicos` `starts_with(<dir>)` fence is
4200    ///     *component-aware* (not canonical-path-aware), so
4201    ///     `root.join("exe/../../escape.lisp")` `starts_with(exe_dir)`
4202    ///     is **true** even though the canonical resolution
4203    ///     `{parent of root}/escape.lisp` lives outside the caixa root
4204    ///     — the fence silently lets the parent-escape through, and
4205    ///     the existence check passes if that escape-target happens
4206    ///     to exist. Caught regardless of where the `..` sits
4207    ///     (leading, mid-path, trailing) so the gate matches the peer
4208    ///     predicate's full coverage.
4209    ///
4210    /// Same `Empty` → `Absolute` → `ParentEscape` arm-ordering every peer
4211    /// `is_sandboxed_relative_path` consumer follows (b0c8389 / 26da2c7);
4212    /// same per-slot diagnostic shape every peer per-axis path-gate
4213    /// exposes (`*Empty { slot }` / `*Absolute { slot, path }` /
4214    /// `*ParentEscape { slot, path }`). Cross-slot precedence is
4215    /// `:bibliotecas` → `:exe` → `:servicos` — the same declaration
4216    /// order [`Caixa::declared_foreign_code_slots`] uses for its
4217    /// canonical foreign-code-slot diagnostic, so a manifest with
4218    /// multiple malformed slots surfaces the lexicographically-earliest
4219    /// slot's diagnostic deterministically.
4220    ///
4221    /// Lifted to the typed surface as a Caixa-level validator (peer
4222    /// of [`Self::validate_nome`] / [`Self::validate_versao`] /
4223    /// [`Self::validate_deps`] / [`Self::validate_restart_window`])
4224    /// and wired into [`crate::StandardLayout::verify`] before the
4225    /// existence-check loops so the diagnostic names the offending
4226    /// slot at the source caixa.lisp rather than reporting a
4227    /// downstream `MissingEntry` / `ExeOutsideDir` /
4228    /// `ServicoOutsideDir` against the resolved sandbox-escape path.
4229    /// The fourth typed code-path surface — every author-supplied
4230    /// path on the manifest — is now structurally accept-shaped
4231    /// past validate, peer with `:behavior :on-*` and
4232    /// `:upgrade-from :state-change :script`.
4233    pub fn validate_code_paths(&self) -> Result<(), ManifestError> {
4234        /// Per-slot file-type contract for the three Caixa-level
4235        /// code-path surfaces (`:bibliotecas`, `:exe`, `:servicos`).
4236        /// Each variant names the predicate the per-entry file-type
4237        /// gate consults; [`Self::None`] opts the slot out of any
4238        /// file-type contract. Lifted as a typed local enum so the
4239        /// per-slot dispatch is exhaustive at the `match` — adding a
4240        /// future axis to the typed-substrate `:` slot set (the
4241        /// future `:assets` resource axis the M5 roadmap names, the
4242        /// future `:nix-flake` derivation axis the caixa-flake
4243        /// emitter consults) lands as one variant + one `match` arm,
4244        /// not a coordinated rewrite of every per-slot bool flag.
4245        ///
4246        /// Peer of the typed-substrate per-slot variant disciplines
4247        /// already established on this surface
4248        /// ([`crate::supervisor::RestartStrategy`] +
4249        /// [`crate::supervisor::RestartPolicy`] on the OTP-shape
4250        /// supervision-tree axis,
4251        /// [`crate::aplicacao::PlacementStrategy`] on the §III.1
4252        /// placement axis, [`crate::aplicacao::WitTarget`] on the
4253        /// `:contratos` payload-target axis): the typed `enum` is
4254        /// the substrate's single source of truth for the per-axis
4255        /// dispatch, and every consumer (the per-arm body here, the
4256        /// future feira-lint per-slot diagnostic renderer, the M4
4257        /// per-axis admission webhook) reaches for the same typed
4258        /// surface rather than re-deriving the partition from inline
4259        /// flag combinations.
4260        enum CodePathFileType {
4261            /// `:exe` — nix-build derivation output, no terminating-
4262            /// extension contract (the canonical `"exe/<name>"`
4263            /// fixtures the layout's `ExeOutsideDir` error message
4264            /// documents carry no extension by convention).
4265            None,
4266            /// `:bibliotecas` — tatara-lisp source files the
4267            /// `feira build` loop reads through `tatara_lisp::read`
4268            /// at parse time. Routes to [`is_lisp_extension`].
4269            LispSource,
4270            /// `:servicos` — ComputeUnit-CR YAML files the
4271            /// caixa-helm / caixa-flux renderers consume through
4272            /// `serde_yaml::from_str`. Routes to
4273            /// [`is_computeunit_yaml_extension`].
4274            ComputeUnitYaml,
4275        }
4276
4277        // The per-slot [`CodePathFileType`] selects which axes carry the
4278        // lifted file-type predicate. `:bibliotecas` is the tatara-lisp
4279        // source axis (the `feira build` loop at
4280        // `caixa-feira/src/cmd/build.rs:33` reads each entry through
4281        // `tatara_lisp::read` at parse time) — the lifted
4282        // [`is_lisp_extension`] predicate gates the `.lisp` extension.
4283        // `:exe` is the nix-built executable surface (per the canonical
4284        // `"exe/<name>"`-shaped fixtures the layout's `ExeOutsideDir`
4285        // error message documents and every in-tree
4286        // `caixa_with_code_paths` positive control uses) — its file-type
4287        // contract is "nix-build derivation output", not a typed source
4288        // file, so [`CodePathFileType::None`] opts the slot out of any
4289        // file-type gate. `:servicos` is the `.computeunit.yaml`
4290        // ComputeUnit-CR axis (the peer caixa-helm / caixa-flux
4291        // renderers consume each entry through `serde_yaml::from_str` as
4292        // a typed `ComputeUnit` CR) — the lifted
4293        // [`is_computeunit_yaml_extension`] predicate gates the compound
4294        // `.computeunit.yaml` suffix. All three axes are surfaced through
4295        // the same iteration so the sandbox-shape + duplicate gates
4296        // apply uniformly; the typed file-type dispatch fires per-slot
4297        // exactly where the downstream consumer's accepted set demands
4298        // it. The third file-type variant ([`ComputeUnitYaml`]) is the
4299        // compounding lift on the peer 64772a9 `:bibliotecas`
4300        // `.lisp`-gate trajectory — the second of the three code-path
4301        // axes to land on a typed compound-suffix gate, with the same
4302        // self-locating per-slot diagnostic shape every peer per-axis
4303        // file-type lift uses (`*NonLispExtension { slot, path }` /
4304        // `*NonComputeUnitYamlExtension { slot, path }`).
4305        for (slot, list, file_type) in [
4306            (
4307                ":bibliotecas",
4308                &self.bibliotecas,
4309                CodePathFileType::LispSource,
4310            ),
4311            (":exe", &self.exe, CodePathFileType::None),
4312            (
4313                ":servicos",
4314                &self.servicos,
4315                CodePathFileType::ComputeUnitYaml,
4316            ),
4317        ] {
4318            // Per-slot set-not-multiset gate on the typed code-path axis.
4319            // Every peer Vec-shaped author-supplied list past validate is
4320            // a set, not a multiset: `:membros :caixa`
4321            // ([`crate::AplicacaoError::MembroDuplicate`]), `:placement
4322            // :clusters` ([`crate::AplicacaoError::PlacementClusterDuplicate`]),
4323            // `:entrada :paths` ([`crate::AplicacaoError::EntradaPathDuplicate`]),
4324            // `:contratos` ([`crate::AplicacaoError::ContratoDuplicate`]),
4325            // `:children :caixa` ([`crate::SupervisorError::DuplicateChild`]),
4326            // `:deps` / `:deps-dev` `:nome` ([`crate::DepError::DuplicateNome`]
4327            // per 359fba5), `:upgrade-from :from` ([`crate::UpgradeError::DuplicateFrom`]),
4328            // `:etiquetas` ([`ManifestError::EtiquetaDuplicate`] per 360a499),
4329            // `:autores` ([`ManifestError::AutorDuplicate`] per 86c769b) —
4330            // the three code-path lists are the last Vec-shaped author-
4331            // supplied slots on the typed Caixa surface still admitting a
4332            // duplicate entry silently. Scope is per-list (`:bibliotecas`
4333            // duplicates are flagged within `:bibliotecas`, not across
4334            // `:bibliotecas` ↔ `:exe`) — the same per-list scope `:deps`
4335            // ↔ `:deps-dev` use (a `:nome` present in both lists is a
4336            // legitimate dev-vs-runtime shape on the dep axis, fenced
4337            // separately by [`crate::dep::validate_no_self_dep`]). On the
4338            // code-path axis a cross-slot collision is structurally
4339            // impossible by the layout's `starts_with(<exe|servicos>_dir)`
4340            // fence — `:exe` and `:servicos` entries are confined to their
4341            // own directory trees, so the only way a string could appear
4342            // on two code-path lists is the (rare, structurally invalid)
4343            // case where `:bibliotecas` carries an `"exe/<x>"` or
4344            // `"servicos/<x>.yaml"`-shaped path.
4345            //
4346            // Without the gate three authoring footguns silently passed:
4347            //
4348            //   - `:bibliotecas ("lib/foo.lisp" "lib/foo.lisp")` — the
4349            //     canonical copy-paste-the-wrong-file footgun. `feira
4350            //     build` (`caixa-feira/src/cmd/build.rs:33`) walks the
4351            //     list and re-parses the same file twice, wasting work
4352            //     and silently masking the author's intent to declare a
4353            //     *second* biblioteca.
4354            //   - `:exe ("exe/cli" "exe/cli")` — the same footgun on the
4355            //     Binario surface. The future `caixa-flake` `nix flake`
4356            //     emitter that materializes each `:exe` entry as a flake
4357            //     `packages.<exe-name>` derivation would collide on the
4358            //     duplicate package name and surface a flake-eval error
4359            //     far from the source `caixa.lisp`.
4360            //   - `:servicos ("servicos/x.computeunit.yaml"
4361            //     "servicos/x.computeunit.yaml")` — the same footgun on
4362            //     the Servico surface. The peer `caixa-helm` / `caixa-flux`
4363            //     renderers already refuse `:servicos.len() != 1` with
4364            //     the narrower [`UnsupportedServicoCount`] diagnostic, but
4365            //     that diagnostic surfaces "too many servicos" without
4366            //     naming "duplicate entry" — the typed self-locating
4367            //     "which entry is the duplicate" framing only lands at
4368            //     this gate.
4369            //
4370            // Same `seen.insert(entry.as_str())` shape every peer per-list
4371            // duplicate gate uses (`:etiquetas` 360a499, `:autores`
4372            // 86c769b, `:deps` 359fba5) and the same "structural shape
4373            // checks fire before the duplicate check on the same entry"
4374            // ordering (a `(:bibliotecas ("" "lib/x.lisp" "lib/x.lisp"))`
4375            // shape surfaces the narrower [`Self::CodePathEmpty`] for the
4376            // empty entry first, not the duplicate on the later pair).
4377            let mut seen = std::collections::HashSet::new();
4378            for entry in list {
4379                let path = Path::new(entry);
4380                match is_sandboxed_relative_path(path) {
4381                    Ok(()) => {}
4382                    Err(PathShapeViolation::Empty) => {
4383                        return Err(ManifestError::CodePathEmpty { slot });
4384                    }
4385                    Err(PathShapeViolation::Absolute) => {
4386                        return Err(ManifestError::CodePathAbsolute {
4387                            slot,
4388                            path: path.to_path_buf(),
4389                        });
4390                    }
4391                    Err(PathShapeViolation::ParentEscape) => {
4392                        return Err(ManifestError::CodePathParentEscape {
4393                            slot,
4394                            path: path.to_path_buf(),
4395                        });
4396                    }
4397                }
4398                // The per-slot file-type gate dispatched through the
4399                // typed [`CodePathFileType`] selector above. Each variant
4400                // routes to the lifted predicate the downstream consumer
4401                // demands:
4402                //
4403                //   - [`LispSource`] → [`is_lisp_extension`] for
4404                //     `:bibliotecas` (the `feira build` loop's
4405                //     `tatara_lisp::read` consumer);
4406                //   - [`ComputeUnitYaml`] → [`is_computeunit_yaml_extension`]
4407                //     for `:servicos` (the caixa-helm / caixa-flux
4408                //     `serde_yaml::from_str` consumer's `ComputeUnit` CR
4409                //     accepted set);
4410                //   - [`None`] for `:exe` — the nix-build derivation-
4411                //     output axis has no terminating-extension contract.
4412                //
4413                // Fires after the sandbox-shape arms so a path that is
4414                // *both* sandbox-escaping and wrong-extension surfaces
4415                // the more fundamental sandbox-shape diagnostic first
4416                // (mirrors the peer `EmptyPath` → `AbsolutePath` →
4417                // `ParentEscape` → `NonLispExtension` arm-ordering on
4418                // `:behavior :on-*` c97815a, and `EmptyScript` →
4419                // `AbsoluteScript` → `ParentEscapeScript` →
4420                // `NonLispExtensionScript` on
4421                // `:upgrade-from :state-change :script` 33cc830), and
4422                // before the duplicate gate so the narrower per-entry
4423                // file-type shape dominates the cross-entry uniqueness
4424                // diagnostic (a
4425                // `("servicos/x.yaml" "servicos/x.yaml")` shape on
4426                // `:servicos` surfaces
4427                // `CodePathNonComputeUnitYamlExtension` on the first
4428                // entry rather than `CodePathDuplicate` on the pair —
4429                // peer with the 64772a9 `:bibliotecas`
4430                // `("lib/x.txt" "lib/x.txt")` ordering).
4431                match file_type {
4432                    CodePathFileType::None => {}
4433                    CodePathFileType::LispSource => {
4434                        if !is_lisp_extension(path) {
4435                            return Err(ManifestError::CodePathNonLispExtension {
4436                                slot,
4437                                path: path.to_path_buf(),
4438                            });
4439                        }
4440                    }
4441                    CodePathFileType::ComputeUnitYaml => {
4442                        if !is_computeunit_yaml_extension(path) {
4443                            return Err(ManifestError::CodePathNonComputeUnitYamlExtension {
4444                                slot,
4445                                path: path.to_path_buf(),
4446                            });
4447                        }
4448                    }
4449                }
4450                crate::render::insert_first_seen(&mut seen, entry.as_str(), || {
4451                    ManifestError::CodePathDuplicate {
4452                        slot,
4453                        path: path.to_path_buf(),
4454                    }
4455                })?;
4456            }
4457        }
4458        Ok(())
4459    }
4460
4461    /// Reject `:etiquetas` lists with an empty entry or with two entries
4462    /// agreeing on the same string. `:etiquetas` is the universal
4463    /// registry-search-tag axis on [`Caixa`] (every kind carries the
4464    /// `Vec<String>` slot) and lands verbatim as the Helm chart
4465    /// `Chart.yaml` `keywords:` array on every Servico (caixa-helm's
4466    /// `build_chart_yaml` at `caixa-helm/src/lib.rs:236` folds it through
4467    /// a [`std::collections::BTreeSet`] alongside the four substrate-
4468    /// fixed tags `lareira` / `wasm` / `tatara-lisp` / `caixa-servico`).
4469    /// Two authoring footguns silently passed validate without this gate:
4470    ///
4471    ///   - Empty entry (`(:etiquetas (""))` — the canonical paste-from-
4472    ///     blank-doc footgun) rendered as `keywords: ["", "caixa-servico",
4473    ///     "lareira", "tatara-lisp", "wasm"]` in `Chart.yaml`. Helm's
4474    ///     `chart.metadata.keywords` admits the value without a strict
4475    ///     parser-side gate, but the empty keyword has no operational
4476    ///     meaning — it indexes nothing in the future caixa-registry
4477    ///     search axis and clutters the rendered chart with a no-op tag.
4478    ///   - Duplicate entries (`(:etiquetas ("demo" "demo"))` — the
4479    ///     copy-paste-the-wrong-tag footgun) silently passed validate
4480    ///     and were silently dedup'd by caixa-helm's `BTreeSet` collect
4481    ///     at chart render — a "second wins / one silently disappears"
4482    ///     shape divergent from every peer typed-graph set gate
4483    ///     ([`crate::AplicacaoError::MembroDuplicate`] on `:membros`,
4484    ///     [`crate::AplicacaoError::PlacementClusterDuplicate`] on
4485    ///     `:placement :clusters`, [`crate::AplicacaoError::EntradaPathDuplicate`]
4486    ///     on `:entrada :paths`, [`crate::AplicacaoError::ContratoDuplicate`]
4487    ///     on `:contratos`, [`crate::DepError::DuplicateNome`] on
4488    ///     `:deps` / `:deps-dev` per 359fba5, [`crate::UpgradeError::DuplicateFrom`]
4489    ///     on `:upgrade-from`, the per-instruction-class singularity
4490    ///     gates [`crate::UpgradeError::DuplicateLoadModule`] /
4491    ///     [`crate::UpgradeError::DuplicateStateChange`] /
4492    ///     [`crate::UpgradeError::DuplicateCleanup`]). The typed-graph
4493    ///     discipline is uniform: every Vec-shaped author-supplied list
4494    ///     past validate is set-not-multiset, by construction.
4495    ///
4496    /// Past the empty arm the gate enforces the chart-keyword shape
4497    /// predicate via [`crate::render::is_chart_keyword_shape`]: Cargo's
4498    /// crates.io `[package] keywords` grammar — 1..=20 bytes, starts
4499    /// with an ASCII letter, ASCII alphanumeric / `_` / `-`
4500    /// continuation. Closes the canonical paste-from-doc footguns the
4501    /// bare empty + duplicate arms left open: paste-from-aligned-doc
4502    /// whitespace (`" mesh"`, `"mesh "`), paste-from-multiline-doc
4503    /// newline (`"mesh\nhttp"` — the author pasted a multi-tag block
4504    /// into one entry instead of splitting), paste-from-Windows-CRLF-doc
4505    /// carriage return, CSV-list-separator confusion (`"mesh,http,grpc"`
4506    /// — the author meant three separate list entries), path-separator
4507    /// confusion (`"caixa/servico"`), namespace-suffix (`"http.1"`),
4508    /// leading-digit (`"1foo"`), kebab-leak (`"-foo"`), snake-leak
4509    /// (`"_foo"`), non-ASCII (`"café"`), and paste-from-binary-blob
4510    /// control bytes that would silently land as malformed search tags
4511    /// in the rendered Chart.yaml `keywords:` array and break the
4512    /// Artifact Hub keyword index lookup far from the source caixa.lisp.
4513    /// Mirrors the [`Self::validate_autores`] shape-predicate cascade
4514    /// established on the sibling universal-axis `Vec<String>` surface
4515    /// — the second universal-axis Vec<String> surface to land the
4516    /// empty-first-then-shape-then-duplicate per-entry cascade.
4517    ///
4518    /// Same empty-first cascade discipline every peer per-axis gate
4519    /// uses: the per-entry empty arm fires before the per-entry shape
4520    /// arm fires before the cross-entry duplicate arm, so an
4521    /// `("" "mesh" "mesh")` authoring shape surfaces the narrower
4522    /// [`ManifestError::EtiquetaEmpty`] (the structural "this entry
4523    /// has no value" defect) before either the shape or the duplicate
4524    /// diagnostic. Walks the list in declaration order so the
4525    /// first-collision diagnostic surfaces the lexicographically-
4526    /// earliest offending position, peer with every other duplicate
4527    /// gate on this surface.
4528    ///
4529    /// Universal-axis (every kind carries `:etiquetas`), so wired at the
4530    /// caixa-build gate alongside the peer universal gates
4531    /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4532    /// [`Self::validate_deps`] / [`Self::validate_code_paths`] — before
4533    /// the kind-coherence gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`]
4534    /// / [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4535    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4536    /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-specific
4537    /// slot sets. The future caixa-registry search axis can reach for
4538    /// `caixa.etiquetas` knowing every entry is a non-empty distinct
4539    /// chart-keyword-shaped string without re-deriving the precondition.
4540    pub fn validate_etiquetas(&self) -> Result<(), ManifestError> {
4541        let mut seen = std::collections::HashSet::new();
4542        for etiqueta in self.etiquetas() {
4543            if etiqueta.is_empty() {
4544                return Err(ManifestError::EtiquetaEmpty);
4545            }
4546            crate::render::is_chart_keyword_shape(etiqueta).map_err(|reason| {
4547                ManifestError::EtiquetaInvalid {
4548                    etiqueta: etiqueta.clone(),
4549                    reason,
4550                }
4551            })?;
4552            crate::render::insert_first_seen(&mut seen, etiqueta.as_str(), || {
4553                ManifestError::EtiquetaDuplicate {
4554                    etiqueta: etiqueta.clone(),
4555                }
4556            })?;
4557        }
4558        Ok(())
4559    }
4560
4561    /// Reject `:autores` lists with an empty entry or with two entries
4562    /// agreeing on the same string. `:autores` is the universal
4563    /// maintainer-axis on [`Caixa`] (every kind carries the
4564    /// `Vec<String>` slot) and lands verbatim as the Helm chart
4565    /// `Chart.yaml` `maintainers:` array on every Servico (caixa-helm's
4566    /// `build_chart_yaml` at `caixa-helm/src/lib.rs:251` maps each entry
4567    /// to a `Maintainer { name, email: None }` without dedup). Two
4568    /// authoring footguns silently passed validate without this gate:
4569    ///
4570    ///   - Empty entry (`(:autores (""))` — the canonical paste-from-
4571    ///     blank-doc footgun) rendered as
4572    ///     `maintainers: [{name: "", email: null}]` in `Chart.yaml`. The
4573    ///     empty maintainer name has no operational meaning — it
4574    ///     identifies no one in the substrate's authorship index and
4575    ///     clutters the rendered chart with a no-op maintainer.
4576    ///   - Duplicate entries (`(:autores ("pleme-io" "pleme-io"))` —
4577    ///     the copy-paste-the-wrong-author footgun) silently passed
4578    ///     validate and rendered as two identical maintainer entries.
4579    ///     Unlike the [`Self::validate_etiquetas`] peer (caixa-helm's
4580    ///     `BTreeSet`-collect on `:etiquetas` silently dedups the
4581    ///     rendered `keywords:` array at chart-render time), the
4582    ///     `maintainers:` rendering has *no* dedup — duplicate `:autores`
4583    ///     entries stack verbatim in the chart, divergent from every
4584    ///     peer typed-graph set gate ([`crate::AplicacaoError::MembroDuplicate`]
4585    ///     on `:membros`, [`crate::AplicacaoError::PlacementClusterDuplicate`]
4586    ///     on `:placement :clusters`, [`crate::AplicacaoError::EntradaPathDuplicate`]
4587    ///     on `:entrada :paths`, [`crate::AplicacaoError::ContratoDuplicate`]
4588    ///     on `:contratos`, [`crate::DepError::DuplicateNome`] on
4589    ///     `:deps` / `:deps-dev`, [`crate::UpgradeError::DuplicateFrom`]
4590    ///     on `:upgrade-from`, [`ManifestError::EtiquetaDuplicate`] on
4591    ///     `:etiquetas`).
4592    ///
4593    /// Past the empty arm the gate enforces the chart-maintainer-name
4594    /// shape predicate via [`crate::render::is_chart_maintainer_name_shape`]:
4595    /// the structural single-line printable-UTF-8 floor every realistic
4596    /// Helm chart maintainer name carries — 1..=128 bytes, no leading
4597    /// or trailing whitespace, no ASCII control characters anywhere,
4598    /// Unicode bytes accepted. Closes the canonical paste-from-doc
4599    /// footguns the bare empty + duplicate arms left open:
4600    /// paste-from-aligned-doc whitespace (`" pleme-io"`, `"pleme-io "`),
4601    /// paste-from-multiline-doc newline (`"alice\nbob"` — the author
4602    /// pasted a multi-line block of author records into one `:autores`
4603    /// entry instead of splitting into one entry per author),
4604    /// paste-from-Windows-CRLF-doc carriage return, tab-from-aligned-doc,
4605    /// and the paste-from-binary-blob control bytes that would silently
4606    /// land as YAML-illegal byte sequences in the rendered Chart.yaml
4607    /// `maintainers:` array. Mirrors the shape-predicate cascade
4608    /// [`Self::validate_descricao`] / [`Self::validate_licenca`] /
4609    /// [`Self::validate_edicao`] / [`Self::validate_repositorio`]
4610    /// establish past their own empty arms on the sibling universal-axis
4611    /// `Option<String>` surfaces — the first universal-axis Vec<String>
4612    /// surface to land the empty-first-then-shape-then-duplicate per-entry
4613    /// cascade.
4614    ///
4615    /// Same empty-first cascade discipline every peer per-axis gate
4616    /// uses: the per-entry empty arm fires before the per-entry shape
4617    /// arm before the cross-entry duplicate arm. Walks the list in
4618    /// declaration order so the first-collision diagnostic surfaces the
4619    /// lexicographically-earliest offending position, peer with every
4620    /// other duplicate gate on this surface.
4621    ///
4622    /// Universal-axis (every kind carries `:autores`), so wired at the
4623    /// caixa-build gate alongside the peer universal gates
4624    /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4625    /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
4626    /// [`Self::validate_code_paths`] — before the kind-coherence gates
4627    /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
4628    /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4629    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4630    /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-specific
4631    /// slot sets.
4632    pub fn validate_autores(&self) -> Result<(), ManifestError> {
4633        let mut seen = std::collections::HashSet::new();
4634        for autor in self.autores() {
4635            if autor.is_empty() {
4636                return Err(ManifestError::AutorEmpty);
4637            }
4638            crate::render::is_chart_maintainer_name_shape(autor).map_err(|reason| {
4639                ManifestError::AutorInvalid {
4640                    autor: autor.clone(),
4641                    reason,
4642                }
4643            })?;
4644            crate::render::insert_first_seen(&mut seen, autor.as_str(), || {
4645                ManifestError::AutorDuplicate {
4646                    autor: autor.clone(),
4647                }
4648            })?;
4649        }
4650        Ok(())
4651    }
4652
4653    /// Reject `:repositorio` values whose shape the shared
4654    /// [`crate::render::is_git_repo_url`] predicate refuses. The flat
4655    /// `repositorio: Option<String>` slot on [`Caixa`] is the
4656    /// universal git-shaped homepage axis every kind carries — the
4657    /// substrate routes the same string through two load-bearing
4658    /// consumers:
4659    ///
4660    ///   - [`caixa-helm`] folds it verbatim into the rendered
4661    ///     `lareira-<nome>` Helm chart's `Chart.yaml` `home:` field
4662    ///     (`build_chart_yaml` at `caixa-helm/src/lib.rs:268`) and into
4663    ///     the chart `README.md` `repo = …` interpolation
4664    ///     (`caixa-helm/src/lib.rs:359`).
4665    ///   - [`caixa-flux`] folds it verbatim into the standalone
4666    ///     `ClusterBundleOpts::for_caixa` `git_url:` field
4667    ///     (`caixa-flux/src/lib.rs:293`), which becomes the `FluxCD`
4668    ///     `GitRepository.spec.url` the cluster's source-controller
4669    ///     polls — the load-bearing deploy-time axis.
4670    ///
4671    /// Both consumers use `Option::unwrap_or_else(|| <fallback>)` to
4672    /// substitute a placeholder when the slot is absent (`None` → the
4673    /// fallback fires); a `Some("")` *skips the fallback* and silently
4674    /// passes the empty string through to `Chart.yaml home: ""` /
4675    /// `GitRepository url: ""` — Helm's chart lint and `FluxCD`'s source
4676    /// controller both reject the empty URL far from the source
4677    /// `caixa.lisp`, with no field naming the offending `:repositorio`.
4678    /// Similarly a malformed `:repositorio` (whitespace, control char,
4679    /// missing `:` separator, leading `-`) silently lands in the
4680    /// rendered artifacts and breaks at `git clone` / `helm template`
4681    /// / `flux reconcile` time.
4682    ///
4683    /// Thin wrapper around [`crate::render::is_git_repo_url`] — the
4684    /// same shared predicate the peer [`crate::DepSource::validate`]
4685    /// routes the `:fonte (:tipo git :repo …)` axis through. With this
4686    /// gate the two `git URL`-shaped surfaces on the typed Caixa
4687    /// (`:repositorio` here, `:deps :fonte :repo` peer) are
4688    /// structurally equivalent: every value past validate is
4689    /// guaranteed-acceptable by the predicate's union of constraints
4690    /// (non-empty, length-bounded, no leading `-`, no whitespace, no
4691    /// control chars, ASCII only, no leading `:`, contains a `:`
4692    /// separator). The predicate accepts every documented authoring
4693    /// shape — `github:org/repo` shorthand, `https://host/path`,
4694    /// `ssh://[user@]host/path`, `git://host/path`, `git@host:path`
4695    /// scp-style SSH, `file:///path` — and refuses the canonical
4696    /// paste-from-blank-doc / paste-from-multiline-doc / CLI-arg-
4697    /// injection footguns at validate time. Maps the predicate's
4698    /// `String` reason verbatim into the
4699    /// [`ManifestError::RepositorioInvalid`] variant, carrying the
4700    /// offending value + parser-shaped reason so the diagnostic is
4701    /// self-locating (the author can grep their `caixa.lisp` for
4702    /// `:repositorio "<value>"` and fix it in one edit).
4703    ///
4704    /// `None` (the canonical "omit the slot to express no published
4705    /// homepage" shape) is accepted trivially — the gate is a no-op
4706    /// when the author didn't declare a value. `Some("")` is gated by
4707    /// the narrower [`ManifestError::RepositorioEmpty`] arm before the
4708    /// shape predicate is consulted, mirroring the empty-first cascade
4709    /// every peer per-axis identity gate uses
4710    /// ([`ManifestError::NomeEmpty`] → [`ManifestError::NomeInvalid`],
4711    /// [`ManifestError::VersaoEmpty`] → [`ManifestError::VersaoInvalid`],
4712    /// [`crate::DepError::FonteRepoEmpty`] →
4713    /// [`crate::DepError::FonteRepoInvalid`]).
4714    ///
4715    /// Universal-axis (every kind carries `:repositorio`), so wired at
4716    /// the caixa-build gate alongside the peer universal gates
4717    /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4718    /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
4719    /// [`Self::validate_autores`] / [`Self::validate_code_paths`] —
4720    /// before the kind-coherence gates
4721    /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
4722    /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4723    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4724    /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
4725    /// specific slot sets.
4726    pub fn validate_repositorio(&self) -> Result<(), ManifestError> {
4727        let Some(s) = self.repositorio() else {
4728            return Ok(());
4729        };
4730        if s.is_empty() {
4731            return Err(ManifestError::RepositorioEmpty);
4732        }
4733        is_git_repo_url(s).map_err(|reason| ManifestError::RepositorioInvalid {
4734            repositorio: s.to_string(),
4735            reason,
4736        })
4737    }
4738
4739    /// Reject `:descricao` values that are the empty string. The flat
4740    /// `descricao: Option<String>` slot on [`Caixa`] is the universal
4741    /// free-form-prose homepage axis every kind carries — the
4742    /// substrate routes the same string through two load-bearing
4743    /// consumers in the [`caixa-helm`] renderer:
4744    ///
4745    ///   - `build_chart_yaml` folds it verbatim into the rendered
4746    ///     `lareira-<nome>` Helm chart's `Chart.yaml` `description:`
4747    ///     field (`caixa-helm/src/lib.rs:232-235`).
4748    ///   - `build_readme` folds it verbatim into the rendered chart
4749    ///     `README.md` header (`caixa-helm/src/lib.rs:333-336`).
4750    ///
4751    /// Both consumers use `Option::unwrap_or_else(|| <fallback>)` to
4752    /// substitute a `caixa.nome`-derived placeholder when the slot is
4753    /// absent (`None` → the fallback fires); a `Some("")` *skips the
4754    /// fallback* and silently passes the empty string through to
4755    /// `Chart.yaml description: ""` / a blank chart `README.md`
4756    /// header. Helm's chart spec requires a non-empty `description:`
4757    /// field on `apiVersion: v2` charts (`helm lint` surfaces it as
4758    /// `WARNING [chart.metadata.description]: description is required`),
4759    /// so the empty `Some("")` silently lands in the rendered
4760    /// artifacts and breaks at `helm lint` / `helm install` time far
4761    /// from the source `caixa.lisp`, with no field naming the
4762    /// offending `:descricao`.
4763    ///
4764    /// `None` (the canonical "omit the slot to defer to the renderer's
4765    /// `caixa.nome`-derived fallback" shape) is accepted trivially —
4766    /// the gate is a no-op when the author didn't declare a value.
4767    /// `Some("")` is gated by the narrower
4768    /// [`ManifestError::DescricaoEmpty`] arm, mirroring the empty-arm
4769    /// shape every peer per-axis empty gate uses
4770    /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
4771    /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
4772    /// [`ManifestError::RepositorioEmpty`]).
4773    ///
4774    /// Universal-axis (every kind carries `:descricao`), so wired at
4775    /// the caixa-build gate alongside the peer universal gates
4776    /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4777    /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
4778    /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
4779    /// [`Self::validate_code_paths`] — before the kind-coherence
4780    /// gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
4781    /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4782    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4783    /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
4784    /// specific slot sets.
4785    ///
4786    /// Past the empty arm the gate enforces the chart-description
4787    /// shape predicate via [`crate::render::is_chart_description_shape`]:
4788    /// the structural single-line UTF-8 floor every realistic chart
4789    /// description in the wild matches — 1..=512 bytes, no leading
4790    /// or trailing whitespace, no ASCII control characters anywhere
4791    /// (`0x00..=0x1F` plus `0x7F` DEL — banning tab, newline,
4792    /// carriage return, and every other control byte), Unicode
4793    /// continuation bytes accepted (the canonical fixtures carry
4794    /// `→` and `—`). Closes the canonical paste-from-doc footguns
4795    /// the bare empty-arm gate left open: paste-from-aligned-doc
4796    /// leading / trailing whitespace (`" Checkout flow."`,
4797    /// `"Checkout flow. "`), paste-from-multiline-doc newline
4798    /// (`"Checkout\nflow."`), paste-from-Windows-CRLF-doc CR
4799    /// (`"Checkout\rflow."`), tab-from-aligned-doc
4800    /// (`"Checkout\tflow."`), and paste-from-binary-blob NUL / BEL /
4801    /// ESC / DEL bytes. Mirrors the shape-predicate cascade
4802    /// [`Self::validate_repositorio`] / [`Self::validate_licenca`] /
4803    /// [`Self::validate_edicao`] establish past their own empty arms
4804    /// on the sibling universal-axis `Option<String>` Caixa-level
4805    /// value-shape surfaces.
4806    ///
4807    /// The empty-first cascade discipline mirrors every peer per-axis
4808    /// identity gate: [`ManifestError::DescricaoEmpty`] runs before
4809    /// [`ManifestError::DescricaoInvalid`], so the narrower empty
4810    /// diagnostic surfaces on `Some("")` rather than the broader
4811    /// shape-predicate diagnostic — peer with how
4812    /// [`ManifestError::LicencaEmpty`] runs before
4813    /// [`ManifestError::LicencaInvalid`],
4814    /// [`ManifestError::EdicaoEmpty`] runs before
4815    /// [`ManifestError::EdicaoInvalid`],
4816    /// [`ManifestError::RepositorioEmpty`] runs before
4817    /// [`ManifestError::RepositorioInvalid`].
4818    pub fn validate_descricao(&self) -> Result<(), ManifestError> {
4819        let Some(s) = self.descricao() else {
4820            return Ok(());
4821        };
4822        if s.is_empty() {
4823            return Err(ManifestError::DescricaoEmpty);
4824        }
4825        crate::render::is_chart_description_shape(s).map_err(|reason| {
4826            ManifestError::DescricaoInvalid {
4827                descricao: s.to_string(),
4828                reason,
4829            }
4830        })?;
4831        Ok(())
4832    }
4833
4834    /// Reject `:licenca` values that are the empty string. The flat
4835    /// `licenca: Option<String>` slot on [`Caixa`] is the universal
4836    /// SPDX-shaped license-expression axis every kind carries — the
4837    /// substrate routes the same string through the [`caixa-helm`]
4838    /// renderer's `build_readme` which folds it verbatim into the
4839    /// rendered `lareira-<nome>` Helm chart's `README.md` `## License`
4840    /// section (`caixa-helm/src/lib.rs:361`) via
4841    /// `caixa.licenca.clone().unwrap_or_else(|| "MIT".into())`. The
4842    /// fallback only fires on `None`; a `Some("")` *skips the
4843    /// fallback* and silently passes the empty string through to a
4844    /// chart `README.md` whose `License` section renders as the bare
4845    /// trailing period (`.\n`) — peer footgun with the
4846    /// `Some("")`-skips-`unwrap_or_else` shape the
4847    /// [`Self::validate_descricao`] and [`Self::validate_repositorio`]
4848    /// gates close on the sibling free-form-prose and git-URL axes.
4849    ///
4850    /// `None` (the canonical "omit the slot to defer to the
4851    /// renderer's `MIT` fallback" shape every existing fixture
4852    /// carries) is accepted trivially — the gate is a no-op when the
4853    /// author didn't declare a value. `Some("")` is gated by the
4854    /// narrower [`ManifestError::LicencaEmpty`] arm, mirroring the
4855    /// empty-arm shape every peer per-axis empty gate uses
4856    /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
4857    /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
4858    /// [`ManifestError::RepositorioEmpty`],
4859    /// [`ManifestError::DescricaoEmpty`]).
4860    ///
4861    /// Universal-axis (every kind carries `:licenca`), so wired at
4862    /// the caixa-build gate alongside the peer universal gates
4863    /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4864    /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
4865    /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
4866    /// [`Self::validate_descricao`] / [`Self::validate_code_paths`]
4867    /// — before the kind-coherence gates
4868    /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
4869    /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4870    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4871    /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
4872    /// specific slot sets.
4873    ///
4874    /// Past the empty arm the gate enforces the SPDX-expression shape
4875    /// predicate via [`crate::render::is_spdx_expression_shape`]: the
4876    /// structural alphabet floor every realistic SPDX expression in
4877    /// the wild uses — ASCII alphanumeric plus `.`, `-`, `+`, `(`,
4878    /// `)`, `:` (the `DocumentRef-…:LicenseRef-…` separator), and a
4879    /// single ASCII space (token separator). Closes the canonical
4880    /// paste-from-doc footguns the bare empty-arm gate left open:
4881    /// paste-from-doc whitespace (`"MIT "`, `" MIT"`), paste-from-
4882    /// multiline-doc CRLF (`"MIT\n"`), tab-from-aligned-doc
4883    /// (`"MIT\tOR Apache-2.0"`), non-ASCII smart-quote paste,
4884    /// underscore-instead-of-hyphen typo (`"Apache_2.0"`),
4885    /// comma-instead-of-`OR`-keyword colloquial idiom (`"MIT,
4886    /// Apache-2.0"`), slash-dual-license colloquial idiom (`"MIT/
4887    /// Apache-2.0"`), and semicolon-list-separator confusion
4888    /// (`"MIT; Apache-2.0"`). Mirrors the shape-predicate cascade
4889    /// [`Self::validate_repositorio`] / [`Self::validate_edicao`]
4890    /// establish past their own empty arms.
4891    ///
4892    /// The empty-first cascade discipline mirrors every peer per-axis
4893    /// identity gate: [`ManifestError::LicencaEmpty`] runs before
4894    /// [`ManifestError::LicencaInvalid`], so the narrower empty
4895    /// diagnostic surfaces on `Some("")` rather than the broader
4896    /// shape-predicate diagnostic — peer with how
4897    /// [`ManifestError::EdicaoEmpty`] runs before
4898    /// [`ManifestError::EdicaoInvalid`],
4899    /// [`ManifestError::RepositorioEmpty`] runs before
4900    /// [`ManifestError::RepositorioInvalid`].
4901    ///
4902    /// A future tightening on this axis can extend the alphabet
4903    /// floor into a full SPDX expression parser + license-id
4904    /// allowlist (rejecting alphabet-valid values that don't name a
4905    /// real SPDX license identifier — e.g., `"NotAReal"` is
4906    /// alphabet-valid but no `NotAReal` license-id exists). That
4907    /// parser only becomes meaningful past a real SPDX-spec
4908    /// dependency; this gate establishes the structural floor by
4909    /// refusing every non-SPDX-alphabet value at validate time.
4910    pub fn validate_licenca(&self) -> Result<(), ManifestError> {
4911        let Some(s) = self.licenca() else {
4912            return Ok(());
4913        };
4914        if s.is_empty() {
4915            return Err(ManifestError::LicencaEmpty);
4916        }
4917        crate::render::is_spdx_expression_shape(s).map_err(|reason| {
4918            ManifestError::LicencaInvalid {
4919                licenca: s.to_string(),
4920                reason,
4921            }
4922        })?;
4923        Ok(())
4924    }
4925
4926    /// Reject `:edicao` values that are the empty string. The flat
4927    /// `edicao: Option<String>` slot on [`Caixa`] is the universal
4928    /// language-edition axis every kind carries — it determines the
4929    /// tatara-lisp macro surface + compatibility flags the substrate
4930    /// applies when building a caixa, and lands verbatim in the
4931    /// `Caixa::template` author-time scaffold (the canonical
4932    /// `:edicao "2026"` line every `feira init` emits via
4933    /// [`Caixa::template`] at `caixa-core/src/manifest.rs:1193`) and
4934    /// in every renderer-side fixture (`caixa-helm/src/lib.rs:375`,
4935    /// `caixa-flux/src/lib.rs:445`, `caixa-mesh/src/lib.rs:629`,
4936    /// `caixa-core/src/render.rs:2510`) via
4937    /// `edicao: Some("2026".into())`.
4938    ///
4939    /// `None` (the canonical "omit the slot to defer to the
4940    /// substrate's default edition" shape every existing
4941    /// [`caixa-resolver`] integration test fixture carries via
4942    /// `edicao: None` — see `caixa-resolver/tests/git_integration.rs`)
4943    /// is accepted trivially — the gate is a no-op when the author
4944    /// didn't declare a value. `Some("")` is gated by the narrower
4945    /// [`ManifestError::EdicaoEmpty`] arm, mirroring the empty-arm
4946    /// shape every peer per-axis empty gate uses
4947    /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
4948    /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
4949    /// [`ManifestError::RepositorioEmpty`],
4950    /// [`ManifestError::DescricaoEmpty`], [`ManifestError::LicencaEmpty`]).
4951    ///
4952    /// Universal-axis (every kind carries `:edicao`), so wired at
4953    /// the caixa-build gate alongside the peer universal gates
4954    /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4955    /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
4956    /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
4957    /// [`Self::validate_descricao`] / [`Self::validate_licenca`] /
4958    /// [`Self::validate_code_paths`] — before the kind-coherence
4959    /// gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
4960    /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4961    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4962    /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
4963    /// specific slot sets.
4964    ///
4965    /// Past the empty arm the gate enforces the canonical year-shape
4966    /// predicate: every documented tatara-lisp edition is a 4-digit
4967    /// ASCII decimal year (`"2026"` is the only edition currently
4968    /// minted; future-introduced siblings will follow the same
4969    /// shape, peer with Cargo's `[package] edition` grammar which
4970    /// every value Cargo has ever accepted matches — `"2015"`,
4971    /// `"2018"`, `"2021"`, `"2024"`). Any value that's not exactly
4972    /// 4 ASCII decimal bytes is rejected with the narrower
4973    /// [`ManifestError::EdicaoInvalid`] arm, mirroring the
4974    /// shape-predicate cascade [`Self::validate_repositorio`]
4975    /// establishes past its own empty arm
4976    /// ([`ManifestError::RepositorioEmpty`] →
4977    /// [`ManifestError::RepositorioInvalid`]). Closes the canonical
4978    /// paste-from-doc footguns the bare empty-arm gate left open:
4979    ///
4980    ///   - leading / trailing whitespace from a paste-from-doc
4981    ///     (`"2026 "`, `" 2026"`)
4982    ///   - control characters / CRLF from a paste-from-multiline-doc
4983    ///     (`"2026\n"`)
4984    ///   - non-ASCII look-alikes from a fullwidth keyboard
4985    ///     (`"2026"`) which would silently land as a non-ASCII
4986    ///     string in the rendered caixa.lisp
4987    ///   - free-form non-year values (`"x"`, `"latest"`,
4988    ///     `"nightly"`) that have no operational meaning on the
4989    ///     substrate's build-time edition selector
4990    ///   - leading non-digit prefixes (`"v2026"`, `"e2026"`,
4991    ///     `"r2026"`) — common version-tag idioms that don't apply
4992    ///     to the year-shaped edition axis
4993    ///   - decimal-shaped values (`"2026.1"`, `"2026.0"`) — every
4994    ///     edition is a year, not a fractional version
4995    ///   - wrong-length numeric values (`"26"`, `"202"`, `"20260"`,
4996    ///     `"00026"`) that don't name a year
4997    ///
4998    /// `None` (the canonical "omit the slot to defer to the
4999    /// substrate's default edition" shape every existing
5000    /// [`caixa-resolver`] integration test fixture carries via
5001    /// `edicao: None` — see `caixa-resolver/tests/git_integration.rs`)
5002    /// is accepted trivially — the gate is a no-op when the author
5003    /// didn't declare a value. The empty-first cascade discipline
5004    /// mirrors every peer per-axis identity gate:
5005    /// [`ManifestError::EdicaoEmpty`] runs before
5006    /// [`ManifestError::EdicaoInvalid`], so the narrower empty
5007    /// diagnostic surfaces on `Some("")` rather than the broader
5008    /// shape-predicate diagnostic — peer with how
5009    /// [`ManifestError::NomeEmpty`] runs before
5010    /// [`ManifestError::NomeInvalid`],
5011    /// [`ManifestError::VersaoEmpty`] runs before
5012    /// [`ManifestError::VersaoInvalid`],
5013    /// [`ManifestError::RepositorioEmpty`] runs before
5014    /// [`ManifestError::RepositorioInvalid`].
5015    ///
5016    /// A future tightening on this axis can extend the shape
5017    /// predicate into a known-edition allowlist (rejecting
5018    /// year-shaped values that don't name a tatara-lisp edition
5019    /// the substrate actually understands — e.g., `"1999"` is
5020    /// year-shaped but no `1999` edition exists). That allowlist
5021    /// only becomes meaningful past the introduction of a sibling
5022    /// edition to `"2026"`; this gate establishes the structural
5023    /// floor by refusing every non-year-shaped value at validate
5024    /// time.
5025    pub fn validate_edicao(&self) -> Result<(), ManifestError> {
5026        let Some(s) = self.edicao() else {
5027            return Ok(());
5028        };
5029        if s.is_empty() {
5030            return Err(ManifestError::EdicaoEmpty);
5031        }
5032        if s.len() != 4 || !s.bytes().all(|b| b.is_ascii_digit()) {
5033            return Err(ManifestError::EdicaoInvalid {
5034                edicao: s.to_string(),
5035                reason: "must be a 4-digit ASCII decimal year (canonical \"2026\")".to_string(),
5036            });
5037        }
5038        Ok(())
5039    }
5040
5041    /// Compose the supervisor-related flat slots into a single
5042    /// [`SupervisorSpec`] for validation. Returns `None` when the
5043    /// caixa isn't a `:kind Supervisor`.
5044    ///
5045    /// The flat representation in [`Caixa`] keeps tatara-lisp authoring
5046    /// simple (one form, no nested `:supervisor (…)` block); this view
5047    /// is the "typed shape" the operator + supervisor reconciler
5048    /// consume.
5049    #[must_use]
5050    pub fn supervisor_view(&self) -> Option<SupervisorSpec> {
5051        if !self.kind().is_supervisor() {
5052            return None;
5053        }
5054        // Fold through the shared `supervisor::duration_codec::parse`
5055        // — the same parser the serde-routed `with = "duration_codec"`
5056        // on `SupervisorSpec::restart_window`, the `:politicas
5057        // :timeout` codec, and the `:politicas :circuit-breaker
5058        // :window` codec all consume. The prior inline f64-shaped
5059        // duplicate (`parse_window_inline`) admitted every magnitude
5060        // the integer-magnitude gate (1c55a2a) rejects on the three
5061        // serde-routed siblings — `"1.5s"`, `"1.0s"`, `"0.5m"`,
5062        // `"+30s"`, `"-30s"` — and silently dropped malformed input as
5063        // `None` (i.e. "no reset"), divergent from the shared codec's
5064        // integer-magnitude discipline by construction. The fold
5065        // closes the divergence: every value the typed
5066        // `SupervisorSpec` carries past `supervisor_view` is in the
5067        // shared codec's accepted set. The `.ok()` here preserves the
5068        // existing soft-swallow shape on this view-construction path;
5069        // the new [`Caixa::validate_restart_window`] (sibling of
5070        // [`Self::validate_nome`] / [`Self::validate_versao`]) names
5071        // the offending raw string at build time so authoring tools
5072        // (`feira lint`, the future layout-side wire-up) surface a
5073        // self-locating diagnostic instead of a silently dropped
5074        // window.
5075        let restart_window = self
5076            .restart_window()
5077            .and_then(|s| crate::supervisor::duration_codec::parse(s).ok());
5078        Some(SupervisorSpec {
5079            estrategia: self.estrategia().unwrap_or_default(),
5080            max_restarts: self.max_restarts().unwrap_or(5),
5081            restart_window,
5082            children: self.children().to_vec(),
5083        })
5084    }
5085
5086    /// A minimal starter manifest emitted by `feira init`.
5087    #[must_use]
5088    pub fn template(nome: &str) -> String {
5089        format!(
5090            "(defcaixa\n  \
5091               :nome        {nome:?}\n  \
5092               :versao      \"0.1.0\"\n  \
5093               :kind        Biblioteca\n  \
5094               :edicao      \"2026\"\n  \
5095               :descricao   \"FIXME — describe this caixa\"\n  \
5096               :autores     ()\n  \
5097               :etiquetas   ()\n  \
5098               :deps        ()\n  \
5099               :deps-dev    ()\n  \
5100               :bibliotecas (\"lib/{nome}.lisp\"))\n"
5101        )
5102    }
5103
5104    /// Serialize to a canonical `caixa.lisp` source — suitable for writing
5105    /// back after mutation (e.g. `feira add`).
5106    ///
5107    /// Goes through serde JSON → canonical Sexp → per-field pretty print.
5108    /// The derive-macro `compile_from_sexp` path is the inverse, so any
5109    /// `Caixa` round-trips through `to_lisp` + `from_lisp`.
5110    #[must_use]
5111    pub fn to_lisp(&self) -> String {
5112        let json = serde_json::to_value(self).expect("Caixa serialize");
5113        let sexp = tatara_lisp::domain::json_to_sexp(&json);
5114        let tatara_lisp::Sexp::List(items) = sexp else {
5115            return format!("(defcaixa {sexp})\n");
5116        };
5117        let mut out = String::from("(defcaixa");
5118        let mut i = 0;
5119        while i + 1 < items.len() {
5120            out.push_str("\n  ");
5121            out.push_str(&items[i].to_string());
5122            out.push(' ');
5123            out.push_str(&items[i + 1].to_string());
5124            i += 2;
5125        }
5126        out.push_str(")\n");
5127        out
5128    }
5129}
5130
5131/// Errors raised by top-level [`Caixa`] validators that don't fit
5132/// the per-axis [`DepError`] / [`crate::AplicacaoError`] /
5133/// [`crate::SupervisorError`] / [`crate::LayoutError`] families —
5134/// the Caixa's own identity axes (`:nome`, `:versao`) that flow
5135/// through every substrate-side artifact's `metadata.name` /
5136/// version derivation.
5137///
5138/// A future top-level sum (the M4 `CaixaError` the [`DepError`]
5139/// doc-comment anticipates) can hold one of each per-axis error
5140/// family without reshaping individual diagnostics; this enum is
5141/// the first such per-Caixa-identity family.
5142#[derive(Debug, Error, PartialEq, Eq)]
5143pub enum ManifestError {
5144    #[error(
5145        ":nome is empty (every caixa must name itself; the value flows \
5146         into every K8s artifact's `metadata.name` derivation and into \
5147         the default `lib/<nome>.lisp` / `exe/<nome>` layout paths)"
5148    )]
5149    NomeEmpty,
5150    #[error(
5151        ":nome {nome:?} is not a valid DNS-1123 label: {reason} (the K8s \
5152         apiserver enforces this rule on every `metadata.name` the \
5153         caixa's substrate-side renderers derive from `:nome` — the \
5154         `lareira-<nome>` Helm chart name, the programs.yaml entry \
5155         name, the `LABEL_APLICACAO` label value, the `<aplicacao>-<de>-to-<para>` \
5156         CiliumNetworkPolicy name, the `<aplicacao>-<para>` HTTPRoute \
5157         name; use a lowercase alphanumeric + hyphen identifier like \
5158         `\"checkout\"` or `\"cart-v2\"`)"
5159    )]
5160    NomeInvalid { nome: String, reason: String },
5161    #[error(
5162        ":nome {nome:?} overflows the joint-length budget on the canonical \
5163         `lareira-<nome>` chart-name shape: {reason} (every per-Servico / \
5164         per-Aplicacao renderer the substrate carries — `caixa-helm`'s \
5165         `Chart.yaml::name`, `caixa-flux`'s `cluster_bundle` HelmRelease \
5166         `chart:` slot, `caixa-tatara`'s `release_name` + \
5167         `oci://<registry>/lareira-<nome>` chart ref — derives the same \
5168         joint name through the canonical `lareira_chart_name` helper, and \
5169         Helm's `Chart.yaml::name` admission rule + the K8s apiserver's \
5170         DNS-1123 label cap on every chart-name-derived `metadata.name` \
5171         reject any joint name exceeding 63 bytes; the narrower \
5172         `:nome` shape (`NomeInvalid`) gates the bare-`:nome` budget, this \
5173         arm gates the chart-name budget downstream renderers inherit)"
5174    )]
5175    NomeChartNameBudgetExceeded { nome: String, reason: String },
5176    #[error(
5177        ":versao is empty (every caixa must pin its own version; the value flows \
5178         into the `lareira-<nome>` Helm chart's `Chart.yaml` version + appVersion, \
5179         the `feira publish` `v<versao>` git tag, the OCI image's `:v<versao>` / \
5180         `:latest` tags, the lacre closure's `concrete_versao`, and the \
5181         `:upgrade-from :from` peers — use a SemVer-2 literal like `\"0.1.0\"`)"
5182    )]
5183    VersaoEmpty,
5184    #[error(
5185        ":versao {versao:?} is not a valid SemVer-2 version: {reason} (the substrate \
5186         consumes this string as `semver::Version` — three-part `MAJOR.MINOR.PATCH` \
5187         with optional `-prerelease` and `+build` — across every artifact derived \
5188         from `:versao`: the `lareira-<nome>` Helm chart's `Chart.yaml` version + \
5189         appVersion (Helm SemVer-2-strict), the `feira publish` `v<versao>` git tag, \
5190         the OCI image's `:v<versao>` tag, the lacre closure's `concrete_versao`, \
5191         and the `:upgrade-from :from` peers that match against this exact shape; \
5192         use a literal like `\"0.1.0\"`, `\"0.2.0-rc.1\"`, or `\"1.0.0+build.42\"` — \
5193         not a git-tag-shape like `\"v0.1.0\"`, a docker-tag-shape like `\"latest\"`, \
5194         a requirement-shape like `\"^0.1\"`, or a four-part `\"0.1.0.0\"`)"
5195    )]
5196    VersaoInvalid { versao: String, reason: String },
5197    #[error(
5198        ":restart-window {restart_window:?} is not a valid duration: {reason} (the \
5199         substrate consumes this string through the shared \
5200         `supervisor::duration_codec` — the same parser routed via `with = \
5201         \"duration_codec\"` onto the typed `SupervisorSpec::restart_window`, \
5202         `:politicas :timeout`, and `:politicas :circuit-breaker :window` slots; \
5203         the canonical authoring form is `<integer><unit>` where the unit is one \
5204         of `ms` / `s` / `m` / `h` and the magnitude has no decimal point and no \
5205         leading `+` / `-` sign — e.g. `\"60s\"`, `\"5m\"`, `\"1h\"`, `\"500ms\"`. \
5206         Without this gate a malformed `:restart-window` silently produced a \
5207         supervisor with `restart_window: None` (\"never reset\"), turning OTP's \
5208         `MaxIntensity / Period` invariant into a never-reset supervisor far from \
5209         the source `caixa.lisp`; the gate moves the diagnostic to the manifest \
5210         layer with the offending value named verbatim. Omit the slot entirely to \
5211         express \"no reset\"; carry a positive integer duration to express the \
5212         sliding window)"
5213    )]
5214    RestartWindowMalformed {
5215        restart_window: String,
5216        reason: String,
5217    },
5218    #[error(
5219        "{slot} entry is an empty path string — every {slot} entry must name \
5220         a file relative to the caixa root; omit the entry to omit the file \
5221         (the layout checker's `root.join(\"\")` resolves to the caixa root \
5222         itself, so an empty entry silently aliases the project root as a \
5223         declared {slot} file, then fails downstream at parse / existence \
5224         time with a diagnostic that names the root rather than the offending \
5225         entry)"
5226    )]
5227    CodePathEmpty { slot: &'static str },
5228    #[error(
5229        "{slot} entry {} is an absolute path — entries must be relative to \
5230         the caixa root, since `Path::join` replaces the base with an absolute \
5231         right-hand side and `root.join(\"/abs/...\")` resolves to \"/abs/...\" \
5232         outside the caixa root sandbox; rewrite the entry as a relative path \
5233         under the caixa root (e.g. `\"lib/<name>.lisp\"`, `\"exe/<name>\"`, \
5234         `\"servicos/<name>.computeunit.yaml\"`)",
5235        path.display()
5236    )]
5237    CodePathAbsolute { slot: &'static str, path: PathBuf },
5238    #[error(
5239        "{slot} entry {} contains a `..` component — entries must not traverse \
5240         above the caixa root (the layout's `starts_with(<dir>)` fence on \
5241         `:exe` / `:servicos` is component-aware, not canonical-path-aware, \
5242         so a mid-path `..` silently traverses the sandbox; `:bibliotecas` \
5243         has no such fence, so a leading `..` escapes unconditionally if the \
5244         resolved target happens to exist)",
5245        path.display()
5246    )]
5247    CodePathParentEscape { slot: &'static str, path: PathBuf },
5248    #[error(
5249        "{slot} entry {} does not terminate in the `.lisp` extension — every \
5250         `:bibliotecas` entry is a tatara-lisp source file the `feira build` \
5251         loop reads through `tatara_lisp::read` at parse time, so any other \
5252         extension (`.rs`, `.txt`, `.lisp.bak`) or no-extension shape is \
5253         structurally a parser error far from the source caixa.lisp, with \
5254         no field naming the offending `:bibliotecas` entry. Pin a relative \
5255         path under the caixa root whose terminating extension is \
5256         lowercase-`.lisp` (e.g. `\"lib/<name>.lisp\"`, \
5257         `\"lib/handlers.lisp\"`) — the same file-type contract the peer \
5258         `:behavior :on-*` (c97815a) and `:upgrade-from :state-change :script` \
5259         (33cc830) axes already carry through the same lifted \
5260         `is_lisp_extension` predicate",
5261        path.display()
5262    )]
5263    CodePathNonLispExtension { slot: &'static str, path: PathBuf },
5264    #[error(
5265        "{slot} entry {} does not terminate in the `.computeunit.yaml` \
5266         compound suffix — every `:servicos` entry is a typed `ComputeUnit` \
5267         CR YAML file the peer caixa-helm / caixa-flux renderers consume \
5268         through `serde_yaml::from_str` at chart / FluxCD bundle render \
5269         time, so any other extension (`.yaml`, `.yml`, `.json`, the \
5270         off-by-one-segment `.computeunit-yaml`, the editor-backup \
5271         `.computeunit.yaml.bak`) or no-extension shape is structurally a \
5272         YAML-parser error / `ComputeUnit` schema-mismatch far from the \
5273         source caixa.lisp, with no field naming the offending `:servicos` \
5274         entry. Pin a relative path under the caixa root whose terminating \
5275         compound suffix is lowercase-`.computeunit.yaml` (e.g. \
5276         `\"servicos/<name>.computeunit.yaml\"`, \
5277         `\"servicos/hello-rio.computeunit.yaml\"`) — the same file-type \
5278         contract the sibling `:bibliotecas` axis (64772a9) already carries \
5279         on the tatara-lisp-source axis through the peer lifted \
5280         `is_lisp_extension` predicate, here on the compound-suffix axis \
5281         `Path::extension` can't express on its own through the lifted \
5282         `is_computeunit_yaml_extension` predicate",
5283        path.display()
5284    )]
5285    CodePathNonComputeUnitYamlExtension { slot: &'static str, path: PathBuf },
5286    #[error(
5287        "{slot} entry {} appears more than once (the code-path list is \
5288         a set, not a multiset; every peer Vec-shaped author-supplied \
5289         list past validate is set-not-multiset — `:membros :caixa`, \
5290         `:placement :clusters`, `:entrada :paths`, `:contratos`, \
5291         `:children :caixa`, `:deps` / `:deps-dev` `:nome`, \
5292         `:upgrade-from :from`, `:etiquetas`, `:autores` — and the three \
5293         code-path lists are the last Vec-shaped author-supplied slots on \
5294         the typed Caixa surface still admitting a duplicate entry. \
5295         `:bibliotecas` duplicates re-parse the same file at \
5296         `feira build` time and silently mask the author's intent to \
5297         declare a *second* biblioteca; `:exe` duplicates collide on the \
5298         flake `packages.<name>` derivation key at the future \
5299         `caixa-flake` materializer; `:servicos` duplicates surface as the \
5300         narrower [`caixa-helm`] / [`caixa-flux`] `UnsupportedServicoCount` \
5301         rejection far from the source `caixa.lisp`. Drop the duplicate \
5302         or rename it to the actual second file intended)",
5303        path.display()
5304    )]
5305    CodePathDuplicate { slot: &'static str, path: PathBuf },
5306    #[error(
5307        ":etiquetas entry is empty (every tag must carry a non-empty \
5308         registry-search identifier; the empty entry has no operational \
5309         meaning — it indexes nothing in the future caixa-registry search \
5310         axis and clutters the rendered Helm `Chart.yaml` `keywords:` array \
5311         with a no-op tag; omit the entry to express \"no tag on this \
5312         position\")"
5313    )]
5314    EtiquetaEmpty,
5315    #[error(
5316        ":etiquetas entry {etiqueta:?} appears more than once (the \
5317         registry-search tag set is a set, not a multiset; duplicate \
5318         entries are silently dedup'd by caixa-helm's `BTreeSet` collect \
5319         at chart render — a \"second wins / one silently disappears\" \
5320         shape divergent from every peer typed-graph set gate \
5321         (`:membros :caixa`, `:placement :clusters`, `:entrada :paths`, \
5322         `:contratos`, `:deps :nome`, `:upgrade-from :from`); drop the \
5323         duplicate or rename it to the actual tag intended)"
5324    )]
5325    EtiquetaDuplicate { etiqueta: String },
5326    #[error(
5327        ":etiquetas entry {etiqueta:?} is not a valid chart-keyword shape: \
5328         {reason} (the substrate consumes this string through the shared \
5329         `crate::render::is_chart_keyword_shape` predicate — the same \
5330         Cargo crates.io `[package] keywords` grammar entry shape: 1..=20 \
5331         bytes, starts with an ASCII letter, ASCII alphanumeric / `_` / `-` \
5332         continuation. The canonical authoring shapes are short kebab-case \
5333         identifiers like `\"mesh\"`, `\"wasm\"`, `\"tatara-lisp\"`, \
5334         `\"hello-world\"`, `\"caixa-servico\"`, `\"infrastructure\"`. \
5335         Without this gate a malformed `:etiquetas` entry (paste-from-doc \
5336         leading / trailing whitespace `\" mesh\"` / `\"mesh \"`; \
5337         paste-from-multiline-doc newline `\"mesh\\nhttp\"`; \
5338         paste-from-Windows-CRLF-doc CR; CSV-list-separator confusion \
5339         `\"mesh,http,grpc\"` — the author meant to author three separate \
5340         list entries; path-separator confusion `\"caixa/servico\"`; \
5341         namespace-suffix `\"http.1\"`; leading-digit `\"1foo\"`; \
5342         kebab-leak `\"-foo\"`; snake-leak `\"_foo\"`; non-ASCII \
5343         `\"café\"` — every legitimate search tag is strict ASCII; \
5344         paste-from-binary-blob NUL / BEL / ESC / DEL byte) silently \
5345         passed `from_lisp` + `validate_etiquetas` + \
5346         `StandardLayout::verify` and landed in the rendered \
5347         `lareira-<nome>` Helm chart's `Chart.yaml keywords:` array as a \
5348         malformed search tag — Artifact Hub's keyword index + the future \
5349         caixa-registry's keyword index would either silently drop the \
5350         tag or fail to index it far from the source caixa.lisp; the gate \
5351         moves the diagnostic to the manifest layer with the offending \
5352         value named verbatim)"
5353    )]
5354    EtiquetaInvalid { etiqueta: String, reason: String },
5355    #[error(
5356        ":autores entry is empty (every maintainer must carry a non-empty \
5357         identifier; the empty entry has no operational meaning — it \
5358         identifies no one in the substrate's authorship index and renders \
5359         as `maintainers: [{{name: \"\", email: null}}]` in the Helm chart's \
5360         `Chart.yaml`, a no-op maintainer the substrate cannot route to; \
5361         omit the entry to express \"no maintainer on this position\")"
5362    )]
5363    AutorEmpty,
5364    #[error(
5365        ":autores entry {autor:?} appears more than once (the maintainer \
5366         set is a set, not a multiset; unlike `:etiquetas`, caixa-helm's \
5367         `maintainers:` rendering does *no* dedup — duplicate entries \
5368         stack verbatim in `Chart.yaml` as two identical \
5369         `Maintainer {{ name, email: None }}` records, divergent from every \
5370         peer typed-graph set gate (`:etiquetas`, `:membros :caixa`, \
5371         `:placement :clusters`, `:entrada :paths`, `:contratos`, \
5372         `:deps :nome`, `:upgrade-from :from`); drop the duplicate or \
5373         rename it to the actual author intended)"
5374    )]
5375    AutorDuplicate { autor: String },
5376    #[error(
5377        ":autores entry {autor:?} is not a valid chart-maintainer-name shape: \
5378         {reason} (the substrate consumes this string through the shared \
5379         `crate::render::is_chart_maintainer_name_shape` predicate — the same \
5380         single-line-UTF-8 floor every realistic chart maintainer name carries: \
5381         1..=128 bytes, no leading or trailing whitespace, no ASCII control \
5382         characters anywhere, Unicode bytes accepted. The canonical authoring \
5383         shapes are short single-line identifiers like `\"pleme-io\"`, \
5384         `\"Pleme Contributors\"`, `\"alice <alice@example.com>\"`, \
5385         `\"François Dupont\"`. Without this gate a malformed `:autores` entry \
5386         (paste-from-aligned-doc leading whitespace `\" pleme-io\"` / trailing \
5387         whitespace `\"pleme-io \"`; paste-from-multiline-doc newline \
5388         `\"alice\\nbob\"` — the author pasted a multi-line block of author \
5389         records into one entry instead of splitting into one entry per author; \
5390         paste-from-Windows-CRLF-doc carriage return `\"alice\\rbob\"`; \
5391         tab-from-aligned-doc `\"Pleme\\tContributors\"`; paste-from-binary-blob \
5392         NUL / BEL / ESC / DEL byte) silently passed `from_lisp` + \
5393         `validate_autores` + `StandardLayout::verify` and landed in the \
5394         rendered `lareira-<nome>` Helm chart's `Chart.yaml maintainers:` array \
5395         as a YAML-illegal multi-line scalar or a silently-trimmed whitespace \
5396         round-trip — every chart-aware UI (`helm list`, `helm search`, \
5397         Artifact Hub maintainer index) would render the maintainer name in a \
5398         single-line column far from the source caixa.lisp; the gate moves the \
5399         diagnostic to the manifest layer with the offending value named \
5400         verbatim)"
5401    )]
5402    AutorInvalid { autor: String, reason: String },
5403    #[error(
5404        ":repositorio is the empty string (every published caixa names its \
5405         git source via a non-empty `:repositorio` locator — the value \
5406         flows verbatim into the rendered `lareira-<nome>` Helm chart's \
5407         `Chart.yaml` `home:` field via `caixa-helm` and into the FluxCD \
5408         `GitRepository.spec.url` via `caixa-flux`'s \
5409         `ClusterBundleOpts::for_caixa`; both consumers' \
5410         `Option::unwrap_or_else` fallbacks only fire when the slot is \
5411         `None`, so an empty `Some(\"\")` silently lands as `home: \"\"` / \
5412         `url: \"\"` in the rendered artifacts and breaks at `helm \
5413         template` / FluxCD source-controller reconcile time far from the \
5414         source caixa.lisp; omit the slot entirely to defer to the \
5415         renderer's `https://github.com/pleme-io/<nome>` / \
5416         `caixa.nome`-derived fallback, or carry a canonical authoring \
5417         shape like `\"github:org/repo\"`, `\"https://host/path\"`, \
5418         `\"ssh://[user@]host/path\"`, `\"git@host:path\"`, or \
5419         `\"file:///path\"`)"
5420    )]
5421    RepositorioEmpty,
5422    #[error(
5423        ":repositorio {repositorio:?} is not a valid git repo URL: {reason} \
5424         (the substrate consumes this string through the shared \
5425         `crate::render::is_git_repo_url` predicate — the same parser the \
5426         peer `:deps :fonte (:tipo git :repo …)` axis routes its `:repo` \
5427         value through via `DepSource::validate`; the canonical authoring \
5428         shapes are `\"github:org/repo\"` shorthand, `\"https://host/path\"` \
5429         / `\"ssh://[user@]host/path\"` / `\"git://host/path\"` / \
5430         `\"file:///path\"` URL schemes, or the `\"git@host:path\"` \
5431         scp-style SSH form. Without this gate a malformed `:repositorio` \
5432         (whitespace from a paste-from-doc; control characters / CRLF \
5433         from a paste-from-multiline-doc; a leading `-` from a \
5434         CLI-argument-injection footgun; a missing `:` separator from a \
5435         bare `org/repo` shape git treats as a relative filesystem path) \
5436         silently landed in the rendered `Chart.yaml home:` and the \
5437         FluxCD `GitRepository.spec.url` and broke at `git clone` / \
5438         FluxCD reconcile time far from the source caixa.lisp; the gate \
5439         moves the diagnostic to the manifest layer with the offending \
5440         value named verbatim)"
5441    )]
5442    RepositorioInvalid { repositorio: String, reason: String },
5443    #[error(
5444        ":descricao is the empty string (every published caixa names \
5445         its purpose via a non-empty `:descricao` summary — the value \
5446         flows verbatim into the rendered `lareira-<nome>` Helm \
5447         chart's `Chart.yaml` `description:` field via `caixa-helm`'s \
5448         `build_chart_yaml` and into the chart `README.md` header via \
5449         `build_readme`; both consumers' `Option::unwrap_or_else` \
5450         `caixa.nome`-derived fallbacks only fire when the slot is \
5451         `None`, so an empty `Some(\"\")` silently lands as \
5452         `description: \"\"` / a blank `README.md` header in the \
5453         rendered artifacts and breaks at `helm lint` time \
5454         (`WARNING [chart.metadata.description]: description is \
5455         required` on `apiVersion: v2` charts) far from the source \
5456         caixa.lisp; omit the slot entirely to defer to the \
5457         renderer's `\"Generated chart for caixa Servico <nome>\"` / \
5458         `\"caixa Servico <nome>\"` fallbacks, or carry a non-empty \
5459         summary like `\"Canonical Rust→wasm32-wasip2 caixa \
5460         Servico.\"`)"
5461    )]
5462    DescricaoEmpty,
5463    #[error(
5464        ":descricao {descricao:?} is not a valid chart-description shape: \
5465         {reason} (the substrate consumes this string through the shared \
5466         `crate::render::is_chart_description_shape` predicate — the same \
5467         single-line-UTF-8 floor every realistic chart description carries: \
5468         1..=512 bytes, no leading or trailing whitespace, no ASCII control \
5469         characters anywhere, Unicode prose bytes accepted. The canonical \
5470         authoring shapes are short single-line summaries like `\"Canonical \
5471         Rust→wasm32-wasip2 caixa Servico.\"`, `\"Checkout flow.\"`, \
5472         `\"AWS provider caixa for tatara-lisp\"`. Without this gate a \
5473         malformed `:descricao` (paste-from-aligned-doc leading whitespace \
5474         `\" Checkout flow.\"` / trailing whitespace `\"Checkout flow. \"`; \
5475         paste-from-multiline-doc newline `\"Checkout\\nflow.\"`; \
5476         paste-from-Windows-CRLF-doc carriage return `\"Checkout\\rflow.\"`; \
5477         tab-from-aligned-doc `\"Checkout\\tflow.\"`; paste-from-binary-blob \
5478         NUL / BEL / ESC / DEL byte) silently passed `from_lisp` + \
5479         `validate_descricao` + `StandardLayout::verify` and landed in the \
5480         rendered `lareira-<nome>` Helm chart's `Chart.yaml description:` \
5481         field + `README.md` header paragraph as a YAML-illegal multi-line \
5482         scalar or a silently-trimmed whitespace round-trip — every \
5483         chart-aware UI (`helm list`, `helm search`, Artifact Hub) would \
5484         render the description in a single-line column far from the source \
5485         caixa.lisp; the gate moves the diagnostic to the manifest layer \
5486         with the offending value named verbatim)"
5487    )]
5488    DescricaoInvalid { descricao: String, reason: String },
5489    #[error(
5490        ":licenca is the empty string (every published caixa names \
5491         its license via a non-empty `:licenca` SPDX expression — the \
5492         value flows verbatim into the rendered `lareira-<nome>` Helm \
5493         chart's `README.md` `## License` section via `caixa-helm`'s \
5494         `build_readme` at `caixa-helm/src/lib.rs:361`; the consumer's \
5495         `Option::unwrap_or_else(|| \"MIT\".into())` `MIT` fallback \
5496         only fires when the slot is `None`, so an empty `Some(\"\")` \
5497         silently lands as a bare trailing period in the rendered \
5498         chart `README.md` `License` section far from the source \
5499         caixa.lisp; omit the slot entirely to defer to the \
5500         renderer's `MIT` fallback, or carry a canonical SPDX \
5501         expression like `\"MIT\"`, `\"Apache-2.0\"`, \
5502         `\"Apache-2.0 OR MIT\"`)"
5503    )]
5504    LicencaEmpty,
5505    #[error(
5506        ":licenca {licenca:?} is not a valid SPDX expression shape: {reason} \
5507         (the substrate consumes this string through the shared \
5508         `crate::render::is_spdx_expression_shape` predicate — the same \
5509         alphabet-floor parser every peer per-axis value-shape gate routes \
5510         its value through; the canonical authoring shapes are single \
5511         license identifiers like `\"MIT\"`, `\"Apache-2.0\"`, `\"BSD-3-Clause\"`, \
5512         compound expressions like `\"Apache-2.0 OR MIT\"`, \
5513         `\"MIT AND BSD-3-Clause\"`, `\"(MIT OR Apache-2.0) AND ISC\"`, \
5514         license-with-exception forms like `\"Apache-2.0 WITH LLVM-exception\"`, \
5515         `+`-suffix variants like `\"GPL-2.0+\"`, and user-defined references \
5516         like `\"LicenseRef-MyLicense\"` / \
5517         `\"DocumentRef-doc:LicenseRef-MyLicense\"`. Without this gate a \
5518         malformed `:licenca` (paste-from-doc whitespace `\"MIT \"` / \
5519         `\" MIT\"`; paste-from-multiline-doc CRLF `\"MIT\\n\"`; \
5520         tab-from-aligned-doc `\"MIT\\tOR Apache-2.0\"`; non-ASCII byte from \
5521         a smart-quote paste; underscore-instead-of-hyphen typo \
5522         `\"Apache_2.0\"`; comma-instead-of-`OR`-keyword colloquial idiom \
5523         `\"MIT, Apache-2.0\"`; slash-dual-license colloquial idiom \
5524         `\"MIT/Apache-2.0\"`; semicolon-list-separator confusion \
5525         `\"MIT; Apache-2.0\"`) silently landed in the rendered chart \
5526         `README.md` `## License` section + a future SPDX-aware \
5527         `Chart.yaml license:` emitter would refuse the value at \
5528         `helm lint` time far from the source caixa.lisp; the gate moves \
5529         the diagnostic to the manifest layer with the offending value \
5530         named verbatim)"
5531    )]
5532    LicencaInvalid { licenca: String, reason: String },
5533    #[error(
5534        ":edicao is the empty string (every published caixa names \
5535         its language edition via a non-empty `:edicao` value — the \
5536         edition determines the tatara-lisp macro surface + \
5537         compatibility flags the substrate applies when building \
5538         the caixa; the canonical `Caixa::template` scaffold every \
5539         `feira init` emits carries `:edicao \"2026\"` verbatim and \
5540         every renderer-side fixture (`caixa-helm`, `caixa-flux`, \
5541         `caixa-mesh`) carries `edicao: Some(\"2026\".into())` by \
5542         construction, so an empty `Some(\"\")` silently lands as a \
5543         bare `(:edicao \"\")` line in the rendered `caixa.lisp` and \
5544         a future renderer-side consumer that folds it through \
5545         `Option::unwrap_or_else` will skip the fallback and pass the \
5546         empty edition through to the substrate's build-time edition \
5547         selector far from the source caixa.lisp; omit the slot \
5548         entirely to defer to the substrate's default edition, or \
5549         carry a canonical edition like `\"2026\"`)"
5550    )]
5551    EdicaoEmpty,
5552    #[error(
5553        ":edicao {edicao:?} is not a valid edition: {reason} (every \
5554         documented tatara-lisp edition is a 4-digit ASCII decimal \
5555         year — `\"2026\"` is the only edition currently minted; \
5556         future-introduced siblings will follow the same shape, peer \
5557         with Cargo's `[package] edition` grammar which every value \
5558         Cargo has ever accepted matches: `\"2015\"`, `\"2018\"`, \
5559         `\"2021\"`, `\"2024\"`. Without this gate the canonical \
5560         paste-from-doc footguns silently passed: a trailing space \
5561         (`\"2026 \"`) from a paste-from-doc, a CRLF (`\"2026\\n\"`) \
5562         from a paste-from-multiline-doc, a fullwidth-keyboard \
5563         look-alike (`\"2026\"`), a free-form non-year value \
5564         (`\"x\"`, `\"latest\"`, `\"nightly\"`), a leading non-digit \
5565         version-tag prefix (`\"v2026\"`, `\"e2026\"`), a \
5566         decimal-shaped pseudo-version (`\"2026.1\"`), or a \
5567         wrong-length numeric value (`\"26\"`, `\"202\"`, \
5568         `\"20260\"`) all landed as `(:edicao \"<garbage>\")` in the \
5569         rendered caixa.lisp and broke at the substrate's \
5570         build-time edition selector far from the source caixa.lisp; \
5571         omit the slot entirely to defer to the substrate's default \
5572         edition, or carry a canonical 4-digit ASCII decimal year \
5573         like `\"2026\"`)"
5574    )]
5575    EdicaoInvalid { edicao: String, reason: String },
5576}
5577
5578#[cfg(test)]
5579mod tests {
5580    use super::*;
5581
5582    #[test]
5583    fn template_round_trips() {
5584        let src = Caixa::template("demo");
5585        let c = Caixa::from_lisp(&src).expect("template must parse");
5586        assert_eq!(c.nome, "demo");
5587        assert_eq!(c.versao, "0.1.0");
5588        assert_eq!(c.kind, CaixaKind::Biblioteca);
5589        assert_eq!(c.bibliotecas, vec!["lib/demo.lisp".to_string()]);
5590        assert!(c.deps.is_empty());
5591        assert!(c.deps_dev.is_empty());
5592    }
5593
5594    #[test]
5595    fn register_populates_registry() {
5596        Caixa::register().expect("first register call in this test process must succeed");
5597        let kws = tatara_lisp::domain::registered_keywords();
5598        assert!(kws.contains(&"defcaixa"));
5599    }
5600
5601    #[test]
5602    fn to_lisp_round_trips() {
5603        let src = Caixa::template("demo");
5604        let c1 = Caixa::from_lisp(&src).unwrap();
5605        let emitted = c1.to_lisp();
5606        let c2 = Caixa::from_lisp(&emitted).expect("emitted lisp parses back");
5607        assert_eq!(c1, c2);
5608    }
5609
5610    // ── DialetoEstrangeiro carries a single typed axis ────────────────────
5611    //
5612    // The compounding pin: the variant stores only the typed
5613    // [`crate::dialeto::CaixaDialeto`], and every user-facing byte-string
5614    // (canonical keyword, description, consumer) routes through the enum's
5615    // own accessors at Display time. Prior to that closure the variant
5616    // carried each accessor's return value as a stored `&'static str`
5617    // snapshot alongside `dialeto`; a caller could construct the variant
5618    // with a snapshot that drifted from what `dialeto`'s accessors would
5619    // return, and every downstream user-facing projection would silently
5620    // disagree with the classification. Storing only the axis makes the
5621    // drift structurally impossible.
5622
5623    #[test]
5624    fn dialeto_estrangeiro_variant_carries_only_the_typed_dialeto_axis() {
5625        // Single-field construction is the whole compounding shape — a
5626        // future re-introduction of a snapshot field (a `palavra_canonica:
5627        // &'static str`, a stored `descricao:`, a stored `consumidor:`)
5628        // would re-open the drift surface and this construction would fail
5629        // to compile with "missing field" until every snapshot was seeded
5630        // at the call site again. The compile-time guarantee is the
5631        // invariant; the assertion below only witnesses that the
5632        // construction is well-formed after the closure.
5633        let err = LeituraError::DialetoEstrangeiro {
5634            dialeto: crate::dialeto::CaixaDialeto::Molde,
5635        };
5636        assert!(matches!(
5637            err,
5638            LeituraError::DialetoEstrangeiro {
5639                dialeto: crate::dialeto::CaixaDialeto::Molde,
5640            }
5641        ));
5642    }
5643
5644    #[test]
5645    fn dialeto_estrangeiro_display_routes_through_typed_dialeto_accessors() {
5646        // For every foreign-dialect classification the variant surfaces —
5647        // [`crate::dialeto::CaixaDialeto::Molde`] and
5648        // [`crate::dialeto::CaixaDialeto::MoldePosicional`], the two
5649        // variants [`Caixa::from_lisp`] raises this error for — the
5650        // rendered [`std::fmt::Display`] byte-string must interpolate each
5651        // typed accessor's return verbatim. A future re-introduction of a
5652        // stored `&'static str` snapshot alongside `dialeto` that Display
5653        // read instead of the accessor would fail this pin as soon as the
5654        // two disagreed; a future accessor rebrand (a per-dialect
5655        // consumer rename, a canonical-keyword shift once the substrate
5656        // migration named in [`crate::dialeto`] completes) reaches every
5657        // consumer through one typed dispatch and this pin verifies the
5658        // display path is one of them.
5659        for d in [
5660            crate::dialeto::CaixaDialeto::Molde,
5661            crate::dialeto::CaixaDialeto::MoldePosicional,
5662        ] {
5663            let rendered = LeituraError::DialetoEstrangeiro { dialeto: d }.to_string();
5664            assert!(
5665                rendered.contains(d.palavra_canonica()),
5666                "Display must interpolate `dialeto.palavra_canonica()` \
5667                 verbatim — a stored snapshot would silently drift from \
5668                 the typed accessor. dialect: {d}, rendered: {rendered:?}"
5669            );
5670            assert!(
5671                rendered.contains(d.descricao()),
5672                "Display must interpolate `dialeto.descricao()` verbatim. \
5673                 dialect: {d}, rendered: {rendered:?}"
5674            );
5675            assert!(
5676                rendered.contains(d.consumidor()),
5677                "Display must interpolate `dialeto.consumidor()` verbatim. \
5678                 dialect: {d}, rendered: {rendered:?}"
5679            );
5680        }
5681    }
5682
5683    #[test]
5684    fn from_lisp_rejects_molde_dialect_via_typed_variant() {
5685        // The end-to-end pin the compounding closure defends: a
5686        // Molde-dialect source lands as [`LeituraError::DialetoEstrangeiro`]
5687        // carrying [`crate::dialeto::CaixaDialeto::Molde`], and the
5688        // rendered Display byte-string names the Molde accessors'
5689        // returns verbatim. Any future path that constructed the variant
5690        // with a mismatched snapshot (a stored `palavra_canonica:
5691        // "defcaixa"` on a `Molde` classification) would land Display
5692        // pointing at `defcaixa` while the typed axis said `Molde` — the
5693        // exact drift the closure removes.
5694        let src = r#"
5695          (defcaixa
5696            :name "x"
5697            :kind :Biblioteca
5698            :ecosystem :rust-single-crate
5699            :package {:name "x" :version "0.1.0"})
5700        "#;
5701        let err = Caixa::from_lisp(src).expect_err("Molde dialect must not parse as Pacote");
5702        match err {
5703            LeituraError::DialetoEstrangeiro { dialeto } => {
5704                assert_eq!(dialeto, crate::dialeto::CaixaDialeto::Molde);
5705                let rendered = LeituraError::DialetoEstrangeiro { dialeto }.to_string();
5706                assert!(rendered.contains(dialeto.palavra_canonica()));
5707                assert!(rendered.contains(dialeto.consumidor()));
5708                assert!(rendered.contains(dialeto.descricao()));
5709            }
5710            other => panic!("expected DialetoEstrangeiro, got {other:?}"),
5711        }
5712    }
5713
5714    #[test]
5715    fn from_lisp_rejects_molde_posicional_dialect_via_typed_variant() {
5716        // Coverage pin for the [`crate::dialeto::CaixaDialeto::MoldePosicional`]
5717        // arm of the [`Caixa::from_lisp`] foreign-dialect gate — the
5718        // positional-arity `defmolde` form written under a `(defcaixa …)`
5719        // head (`(defcaixa todoku-go :kind :Biblioteca :ecosystem :go
5720        // …)`). Pre-lift this arm rode the same `foreign =>` wildcard
5721        // the [`crate::dialeto::CaixaDialeto::Molde`] sibling arm rode,
5722        // so no test exercised the positional-arity path through
5723        // `Caixa::from_lisp` specifically; the sibling
5724        // [`from_lisp_rejects_molde_dialect_via_typed_variant`] only
5725        // covered [`crate::dialeto::CaixaDialeto::Molde`]. Post-lift the
5726        // two arms route through the lifted
5727        // [`crate::dialeto::CaixaDialeto::is_molde_family`] (e9d2315)
5728        // typed predicate — the same predicate the pre-lift `foreign =>`
5729        // wildcard resolved to today — and this pin makes the
5730        // positional-arity arm's byte-shape at the gate explicit rather
5731        // than implied by wildcard-absorption. A future regression that
5732        // silently reordered [`crate::dialeto::CaixaDialeto::is_molde_family`]'s
5733        // arm-set (dropped [`crate::dialeto::CaixaDialeto::MoldePosicional`]
5734        // from the two-arity closure) would fail this pin at caixa-core
5735        // test time rather than surfacing far from the change as a
5736        // `caixa.lisp` carrying a `(defcaixa todoku-go :ecosystem :go
5737        // …)` silently parsing past the derive.
5738        let src = r#"
5739          (defcaixa todoku-go
5740            :kind :Biblioteca
5741            :ecosystem :go
5742            :package {:name "todoku-go" :version "0.3.0"})
5743        "#;
5744        let err =
5745            Caixa::from_lisp(src).expect_err("MoldePosicional dialect must not parse as Pacote");
5746        match err {
5747            LeituraError::DialetoEstrangeiro { dialeto } => {
5748                assert_eq!(
5749                    dialeto,
5750                    crate::dialeto::CaixaDialeto::MoldePosicional,
5751                    "DialetoEstrangeiro must carry the MoldePosicional \
5752                     variant verbatim — the positional-arity `defmolde` \
5753                     form under a `(defcaixa …)` head is the \
5754                     `MoldePosicional` arm's canonical byte-shape"
5755                );
5756                let rendered = LeituraError::DialetoEstrangeiro { dialeto }.to_string();
5757                assert!(
5758                    rendered.contains(dialeto.palavra_canonica()),
5759                    "Display must interpolate `dialeto.palavra_canonica()` \
5760                     verbatim on the MoldePosicional arm; rendered: \
5761                     {rendered:?}"
5762                );
5763                assert!(
5764                    rendered.contains(dialeto.consumidor()),
5765                    "Display must interpolate `dialeto.consumidor()` \
5766                     verbatim on the MoldePosicional arm; rendered: \
5767                     {rendered:?}"
5768                );
5769                assert!(
5770                    rendered.contains(dialeto.descricao()),
5771                    "Display must interpolate `dialeto.descricao()` \
5772                     verbatim on the MoldePosicional arm; rendered: \
5773                     {rendered:?}"
5774                );
5775            }
5776            other => panic!("expected DialetoEstrangeiro, got {other:?}"),
5777        }
5778    }
5779
5780    #[test]
5781    fn from_lisp_dialect_gate_dispatches_through_caixa_dialeto_is_molde_family_predicate() {
5782        // Load-bearing byte-parity pin: for every arm in
5783        // [`crate::dialeto::CaixaDialeto::ALL`], the
5784        // [`Caixa::from_lisp`] foreign-dialect gate's DialetoEstrangeiro
5785        // partition must agree with the lifted
5786        // [`crate::dialeto::CaixaDialeto::is_molde_family`] (e9d2315)
5787        // typed predicate — i.e. from_lisp raises
5788        // [`LeituraError::DialetoEstrangeiro`] carrying `d` iff
5789        // `d.is_molde_family()` returns `true`, and does NOT raise
5790        // [`LeituraError::DialetoEstrangeiro`] on any arm where the
5791        // predicate returns `false` (the arm's source falls through to
5792        // the derive — parses cleanly on
5793        // [`crate::dialeto::CaixaDialeto::Pacote`], surfaces a
5794        // [`LeituraError::Leitura`] on
5795        // [`crate::dialeto::CaixaDialeto::Desconhecido`]).
5796        //
5797        // Pre-lift the gate hand-rolled a three-arm match
5798        // (`Pacote => {}`, `Desconhecido => {}`, `foreign => Err(…)`)
5799        // whose `foreign =>` wildcard expressed no compile-time link
5800        // back to the substrate primitive's arm-family; a future fifth
5801        // dialect the [`crate::dialeto`] module doc's "third dialect"
5802        // hazard actualises would fall silently onto the wildcard
5803        // regardless of whether it belonged to the `defmolde` family or
5804        // to a distinct `defcaixa`-family. Post-lift the partition
5805        // resolves through
5806        // [`crate::dialeto::CaixaDialeto::is_molde_family`]'s single
5807        // typed dispatch, and this pin refuses any future regression
5808        // that silently split the from_lisp partition from the typed
5809        // predicate — the two paths now migrate as one on any future
5810        // arm addition.
5811        //
5812        // Sibling in shape to the peer
5813        // [`crate::dialeto::tests::caixa_dialeto_is_molde_family_agrees_with_palavra_canonica_defmolde_projection`]
5814        // (e9d2315) that pins the same byte-parity between
5815        // [`crate::dialeto::CaixaDialeto::is_molde_family`] and the
5816        // sibling [`crate::dialeto::CaixaDialeto::palavra_canonica`]
5817        // `== "defmolde"` classifier — extends the discipline from the
5818        // two paths within the [`crate::dialeto`] primitive onto the
5819        // third external consumer of the `defmolde`-family partition
5820        // (the [`Caixa::from_lisp`] gate that raises
5821        // [`LeituraError::DialetoEstrangeiro`]).
5822        let fixtures: &[(crate::dialeto::CaixaDialeto, &str)] = &[
5823            (
5824                crate::dialeto::CaixaDialeto::Pacote,
5825                r#"
5826                  (defcaixa
5827                    :nome   "checkout"
5828                    :versao "0.1.0"
5829                    :kind   Biblioteca
5830                    :edicao "2026"
5831                    :descricao "canonical Pacote source"
5832                    :autores ()
5833                    :etiquetas ()
5834                    :deps ()
5835                    :deps-dev ()
5836                    :bibliotecas ("lib/checkout.lisp"))
5837                "#,
5838            ),
5839            (
5840                crate::dialeto::CaixaDialeto::Molde,
5841                r#"
5842                  (defcaixa
5843                    :name "base64"
5844                    :kind :Biblioteca
5845                    :ecosystem :rust-single-crate
5846                    :package {:name "base64" :version "0.22.1"}
5847                    :workflows [:auto-release])
5848                "#,
5849            ),
5850            (
5851                crate::dialeto::CaixaDialeto::MoldePosicional,
5852                r#"
5853                  (defcaixa todoku-go
5854                    :kind :Biblioteca
5855                    :ecosystem :go
5856                    :package {:name "todoku-go" :version "0.3.0"})
5857                "#,
5858            ),
5859            (
5860                crate::dialeto::CaixaDialeto::Desconhecido,
5861                r#"(defcaixa :licenca "MIT")"#,
5862            ),
5863        ];
5864
5865        // Coverage: every arm in [`crate::dialeto::CaixaDialeto::ALL`]
5866        // must appear in the fixture table so the pin's arm-set stays
5867        // synchronised with the enum's arm-set. Fails at test time if a
5868        // future fifth arm added to [`crate::dialeto::CaixaDialeto`]
5869        // (with a corresponding `is_molde_family` return) forgot to
5870        // extend this fixture table with a canonical source for the new
5871        // arm — the pin cannot cover an arm it has no source for.
5872        for &expected in crate::dialeto::CaixaDialeto::ALL {
5873            assert!(
5874                fixtures.iter().any(|(d, _)| *d == expected),
5875                "fixture table must carry a canonical source for every \
5876                 CaixaDialeto arm; missing: {expected:?}"
5877            );
5878        }
5879
5880        for &(expected_dialect, src) in fixtures {
5881            let classified = crate::dialeto::classify(src.trim()).unwrap_or_else(|err| {
5882                panic!(
5883                    "fixture source for {expected_dialect:?} must classify \
5884                     cleanly, got err: {err:?}"
5885                )
5886            });
5887            assert_eq!(
5888                classified, expected_dialect,
5889                "fixture source for {expected_dialect:?} must classify as \
5890                 {expected_dialect:?} (drift here defeats the byte-parity \
5891                 pin below — a source labelled for one arm but classifying \
5892                 as another would silently satisfy or violate the pin for \
5893                 the wrong reason)"
5894            );
5895
5896            let outcome = Caixa::from_lisp(src);
5897            match (expected_dialect.is_molde_family(), &outcome) {
5898                (true, Err(LeituraError::DialetoEstrangeiro { dialeto })) => {
5899                    assert_eq!(
5900                        *dialeto, expected_dialect,
5901                        "DialetoEstrangeiro must carry the same typed arm \
5902                         the classifier returned — a drift here would let \
5903                         from_lisp raise the error while pointing at the \
5904                         wrong dialect (e.g. rejecting a \
5905                         MoldePosicional source as Molde). arm: \
5906                         {expected_dialect:?}"
5907                    );
5908                }
5909                (true, other) => panic!(
5910                    "arm {expected_dialect:?} has is_molde_family() = true \
5911                     so from_lisp must raise DialetoEstrangeiro carrying \
5912                     {expected_dialect:?}; got: {other:?}"
5913                ),
5914                (false, Err(LeituraError::DialetoEstrangeiro { dialeto })) => panic!(
5915                    "arm {expected_dialect:?} has is_molde_family() = false \
5916                     so from_lisp must NOT raise DialetoEstrangeiro; got \
5917                     one carrying: {dialeto:?}. This means the typed \
5918                     predicate and the from_lisp partition disagree on \
5919                     this arm — exactly the drift this pin refuses."
5920                ),
5921                (false, _) => {
5922                    // A non-molde arm's source falls through to the
5923                    // derive: Pacote sources parse to Ok(_); Desconhecido
5924                    // sources surface as LeituraError::Leitura from the
5925                    // derive's own unknown-keyword rejection. Either
5926                    // shape is acceptable here — the pin's promise is
5927                    // narrower: "no DialetoEstrangeiro on
5928                    // is_molde_family() == false".
5929                }
5930            }
5931        }
5932    }
5933
5934    // ── M2 typed-substrate slot tests (limits, behavior, upgrade-from, supervisor) ──
5935
5936    #[test]
5937    fn limits_round_trip_via_json() {
5938        use crate::LimitsSpec;
5939        use std::time::Duration;
5940        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5941        c.limits = Some(LimitsSpec {
5942            memory: Some(64 * 1024 * 1024),
5943            fuel: Some(1_000_000),
5944            wall_clock: Some(Duration::from_secs(30)),
5945            cpu: Some(500),
5946        });
5947        let json = serde_json::to_string(&c).unwrap();
5948        assert!(json.contains("\"limits\""));
5949        assert!(json.contains("\"64MiB\""));
5950        assert!(json.contains("\"30s\""));
5951        assert!(json.contains("\"500m\""));
5952        let back: Caixa = serde_json::from_str(&json).unwrap();
5953        assert_eq!(c.limits, back.limits);
5954    }
5955
5956    #[test]
5957    fn behavior_round_trip_via_json() {
5958        use crate::BehaviorSpec;
5959        use std::path::PathBuf;
5960        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5961        c.behavior = Some(BehaviorSpec {
5962            on_init: Some(PathBuf::from("lib/init.lisp")),
5963            on_call: Some(PathBuf::from("lib/handlers.lisp")),
5964            ..Default::default()
5965        });
5966        let json = serde_json::to_string(&c).unwrap();
5967        let back: Caixa = serde_json::from_str(&json).unwrap();
5968        assert_eq!(c.behavior, back.behavior);
5969    }
5970
5971    #[test]
5972    fn upgrade_from_round_trip_via_json() {
5973        use crate::{UpgradeFromEntry, UpgradeInstruction};
5974        use std::path::PathBuf;
5975        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5976        c.upgrade_from = vec![UpgradeFromEntry {
5977            from: "0.1.0".into(),
5978            instructions: vec![
5979                UpgradeInstruction::LoadModule {
5980                    module: "demo".into(),
5981                },
5982                UpgradeInstruction::StateChange {
5983                    script: PathBuf::from("lib/migrations/v01-to-v02.lisp"),
5984                },
5985                UpgradeInstruction::SoftPurge {
5986                    module: "demo-old".into(),
5987                },
5988            ],
5989        }];
5990        let json = serde_json::to_string(&c).unwrap();
5991        let back: Caixa = serde_json::from_str(&json).unwrap();
5992        assert_eq!(c.upgrade_from, back.upgrade_from);
5993    }
5994
5995    #[test]
5996    fn supervisor_view_returns_typed_shape() {
5997        use crate::{ChildSpec, RestartPolicy, RestartStrategy};
5998        let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
5999        c.kind = CaixaKind::Supervisor;
6000        c.bibliotecas.clear();
6001        c.estrategia = Some(RestartStrategy::OneForOne);
6002        c.max_restarts = Some(5);
6003        c.restart_window = Some("60s".into());
6004        c.children = vec![ChildSpec {
6005            caixa: "worker".into(),
6006            versao: "^0.1".into(),
6007            restart: RestartPolicy::Permanent,
6008        }];
6009        let view = c.supervisor_view().expect("Supervisor kind has a view");
6010        assert_eq!(view.estrategia, RestartStrategy::OneForOne);
6011        assert_eq!(view.max_restarts, 5);
6012        assert_eq!(
6013            view.restart_window,
6014            Some(std::time::Duration::from_secs(60))
6015        );
6016        assert_eq!(view.children.len(), 1);
6017        view.validate().unwrap();
6018    }
6019
6020    #[test]
6021    fn supervisor_view_none_for_non_supervisor_kinds() {
6022        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6023        assert!(c.supervisor_view().is_none());
6024    }
6025
6026    #[test]
6027    fn declared_mesh_slots_empty_for_bare_caixa() {
6028        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6029        assert!(c.declared_mesh_slots().is_empty());
6030    }
6031
6032    #[test]
6033    fn declared_mesh_slots_reports_only_set_slots_in_canonical_order() {
6034        use crate::{Entrada, Membro};
6035        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6036        // Set a non-adjacent pair (:membros + :entrada) to pin that the
6037        // canonical declaration order is preserved regardless of which
6038        // subset is populated.
6039        c.membros = vec![Membro {
6040            caixa: "a".into(),
6041            versao: "^0.1".into(),
6042        }];
6043        c.entrada = Some(Entrada {
6044            host: "x.example.com".into(),
6045            para: "a".into(),
6046            paths: vec![],
6047            port: 8080,
6048        });
6049        assert_eq!(
6050            c.declared_mesh_slots(),
6051            vec![
6052                crate::render::M3_AUTHOR_KEY_MEMBROS,
6053                crate::render::M3_AUTHOR_KEY_ENTRADA,
6054            ]
6055        );
6056    }
6057
6058    #[test]
6059    fn m3_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
6060        // Scalar-value pin: the five author-facing kebab-case labels the
6061        // `(defcaixa … :<slot> (…))` surface admits on the M3 top-level
6062        // mesh slot axis, one arm per typed slot. Mirrors the peer
6063        // scalar-value pin the sibling
6064        // [`crate::M2_AUTHOR_KEY_LIMITS`] /
6065        // [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
6066        // [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] M2 top-level slot consts
6067        // carry (f49c8b0), so both altitudes of the typed-slot algebra
6068        // (per-Servico M2 + per-Aplicacao M3) share the same
6069        // "one canonical byte-string per arm" discipline. A future
6070        // rebrand (`:membros` → `:members`, `:contratos` → `:contracts`,
6071        // `:politicas` → `:policies`, `:placement` → `:distribution`,
6072        // `:entrada` → `:ingress`) lands as an edit to exactly one const,
6073        // and every consumer that reaches for the label picks it up at
6074        // build time rather than at runtime as a downstream mismatch.
6075        assert_eq!(crate::render::M3_AUTHOR_KEY_MEMBROS, ":membros");
6076        assert_eq!(crate::render::M3_AUTHOR_KEY_CONTRATOS, ":contratos");
6077        assert_eq!(crate::render::M3_AUTHOR_KEY_POLITICAS, ":politicas");
6078        assert_eq!(crate::render::M3_AUTHOR_KEY_PLACEMENT, ":placement");
6079        assert_eq!(crate::render::M3_AUTHOR_KEY_ENTRADA, ":entrada");
6080    }
6081
6082    #[test]
6083    fn declared_mesh_slots_route_through_lifted_m3_author_key_consts() {
6084        // Production-through-const pin: the five per-arm labels the
6085        // [`Caixa::declared_mesh_slots`] tagger pushes onto its return
6086        // `Vec` route through the lifted
6087        // [`crate::M3_AUTHOR_KEY_MEMBROS`] /
6088        // [`crate::M3_AUTHOR_KEY_CONTRATOS`] /
6089        // [`crate::M3_AUTHOR_KEY_POLITICAS`] /
6090        // [`crate::M3_AUTHOR_KEY_PLACEMENT`] /
6091        // [`crate::M3_AUTHOR_KEY_ENTRADA`] consts, in canonical
6092        // declaration order. A future re-order or drift at the tagger
6093        // (a rename that reaches the tagger but not the const, or vice
6094        // versa) surfaces here at build time rather than at runtime as
6095        // a [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
6096        // `slots: <stale-kebab-case>` diagnostic far from the rename's
6097        // commit. Mirror of the peer
6098        // [`declared_servico_slots_route_through_lifted_m2_author_key_consts`]
6099        // pin (f49c8b0) on the sibling per-Servico M2 top-level slot
6100        // axis.
6101        use crate::{Entrada, Membro, MeshPolicy, Placement, PlacementStrategy, WitContract};
6102        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6103        c.membros = vec![Membro {
6104            caixa: "a".into(),
6105            versao: "^0.1".into(),
6106        }];
6107        c.contratos = vec![WitContract {
6108            de: "a".into(),
6109            para: "a".into(),
6110            wit: "wasi:http/proxy".into(),
6111            endpoint: Some("/x".into()),
6112            subject: None,
6113            slot: None,
6114        }];
6115        c.politicas = Some(MeshPolicy::default());
6116        c.placement = Some(Placement {
6117            estrategia: PlacementStrategy::Replicated,
6118            clusters: vec!["rio".into()],
6119            affinity: None,
6120            shard_key: None,
6121        });
6122        c.entrada = Some(Entrada {
6123            host: "x.example.com".into(),
6124            para: "a".into(),
6125            paths: vec![],
6126            port: 8080,
6127        });
6128        assert_eq!(
6129            c.declared_mesh_slots(),
6130            vec![
6131                crate::render::M3_AUTHOR_KEY_MEMBROS,
6132                crate::render::M3_AUTHOR_KEY_CONTRATOS,
6133                crate::render::M3_AUTHOR_KEY_POLITICAS,
6134                crate::render::M3_AUTHOR_KEY_PLACEMENT,
6135                crate::render::M3_AUTHOR_KEY_ENTRADA,
6136            ]
6137        );
6138    }
6139
6140    #[test]
6141    fn declared_supervisor_slots_empty_for_bare_caixa() {
6142        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6143        assert!(c.declared_supervisor_slots().is_empty());
6144    }
6145
6146    #[test]
6147    fn declared_supervisor_slots_reports_only_set_slots_in_canonical_order() {
6148        use crate::RestartStrategy;
6149        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6150        // Set a non-adjacent pair (:estrategia + :restart-window) to pin
6151        // that the canonical declaration order is preserved regardless
6152        // of which subset is populated.
6153        c.estrategia = Some(RestartStrategy::OneForOne);
6154        c.restart_window = Some("60s".into());
6155        assert_eq!(
6156            c.declared_supervisor_slots(),
6157            vec![
6158                crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
6159                crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
6160            ]
6161        );
6162    }
6163
6164    #[test]
6165    fn supervisor_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
6166        // Scalar-value pin: the four author-facing kebab-case labels the
6167        // `(defcaixa … :<slot> (…))` surface admits on the Supervisor
6168        // supervision-tree slot axis, one arm per typed slot. Mirrors the
6169        // peer scalar-value pins the sibling
6170        // [`crate::render::M2_AUTHOR_KEY_LIMITS`] /
6171        // [`crate::render::M2_AUTHOR_KEY_BEHAVIOR`] /
6172        // [`crate::render::M2_AUTHOR_KEY_UPGRADE_FROM`] top-level M2 slot
6173        // consts and [`crate::render::M3_AUTHOR_KEY_MEMBROS`] etc.
6174        // top-level M3 slot consts carry, so all three kind-scoped
6175        // typed-slot-family author-facing-label axes route through one
6176        // canonical per-arm declaration. A future rebrand
6177        // (`:estrategia` → `:strategy` for English uniformity,
6178        // `:max-restarts` → `:max-intensity` matching Erlang/OTP's
6179        // `MaxIntensity` name, `:restart-window` → `:period` matching
6180        // OTP's `Period` name, `:children` → `:workers` matching Elixir
6181        // idiom) lands as an edit to exactly one const, and every
6182        // consumer that reaches for the label picks it up at build time
6183        // rather than at runtime as a downstream mismatch.
6184        assert_eq!(
6185            crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
6186            ":estrategia"
6187        );
6188        assert_eq!(
6189            crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS,
6190            ":max-restarts"
6191        );
6192        assert_eq!(
6193            crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
6194            ":restart-window"
6195        );
6196        assert_eq!(crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN, ":children");
6197    }
6198
6199    #[test]
6200    fn declared_supervisor_slots_route_through_lifted_supervisor_author_key_consts() {
6201        // Production-through-const pin: the four per-arm labels the
6202        // [`Caixa::declared_supervisor_slots`] tagger pushes onto its
6203        // return `Vec` route through the lifted
6204        // [`crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA`] /
6205        // [`crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS`] /
6206        // [`crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW`] /
6207        // [`crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN`] consts, in
6208        // canonical declaration order. A future re-order or drift at the
6209        // tagger (a rename that reaches the tagger but not the const, or
6210        // vice versa) surfaces here at build time rather than at runtime
6211        // as a [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
6212        // `slots: <stale-kebab-case>` diagnostic far from the rename's
6213        // commit. Mirror of the peer
6214        // [`declared_servico_slots_route_through_lifted_m2_author_key_consts`]
6215        // (f49c8b0) and
6216        // [`declared_mesh_slots_route_through_lifted_m3_author_key_consts`]
6217        // (882f498) pins on the sibling M2 / M3 top-level slot axes.
6218        use crate::{ChildSpec, RestartPolicy, RestartStrategy};
6219        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6220        c.estrategia = Some(RestartStrategy::OneForOne);
6221        c.max_restarts = Some(5);
6222        c.restart_window = Some("60s".into());
6223        c.children = vec![ChildSpec {
6224            caixa: "worker".into(),
6225            versao: "^0.1".into(),
6226            restart: RestartPolicy::Permanent,
6227        }];
6228        assert_eq!(
6229            c.declared_supervisor_slots(),
6230            vec![
6231                crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
6232                crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS,
6233                crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
6234                crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN,
6235            ]
6236        );
6237    }
6238
6239    #[test]
6240    fn declared_servico_slots_empty_for_bare_caixa() {
6241        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6242        assert!(c.declared_servico_slots().is_empty());
6243    }
6244
6245    #[test]
6246    fn declared_servico_slots_reports_only_set_slots_in_canonical_order() {
6247        use crate::{UpgradeFromEntry, UpgradeInstruction};
6248        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6249        // Set a non-adjacent pair (:limits + :upgrade-from) to pin that
6250        // the canonical declaration order is preserved regardless of
6251        // which subset is populated.
6252        c.limits = Some(crate::LimitsSpec {
6253            fuel: Some(1_000_000),
6254            ..Default::default()
6255        });
6256        c.upgrade_from = vec![UpgradeFromEntry {
6257            from: "0.1.0".into(),
6258            instructions: vec![UpgradeInstruction::Restart],
6259        }];
6260        assert_eq!(
6261            c.declared_servico_slots(),
6262            vec![
6263                crate::render::M2_AUTHOR_KEY_LIMITS,
6264                crate::render::M2_AUTHOR_KEY_UPGRADE_FROM,
6265            ]
6266        );
6267    }
6268
6269    #[test]
6270    fn m2_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
6271        // Scalar-value pin: the three author-facing kebab-case labels
6272        // the `(defcaixa … :<slot> (…))` surface admits on the M2
6273        // top-level slot axis, one arm per typed slot. Mirrors the peer
6274        // scalar-value pin the sibling renderer-side
6275        // [`crate::M2_KEY_LIMITS`] / [`crate::M2_KEY_BEHAVIOR`] /
6276        // [`crate::M2_KEY_UPGRADE_FROM`] camelCase overlay-container
6277        // consts carry, so both halves of the M2 top-level slot dual
6278        // axis (author-facing kebab-case label + renderer-side
6279        // camelCase overlay-container wire key) route through one
6280        // canonical per-arm declaration. A future rebrand
6281        // (`:limits` → `:sandbox` matching Lunatic per-process
6282        // terminology INSPIRATIONS §III.1, `:behavior` → `:gen-server`
6283        // matching Erlang's verbatim name, `:upgrade-from` → `:appup`
6284        // matching Erlang's verbatim appup name) lands as an edit to
6285        // exactly one const, and every consumer that reaches for the
6286        // label picks it up at build time rather than at runtime as a
6287        // downstream mismatch.
6288        assert_eq!(crate::render::M2_AUTHOR_KEY_LIMITS, ":limits");
6289        assert_eq!(crate::render::M2_AUTHOR_KEY_BEHAVIOR, ":behavior");
6290        assert_eq!(crate::render::M2_AUTHOR_KEY_UPGRADE_FROM, ":upgrade-from");
6291    }
6292
6293    #[test]
6294    fn declared_servico_slots_route_through_lifted_m2_author_key_consts() {
6295        // Production-through-const pin: the three per-arm labels the
6296        // [`Caixa::declared_servico_slots`] tagger pushes onto its
6297        // return `Vec` route through the lifted
6298        // [`crate::M2_AUTHOR_KEY_LIMITS`] /
6299        // [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
6300        // [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] consts, in canonical
6301        // declaration order. A future re-order or drift at the tagger
6302        // (a rename that reaches the tagger but not the const, or vice
6303        // versa) surfaces here at build time rather than at runtime as
6304        // a [`crate::LayoutError::ServicoSlotsOnNonServico`]
6305        // `slots: <stale-kebab-case>` diagnostic far from the rename's
6306        // commit. Mirror of the peer
6307        // [`crate::behavior::BehaviorSpec::declared_slots`] production
6308        // tagger pin (889dc18) on the sibling per-callback axis.
6309        use crate::{BehaviorSpec, UpgradeFromEntry, UpgradeInstruction};
6310        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6311        c.limits = Some(crate::LimitsSpec {
6312            fuel: Some(1_000_000),
6313            ..Default::default()
6314        });
6315        c.behavior = Some(BehaviorSpec {
6316            on_init: Some(PathBuf::from("lib/init.lisp")),
6317            ..Default::default()
6318        });
6319        c.upgrade_from = vec![UpgradeFromEntry {
6320            from: "0.1.0".into(),
6321            instructions: vec![UpgradeInstruction::Restart],
6322        }];
6323        assert_eq!(
6324            c.declared_servico_slots(),
6325            vec![
6326                crate::render::M2_AUTHOR_KEY_LIMITS,
6327                crate::render::M2_AUTHOR_KEY_BEHAVIOR,
6328                crate::render::M2_AUTHOR_KEY_UPGRADE_FROM,
6329            ]
6330        );
6331    }
6332
6333    #[test]
6334    fn existing_manifests_unaffected_by_new_optional_slots() {
6335        // Regression test: a caixa.lisp authored before M2 typed slots
6336        // should still parse + serialize cleanly. The bare `defcaixa`
6337        // emitted by `Caixa::template` has none of the new fields.
6338        let src = Caixa::template("legacy");
6339        let c = Caixa::from_lisp(&src).unwrap();
6340        assert!(c.limits.is_none());
6341        assert!(c.behavior.is_none());
6342        assert!(c.upgrade_from.is_empty());
6343        assert!(c.estrategia.is_none());
6344        assert!(c.children.is_empty());
6345
6346        // And to_lisp emits a manifest with the new slots in the
6347        // empty/default state — round-trippable.
6348        let emitted = c.to_lisp();
6349        let back = Caixa::from_lisp(&emitted).unwrap();
6350        assert_eq!(c, back);
6351    }
6352
6353    #[test]
6354    fn validate_deps_accepts_canonical_caixa() {
6355        // Positive control: the bare template — zero deps, zero
6356        // deps_dev — passes the gate trivially. A future axis added to
6357        // `Dep::validate` mustn't regress an empty-deps caixa to a
6358        // build error.
6359        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6360        c.validate_deps().unwrap();
6361    }
6362
6363    #[test]
6364    fn validate_deps_rejects_invalid_versao_in_deps() {
6365        // Fail-before-pass-after pin: a malformed `:deps :versao`
6366        // surfaces at validate_deps() time, not at lacre-resolve time.
6367        // Mirrors `rejects_invalid_membro_versao_requirement` and
6368        // `validate_rejects_invalid_child_versao_requirement` on the
6369        // other two `:versao` axes.
6370        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6371        c.deps = vec![Dep::simple("caixa-teia", "^bad-version")];
6372        let err = c.validate_deps().unwrap_err();
6373        assert!(
6374            matches!(
6375                err,
6376                crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
6377                    if nome == "caixa-teia" && versao == "^bad-version"
6378            ),
6379            "got {err:?}"
6380        );
6381    }
6382
6383    #[test]
6384    fn validate_deps_rejects_invalid_versao_in_deps_dev() {
6385        // Parity pin: `:deps-dev` must run through the same per-entry
6386        // validator as `:deps` — a typo in either axis surfaces the
6387        // same diagnostic. Without this leg, `:deps-dev` would be a
6388        // second-class citizen of the typed surface and an author
6389        // could land a build that passes validate_deps but fails at
6390        // `feira lock`-time when the dev-dep is resolved for a test
6391        // build.
6392        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6393        c.deps_dev = vec![Dep::simple("tatara-check", "^^0.1")];
6394        let err = c.validate_deps().unwrap_err();
6395        assert!(
6396            matches!(
6397                err,
6398                crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
6399                    if nome == "tatara-check" && versao == "^^0.1"
6400            ),
6401            "got {err:?}"
6402        );
6403    }
6404
6405    #[test]
6406    fn validate_deps_runs_deps_before_deps_dev() {
6407        // Order pin: when both lists carry typos, the `:deps`
6408        // diagnostic surfaces first. The author's mental model is
6409        // "runtime deps are load-bearing; dev deps are scaffolding";
6410        // surfacing the runtime axis first matches that hierarchy.
6411        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6412        c.deps = vec![Dep::simple("runtime-dep", "^bad-runtime")];
6413        c.deps_dev = vec![Dep::simple("dev-dep", "^bad-dev")];
6414        let err = c.validate_deps().unwrap_err();
6415        assert!(
6416            matches!(
6417                err,
6418                crate::dep::DepError::VersaoInvalid { ref nome, .. }
6419                    if nome == "runtime-dep"
6420            ),
6421            "expected `:deps` typo to surface first, got {err:?}"
6422        );
6423    }
6424
6425    #[test]
6426    fn validate_deps_accepts_canonical_versao_forms_in_both_lists() {
6427        // Positive control sweep across both lists. Pin every
6428        // canonical Cargo-shaped form so a future tightening of the
6429        // accepted set surfaces here as a test failure (parity with
6430        // `accepts_canonical_membro_versao_forms` and
6431        // `validate_accepts_canonical_child_versao_forms`).
6432        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6433        c.deps = vec![
6434            Dep::simple("caret", "^0.1"),
6435            Dep::simple("tilde", "~0.1.2"),
6436            Dep::simple("exact", "0.1.0"),
6437            Dep::simple("wildcard", "*"),
6438            Dep::simple("multi-range", ">=0.1, <2"),
6439        ];
6440        c.deps_dev = vec![
6441            Dep::simple("dev-caret", "^0.1"),
6442            Dep::simple("dev-wildcard", "*"),
6443        ];
6444        c.validate_deps().unwrap();
6445    }
6446
6447    #[test]
6448    fn validate_deps_diagnostic_carries_offending_dep() {
6449        // Diagnostic-shape pin: the error names the offending entry's
6450        // `:nome` + `:versao` verbatim and carries a non-empty
6451        // `reason` from `semver::VersionReq::parse`, so a `feira lint`
6452        // run can render the diagnostic without re-parsing.
6453        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6454        c.deps = vec![Dep::simple("caixa-teia", "not-a-req")];
6455        let err = c.validate_deps().unwrap_err();
6456        let crate::dep::DepError::VersaoInvalid {
6457            nome,
6458            versao,
6459            reason,
6460        } = err
6461        else {
6462            panic!("expected VersaoInvalid, got other variant");
6463        };
6464        assert_eq!(nome, "caixa-teia");
6465        assert_eq!(versao, "not-a-req");
6466        assert!(
6467            !reason.is_empty(),
6468            "VersaoInvalid `reason` must carry the parser's wording verbatim"
6469        );
6470    }
6471
6472    #[test]
6473    fn validate_deps_rejects_ambiguous_fonte_in_deps_dev() {
6474        // Cross-axis pin: `validate_deps` walks both :deps and
6475        // :deps-dev through `Dep::validate`, and the new fonte gate
6476        // (`:tag` + `:branch` both set — the canonical "pin drift"
6477        // footgun) must surface from the :deps-dev arm with the
6478        // offending entry's :nome named. Pin the :deps-dev arm
6479        // explicitly so a future shortcut that only walks :deps
6480        // surfaces here as a regression.
6481        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6482        c.deps_dev = vec![Dep {
6483            nome: "dev-only".into(),
6484            versao: "^0.1".into(),
6485            fonte: Some(crate::DepSource::Git {
6486                repo: "github:p/x".into(),
6487                tag: Some("v1".into()),
6488                rev: None,
6489                branch: Some("main".into()),
6490            }),
6491            opcional: false,
6492            caracteristicas: vec![],
6493        }];
6494        let err = c.validate_deps().unwrap_err();
6495        let crate::dep::DepError::FontePinAmbiguous { nome, pins } = err else {
6496            panic!("expected FontePinAmbiguous from :deps-dev walk");
6497        };
6498        assert_eq!(nome, "dev-only");
6499        assert!(pins.contains(":tag") && pins.contains(":branch"));
6500    }
6501
6502    #[test]
6503    fn validate_deps_rejects_empty_repo_in_deps() {
6504        // Parity pin on the :deps arm: an empty :repo on the runtime
6505        // deps list surfaces the same FonteRepoEmpty diagnostic the
6506        // dep.rs per-entry tests pin, naming the offending entry.
6507        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6508        c.deps = vec![Dep {
6509            nome: "runtime".into(),
6510            versao: "^0.1".into(),
6511            fonte: Some(crate::DepSource::Git {
6512                repo: String::new(),
6513                tag: Some("v1".into()),
6514                rev: None,
6515                branch: None,
6516            }),
6517            opcional: false,
6518            caracteristicas: vec![],
6519        }];
6520        let err = c.validate_deps().unwrap_err();
6521        assert!(
6522            matches!(
6523                err,
6524                crate::dep::DepError::FonteRepoEmpty { ref nome }
6525                    if nome == "runtime"
6526            ),
6527            "got {err:?}"
6528        );
6529    }
6530
6531    // ── validate_deps: within-list :nome set-not-multiset gate ─────────
6532
6533    #[test]
6534    fn validate_deps_rejects_duplicate_nome_in_deps() {
6535        // Fail-before-pass-after pin: two `:deps` entries naming the same
6536        // caixa carry two `:versao` / `:fonte` / feature triples that the
6537        // caixa-resolver's lacre pipeline collapses (the second silently
6538        // overwrites the first at `concrete_versao`-resolve time). The
6539        // gate surfaces the duplicate at validate-time, naming the
6540        // offending caixa + the list, before the resolver-side silent
6541        // drop. Mirrors the peer typed-graph duplicate gates
6542        // (`DuplicateChildCaixa`, `MembroDuplicate`, `DuplicateFrom`, …).
6543        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6544        c.deps = vec![
6545            Dep::simple("caixa-teia", "^0.1"),
6546            Dep::simple("caixa-teia", "^0.2"),
6547        ];
6548        let err = c.validate_deps().unwrap_err();
6549        assert!(
6550            matches!(
6551                err,
6552                crate::dep::DepError::DuplicateNome { ref nome, list }
6553                    if nome == "caixa-teia" && list == crate::render::DEP_AUTHOR_KEY_DEPS
6554            ),
6555            "got {err:?}"
6556        );
6557    }
6558
6559    #[test]
6560    fn validate_deps_rejects_duplicate_nome_in_deps_dev() {
6561        // Parity pin: `:deps-dev` runs through the same per-list
6562        // duplicate check as `:deps` — neither axis is a second-class
6563        // citizen of the set-not-multiset discipline.
6564        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6565        c.deps_dev = vec![
6566            Dep::simple("tatara-check", "*"),
6567            Dep::simple("tatara-check", "^0.1"),
6568        ];
6569        let err = c.validate_deps().unwrap_err();
6570        assert!(
6571            matches!(
6572                err,
6573                crate::dep::DepError::DuplicateNome { ref nome, list }
6574                    if nome == "tatara-check" && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
6575            ),
6576            "got {err:?}"
6577        );
6578    }
6579
6580    #[test]
6581    fn validate_deps_accepts_cross_list_same_nome() {
6582        // The Cargo `[dependencies]` + `[dev-dependencies]` override
6583        // convention is preserved: a name appearing in *both* lists is
6584        // valid (the dev-pin overrides at test/dev time). Only
6585        // within-list duplicates are structurally incoherent — pin the
6586        // permissive cross-list semantics so a future shortcut that
6587        // collapses the two seen-sets into one surfaces here as a test
6588        // failure.
6589        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6590        c.deps = vec![Dep::simple("caixa-teia", "^0.1")];
6591        c.deps_dev = vec![Dep::simple("caixa-teia", "^0.2")];
6592        c.validate_deps().unwrap();
6593    }
6594
6595    #[test]
6596    fn validate_deps_accepts_distinct_nome_in_both_lists() {
6597        // Positive control: distinct names within each list pass — the
6598        // gate's identity element on the canonical authoring shape.
6599        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6600        c.deps = vec![
6601            Dep::simple("caixa-teia", "^0.1"),
6602            Dep::simple("pleme-mesh", "*"),
6603        ];
6604        c.deps_dev = vec![
6605            Dep::simple("tatara-check", "*"),
6606            Dep::simple("dev-shim", "^0.1"),
6607        ];
6608        c.validate_deps().unwrap();
6609    }
6610
6611    #[test]
6612    fn validate_deps_per_entry_validate_fires_before_duplicate_in_deps() {
6613        // Diagnostic-precedence pin: a malformed `:versao` on the
6614        // duplicating entry surfaces its narrower `VersaoInvalid`
6615        // diagnostic first, before the cross-entry duplicate gate fires
6616        // — the canonical "per-entry shape before cross-entry uniqueness"
6617        // precedence every peer set-not-multiset gate establishes
6618        // (`*_invalid_fires_before_duplicate_check` pins on
6619        // `SupervisorSpec::validate`, `AplicacaoSpec::validate_membros`,
6620        // `validate_upgrade_from`).
6621        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6622        c.deps = vec![
6623            Dep::simple("caixa-teia", "^0.1"),
6624            Dep::simple("caixa-teia", "^bad-version"),
6625        ];
6626        let err = c.validate_deps().unwrap_err();
6627        assert!(
6628            matches!(
6629                err,
6630                crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
6631                    if nome == "caixa-teia" && versao == "^bad-version"
6632            ),
6633            "expected VersaoInvalid to surface before DuplicateNome, got {err:?}"
6634        );
6635    }
6636
6637    #[test]
6638    fn validate_deps_duplicate_diagnostic_names_first_collision() {
6639        // First-collision determinism pin: with three entries naming the
6640        // same caixa, the first colliding pair surfaces — not the last.
6641        // Mirrors the peer first-collision posture on every
6642        // duplicate-target gate
6643        // (`validate_upgrade_from_duplicate_diagnostic_names_second_collision`
6644        // — the second entry is the first collision; this gate uses the
6645        // same shape: the second entry's `:nome` lands in the diagnostic
6646        // because `seen.insert(first.nome)` already populated the set).
6647        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6648        c.deps = vec![
6649            Dep::simple("caixa-teia", "^0.1"),
6650            Dep::simple("caixa-teia", "^0.2"),
6651            Dep::simple("caixa-teia", "^0.3"),
6652        ];
6653        let err = c.validate_deps().unwrap_err();
6654        // The diagnostic carries the offending caixa name; the
6655        // implementation surfaces on the *second* entry (the first
6656        // collision), so the test pins the `:nome` value.
6657        assert!(
6658            matches!(
6659                err,
6660                crate::dep::DepError::DuplicateNome { ref nome, list }
6661                    if nome == "caixa-teia" && list == crate::render::DEP_AUTHOR_KEY_DEPS
6662            ),
6663            "got {err:?}"
6664        );
6665    }
6666
6667    #[test]
6668    fn validate_deps_duplicate_in_deps_fires_before_duplicate_in_deps_dev() {
6669        // Cross-list precedence pin: when both lists carry duplicates,
6670        // the `:deps` diagnostic surfaces first — same author-mental-
6671        // model ordering the `validate_deps_runs_deps_before_deps_dev`
6672        // pin establishes for malformed `:versao` (runtime axis before
6673        // dev axis).
6674        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6675        c.deps = vec![
6676            Dep::simple("runtime-dep", "^0.1"),
6677            Dep::simple("runtime-dep", "^0.2"),
6678        ];
6679        c.deps_dev = vec![Dep::simple("dev-dep", "*"), Dep::simple("dev-dep", "^0.1")];
6680        let err = c.validate_deps().unwrap_err();
6681        assert!(
6682            matches!(
6683                err,
6684                crate::dep::DepError::DuplicateNome { ref nome, list }
6685                    if nome == "runtime-dep" && list == crate::render::DEP_AUTHOR_KEY_DEPS
6686            ),
6687            "expected :deps duplicate to surface before :deps-dev duplicate, got {err:?}"
6688        );
6689    }
6690
6691    #[test]
6692    fn validate_deps_empty_lists_pass_duplicate_gate() {
6693        // Empty-set identity pin: the bare template (zero deps, zero
6694        // deps_dev) passes the duplicate gate as the gate's identity
6695        // element. A future tighten that conflates "empty" with
6696        // "missing" would regress this baseline.
6697        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6698        c.validate_deps().unwrap();
6699    }
6700
6701    #[test]
6702    fn validate_deps_duplicate_diagnostic_carries_list_tag() {
6703        // Diagnostic-shape pin: the `list:` field tags which list the
6704        // duplicate landed in (`:deps` vs `:deps-dev`) verbatim, so a
6705        // `feira lint` run can route the author to the right block in
6706        // their caixa.lisp without re-deriving the list from context.
6707        // Same self-locating shape every peer per-axis diagnostic
6708        // already exposes.
6709        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6710        c.deps_dev = vec![
6711            Dep::simple("dev-thing", "*"),
6712            Dep::simple("dev-thing", "^0.1"),
6713        ];
6714        let err = c.validate_deps().unwrap_err();
6715        let crate::dep::DepError::DuplicateNome { nome, list } = err else {
6716            panic!("expected DuplicateNome from :deps-dev walk");
6717        };
6718        assert_eq!(nome, "dev-thing");
6719        assert_eq!(list, crate::render::DEP_AUTHOR_KEY_DEPS_DEV);
6720    }
6721
6722    // ── validate_deps: per-entry :caracteristicas set-discipline gate ──
6723
6724    #[test]
6725    fn validate_deps_surfaces_caracteristicas_duplicate_in_deps_list() {
6726        // Thread-through pin on `:deps`: the per-entry
6727        // `Dep::validate_caracteristicas` gate fires inside
6728        // `Caixa::validate_deps`'s linear walk, so a malformed feature
6729        // list on any `:deps` entry surfaces as a `DepError` from
6730        // `validate_deps` — the same reachability shape every per-entry
6731        // `Dep::validate` arm threads through. Without this pin a future
6732        // shortcut that skips the per-entry `Dep::validate` call on the
6733        // cross-entry-uniqueness path would mask the within-entry
6734        // `:caracteristicas` gates.
6735        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6736        c.deps = vec![Dep {
6737            nome: "caixa-teia".into(),
6738            versao: "^0.1".into(),
6739            fonte: None,
6740            opcional: false,
6741            caracteristicas: vec!["http".into(), "http".into()],
6742        }];
6743        let err = c.validate_deps().unwrap_err();
6744        let crate::dep::DepError::CaracteristicaDuplicate {
6745            nome,
6746            caracteristica,
6747        } = err
6748        else {
6749            panic!("expected CaracteristicaDuplicate from :deps walk, got {err:?}");
6750        };
6751        assert_eq!(nome, "caixa-teia");
6752        assert_eq!(caracteristica, "http");
6753    }
6754
6755    #[test]
6756    fn validate_deps_surfaces_caracteristicas_empty_in_deps_dev_list() {
6757        // Peer thread-through pin on `:deps-dev`: same reachability as
6758        // the `:deps` arm above, on the dev-only authoring axis. Pins
6759        // that the `validate_deps` walk visits both lists' per-entry
6760        // gates uniformly. The empty-feature arm carries here so both
6761        // new `:caracteristicas` arms are surfaced via at least one
6762        // `validate_deps` thread-through.
6763        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6764        c.deps_dev = vec![Dep {
6765            nome: "caixa-teia".into(),
6766            versao: "^0.1".into(),
6767            fonte: None,
6768            opcional: false,
6769            caracteristicas: vec![String::new()],
6770        }];
6771        let err = c.validate_deps().unwrap_err();
6772        let crate::dep::DepError::CaracteristicaEmpty { nome } = err else {
6773            panic!("expected CaracteristicaEmpty from :deps-dev walk, got {err:?}");
6774        };
6775        assert_eq!(nome, "caixa-teia");
6776    }
6777
6778    #[test]
6779    fn validate_deps_surfaces_caracteristicas_invalid_in_deps_list() {
6780        // Thread-through pin on `:deps`: the per-entry
6781        // `Dep::validate_caracteristicas` value-shape gate (lifted via
6782        // `crate::render::is_cargo_feature_name`) fires inside
6783        // `Caixa::validate_deps`'s linear walk on the `:deps` list, so
6784        // a structurally invalid feature name on any `:deps` entry
6785        // surfaces as `DepError::CaracteristicaInvalid` from
6786        // `validate_deps` — the same reachability shape every per-entry
6787        // `Dep::validate` arm threads through. Without this pin a
6788        // future shortcut that skips the per-entry `Dep::validate` call
6789        // on the cross-entry-uniqueness path would mask the within-
6790        // entry `:caracteristicas` value-shape gate.
6791        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6792        c.deps = vec![Dep {
6793            nome: "caixa-teia".into(),
6794            versao: "^0.1".into(),
6795            fonte: None,
6796            opcional: false,
6797            caracteristicas: vec!["+http".into()],
6798        }];
6799        let err = c.validate_deps().unwrap_err();
6800        let crate::dep::DepError::CaracteristicaInvalid {
6801            nome,
6802            caracteristica,
6803            ..
6804        } = err
6805        else {
6806            panic!("expected CaracteristicaInvalid from :deps walk, got {err:?}");
6807        };
6808        assert_eq!(nome, "caixa-teia");
6809        assert_eq!(caracteristica, "+http");
6810    }
6811
6812    #[test]
6813    fn validate_deps_surfaces_caracteristicas_invalid_in_deps_dev_list() {
6814        // Peer thread-through pin on `:deps-dev`: same reachability as
6815        // the `:deps` arm above, on the dev-only authoring axis. The
6816        // `http/json` shape carries here so the segment-separator
6817        // diagnostic (the canonical Cargo `dep/feat` namespaced-dep
6818        // confusion footgun) is surfaced via the cross-entry walk too —
6819        // pinning that the `:deps-dev` list visits the same per-entry
6820        // value-shape gate as the `:deps` list.
6821        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6822        c.deps_dev = vec![Dep {
6823            nome: "caixa-teia".into(),
6824            versao: "^0.1".into(),
6825            fonte: None,
6826            opcional: false,
6827            caracteristicas: vec!["http/json".into()],
6828        }];
6829        let err = c.validate_deps().unwrap_err();
6830        let crate::dep::DepError::CaracteristicaInvalid {
6831            nome,
6832            caracteristica,
6833            ..
6834        } = err
6835        else {
6836            panic!("expected CaracteristicaInvalid from :deps-dev walk, got {err:?}");
6837        };
6838        assert_eq!(nome, "caixa-teia");
6839        assert_eq!(caracteristica, "http/json");
6840    }
6841
6842    #[test]
6843    fn to_lisp_preserves_deps() {
6844        let src = r#"
6845(defcaixa
6846  :nome "x"
6847  :versao "0.1.0"
6848  :kind Biblioteca
6849  :deps ((:nome "a" :versao "^0.1")
6850         (:nome "b" :versao "*" :fonte (:tipo git :repo "github:o/b" :tag "v1"))))
6851"#;
6852        let c1 = Caixa::from_lisp(src).unwrap();
6853        let emitted = c1.to_lisp();
6854        let c2 = Caixa::from_lisp(&emitted).expect("round trip");
6855        assert_eq!(c1.deps, c2.deps);
6856    }
6857
6858    // ── Caixa::validate_nome — top-level :nome value-shape gate ─────────
6859
6860    fn caixa_with_nome(nome: &str) -> Caixa {
6861        let mut c = Caixa::from_lisp(&Caixa::template("placeholder")).unwrap();
6862        c.nome = nome.to_string();
6863        c
6864    }
6865
6866    #[test]
6867    fn validate_nome_accepts_canonical_template() {
6868        // Positive control: the bare `feira init`-style template's
6869        // `:nome` ("demo") is a canonical DNS-1123 label; the gate must
6870        // not regress this baseline shape. A future tightening of the
6871        // accepted set surfaces here as a test failure first.
6872        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6873        c.validate_nome().unwrap();
6874    }
6875
6876    #[test]
6877    fn validate_nome_accepts_canonical_forms() {
6878        // Positive-set sweep: each realistic caixa-name shape the K8s
6879        // apiserver accepts as a `metadata.name` label must pass —
6880        // single-word, hyphen-joined, version-suffixed, single-char,
6881        // two-char, digit-start (DNS-1123 allows this; the stricter
6882        // DNS-1035 Service-name rule doesn't), version-suffix-bearing.
6883        // Mirrors `accepts_canonical_membro_caixa_forms` (3f9d7a0) on
6884        // the peer member-name axis.
6885        for nome in [
6886            "checkout",
6887            "cart-v2",
6888            "a",
6889            "db",
6890            "3rd-party-shim",
6891            "payment-retry",
6892            "0",
6893        ] {
6894            caixa_with_nome(nome)
6895                .validate_nome()
6896                .unwrap_or_else(|e| panic!("canonical :nome {nome:?} must validate, got {e:?}"));
6897        }
6898    }
6899
6900    #[test]
6901    fn validate_nome_rejects_empty() {
6902        // Fail-before-pass-after pin: `Caixa::from_lisp` does not refuse
6903        // an empty `:nome` (the derive macro stores the raw String);
6904        // the gate's empty arm names the offending axis with a narrower
6905        // diagnostic than the `NomeInvalid` parse arm would emit.
6906        let c = caixa_with_nome("");
6907        let err = c.validate_nome().unwrap_err();
6908        assert_eq!(err, ManifestError::NomeEmpty);
6909    }
6910
6911    #[test]
6912    fn validate_nome_rejects_uppercase() {
6913        // The canonical "I copied the TitleCase display name verbatim"
6914        // footgun. The K8s apiserver rejects `metadata.name: MyApp` at
6915        // admission on every derived artifact (Helm chart, ComputeUnit,
6916        // CNP, HTTPRoute, label values); the gate moves the diagnostic
6917        // to the source `caixa.lisp` and the reason suggests the
6918        // lowercased fix verbatim.
6919        let c = caixa_with_nome("MyApp");
6920        let err = c.validate_nome().unwrap_err();
6921        let ManifestError::NomeInvalid { nome, reason } = err else {
6922            panic!("expected NomeInvalid for uppercase :nome");
6923        };
6924        assert_eq!(nome, "MyApp");
6925        assert!(
6926            reason.contains("uppercase") && reason.contains("myapp"),
6927            "diagnostic must name the violation + the lowercased fix, got {reason:?}"
6928        );
6929    }
6930
6931    #[test]
6932    fn validate_nome_rejects_underscore() {
6933        // The Python-/Postgres-style `snake_case` leak. DNS-1123 forbids
6934        // `_`; the apiserver rejects on admission across every derived
6935        // artifact. Same fixture pinned for `:membros :caixa` (3f9d7a0)
6936        // and `:children :caixa` (31bfa43).
6937        let c = caixa_with_nome("my_app");
6938        let err = c.validate_nome().unwrap_err();
6939        assert!(
6940            matches!(
6941                err,
6942                ManifestError::NomeInvalid { ref nome, ref reason }
6943                    if nome == "my_app" && reason.contains('_')
6944            ),
6945            "got {err:?}"
6946        );
6947    }
6948
6949    #[test]
6950    fn validate_nome_rejects_dot() {
6951        // A `:nome` is a single DNS-1123 label, not a subdomain. The
6952        // "I want to namespace with `.`" footgun the gate redirects to
6953        // `-` via the shared predicate's reason wording.
6954        let c = caixa_with_nome("team.app");
6955        let err = c.validate_nome().unwrap_err();
6956        assert!(
6957            matches!(
6958                err,
6959                ManifestError::NomeInvalid { ref nome, ref reason }
6960                    if nome == "team.app" && reason.contains('.')
6961            ),
6962            "got {err:?}"
6963        );
6964    }
6965
6966    #[test]
6967    fn validate_nome_rejects_leading_hyphen() {
6968        // DNS-1123 boundary rule: the label must start with an ASCII
6969        // alphanumeric. Pin the leading-`-` arm explicitly.
6970        let c = caixa_with_nome("-app");
6971        let err = c.validate_nome().unwrap_err();
6972        assert!(
6973            matches!(
6974                err,
6975                ManifestError::NomeInvalid { ref nome, .. } if nome == "-app"
6976            ),
6977            "got {err:?}"
6978        );
6979    }
6980
6981    #[test]
6982    fn validate_nome_rejects_trailing_hyphen() {
6983        // Symmetric arm of the boundary rule, pinned separately so a
6984        // future relaxation that only checks the leading position
6985        // surfaces here. Mirrors `rejects_membro_caixa_with_trailing_hyphen`
6986        // and `_with_trailing_hyphen` on the supervisor / aplicacao
6987        // axes.
6988        let c = caixa_with_nome("app-");
6989        let err = c.validate_nome().unwrap_err();
6990        assert!(
6991            matches!(
6992                err,
6993                ManifestError::NomeInvalid { ref nome, .. } if nome == "app-"
6994            ),
6995            "got {err:?}"
6996        );
6997    }
6998
6999    #[test]
7000    fn validate_nome_rejects_unicode() {
7001        // IDN must be pre-encoded as Punycode (`xn--…`); raw Unicode
7002        // bytes are rejected by the K8s apiserver on every name axis.
7003        let c = caixa_with_nome("café");
7004        let err = c.validate_nome().unwrap_err();
7005        assert!(
7006            matches!(
7007                err,
7008                ManifestError::NomeInvalid { ref nome, .. } if nome == "café"
7009            ),
7010            "got {err:?}"
7011        );
7012    }
7013
7014    #[test]
7015    fn validate_nome_rejects_whitespace() {
7016        // The paste-from-sketch / paste-from-spec footgun. Internal
7017        // whitespace is rejected by every K8s name axis.
7018        let c = caixa_with_nome("my app");
7019        let err = c.validate_nome().unwrap_err();
7020        assert!(
7021            matches!(
7022                err,
7023                ManifestError::NomeInvalid { ref nome, .. } if nome == "my app"
7024            ),
7025            "got {err:?}"
7026        );
7027    }
7028
7029    #[test]
7030    fn validate_nome_rejects_too_long() {
7031        // 64-byte boundary pin: the K8s apiserver rejects any
7032        // `metadata.name` over 63 bytes at admission; the diagnostic
7033        // names both the 63-byte cap and the actual length so the
7034        // author can shorten in one edit. Mirrors `_too_long` on the
7035        // peer member-/cluster-/child-name axes.
7036        let over = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN + 1);
7037        let c = caixa_with_nome(&over);
7038        let err = c.validate_nome().unwrap_err();
7039        let ManifestError::NomeInvalid { nome, reason } = err else {
7040            panic!("expected NomeInvalid for over-cap :nome");
7041        };
7042        assert_eq!(nome.len(), crate::DNS_1123_LABEL_MAX_LEN + 1);
7043        assert!(
7044            reason.contains("63") && reason.contains("64"),
7045            "diagnostic must name the cap + actual length, got {reason:?}"
7046        );
7047    }
7048
7049    #[test]
7050    fn nome_max_length_validates() {
7051        // The 63-byte cap exactly — the boundary-accepting case pinned
7052        // alongside `validate_nome_rejects_too_long` so a future cap
7053        // shift surfaces both arms simultaneously. Mirrors
7054        // `membro_caixa_max_length_validates`,
7055        // `placement_cluster_max_length_validates`,
7056        // `child_caixa_max_length_validates`.
7057        let at_cap = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN);
7058        caixa_with_nome(&at_cap).validate_nome().unwrap();
7059    }
7060
7061    #[test]
7062    fn nome_empty_takes_precedence_over_invalid() {
7063        // Order pin: the empty arm fires before the predicate is
7064        // consulted. Empty < invalid in self-locating-ness — the
7065        // narrower `NomeEmpty` diagnostic doesn't carry a useless
7066        // `nome: ""` reference into the parser-shaped reason. Mirrors
7067        // `membro_caixa_empty_takes_precedence_over_invalid` on the
7068        // peer axis (3f9d7a0).
7069        let c = caixa_with_nome("");
7070        assert_eq!(c.validate_nome().unwrap_err(), ManifestError::NomeEmpty);
7071    }
7072
7073    #[test]
7074    fn nome_invalid_diagnostic_carries_offending_nome() {
7075        // Diagnostic-shape pin: the error names the offending `:nome`
7076        // verbatim with a non-empty parser-shaped reason, so a `feira
7077        // lint` run can render the diagnostic without re-parsing.
7078        // Mirrors `membro_caixa_invalid_diagnostic_carries_offending_caixa`.
7079        let c = caixa_with_nome("MyApp");
7080        let err = c.validate_nome().unwrap_err();
7081        let ManifestError::NomeInvalid { nome, reason } = err else {
7082            panic!("expected NomeInvalid variant");
7083        };
7084        assert_eq!(nome, "MyApp");
7085        assert!(
7086            !reason.is_empty(),
7087            "NomeInvalid `reason` must carry the predicate's wording verbatim"
7088        );
7089    }
7090
7091    // ── Caixa::validate_nome_chart_name_budget — joint-length on `:nome` ──
7092    //
7093    // The bare-`:nome` axis [`Caixa::validate_nome`] caps at 63 bytes
7094    // via DNS-1123; this second-axis gate caps the joint
7095    // `lareira-<nome>` chart name at the same 63-byte ceiling. The
7096    // canonical [`crate::lareira_chart_name`] helper's doc comment
7097    // (f7320d7, caixa-core/src/render.rs:3198) explicitly deferred:
7098    // "the M4 admission webhook will pin the joint-length invariant
7099    // when it lands". These tests pin it at the manifest-validate
7100    // layer instead, fail-before-pass-after on the 56-byte boundary.
7101
7102    #[test]
7103    fn validate_nome_chart_name_budget_accepts_canonical_template() {
7104        // Positive control: the bare `feira init`-style template's
7105        // `:nome` ("demo") sits far below the cap; the gate must not
7106        // regress this baseline. Same shape every peer
7107        // value-shape-gate baseline pin uses.
7108        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7109        c.validate_nome_chart_name_budget().unwrap();
7110    }
7111
7112    #[test]
7113    fn validate_nome_chart_name_budget_accepts_canonical_fixtures() {
7114        // Positive-set sweep across the canonical author surface every
7115        // in-tree fixture uses (`hello-rio`, `cart`, `checkout`,
7116        // `worker`, the `checkout-aplicacao` example members, the
7117        // `akeyless-attest` caixa-tatara fixture). Every value sits
7118        // far below the 55-byte per-`:nome` budget. Same shape every
7119        // peer per-axis baseline pin uses.
7120        for nome in [
7121            "hello-rio",
7122            "cart",
7123            "checkout",
7124            "worker",
7125            "akeyless-attest",
7126            "demo",
7127            "a",
7128        ] {
7129            caixa_with_nome(nome)
7130                .validate_nome_chart_name_budget()
7131                .unwrap_or_else(|e| {
7132                    panic!("canonical :nome {nome:?} must pass chart-name budget, got {e:?}")
7133                });
7134        }
7135    }
7136
7137    #[test]
7138    fn validate_nome_chart_name_budget_accepts_nome_at_cap() {
7139        // Boundary-accepting case at the 55-byte per-`:nome` budget —
7140        // the joint chart name is exactly 63 bytes, the DNS-1123 label
7141        // cap. Pinned alongside the rejecting-arm test so a future cap
7142        // shift surfaces both arms simultaneously. Mirrors
7143        // `nome_max_length_validates` on the peer bare-`:nome` axis.
7144        let at_cap = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN);
7145        caixa_with_nome(&at_cap)
7146            .validate_nome_chart_name_budget()
7147            .unwrap();
7148    }
7149
7150    #[test]
7151    fn validate_nome_chart_name_budget_rejects_nome_one_over_cap() {
7152        // Fail-before-pass-after pin on the 56-byte boundary: the
7153        // smallest `:nome` length that overflows the joint chart-name
7154        // cap. The inner [`is_dns_1123_label`] gate
7155        // (`Caixa::validate_nome`) accepts it (56 ≤ 63), so prior to
7156        // this gate it silently passed the manifest-validate cascade
7157        // and surfaced as a `helm lint` / apiserver rejection on the
7158        // rendered chart name far from the source `caixa.lisp`, with
7159        // no field naming the overflow. With this gate the diagnostic
7160        // names the offending `:nome` verbatim alongside the rendered
7161        // chart name and the budget, so the author can shorten in one
7162        // edit. Mirrors `validate_nome_rejects_too_long` on the peer
7163        // bare-`:nome` axis.
7164        let over = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
7165        let c = caixa_with_nome(&over);
7166        let err = c.validate_nome_chart_name_budget().unwrap_err();
7167        let ManifestError::NomeChartNameBudgetExceeded { nome, reason } = err else {
7168            panic!("expected NomeChartNameBudgetExceeded for over-budget :nome");
7169        };
7170        assert_eq!(nome.len(), crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
7171        assert_eq!(nome, over);
7172        assert!(
7173            reason.contains("63") && reason.contains("64") && reason.contains("55"),
7174            "diagnostic must name the DNS-1123 cap (63), the actual chart-name length (64), \
7175             and the per-`:nome` budget (55), got {reason:?}"
7176        );
7177    }
7178
7179    #[test]
7180    fn validate_nome_chart_name_budget_rejects_nome_at_bare_dns_cap() {
7181        // The 63-byte `:nome` boundary — passes the bare-`:nome`
7182        // [`is_dns_1123_label`] cap exactly, but produces a 71-byte
7183        // joint chart name that overflows the DNS-1123 label cap
7184        // structurally. The most stringent fail-before-pass-after
7185        // surface: every `:nome` in the 56..=63-byte range passed the
7186        // prior cascade and broke at admission.
7187        let bare_max = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN);
7188        let c = caixa_with_nome(&bare_max);
7189        // The bare-`:nome` gate accepts the 63-byte length.
7190        c.validate_nome().unwrap();
7191        // The new joint-length gate rejects it.
7192        let err = c.validate_nome_chart_name_budget().unwrap_err();
7193        assert!(
7194            matches!(
7195                err,
7196                ManifestError::NomeChartNameBudgetExceeded { ref nome, .. }
7197                    if nome.len() == crate::DNS_1123_LABEL_MAX_LEN
7198            ),
7199            "got {err:?}"
7200        );
7201    }
7202
7203    #[test]
7204    fn validate_nome_chart_name_budget_diagnostic_carries_offending_chart_name() {
7205        // Diagnostic-shape pin: the rendered `lareira-<nome>` chart
7206        // name appears verbatim in the diagnostic so the author sees
7207        // exactly the string the apiserver / `helm lint` would have
7208        // rejected — no re-derivation required to grep the source.
7209        // Peer with `nome_invalid_diagnostic_carries_offending_nome`
7210        // on the bare-`:nome` axis.
7211        let over = "x".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 5);
7212        let c = caixa_with_nome(&over);
7213        let err = c.validate_nome_chart_name_budget().unwrap_err();
7214        let ManifestError::NomeChartNameBudgetExceeded { nome, reason } = err else {
7215            panic!("expected NomeChartNameBudgetExceeded variant");
7216        };
7217        assert_eq!(nome, over);
7218        let expected_chart = crate::lareira_chart_name(&over);
7219        assert!(
7220            reason.contains(&expected_chart),
7221            "diagnostic must carry the rendered chart name {expected_chart:?} verbatim, \
7222             got {reason:?}"
7223        );
7224        assert!(
7225            reason.contains("lareira-"),
7226            "diagnostic must name the canonical chart-name prefix verbatim, got {reason:?}"
7227        );
7228    }
7229
7230    #[test]
7231    fn validate_nome_chart_name_budget_runs_after_nome_shape_via_layout_verify() {
7232        // Order pin on the layout cascade: the narrower
7233        // `NomeInvalid` (bare-DNS-1123 shape) fires before the
7234        // joint-length budget. A structurally-malformed `:nome` (here:
7235        // uppercase) surfaces its specific shape error rather than
7236        // the chart-name-budget error, even when the joint length
7237        // would also overflow — the narrower diagnostic is more
7238        // self-locating. Mirrors the cascade-precedence pins peer
7239        // gates already use (e.g. `EntradaParaEmpty` before
7240        // `EntradaParaInvalid`).
7241        let over = "A".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
7242        let c = caixa_with_nome(&over);
7243        // The bare-shape gate fires first.
7244        let err = c.validate_nome().unwrap_err();
7245        assert!(
7246            matches!(err, ManifestError::NomeInvalid { .. }),
7247            "bare-shape gate must fire before chart-name-budget gate; got {err:?}"
7248        );
7249        // And the layout verify cascade surfaces that diagnostic, not
7250        // the budget arm. Inject a path-exists oracle so the cascade
7251        // gets past the manifest-presence check and into the
7252        // value-shape gates.
7253        let layout = crate::StandardLayout::new().with_path_exists(|_| true);
7254        let err = crate::LayoutInvariants::verify(
7255            &layout,
7256            &c,
7257            std::path::Path::new("/tmp/caixa-test-fake-root"),
7258        )
7259        .unwrap_err();
7260        let issue = err.to_string();
7261        assert!(
7262            issue.contains("DNS-1123") || issue.contains("uppercase"),
7263            "layout cascade must surface the bare-DNS-1123 diagnostic on a \
7264             structurally-malformed :nome, not the chart-name-budget diagnostic; got {issue:?}"
7265        );
7266    }
7267
7268    #[test]
7269    fn layout_verify_routes_chart_name_budget_through_nome_violation() {
7270        // Cross-axis envelope pin: the layout cascade wraps both
7271        // bare-`:nome` and joint-length-`:nome` failures through the
7272        // same [`LayoutError::NomeViolation`] envelope, since both
7273        // arms are on the `:nome` axis. The user's diagnostic stays
7274        // self-locating ("which axis"), and a future consumer that
7275        // dispatches on the layout-error variant (e.g. a `feira lint`
7276        // exit-code mapping) sees a single per-axis envelope. The
7277        // wrapped `issue:` carries the full inner diagnostic.
7278        let over = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
7279        let c = caixa_with_nome(&over);
7280        // The bare-shape gate accepts.
7281        c.validate_nome().unwrap();
7282        let layout = crate::StandardLayout::new().with_path_exists(|_| true);
7283        let err = crate::LayoutInvariants::verify(
7284            &layout,
7285            &c,
7286            std::path::Path::new("/tmp/caixa-test-fake-root"),
7287        )
7288        .unwrap_err();
7289        let crate::LayoutError::NomeViolation { caixa, issue } = err else {
7290            panic!("expected LayoutError::NomeViolation, got {err:?}");
7291        };
7292        assert_eq!(caixa, over);
7293        assert!(
7294            issue.contains("lareira-") && issue.contains("63") && issue.contains("55"),
7295            "wrapped issue must carry the joint-length diagnostic verbatim, got {issue:?}"
7296        );
7297    }
7298
7299    // ── Caixa::validate_versao — top-level :versao value-shape gate ─────
7300
7301    fn caixa_with_versao(versao: &str) -> Caixa {
7302        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7303        c.versao = versao.to_string();
7304        c
7305    }
7306
7307    #[test]
7308    fn validate_versao_accepts_canonical_template() {
7309        // Positive control: the bare `feira init`-style template's
7310        // `:versao` ("0.1.0") is a canonical SemVer-2 literal; the gate
7311        // must not regress this baseline shape. A future tightening of
7312        // the accepted set surfaces here as a test failure first.
7313        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7314        c.validate_versao().unwrap();
7315    }
7316
7317    #[test]
7318    fn validate_versao_accepts_canonical_forms() {
7319        // Positive-set sweep: each realistic SemVer-2 shape the
7320        // substrate's downstream consumers accept must pass — bare
7321        // MAJOR.MINOR.PATCH, pre-release tags (`-rc.1`, `-alpha.0`),
7322        // build metadata (`+build.42`), the combined form, and the
7323        // `0.0.0` boundary case. Mirrors `accepts_canonical_forms` on
7324        // the peer `:nome` axis (6c992f8).
7325        for versao in [
7326            "0.1.0",
7327            "0.0.0",
7328            "1.0.0",
7329            "0.2.0-rc.1",
7330            "1.0.0-alpha.0",
7331            "1.0.0+build.42",
7332            "1.0.0-rc.1+build.42",
7333            "10.20.30",
7334        ] {
7335            caixa_with_versao(versao)
7336                .validate_versao()
7337                .unwrap_or_else(|e| {
7338                    panic!("canonical :versao {versao:?} must validate, got {e:?}")
7339                });
7340        }
7341    }
7342
7343    #[test]
7344    fn validate_versao_rejects_empty() {
7345        // Fail-before-pass-after pin: `Caixa::from_lisp` does not refuse
7346        // an empty `:versao` (the derive macro stores the raw String);
7347        // the gate's empty arm names the offending axis with a narrower
7348        // diagnostic than the `VersaoInvalid` parse arm would emit.
7349        // Mirrors `validate_nome_rejects_empty` (6c992f8).
7350        let c = caixa_with_versao("");
7351        let err = c.validate_versao().unwrap_err();
7352        assert_eq!(err, ManifestError::VersaoEmpty);
7353    }
7354
7355    #[test]
7356    fn validate_versao_rejects_git_tag_shape() {
7357        // The canonical "I copied the git tag verbatim" footgun —
7358        // `feira publish` *emits* `v<versao>` git tags, so a leaked
7359        // `v0.1.0` in `:versao` would render as `vv0.1.0` and silently
7360        // shift every downstream consumer's version axis. `semver`
7361        // rejects the leading `v` at parse time; the gate moves the
7362        // diagnostic to the source `caixa.lisp`.
7363        let c = caixa_with_versao("v0.1.0");
7364        let err = c.validate_versao().unwrap_err();
7365        let ManifestError::VersaoInvalid { versao, reason } = err else {
7366            panic!("expected VersaoInvalid for git-tag-shape :versao");
7367        };
7368        assert_eq!(versao, "v0.1.0");
7369        assert!(
7370            !reason.is_empty(),
7371            "VersaoInvalid `reason` must carry the parser's wording, got {reason:?}"
7372        );
7373    }
7374
7375    #[test]
7376    fn validate_versao_rejects_missing_patch() {
7377        // The canonical "I shortened it" footgun — SemVer-2 requires
7378        // three parts. Cargo's `version =` field accepts the shortened
7379        // form as a requirement, conflating the two leaks across the
7380        // typed `:deps :versao` vs top-level `:versao` axes; the gate
7381        // pins the top-level axis to the strict three-part shape.
7382        let c = caixa_with_versao("0.1");
7383        let err = c.validate_versao().unwrap_err();
7384        assert!(
7385            matches!(
7386                err,
7387                ManifestError::VersaoInvalid { ref versao, .. } if versao == "0.1"
7388            ),
7389            "got {err:?}"
7390        );
7391    }
7392
7393    #[test]
7394    fn validate_versao_rejects_requirement_shape() {
7395        // The canonical "I leaked a requirement into a version" footgun —
7396        // the typed `:deps :versao` / `:membros :versao` axes accept
7397        // `^0.1` (a `VersionReq`); the top-level `:versao` requires a
7398        // concrete `Version`. Without this gate the two typed surfaces
7399        // would silently overlap, and a top-level `^0.1` would surface
7400        // at `helm install` time as a Chart.yaml version rejection far
7401        // from the source `caixa.lisp`.
7402        let c = caixa_with_versao("^0.1");
7403        let err = c.validate_versao().unwrap_err();
7404        assert!(
7405            matches!(
7406                err,
7407                ManifestError::VersaoInvalid { ref versao, .. } if versao == "^0.1"
7408            ),
7409            "got {err:?}"
7410        );
7411    }
7412
7413    #[test]
7414    fn validate_versao_rejects_docker_tag_shape() {
7415        // The "I confused it with a docker tag" footgun — `latest`,
7416        // `main`, `stable` parse as identifiers, not SemVer-2 versions.
7417        // SemVer rejects at parse time; the gate moves the diagnostic
7418        // to the source `caixa.lisp`.
7419        for bad in ["latest", "main", "stable"] {
7420            let c = caixa_with_versao(bad);
7421            let err = c.validate_versao().unwrap_err();
7422            assert!(
7423                matches!(
7424                    err,
7425                    ManifestError::VersaoInvalid { ref versao, .. } if versao == bad
7426                ),
7427                "got {err:?} for {bad:?}"
7428            );
7429        }
7430    }
7431
7432    #[test]
7433    fn validate_versao_rejects_four_part_form() {
7434        // The Java/Microsoft "MAJOR.MINOR.PATCH.BUILD" convention
7435        // SemVer-2 forbids. A leak from a non-SemVer ecosystem; the
7436        // semver crate rejects the extra `.0` at parse time.
7437        let c = caixa_with_versao("0.1.0.0");
7438        let err = c.validate_versao().unwrap_err();
7439        assert!(
7440            matches!(
7441                err,
7442                ManifestError::VersaoInvalid { ref versao, .. } if versao == "0.1.0.0"
7443            ),
7444            "got {err:?}"
7445        );
7446    }
7447
7448    #[test]
7449    fn versao_empty_takes_precedence_over_invalid() {
7450        // Order pin: the empty arm fires before the parser is consulted.
7451        // Empty < invalid in self-locating-ness — the narrower
7452        // `VersaoEmpty` diagnostic doesn't carry a useless `versao: ""`
7453        // reference into the parser-shaped reason. Mirrors
7454        // `nome_empty_takes_precedence_over_invalid` (6c992f8) on the
7455        // peer axis.
7456        let c = caixa_with_versao("");
7457        assert_eq!(c.validate_versao().unwrap_err(), ManifestError::VersaoEmpty);
7458    }
7459
7460    #[test]
7461    fn versao_invalid_diagnostic_carries_offending_versao() {
7462        // Diagnostic-shape pin: the error names the offending `:versao`
7463        // verbatim with a non-empty parser-shaped reason, so a `feira
7464        // lint` run can render the diagnostic without re-parsing.
7465        // Mirrors `nome_invalid_diagnostic_carries_offending_nome`.
7466        let c = caixa_with_versao("v0.1.0");
7467        let err = c.validate_versao().unwrap_err();
7468        let ManifestError::VersaoInvalid { versao, reason } = err else {
7469            panic!("expected VersaoInvalid variant");
7470        };
7471        assert_eq!(versao, "v0.1.0");
7472        assert!(
7473            !reason.is_empty(),
7474            "VersaoInvalid `reason` must carry the parser's wording verbatim"
7475        );
7476    }
7477
7478    #[test]
7479    fn validate_versao_accepts_what_upgrade_from_from_accepts() {
7480        // Parity pin: every shape `UpgradeFromEntry::validate` accepts
7481        // for `:upgrade-from :from` must also pass `validate_versao` —
7482        // the two `:versao`-typed surfaces (top-level `:versao`,
7483        // `:upgrade-from :from`) consume the *same* `semver::Version`
7484        // parser, so they must agree on the accepted set. Without this
7485        // pin, a future tightening of one axis could silently diverge
7486        // from the other. Mirrors the `:versao` requirement-axis
7487        // parity (`:deps`/`:deps-dev`/`:membros`/`:children`) the prior
7488        // commits established.
7489        for versao in ["0.1.0", "0.2.0-rc.1", "1.0.0+build.42"] {
7490            // From the canonical UpgradeFromEntry round-trip fixture
7491            // (`upgrade::tests::round_trip_load_module` peers).
7492            let entry = crate::UpgradeFromEntry {
7493                from: versao.to_string(),
7494                instructions: Vec::new(),
7495            };
7496            entry
7497                .validate()
7498                .unwrap_or_else(|e| panic!(":from {versao:?} must validate, got {e:?}"));
7499            caixa_with_versao(versao)
7500                .validate_versao()
7501                .unwrap_or_else(|e| {
7502                    panic!(":versao {versao:?} must validate, got {e:?} — peer axis diverges")
7503                });
7504        }
7505    }
7506
7507    // ── Caixa::validate_restart_window — supervisor restart-window
7508    //    folds through the shared `supervisor::duration_codec` ────────
7509
7510    fn caixa_with_restart_window(window: Option<&str>) -> Caixa {
7511        let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
7512        c.kind = CaixaKind::Supervisor;
7513        c.restart_window = window.map(str::to_string);
7514        c
7515    }
7516
7517    #[test]
7518    fn validate_restart_window_accepts_none() {
7519        // The canonical "omit the slot to express no reset" shape — a
7520        // `None` raw string is the absence of the typed
7521        // `:restart-window` slot, which is exactly the SupervisorSpec
7522        // "never reset" semantics. The gate must be a no-op here; a
7523        // future tightening that rejected `None` would force every
7524        // supervisor caixa to authoring-time pin a window even when
7525        // the OTP semantics call for none.
7526        caixa_with_restart_window(None)
7527            .validate_restart_window()
7528            .unwrap();
7529    }
7530
7531    #[test]
7532    fn validate_restart_window_accepts_canonical_forms() {
7533        // Positive-set sweep across the canonical authoring units the
7534        // shared `supervisor::duration_codec::parse` accepts —
7535        // matches the codec-side `parse_accepts_integer_canonical_units`
7536        // pin in supervisor::tests so a future codec-side tightening
7537        // surfaces simultaneously on both axes.
7538        for window in ["60s", "5m", "1h", "500ms", "30", "0s"] {
7539            caixa_with_restart_window(Some(window))
7540                .validate_restart_window()
7541                .unwrap_or_else(|e| {
7542                    panic!("canonical :restart-window {window:?} must validate, got {e:?}")
7543                });
7544        }
7545    }
7546
7547    #[test]
7548    fn validate_restart_window_rejects_fractional_seconds() {
7549        // Fail-before-pass-after pin: the `"1.5s"` drift class (parses
7550        // as f64 to 1.5 → renders back as `"1500ms"` on first
7551        // serialize). Prior to the fold + this gate, the inline
7552        // `parse_window_inline` accepted f64 magnitudes and silently
7553        // produced a `Duration::from_secs_f64(1.5)`, divergent from
7554        // the shared codec's integer-magnitude discipline on the
7555        // serde-routed siblings. The gate now surfaces a self-locating
7556        // diagnostic at the manifest layer.
7557        let err = caixa_with_restart_window(Some("1.5s"))
7558            .validate_restart_window()
7559            .unwrap_err();
7560        let ManifestError::RestartWindowMalformed {
7561            restart_window,
7562            reason,
7563        } = err
7564        else {
7565            panic!("expected RestartWindowMalformed for fractional seconds");
7566        };
7567        assert_eq!(restart_window, "1.5s");
7568        assert!(
7569            reason.contains("\"1.5\"") && reason.contains("not a non-negative integer"),
7570            "diagnostic must carry shared-codec wording, got {reason:?}"
7571        );
7572    }
7573
7574    #[test]
7575    fn validate_restart_window_rejects_decimal_shaped_integer() {
7576        // The `"1.0s"` class — numerically `1s` exactly, but the
7577        // canonical form is `"1s"` not `"1.0s"`. Decimal-shape leak
7578        // gets the same canonical-form diagnostic.
7579        let err = caixa_with_restart_window(Some("1.0s"))
7580            .validate_restart_window()
7581            .unwrap_err();
7582        assert!(
7583            matches!(
7584                err,
7585                ManifestError::RestartWindowMalformed { ref restart_window, .. }
7586                    if restart_window == "1.0s"
7587            ),
7588            "got {err:?}"
7589        );
7590    }
7591
7592    #[test]
7593    fn validate_restart_window_rejects_half_unit_minute() {
7594        // `"0.5m"` is the unit-fraction footgun — author writes a
7595        // human-readable half-minute, the prior inline parser silently
7596        // produced `Duration::from_secs_f64(30.0)` and serde
7597        // re-emitted as `"30s"`, rewriting author intent. The gate
7598        // closes the loop at the manifest layer.
7599        let err = caixa_with_restart_window(Some("0.5m"))
7600            .validate_restart_window()
7601            .unwrap_err();
7602        let ManifestError::RestartWindowMalformed {
7603            restart_window,
7604            reason,
7605        } = err
7606        else {
7607            panic!("expected RestartWindowMalformed");
7608        };
7609        assert_eq!(restart_window, "0.5m");
7610        assert!(
7611            reason.contains("\"30s\""),
7612            "diagnostic must point at the canonical-form remediation, got {reason:?}"
7613        );
7614    }
7615
7616    #[test]
7617    fn validate_restart_window_rejects_leading_sign() {
7618        // `"+30s"` and `"-30s"` both round-tripped through f64 cleanly
7619        // on the prior parser (`+30` parses as `30.0`; `-30` parsed
7620        // and was caught by the `num < 0.0` arm which silently
7621        // returned `None`, dropping the author-supplied window). The
7622        // shared codec's digit-only gate rejects both with a unified
7623        // canonical-form diagnostic; the manifest-layer wrapper names
7624        // the offending value.
7625        for bad in ["+30s", "-30s"] {
7626            let err = caixa_with_restart_window(Some(bad))
7627                .validate_restart_window()
7628                .unwrap_err();
7629            assert!(
7630                matches!(
7631                    err,
7632                    ManifestError::RestartWindowMalformed { ref restart_window, .. }
7633                        if restart_window == bad
7634                ),
7635                "got {err:?} for {bad:?}"
7636            );
7637        }
7638    }
7639
7640    #[test]
7641    fn validate_restart_window_rejects_unknown_unit() {
7642        // `"30x"` — the typo / wrong-unit footgun. The shared codec's
7643        // unit dispatch surfaces an `unknown duration unit` reason;
7644        // the manifest-layer wrapper names the offending value.
7645        let err = caixa_with_restart_window(Some("30x"))
7646            .validate_restart_window()
7647            .unwrap_err();
7648        let ManifestError::RestartWindowMalformed {
7649            restart_window,
7650            reason,
7651        } = err
7652        else {
7653            panic!("expected RestartWindowMalformed for unknown unit");
7654        };
7655        assert_eq!(restart_window, "30x");
7656        assert!(
7657            reason.contains("unknown duration unit"),
7658            "diagnostic must carry shared-codec unit-rejection wording, got {reason:?}"
7659        );
7660    }
7661
7662    #[test]
7663    fn validate_restart_window_rejects_garbage() {
7664        // Pure non-numeric magnitude (`"abc"`) falls through to the
7665        // shared codec's narrower `"bad duration magnitude"` arm. Same
7666        // diagnostic shape as the codec-side
7667        // `parse_garbage_still_falls_through_to_bad_magnitude` pin.
7668        let err = caixa_with_restart_window(Some("abc"))
7669            .validate_restart_window()
7670            .unwrap_err();
7671        let ManifestError::RestartWindowMalformed {
7672            restart_window,
7673            reason,
7674        } = err
7675        else {
7676            panic!("expected RestartWindowMalformed for garbage");
7677        };
7678        assert_eq!(restart_window, "abc");
7679        assert!(
7680            reason.contains("bad duration magnitude"),
7681            "diagnostic must carry shared-codec garbage-rejection wording, got {reason:?}"
7682        );
7683    }
7684
7685    #[test]
7686    fn validate_restart_window_rejects_empty_string() {
7687        // The empty-after-trim edge case — distinct from the `None`
7688        // canonical "omit the slot" shape. The shared codec's
7689        // digit-only gate refuses an empty magnitude; the manifest
7690        // layer names the offending `""` so the author can grep for
7691        // the literal empty value in their `caixa.lisp` and either
7692        // remove the slot (the canonical "no reset" shape) or pin a
7693        // positive duration.
7694        let err = caixa_with_restart_window(Some(""))
7695            .validate_restart_window()
7696            .unwrap_err();
7697        assert!(
7698            matches!(
7699                err,
7700                ManifestError::RestartWindowMalformed { ref restart_window, .. }
7701                    if restart_window.is_empty()
7702            ),
7703            "got {err:?}"
7704        );
7705    }
7706
7707    #[test]
7708    fn validate_restart_window_diagnostic_carries_offending_value() {
7709        // Diagnostic-shape pin (peer with
7710        // `nome_invalid_diagnostic_carries_offending_nome` /
7711        // `versao_invalid_diagnostic_carries_offending_versao`): the
7712        // error names the offending raw `:restart-window` verbatim
7713        // with a non-empty shared-codec-shaped reason, so a `feira
7714        // lint` run can render the diagnostic without re-parsing.
7715        let err = caixa_with_restart_window(Some("1.5s"))
7716            .validate_restart_window()
7717            .unwrap_err();
7718        let ManifestError::RestartWindowMalformed {
7719            restart_window,
7720            reason,
7721        } = err
7722        else {
7723            panic!("expected RestartWindowMalformed variant");
7724        };
7725        assert_eq!(restart_window, "1.5s");
7726        assert!(
7727            !reason.is_empty(),
7728            "RestartWindowMalformed `reason` must carry the codec's wording verbatim"
7729        );
7730    }
7731
7732    #[test]
7733    fn supervisor_view_folds_through_shared_codec_on_canonical_form() {
7734        // Behavioral parity pin after the fold (`parse_window_inline`
7735        // deletion): the canonical `"60s"` still produces
7736        // `Duration::from_secs(60)` on the typed view — the fold is
7737        // semantically equivalent to the prior inline parser on the
7738        // accepted set. Mirrors the pre-fold `supervisor_view_returns_typed_shape`
7739        // pin, narrowed to the parser-side contract.
7740        let c = caixa_with_restart_window(Some("60s"));
7741        let view = c.supervisor_view().expect("Supervisor kind has a view");
7742        assert_eq!(
7743            view.restart_window,
7744            Some(std::time::Duration::from_secs(60))
7745        );
7746    }
7747
7748    #[test]
7749    fn supervisor_view_soft_swallows_what_validate_rejects() {
7750        // Parity pin between the view-construction path and the
7751        // manifest-level validator: the same `"1.5s"` that surfaces
7752        // `RestartWindowMalformed` at `validate_restart_window` time
7753        // becomes `restart_window: None` on the typed view (the fold
7754        // preserves the existing best-effort shape of `supervisor_view`).
7755        // The contract is: a layout-verifier / `feira lint` flow that
7756        // cares about the malformed-window axis MUST consult
7757        // `validate_restart_window` — relying solely on the view's
7758        // `None` swallows the diagnostic silently. This pin makes the
7759        // expectation a typed invariant.
7760        let c = caixa_with_restart_window(Some("1.5s"));
7761        let view = c.supervisor_view().expect("Supervisor kind has a view");
7762        assert_eq!(
7763            view.restart_window, None,
7764            "view-construction path soft-swallows the parse error to None"
7765        );
7766        // And the manifest-level validator does NOT soft-swallow:
7767        assert!(
7768            matches!(
7769                c.validate_restart_window().unwrap_err(),
7770                ManifestError::RestartWindowMalformed { ref restart_window, .. }
7771                    if restart_window == "1.5s"
7772            ),
7773            "validator must surface the offending value",
7774        );
7775    }
7776
7777    // ── validate_code_paths — per-entry shape on :bibliotecas / :exe / :servicos ──
7778
7779    fn caixa_with_code_paths(bibliotecas: Vec<&str>, exe: Vec<&str>, servicos: Vec<&str>) -> Caixa {
7780        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7781        c.bibliotecas = bibliotecas.into_iter().map(String::from).collect();
7782        c.exe = exe.into_iter().map(String::from).collect();
7783        c.servicos = servicos.into_iter().map(String::from).collect();
7784        c
7785    }
7786
7787    #[test]
7788    fn validate_code_paths_accepts_canonical_template() {
7789        // The bare `Caixa::template` shape is the gate's identity element
7790        // on the canonical authoring shape — `:bibliotecas
7791        // ("lib/demo.lisp")` + empty `:exe` + empty `:servicos`. Pins
7792        // that the gate is non-disruptive against every existing caixa.
7793        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7794        c.validate_code_paths().unwrap();
7795    }
7796
7797    #[test]
7798    fn validate_code_paths_accepts_explicit_relative_paths_on_every_slot() {
7799        // Positive control sweep: a canonical-shaped path on every slot
7800        // passes. Mirrors the peer
7801        // `behavior::validate_every_slot_relative_is_ok` pin.
7802        let c = caixa_with_code_paths(
7803            vec!["lib/demo.lisp", "lib/helpers.lisp"],
7804            vec!["exe/demo", "exe/tool"],
7805            vec!["servicos/demo.computeunit.yaml"],
7806        );
7807        c.validate_code_paths().unwrap();
7808    }
7809
7810    #[test]
7811    fn validate_code_paths_accepts_all_empty_lists() {
7812        // The empty-list identity element: every Caixa with no declared
7813        // code paths trivially passes (Supervisor / Aplicacao kinds rely
7814        // on this — the OwnCode gate already rejected them before the
7815        // path-shape gate runs in the layout, but the validator itself
7816        // must accept the empty shape).
7817        let c = caixa_with_code_paths(vec![], vec![], vec![]);
7818        c.validate_code_paths().unwrap();
7819    }
7820
7821    #[test]
7822    fn validate_code_paths_rejects_empty_bibliotecas_entry() {
7823        let c = caixa_with_code_paths(vec![""], vec![], vec![]);
7824        let err = c.validate_code_paths().unwrap_err();
7825        assert!(
7826            matches!(
7827                err,
7828                ManifestError::CodePathEmpty {
7829                    slot: ":bibliotecas"
7830                }
7831            ),
7832            "got {err:?}",
7833        );
7834    }
7835
7836    #[test]
7837    fn validate_code_paths_rejects_empty_exe_entry() {
7838        let c = caixa_with_code_paths(vec![], vec![""], vec![]);
7839        let err = c.validate_code_paths().unwrap_err();
7840        assert!(
7841            matches!(err, ManifestError::CodePathEmpty { slot: ":exe" }),
7842            "got {err:?}",
7843        );
7844    }
7845
7846    #[test]
7847    fn validate_code_paths_rejects_empty_servicos_entry() {
7848        let c = caixa_with_code_paths(vec![], vec![], vec![""]);
7849        let err = c.validate_code_paths().unwrap_err();
7850        assert!(
7851            matches!(err, ManifestError::CodePathEmpty { slot: ":servicos" }),
7852            "got {err:?}",
7853        );
7854    }
7855
7856    #[test]
7857    fn validate_code_paths_rejects_absolute_bibliotecas_entry() {
7858        // `:bibliotecas` has no `starts_with(<dir>)` fence downstream,
7859        // so an absolute path that resolves on disk silently passes the
7860        // layout's existence check — the canonical sandbox-escape on
7861        // the biblioteca axis.
7862        let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
7863        let err = c.validate_code_paths().unwrap_err();
7864        let ManifestError::CodePathAbsolute { slot, path } = err else {
7865            panic!("expected CodePathAbsolute, got {err:?}");
7866        };
7867        assert_eq!(slot, ":bibliotecas");
7868        assert_eq!(path, PathBuf::from("/etc/passwd"));
7869    }
7870
7871    #[test]
7872    fn validate_code_paths_rejects_absolute_exe_entry() {
7873        let c = caixa_with_code_paths(vec![], vec!["/usr/bin/env"], vec![]);
7874        let err = c.validate_code_paths().unwrap_err();
7875        let ManifestError::CodePathAbsolute { slot, path } = err else {
7876            panic!("expected CodePathAbsolute, got {err:?}");
7877        };
7878        assert_eq!(slot, ":exe");
7879        assert_eq!(path, PathBuf::from("/usr/bin/env"));
7880    }
7881
7882    #[test]
7883    fn validate_code_paths_rejects_absolute_servicos_entry() {
7884        let c = caixa_with_code_paths(vec![], vec![], vec!["/var/servicos/x.yaml"]);
7885        let err = c.validate_code_paths().unwrap_err();
7886        let ManifestError::CodePathAbsolute { slot, path } = err else {
7887            panic!("expected CodePathAbsolute, got {err:?}");
7888        };
7889        assert_eq!(slot, ":servicos");
7890        assert_eq!(path, PathBuf::from("/var/servicos/x.yaml"));
7891    }
7892
7893    #[test]
7894    fn validate_code_paths_rejects_parent_escape_bibliotecas_leading() {
7895        // Canonical "I want a lib from a sibling caixa" footgun on the
7896        // biblioteca axis. `:bibliotecas` has no `starts_with` fence
7897        // downstream, so a leading `..` traverses to the parent of the
7898        // caixa root with no diagnostic at layout time if the resolved
7899        // target exists.
7900        let c = caixa_with_code_paths(vec!["../sibling/x.lisp"], vec![], vec![]);
7901        let err = c.validate_code_paths().unwrap_err();
7902        let ManifestError::CodePathParentEscape { slot, path } = err else {
7903            panic!("expected CodePathParentEscape, got {err:?}");
7904        };
7905        assert_eq!(slot, ":bibliotecas");
7906        assert_eq!(path, PathBuf::from("../sibling/x.lisp"));
7907    }
7908
7909    #[test]
7910    fn validate_code_paths_rejects_parent_escape_exe_mid_path() {
7911        // Mid-path `..` defeats the layout's component-aware
7912        // `starts_with(exe_dir)` fence — `root.join("exe/../../escape")`
7913        // `starts_with(<root>/exe)` is true, but the canonical resolution
7914        // lives outside the caixa root. Caught regardless of where the
7915        // `..` sits — mirrors the peer
7916        // `behavior::validate_rejects_parent_escape_mid_path` pin.
7917        let c = caixa_with_code_paths(vec![], vec!["exe/../../escape"], vec![]);
7918        let err = c.validate_code_paths().unwrap_err();
7919        let ManifestError::CodePathParentEscape { slot, path } = err else {
7920            panic!("expected CodePathParentEscape, got {err:?}");
7921        };
7922        assert_eq!(slot, ":exe");
7923        assert_eq!(path, PathBuf::from("exe/../../escape"));
7924    }
7925
7926    #[test]
7927    fn validate_code_paths_rejects_parent_escape_servicos_trailing() {
7928        let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/foo/../../escape.yaml"]);
7929        let err = c.validate_code_paths().unwrap_err();
7930        let ManifestError::CodePathParentEscape { slot, path } = err else {
7931            panic!("expected CodePathParentEscape, got {err:?}");
7932        };
7933        assert_eq!(slot, ":servicos");
7934        assert_eq!(path, PathBuf::from("servicos/foo/../../escape.yaml"));
7935    }
7936
7937    #[test]
7938    fn validate_code_paths_cross_slot_precedence_bibliotecas_before_exe_before_servicos() {
7939        // Cross-slot precedence pin: `:bibliotecas` → `:exe` →
7940        // `:servicos`. A manifest with malformed entries on all three
7941        // surfaces surfaces the `:bibliotecas` defect first, mirroring
7942        // the canonical declaration order
7943        // `Caixa::declared_foreign_code_slots` already establishes for
7944        // the foreign-code-slot diagnostic.
7945        let c = caixa_with_code_paths(vec![""], vec![""], vec![""]);
7946        let err = c.validate_code_paths().unwrap_err();
7947        assert!(
7948            matches!(
7949                err,
7950                ManifestError::CodePathEmpty {
7951                    slot: ":bibliotecas"
7952                }
7953            ),
7954            "got {err:?}",
7955        );
7956    }
7957
7958    #[test]
7959    fn validate_code_paths_within_slot_precedence_empty_before_absolute_before_parent_escape() {
7960        // Within-slot precedence pin: empty → absolute → parent-escape,
7961        // matching the [`PathShapeViolation`] arm-ordering every peer
7962        // `is_sandboxed_relative_path` caller follows (b0c8389
7963        // BehaviorSpec, 26da2c7 UpgradeInstruction::StateChange). A
7964        // `:bibliotecas` list whose first entry is empty *and* whose
7965        // later entries are absolute/parent-escape surfaces the empty
7966        // arm first, on the lexicographically-earliest offending entry.
7967        let c = caixa_with_code_paths(vec!["", "/etc/passwd", "../escape.lisp"], vec![], vec![]);
7968        let err = c.validate_code_paths().unwrap_err();
7969        assert!(
7970            matches!(
7971                err,
7972                ManifestError::CodePathEmpty {
7973                    slot: ":bibliotecas"
7974                }
7975            ),
7976            "got {err:?}",
7977        );
7978    }
7979
7980    #[test]
7981    fn validate_code_paths_first_offender_per_slot_wins() {
7982        // Within a single slot, the first declaration-order offender
7983        // surfaces — pins that the gate is left-to-right deterministic
7984        // (peer of every `*_first_collision_*` pin on duplicate gates).
7985        let c = caixa_with_code_paths(
7986            vec!["lib/ok.lisp", "/etc/escape", "../also-escape"],
7987            vec![],
7988            vec![],
7989        );
7990        let err = c.validate_code_paths().unwrap_err();
7991        let ManifestError::CodePathAbsolute { slot, path } = err else {
7992            panic!("expected CodePathAbsolute, got {err:?}");
7993        };
7994        assert_eq!(slot, ":bibliotecas");
7995        assert_eq!(path, PathBuf::from("/etc/escape"));
7996    }
7997
7998    #[test]
7999    fn validate_code_paths_diagnostic_carries_offending_slot_and_path() {
8000        // Diagnostic-shape pin (peer with
8001        // `nome_invalid_diagnostic_carries_offending_nome` /
8002        // `versao_invalid_diagnostic_carries_offending_versao`): the
8003        // error's Display surfaces both the offending `:slot` tag and
8004        // the offending path verbatim, so a `feira lint` run can render
8005        // the diagnostic without re-parsing.
8006        let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
8007        let rendered = c.validate_code_paths().unwrap_err().to_string();
8008        assert!(
8009            rendered.contains(":bibliotecas"),
8010            "diagnostic must name the offending slot: {rendered}",
8011        );
8012        assert!(
8013            rendered.contains("/etc/passwd"),
8014            "diagnostic must quote the offending path: {rendered}",
8015        );
8016    }
8017
8018    #[test]
8019    fn validate_code_paths_rejects_duplicate_bibliotecas_entry() {
8020        // Canonical copy-paste-the-wrong-file footgun on the biblioteca
8021        // axis. Without the gate `feira build` re-parses the same lib
8022        // twice, wasting work and silently masking the author's intent
8023        // to declare a *second* biblioteca.
8024        let c = caixa_with_code_paths(vec!["lib/demo.lisp", "lib/demo.lisp"], vec![], vec![]);
8025        let err = c.validate_code_paths().unwrap_err();
8026        let ManifestError::CodePathDuplicate { slot, path } = err else {
8027            panic!("expected CodePathDuplicate, got {err:?}");
8028        };
8029        assert_eq!(slot, ":bibliotecas");
8030        assert_eq!(path, PathBuf::from("lib/demo.lisp"));
8031    }
8032
8033    #[test]
8034    fn validate_code_paths_rejects_duplicate_exe_entry() {
8035        // Same footgun on the Binario surface. The future `caixa-flake`
8036        // emitter that materializes each `:exe` entry as a flake
8037        // `packages.<name>` derivation would collide on the duplicate
8038        // package key — surfaced here at the typed-validate layer with a
8039        // self-locating diagnostic instead.
8040        let c = caixa_with_code_paths(vec![], vec!["exe/cli", "exe/cli"], vec![]);
8041        let err = c.validate_code_paths().unwrap_err();
8042        let ManifestError::CodePathDuplicate { slot, path } = err else {
8043            panic!("expected CodePathDuplicate, got {err:?}");
8044        };
8045        assert_eq!(slot, ":exe");
8046        assert_eq!(path, PathBuf::from("exe/cli"));
8047    }
8048
8049    #[test]
8050    fn validate_code_paths_rejects_duplicate_servicos_entry() {
8051        // Same footgun on the Servico surface. The peer caixa-helm /
8052        // caixa-flux renderers refuse `:servicos.len() != 1` with the
8053        // narrower `UnsupportedServicoCount` diagnostic, but that
8054        // diagnostic surfaces "too many servicos" without naming
8055        // "duplicate entry" — the typed self-locating framing only lands
8056        // at this gate.
8057        let c = caixa_with_code_paths(
8058            vec![],
8059            vec![],
8060            vec![
8061                "servicos/demo.computeunit.yaml",
8062                "servicos/demo.computeunit.yaml",
8063            ],
8064        );
8065        let err = c.validate_code_paths().unwrap_err();
8066        let ManifestError::CodePathDuplicate { slot, path } = err else {
8067            panic!("expected CodePathDuplicate, got {err:?}");
8068        };
8069        assert_eq!(slot, ":servicos");
8070        assert_eq!(path, PathBuf::from("servicos/demo.computeunit.yaml"));
8071    }
8072
8073    #[test]
8074    fn validate_code_paths_accepts_same_path_across_slots() {
8075        // Per-list scope pin: a `:bibliotecas` entry that happens to
8076        // collide with an `:exe` or `:servicos` entry as a *string* is
8077        // not a duplicate by this gate (each list gets its own HashSet),
8078        // mirroring the peer `:deps` ↔ `:deps-dev` per-list scope
8079        // (a `:nome` present in both lists is a legitimate dev-vs-runtime
8080        // shape on the dep axis). The structural `starts_with(<exe |
8081        // servicos>_dir)` fence at layout time prevents the realistic
8082        // cross-slot collision case from existing on disk, but the gate's
8083        // per-list scope is correct independent of that downstream fence.
8084        let c = caixa_with_code_paths(
8085            vec!["lib/x.lisp"],
8086            vec!["exe/x"],
8087            vec!["servicos/x.computeunit.yaml"],
8088        );
8089        c.validate_code_paths().unwrap();
8090    }
8091
8092    #[test]
8093    fn validate_code_paths_duplicate_fires_after_structural_checks_on_same_slot() {
8094        // Within-slot ordering pin: structural defects (empty / absolute
8095        // / parent-escape) fire before the duplicate gate on the same
8096        // slot. A `:bibliotecas ("" "lib/x.lisp" "lib/x.lisp")` shape
8097        // surfaces the narrower `CodePathEmpty` for the empty entry
8098        // first, not the duplicate on the later pair — same arm-ordering
8099        // every peer per-list duplicate gate uses (`:etiquetas` 360a499,
8100        // `:autores` 86c769b, `:deps` 359fba5).
8101        let c = caixa_with_code_paths(vec!["", "lib/x.lisp", "lib/x.lisp"], vec![], vec![]);
8102        let err = c.validate_code_paths().unwrap_err();
8103        assert!(
8104            matches!(
8105                err,
8106                ManifestError::CodePathEmpty {
8107                    slot: ":bibliotecas"
8108                }
8109            ),
8110            "got {err:?}",
8111        );
8112    }
8113
8114    #[test]
8115    fn validate_code_paths_duplicate_in_bibliotecas_fires_before_duplicate_in_exe() {
8116        // Cross-slot ordering pin on the duplicate arm: `:bibliotecas`
8117        // duplicates surface before `:exe` duplicates, matching the
8118        // canonical `:bibliotecas` → `:exe` → `:servicos` declaration
8119        // order every peer per-slot diagnostic on this surface follows.
8120        let c = caixa_with_code_paths(
8121            vec!["lib/x.lisp", "lib/x.lisp"],
8122            vec!["exe/y", "exe/y"],
8123            vec![],
8124        );
8125        let err = c.validate_code_paths().unwrap_err();
8126        let ManifestError::CodePathDuplicate { slot, path } = err else {
8127            panic!("expected CodePathDuplicate, got {err:?}");
8128        };
8129        assert_eq!(slot, ":bibliotecas");
8130        assert_eq!(path, PathBuf::from("lib/x.lisp"));
8131    }
8132
8133    #[test]
8134    fn validate_code_paths_duplicate_diagnostic_carries_offending_slot_and_path() {
8135        // Diagnostic-shape pin (peer with
8136        // `validate_code_paths_diagnostic_carries_offending_slot_and_path`
8137        // on the structural arm): the duplicate-arm Display surfaces both
8138        // the offending `:slot` tag and the offending path verbatim, so a
8139        // `feira lint` run can render the diagnostic without re-parsing.
8140        let c = caixa_with_code_paths(
8141            vec![],
8142            vec![],
8143            vec![
8144                "servicos/demo.computeunit.yaml",
8145                "servicos/demo.computeunit.yaml",
8146            ],
8147        );
8148        let rendered = c.validate_code_paths().unwrap_err().to_string();
8149        assert!(
8150            rendered.contains(":servicos"),
8151            "diagnostic must name the offending slot: {rendered}",
8152        );
8153        assert!(
8154            rendered.contains("servicos/demo.computeunit.yaml"),
8155            "diagnostic must quote the offending path: {rendered}",
8156        );
8157    }
8158
8159    // ── validate_code_paths — `.lisp` extension gate on :bibliotecas ──
8160    //
8161    // The lifted [`crate::render::is_lisp_extension`] predicate (33cc830)
8162    // now gates `:bibliotecas` entries on the tatara-lisp-source file-type
8163    // contract. The `feira build` loop (`caixa-feira/src/cmd/build.rs:33`)
8164    // reads every declared `:bibliotecas` entry through `tatara_lisp::read`
8165    // at parse time — the same downstream consumer the peer `:behavior
8166    // :on-*` (c97815a, [`crate::BehaviorError::NonLispExtension`]) and
8167    // `:upgrade-from :state-change :script` (33cc830,
8168    // [`crate::UpgradeError::NonLispExtensionScript`]) axes route through.
8169    // `:exe` and `:servicos` are deliberately excluded — `:exe` is the
8170    // nix-built executable surface (`"exe/<name>"` shape per the canonical
8171    // [`crate::LayoutError::ExeOutsideDir`] error message and every
8172    // in-tree `caixa_with_code_paths` positive control), and `:servicos`
8173    // is the `.computeunit.yaml` ComputeUnit-CR axis.
8174
8175    #[test]
8176    fn validate_code_paths_rejects_no_extension_bibliotecas_entry() {
8177        // Canonical "I dragged the wrong file from the workspace tree"
8178        // footgun on the biblioteca axis. Without the gate `feira build`
8179        // hands the extensionless path to `tatara_lisp::read` and fails
8180        // with a parser-shaped diagnostic far from the source caixa.lisp,
8181        // with no field naming the offending `:bibliotecas` entry.
8182        for relpath in ["lib/demo", "demo", "lib/handlers/inner"] {
8183            let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
8184            let err = c.validate_code_paths().unwrap_err();
8185            let ManifestError::CodePathNonLispExtension { slot, path } = err else {
8186                panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
8187            };
8188            assert_eq!(slot, ":bibliotecas");
8189            assert_eq!(path, PathBuf::from(relpath));
8190        }
8191    }
8192
8193    #[test]
8194    fn validate_code_paths_rejects_wrong_extension_bibliotecas_entry() {
8195        // Wrong-extension sweep across common authoring footguns. Same
8196        // sweep posture as the peer
8197        // `behavior::validate_rejects_wrong_extension` (c97815a) and
8198        // `upgrade::tests::state_change_rejects_wrong_extension_script`
8199        // (33cc830) cases.
8200        for relpath in [
8201            "lib/demo.rs",
8202            "lib/demo.txt",
8203            "lib/demo.md",
8204            "lib/demo.json",
8205            "lib/demo.yaml",
8206            "lib/demo.toml",
8207            "lib/demo.lisp.bak",
8208            "lib/demo.lispx",
8209            "lib/demo.lis",
8210        ] {
8211            let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
8212            let err = c.validate_code_paths().unwrap_err();
8213            let ManifestError::CodePathNonLispExtension { slot, path } = err else {
8214                panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
8215            };
8216            assert_eq!(slot, ":bibliotecas");
8217            assert_eq!(path, PathBuf::from(relpath));
8218        }
8219    }
8220
8221    #[test]
8222    fn validate_code_paths_rejects_case_folded_extension_bibliotecas_entry() {
8223        // Case-sensitivity sweep — pins the strict lowercase `.lisp`
8224        // contract. An uppercase `.LISP` shape that the layout's existence
8225        // check would (case-insensitively, on case-insensitive volumes)
8226        // match the on-disk file still mismatches the canonical form the
8227        // codec emits, breaking the THEORY.md §V.2.7 render-determinism
8228        // contract. Mirrors the peer
8229        // `behavior::validate_rejects_case_folded_extension` (c97815a) and
8230        // `upgrade::tests::state_change_rejects_case_folded_extension_script`
8231        // (33cc830) sweeps.
8232        for relpath in [
8233            "lib/demo.LISP",
8234            "lib/demo.Lisp",
8235            "lib/demo.LiSp",
8236            "lib/demo.lISP",
8237        ] {
8238            let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
8239            let err = c.validate_code_paths().unwrap_err();
8240            let ManifestError::CodePathNonLispExtension { slot, path } = err else {
8241                panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
8242            };
8243            assert_eq!(slot, ":bibliotecas");
8244            assert_eq!(path, PathBuf::from(relpath));
8245        }
8246    }
8247
8248    #[test]
8249    fn validate_code_paths_accepts_canonical_lisp_shapes() {
8250        // Positive-control sweep through every canonical authoring shape
8251        // every in-tree fixture and the `Caixa::template` scaffold use.
8252        // Mirrors the peer `behavior::validate_accepts_canonical_lisp_paths`
8253        // (c97815a) and the lifted predicate's own
8254        // `is_lisp_extension_accepts_canonical_shapes` sweep in render.rs
8255        // (33cc830).
8256        for relpath in [
8257            "lib/demo.lisp",
8258            "lib/handlers.lisp",
8259            "lib/migrations/v01-to-v02.lisp",
8260            "demo.lisp",
8261            "a.lisp",
8262            "./lib/demo.lisp",
8263            "lib/./handlers.lisp",
8264            "lib/migrations/v.0.1.lisp",
8265        ] {
8266            let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
8267            c.validate_code_paths()
8268                .unwrap_or_else(|e| panic!("canonical shape {relpath:?} must pass, got {e:?}"));
8269        }
8270    }
8271
8272    #[test]
8273    fn validate_code_paths_non_lisp_extension_does_not_fire_on_exe_or_servicos() {
8274        // The file-type gate is per-slot — only `:bibliotecas` carries the
8275        // tatara-lisp-source contract. An extensionless `:exe` entry
8276        // (`exe/demo`) and a `.computeunit.yaml` `:servicos` entry are the
8277        // canonical shapes every in-tree fixture uses, and must continue
8278        // to pass validate. Pins that a future tightening that broadens
8279        // the `.lisp` gate to either axis surfaces as a test failure
8280        // rather than as a silent breaking change to existing valid
8281        // manifests.
8282        let c = caixa_with_code_paths(
8283            vec![],
8284            vec!["exe/demo", "exe/tool"],
8285            vec!["servicos/demo.computeunit.yaml"],
8286        );
8287        c.validate_code_paths().unwrap();
8288    }
8289
8290    #[test]
8291    fn validate_code_paths_sandbox_shape_arms_precede_non_lisp_extension() {
8292        // Cross-arm precedence pin: a `:bibliotecas` entry that is *both*
8293        // sandbox-escaping and non-`.lisp` surfaces the more fundamental
8294        // sandbox-shape diagnostic first (the `.lisp` remediation would
8295        // be misleading when the offending path can never resolve under
8296        // the caixa root anyway). Mirrors the peer
8297        // `EmptyPath` → `AbsolutePath` → `ParentEscape` → `NonLispExtension`
8298        // ordering on `:behavior :on-*` (c97815a) and `EmptyScript` →
8299        // `AbsoluteScript` → `ParentEscapeScript` → `NonLispExtensionScript`
8300        // on `:upgrade-from :state-change :script` (33cc830).
8301        //
8302        // Empty wins (the strictly-smaller-scope structural arm).
8303        let c = caixa_with_code_paths(vec![""], vec![], vec![]);
8304        assert!(
8305            matches!(
8306                c.validate_code_paths().unwrap_err(),
8307                ManifestError::CodePathEmpty {
8308                    slot: ":bibliotecas"
8309                }
8310            ),
8311            "empty must win over non-lisp-extension",
8312        );
8313        // Absolute wins (the path can't resolve under the caixa root).
8314        let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
8315        let err = c.validate_code_paths().unwrap_err();
8316        let ManifestError::CodePathAbsolute { slot, .. } = err else {
8317            panic!("absolute must win over non-lisp-extension, got {err:?}");
8318        };
8319        assert_eq!(slot, ":bibliotecas");
8320        // ParentEscape wins (the path escapes the caixa root).
8321        let c = caixa_with_code_paths(vec!["../sibling/x.txt"], vec![], vec![]);
8322        let err = c.validate_code_paths().unwrap_err();
8323        let ManifestError::CodePathParentEscape { slot, .. } = err else {
8324            panic!("parent-escape must win over non-lisp-extension, got {err:?}");
8325        };
8326        assert_eq!(slot, ":bibliotecas");
8327    }
8328
8329    #[test]
8330    fn validate_code_paths_non_lisp_extension_precedes_duplicate() {
8331        // Within-slot precedence pin: the per-entry file-type shape gate
8332        // fires before the cross-entry duplicate gate, so the narrower
8333        // structural defect dominates the uniqueness diagnostic. A
8334        // `("lib/x.txt" "lib/x.txt")` shape surfaces
8335        // `CodePathNonLispExtension` on the first entry rather than
8336        // `CodePathDuplicate` on the pair — same posture every per-entry
8337        // shape-gate-precedes-duplicate cascade follows on this surface
8338        // (the empty / absolute / parent-escape arms already precede the
8339        // duplicate arm; the lifted file-type arm joins that set).
8340        let c = caixa_with_code_paths(vec!["lib/x.txt", "lib/x.txt"], vec![], vec![]);
8341        let err = c.validate_code_paths().unwrap_err();
8342        let ManifestError::CodePathNonLispExtension { slot, path } = err else {
8343            panic!("expected CodePathNonLispExtension, got {err:?}");
8344        };
8345        assert_eq!(slot, ":bibliotecas");
8346        assert_eq!(path, PathBuf::from("lib/x.txt"));
8347    }
8348
8349    #[test]
8350    fn validate_code_paths_non_lisp_extension_diagnostic_carries_offending_slot_and_path() {
8351        // Diagnostic-shape pin (peer with
8352        // `validate_code_paths_diagnostic_carries_offending_slot_and_path`
8353        // on the sandbox-shape arms and
8354        // `validate_code_paths_duplicate_diagnostic_carries_offending_slot_and_path`
8355        // on the duplicate arm): the file-type-arm Display surfaces both
8356        // the offending `:slot` tag, the offending path verbatim, and the
8357        // expected `.lisp` extension named in the remediation text, so a
8358        // `feira lint` run can render the diagnostic without re-parsing.
8359        let c = caixa_with_code_paths(vec!["lib/demo.rs"], vec![], vec![]);
8360        let rendered = c.validate_code_paths().unwrap_err().to_string();
8361        assert!(
8362            rendered.contains(":bibliotecas"),
8363            "diagnostic must name the offending slot: {rendered}",
8364        );
8365        assert!(
8366            rendered.contains("lib/demo.rs"),
8367            "diagnostic must quote the offending path: {rendered}",
8368        );
8369        assert!(
8370            rendered.contains(".lisp"),
8371            "diagnostic must name the expected extension: {rendered}",
8372        );
8373    }
8374
8375    // ── validate_code_paths — `.computeunit.yaml` compound-suffix gate on :servicos ──
8376    //
8377    // The lifted [`crate::render::is_computeunit_yaml_extension`] predicate
8378    // now gates `:servicos` entries on the ComputeUnit-CR YAML file-type
8379    // contract. The peer caixa-helm / caixa-flux renderers consume each
8380    // `:servicos` entry through `serde_yaml::from_str` as a typed
8381    // `ComputeUnit` CR — same downstream-consumer-shape lift as the peer
8382    // `:bibliotecas` `.lisp` gate (64772a9), here on the compound-suffix
8383    // axis `Path::extension` can't express on its own.
8384
8385    #[test]
8386    fn validate_code_paths_rejects_no_extension_servicos_entry() {
8387        // Canonical "I dragged the wrong file from the workspace tree"
8388        // footgun on the Servico axis. Without the gate the peer
8389        // caixa-helm / caixa-flux renderers hand the extensionless path
8390        // to `serde_yaml::from_str` and fail with a parser-shaped
8391        // diagnostic far from the source caixa.lisp, with no field
8392        // naming the offending `:servicos` entry.
8393        for relpath in ["servicos/demo", "demo", "servicos/sub/nested"] {
8394            let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
8395            let err = c.validate_code_paths().unwrap_err();
8396            let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
8397                panic!(
8398                    "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
8399                     got {err:?}"
8400                );
8401            };
8402            assert_eq!(slot, ":servicos");
8403            assert_eq!(path, PathBuf::from(relpath));
8404        }
8405    }
8406
8407    #[test]
8408    fn validate_code_paths_rejects_wrong_extension_servicos_entry() {
8409        // Wrong-extension sweep across common authoring footguns on the
8410        // Servico axis. Bare `.yaml` is the canonical "I forgot the
8411        // `.computeunit` segment" typo; the off-by-one-segment shapes
8412        // (`.computeunit-yaml` / `.computeunit_yaml`) silently pass the
8413        // bare `Path::extension` view but mismatch the typed compound
8414        // suffix the renderers' `serde_yaml::from_str` consumer demands.
8415        // Same sweep-posture as the peer
8416        // `validate_code_paths_rejects_wrong_extension_bibliotecas_entry`
8417        // (64772a9) on the sibling tatara-lisp-source axis.
8418        for relpath in [
8419            "servicos/demo.yaml",
8420            "servicos/demo.yml",
8421            "servicos/demo.json",
8422            "servicos/demo.toml",
8423            "servicos/demo.txt",
8424            "servicos/demo.computeunit.yaml.bak",
8425            "servicos/demo.computeunit.yam",
8426            "servicos/demo.computeunit",
8427            "servicos/demo-computeunit.yaml",
8428            "servicos/demo_computeunit.yaml",
8429        ] {
8430            let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
8431            let err = c.validate_code_paths().unwrap_err();
8432            let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
8433                panic!(
8434                    "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
8435                     got {err:?}"
8436                );
8437            };
8438            assert_eq!(slot, ":servicos");
8439            assert_eq!(path, PathBuf::from(relpath));
8440        }
8441    }
8442
8443    #[test]
8444    fn validate_code_paths_rejects_case_folded_extension_servicos_entry() {
8445        // Case-sensitivity sweep — pins the strict lowercase
8446        // `.computeunit.yaml` contract. A case-folded shape that the
8447        // layout's existence check would (case-insensitively, on
8448        // case-insensitive volumes) match the on-disk file still
8449        // mismatches the canonical form the codec emits, breaking the
8450        // THEORY.md §V.2.7 render-determinism contract. Mirrors the peer
8451        // `validate_code_paths_rejects_case_folded_extension_bibliotecas_entry`
8452        // (64772a9) sweep on the sibling tatara-lisp-source axis.
8453        for relpath in [
8454            "servicos/demo.ComputeUnit.yaml",
8455            "servicos/demo.COMPUTEUNIT.yaml",
8456            "servicos/demo.computeunit.YAML",
8457            "servicos/demo.computeunit.Yaml",
8458            "servicos/demo.COMPUTEUNIT.YAML",
8459        ] {
8460            let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
8461            let err = c.validate_code_paths().unwrap_err();
8462            let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
8463                panic!(
8464                    "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
8465                     got {err:?}"
8466                );
8467            };
8468            assert_eq!(slot, ":servicos");
8469            assert_eq!(path, PathBuf::from(relpath));
8470        }
8471    }
8472
8473    #[test]
8474    fn validate_code_paths_rejects_empty_stem_servicos_entry() {
8475        // Degenerate hidden-file shape: a file name exactly equal to the
8476        // suffix (`.computeunit.yaml` — no stem preceding the suffix) is
8477        // the structural "Servico declared with no identity" footgun.
8478        // The substrate identifies each ComputeUnit by the file-stem
8479        // segment that precedes `.computeunit.yaml` (the rendered
8480        // `lareira-<stem>` Helm chart, the per-Servico `metadata.name`,
8481        // the M3 `:contratos` membership lookup), so an empty stem
8482        // leaves the Servico unidentifiable. Pinned at the typed-axis
8483        // level so a future regression that drops the `name.len() >
8484        // SUFFIX.len()` bound at the predicate surfaces here, not
8485        // piecemeal as a `lareira-` chart-name collision at render time.
8486        for relpath in ["servicos/.computeunit.yaml"] {
8487            let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
8488            let err = c.validate_code_paths().unwrap_err();
8489            let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
8490                panic!(
8491                    "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
8492                     got {err:?}"
8493                );
8494            };
8495            assert_eq!(slot, ":servicos");
8496            assert_eq!(path, PathBuf::from(relpath));
8497        }
8498    }
8499
8500    #[test]
8501    fn validate_code_paths_accepts_canonical_computeunit_yaml_shapes() {
8502        // Positive-control sweep through every canonical authoring shape
8503        // every in-tree fixture and the `Caixa::template` scaffold use.
8504        // Mirrors the peer
8505        // `validate_code_paths_accepts_canonical_lisp_shapes` (64772a9)
8506        // and the lifted predicate's own
8507        // `computeunit_yaml_extension_accepts_canonical_shapes` sweep in
8508        // render.rs.
8509        for relpath in [
8510            "servicos/demo.computeunit.yaml",
8511            "servicos/hello-rio.computeunit.yaml",
8512            "servicos/my-service.computeunit.yaml",
8513            "servicos/a.computeunit.yaml",
8514            "./servicos/demo.computeunit.yaml",
8515            "servicos/./demo.computeunit.yaml",
8516            "servicos/sub/nested.computeunit.yaml",
8517            "servicos/v0.1.computeunit.yaml",
8518        ] {
8519            let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
8520            c.validate_code_paths()
8521                .unwrap_or_else(|e| panic!("canonical shape {relpath:?} must pass, got {e:?}"));
8522        }
8523    }
8524
8525    #[test]
8526    fn validate_code_paths_non_computeunit_yaml_extension_does_not_fire_on_bibliotecas_or_exe() {
8527        // The file-type gate is per-slot — only `:servicos` carries the
8528        // ComputeUnit-CR YAML contract. A canonical `.lisp` `:bibliotecas`
8529        // entry and an extensionless `:exe` entry are the canonical
8530        // shapes every in-tree fixture uses, and must continue to pass
8531        // validate. Peer of
8532        // `validate_code_paths_non_lisp_extension_does_not_fire_on_exe_or_servicos`
8533        // (64772a9) — together pin that the typed
8534        // [`CodePathFileType`] dispatch is exhaustively per-slot, with no
8535        // cross-axis leakage in either direction.
8536        let c = caixa_with_code_paths(
8537            vec!["lib/demo.lisp"],
8538            vec!["exe/demo", "exe/tool"],
8539            vec!["servicos/demo.computeunit.yaml"],
8540        );
8541        c.validate_code_paths().unwrap();
8542    }
8543
8544    #[test]
8545    fn validate_code_paths_sandbox_shape_arms_precede_non_computeunit_yaml_extension() {
8546        // Cross-arm precedence pin: a `:servicos` entry that is *both*
8547        // sandbox-escaping and wrong-extension surfaces the more
8548        // fundamental sandbox-shape diagnostic first (the
8549        // `.computeunit.yaml` remediation would be misleading when the
8550        // offending path can never resolve under the caixa root
8551        // anyway). Mirrors the peer
8552        // `validate_code_paths_sandbox_shape_arms_precede_non_lisp_extension`
8553        // (64772a9) ordering on the sibling `:bibliotecas` axis and the
8554        // peer `EmptyPath` → `AbsolutePath` → `ParentEscape` →
8555        // `NonComputeUnitYamlExtension` arm-ordering the dispatch
8556        // table establishes.
8557        //
8558        // Empty wins (the strictly-smaller-scope structural arm).
8559        let c = caixa_with_code_paths(vec![], vec![], vec![""]);
8560        assert!(
8561            matches!(
8562                c.validate_code_paths().unwrap_err(),
8563                ManifestError::CodePathEmpty { slot: ":servicos" }
8564            ),
8565            "empty must win over non-computeunit-yaml-extension",
8566        );
8567        // Absolute wins (the path can't resolve under the caixa root).
8568        let c = caixa_with_code_paths(vec![], vec![], vec!["/etc/foo.yaml"]);
8569        let err = c.validate_code_paths().unwrap_err();
8570        let ManifestError::CodePathAbsolute { slot, .. } = err else {
8571            panic!("absolute must win over non-computeunit-yaml-extension, got {err:?}");
8572        };
8573        assert_eq!(slot, ":servicos");
8574        // ParentEscape wins (the path escapes the caixa root).
8575        let c = caixa_with_code_paths(vec![], vec![], vec!["../sibling/x.yaml"]);
8576        let err = c.validate_code_paths().unwrap_err();
8577        let ManifestError::CodePathParentEscape { slot, .. } = err else {
8578            panic!("parent-escape must win over non-computeunit-yaml-extension, got {err:?}");
8579        };
8580        assert_eq!(slot, ":servicos");
8581    }
8582
8583    #[test]
8584    fn validate_code_paths_non_computeunit_yaml_extension_precedes_duplicate() {
8585        // Within-slot precedence pin: the per-entry file-type shape gate
8586        // fires before the cross-entry duplicate gate, so the narrower
8587        // structural defect dominates the uniqueness diagnostic. A
8588        // `("servicos/x.yaml" "servicos/x.yaml")` shape surfaces
8589        // `CodePathNonComputeUnitYamlExtension` on the first entry
8590        // rather than `CodePathDuplicate` on the pair — same posture
8591        // every per-entry shape-gate-precedes-duplicate cascade follows
8592        // on this surface, peer of the 64772a9 `:bibliotecas`
8593        // `("lib/x.txt" "lib/x.txt")` ordering.
8594        let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/x.yaml", "servicos/x.yaml"]);
8595        let err = c.validate_code_paths().unwrap_err();
8596        let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
8597            panic!("expected CodePathNonComputeUnitYamlExtension, got {err:?}");
8598        };
8599        assert_eq!(slot, ":servicos");
8600        assert_eq!(path, PathBuf::from("servicos/x.yaml"));
8601    }
8602
8603    #[test]
8604    fn validate_code_paths_non_computeunit_yaml_extension_diagnostic_carries_offending_slot_and_path()
8605     {
8606        // Diagnostic-shape pin (peer with
8607        // `validate_code_paths_non_lisp_extension_diagnostic_carries_offending_slot_and_path`
8608        // on the sibling tatara-lisp-source axis): the file-type-arm
8609        // Display surfaces both the offending `:slot` tag, the
8610        // offending path verbatim, and the expected
8611        // `.computeunit.yaml` compound suffix named in the remediation
8612        // text, so a `feira lint` run can render the diagnostic without
8613        // re-parsing.
8614        let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/demo.yaml"]);
8615        let rendered = c.validate_code_paths().unwrap_err().to_string();
8616        assert!(
8617            rendered.contains(":servicos"),
8618            "diagnostic must name the offending slot: {rendered}",
8619        );
8620        assert!(
8621            rendered.contains("servicos/demo.yaml"),
8622            "diagnostic must quote the offending path: {rendered}",
8623        );
8624        assert!(
8625            rendered.contains(".computeunit.yaml"),
8626            "diagnostic must name the expected compound suffix: {rendered}",
8627        );
8628    }
8629
8630    // ── validate_etiquetas — universal-axis registry-search-tag shape ──
8631
8632    fn caixa_with_etiquetas(etiquetas: Vec<&str>) -> Caixa {
8633        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8634        c.etiquetas = etiquetas.into_iter().map(String::from).collect();
8635        c
8636    }
8637
8638    #[test]
8639    fn validate_etiquetas_accepts_empty_list() {
8640        // The empty-list identity: every caixa with no declared tags
8641        // trivially passes — `Caixa::template` emits `:etiquetas ()`,
8642        // so the gate is non-disruptive against every existing manifest.
8643        let c = caixa_with_etiquetas(vec![]);
8644        c.validate_etiquetas().unwrap();
8645    }
8646
8647    #[test]
8648    fn validate_etiquetas_accepts_canonical_forms() {
8649        // Positive control sweep: a canonical-shaped non-empty distinct
8650        // tag list passes, mirroring the example checkout-aplicacao
8651        // (`:etiquetas ("example" "aplicacao" "mesh" "ecommerce" "demo")`)
8652        // and the hello-rio fixture (`("hello-world" "wasm" "rust")`).
8653        let c = caixa_with_etiquetas(vec!["example", "aplicacao", "mesh", "ecommerce", "demo"]);
8654        c.validate_etiquetas().unwrap();
8655    }
8656
8657    #[test]
8658    fn validate_etiquetas_rejects_empty_entry() {
8659        // Canonical paste-from-blank-doc footgun. Without the gate the
8660        // empty entry rendered as `keywords: [""]` in `Chart.yaml`, a
8661        // no-op tag indexing nothing in the future caixa-registry.
8662        let c = caixa_with_etiquetas(vec![""]);
8663        let err = c.validate_etiquetas().unwrap_err();
8664        assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
8665    }
8666
8667    #[test]
8668    fn validate_etiquetas_rejects_duplicate_entry() {
8669        // Canonical copy-paste-the-wrong-tag footgun. Without the gate
8670        // the duplicate was silently dedup'd by caixa-helm's BTreeSet
8671        // collect at chart render — a "second wins / one silently
8672        // disappears" shape divergent from every peer typed-graph set
8673        // gate. The duplicate-arm names the offending tag verbatim.
8674        let c = caixa_with_etiquetas(vec!["demo", "demo"]);
8675        let err = c.validate_etiquetas().unwrap_err();
8676        let ManifestError::EtiquetaDuplicate { etiqueta } = err else {
8677            panic!("expected EtiquetaDuplicate, got {err:?}");
8678        };
8679        assert_eq!(etiqueta, "demo");
8680    }
8681
8682    #[test]
8683    fn validate_etiquetas_empty_takes_precedence_over_duplicate() {
8684        // Empty-first cascade pin: `("" "demo" "demo")` surfaces
8685        // `EtiquetaEmpty` not `EtiquetaDuplicate` — the narrower
8686        // structural "this entry has no value" defect dominates the
8687        // cross-entry uniqueness diagnostic. Mirrors the peer
8688        // empty-before-duplicate cascades on `:caracteristicas`
8689        // (`CaracteristicaEmpty` before `CaracteristicaDuplicate`,
8690        // fc3b4d5) and `:membros :caixa` (`MembroCaixaEmpty` before
8691        // `MembroDuplicate`).
8692        let c = caixa_with_etiquetas(vec!["", "demo", "demo"]);
8693        let err = c.validate_etiquetas().unwrap_err();
8694        assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
8695    }
8696
8697    #[test]
8698    fn validate_etiquetas_duplicate_reports_first_collision() {
8699        // First-collision pin: `("a" "b" "a" "b")` surfaces the `"a"`
8700        // duplicate (the lexicographically-earliest offending position
8701        // — the second `"a"` at index 2 collides with the first `"a"`
8702        // at index 0), not the later `"b"` collision at index 3,
8703        // peer with every other first-collision diagnostic posture on
8704        // this surface (`validate_load_singularity_reports_first_collision`,
8705        // `validate_cleanup_singularity_reports_first_collision`).
8706        let c = caixa_with_etiquetas(vec!["a", "b", "a", "b"]);
8707        let err = c.validate_etiquetas().unwrap_err();
8708        let ManifestError::EtiquetaDuplicate { etiqueta } = err else {
8709            panic!("expected EtiquetaDuplicate, got {err:?}");
8710        };
8711        assert_eq!(etiqueta, "a");
8712    }
8713
8714    #[test]
8715    fn validate_etiquetas_case_sensitive() {
8716        // Case-sensitivity pin: `("Foo" "foo")` is two distinct entries,
8717        // mirroring the peer `:membros :caixa` / `:children :caixa`
8718        // exact-string-match discipline. The shape gate this routine
8719        // landed (`is_chart_keyword_shape`, Cargo crates.io keyword
8720        // grammar) accepts mixed case — crates.io's keyword rule is
8721        // "case-insensitive" at the index layer but admits mixed case
8722        // at the entry layer (the canonical Helm chart `keywords:`
8723        // shape is lowercase by convention, but the grammar admits
8724        // uppercase). Case-sensitivity at the duplicate-set layer
8725        // remains structural — two distinct strings are two distinct
8726        // entries.
8727        let c = caixa_with_etiquetas(vec!["Foo", "foo"]);
8728        c.validate_etiquetas().unwrap();
8729    }
8730
8731    #[test]
8732    fn validate_etiquetas_diagnostic_carries_offending_tag() {
8733        // Diagnostic-shape pin (peer with
8734        // `validate_code_paths_diagnostic_carries_offending_slot_and_path`):
8735        // the error's Display surfaces the offending tag verbatim, so a
8736        // `feira lint` run can render the diagnostic without re-parsing
8737        // and the author can grep their caixa.lisp for the offending
8738        // value.
8739        let c = caixa_with_etiquetas(vec!["demo", "demo"]);
8740        let rendered = c.validate_etiquetas().unwrap_err().to_string();
8741        assert!(
8742            rendered.contains(":etiquetas"),
8743            "diagnostic must name the offending slot: {rendered}",
8744        );
8745        assert!(
8746            rendered.contains("demo"),
8747            "diagnostic must quote the offending tag: {rendered}",
8748        );
8749    }
8750
8751    #[test]
8752    fn validate_etiquetas_rejects_leading_whitespace_entry() {
8753        // Canonical paste-from-aligned-doc footgun. Without the shape
8754        // gate `" mesh"` silently passed validate and landed as a
8755        // YAML plain-style scalar with leading whitespace in the
8756        // rendered Chart.yaml `keywords:` array — every YAML 1.2
8757        // dumper trims leading whitespace from plain-style scalars,
8758        // so the authored space round-tripped inconsistently back
8759        // through `caixa.lisp`. Mirrors the peer
8760        // `validate_autores_rejects_leading_whitespace_entry`.
8761        let c = caixa_with_etiquetas(vec![" mesh"]);
8762        let err = c.validate_etiquetas().unwrap_err();
8763        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8764            panic!("expected EtiquetaInvalid, got {err:?}");
8765        };
8766        assert_eq!(etiqueta, " mesh");
8767        assert!(reason.contains("whitespace"), "got: {reason}");
8768    }
8769
8770    #[test]
8771    fn validate_etiquetas_rejects_embedded_newline_entry() {
8772        // Canonical paste-from-multiline-doc footgun — the author
8773        // pasted a multi-tag block into one `:etiquetas` entry
8774        // instead of splitting into one entry per tag. Without the
8775        // shape gate `"mesh\nhttp"` silently passed validate and
8776        // landed as a YAML-illegal multi-line scalar in the rendered
8777        // Chart.yaml `keywords:` array.
8778        let c = caixa_with_etiquetas(vec!["mesh\nhttp"]);
8779        let err = c.validate_etiquetas().unwrap_err();
8780        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8781            panic!("expected EtiquetaInvalid, got {err:?}");
8782        };
8783        assert_eq!(etiqueta, "mesh\nhttp");
8784        assert!(reason.contains("newline"), "got: {reason}");
8785    }
8786
8787    #[test]
8788    fn validate_etiquetas_rejects_embedded_comma_entry() {
8789        // Canonical CSV-list-separator-confusion footgun: the author
8790        // confused the CSV-style separator convention with the
8791        // `:etiquetas` list grammar. Without the shape gate
8792        // `"mesh,http,grpc"` silently passed validate and landed as a
8793        // single malformed search tag in the rendered Chart.yaml
8794        // `keywords:` array — Artifact Hub's keyword index would
8795        // either silently drop the tag or index it as
8796        // `mesh,http,grpc` instead of three separate tags.
8797        let c = caixa_with_etiquetas(vec!["mesh,http,grpc"]);
8798        let err = c.validate_etiquetas().unwrap_err();
8799        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8800            panic!("expected EtiquetaInvalid, got {err:?}");
8801        };
8802        assert_eq!(etiqueta, "mesh,http,grpc");
8803        assert!(reason.contains('`'), "got: {reason}");
8804        assert!(reason.contains(','), "got: {reason}");
8805    }
8806
8807    #[test]
8808    fn validate_etiquetas_rejects_embedded_slash_entry() {
8809        // Canonical path-separator-confusion footgun: the author
8810        // confused namespace-path notation with the keyword grammar.
8811        let c = caixa_with_etiquetas(vec!["caixa/servico"]);
8812        let err = c.validate_etiquetas().unwrap_err();
8813        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8814            panic!("expected EtiquetaInvalid, got {err:?}");
8815        };
8816        assert_eq!(etiqueta, "caixa/servico");
8817        assert!(reason.contains('/'), "got: {reason}");
8818    }
8819
8820    #[test]
8821    fn validate_etiquetas_rejects_leading_digit_entry() {
8822        // Canonical paste-from-numbered-list footgun: the author
8823        // copied `1. mesh` from a numbered doc and the `1` leaked
8824        // into the tag.
8825        let c = caixa_with_etiquetas(vec!["1mesh"]);
8826        let err = c.validate_etiquetas().unwrap_err();
8827        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8828            panic!("expected EtiquetaInvalid, got {err:?}");
8829        };
8830        assert_eq!(etiqueta, "1mesh");
8831        assert!(reason.contains("digit"), "got: {reason}");
8832    }
8833
8834    #[test]
8835    fn validate_etiquetas_rejects_leading_hyphen_entry() {
8836        // Canonical kebab-leak footgun.
8837        let c = caixa_with_etiquetas(vec!["-foo"]);
8838        let err = c.validate_etiquetas().unwrap_err();
8839        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8840            panic!("expected EtiquetaInvalid, got {err:?}");
8841        };
8842        assert_eq!(etiqueta, "-foo");
8843        assert!(reason.contains('-'), "got: {reason}");
8844    }
8845
8846    #[test]
8847    fn validate_etiquetas_rejects_non_ascii_entry() {
8848        // Canonical paste-from-Unicode-doc footgun. Every legitimate
8849        // search tag is strict ASCII; raw non-ASCII silently
8850        // round-trips inconsistently across NFC/NFD normalization on
8851        // APFS / case-folding filesystems and breaks the Artifact Hub
8852        // keyword search index lookup.
8853        let c = caixa_with_etiquetas(vec!["café"]);
8854        let err = c.validate_etiquetas().unwrap_err();
8855        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8856            panic!("expected EtiquetaInvalid, got {err:?}");
8857        };
8858        assert_eq!(etiqueta, "café");
8859        assert!(reason.contains("non-ASCII"), "got: {reason}");
8860    }
8861
8862    #[test]
8863    fn validate_etiquetas_rejects_period_entry() {
8864        // Canonical namespace-confusion / version-suffix footgun
8865        // (`"http.1"` / `"v1.0"`): Cargo's crates.io keyword grammar
8866        // excludes `.` from the continuation set even though the
8867        // sibling `:caracteristicas` axis (Cargo's feature-name
8868        // grammar) admits it. Tighter than the sibling axis, peer
8869        // with Cargo's own crates.io keyword shape.
8870        let c = caixa_with_etiquetas(vec!["http.1"]);
8871        let err = c.validate_etiquetas().unwrap_err();
8872        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8873            panic!("expected EtiquetaInvalid, got {err:?}");
8874        };
8875        assert_eq!(etiqueta, "http.1");
8876        assert!(reason.contains('.'), "got: {reason}");
8877    }
8878
8879    #[test]
8880    fn validate_etiquetas_empty_takes_precedence_over_shape() {
8881        // Per-entry empty-first cascade pin: an entry that is both
8882        // empty *and* shape-invalid surfaces `EtiquetaEmpty` (the
8883        // narrower "this entry has no value" structural defect
8884        // dominates the broader shape-predicate diagnostic). The
8885        // empty arm fires before the shape predicate is consulted,
8886        // mirroring the peer `validate_autores_empty_takes_precedence_over_shape`
8887        // cascade established on the sibling universal-axis Vec<String>
8888        // surface.
8889        let c = caixa_with_etiquetas(vec![""]);
8890        let err = c.validate_etiquetas().unwrap_err();
8891        assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
8892    }
8893
8894    #[test]
8895    fn validate_etiquetas_shape_takes_precedence_over_duplicate() {
8896        // Per-entry shape-before-cross-entry-duplicate cascade pin: an
8897        // entry that is malformed surfaces `EtiquetaInvalid` even when
8898        // a later entry would have collided on duplicate. The
8899        // per-entry shape arm fires inside the same loop iteration as
8900        // the empty arm, before the seen-set insert at end-of-iteration
8901        // — structural per-entry defects dominate the cross-entry
8902        // uniqueness diagnostic. Mirrors the peer
8903        // `validate_autores_shape_takes_precedence_over_duplicate`.
8904        let c = caixa_with_etiquetas(vec!["mesh\nhttp", "mesh\nhttp"]);
8905        let err = c.validate_etiquetas().unwrap_err();
8906        assert!(
8907            matches!(err, ManifestError::EtiquetaInvalid { .. }),
8908            "got {err:?}",
8909        );
8910    }
8911
8912    #[test]
8913    fn validate_etiquetas_invalid_diagnostic_names_offending_slot_and_value() {
8914        // Diagnostic-shape pin on the new shape arm (peer with
8915        // `validate_autores_invalid_diagnostic_names_offending_slot_and_value`):
8916        // the rendered Display surfaces both the offending slot name
8917        // and the offending value verbatim, so a `feira lint` run
8918        // points the author at the exact `:etiquetas` entry to fix.
8919        let c = caixa_with_etiquetas(vec!["mesh\nhttp"]);
8920        let rendered = c.validate_etiquetas().unwrap_err().to_string();
8921        assert!(
8922            rendered.contains(":etiquetas"),
8923            "diagnostic must name the offending slot: {rendered}",
8924        );
8925        assert!(
8926            rendered.contains("mesh\\nhttp"),
8927            "diagnostic must quote the offending value (debug-escaped): {rendered}",
8928        );
8929    }
8930
8931    #[test]
8932    fn validate_etiquetas_rejects_at_21_byte_boundary() {
8933        // The 20-byte cap pin — boundary-exceeding case rejected,
8934        // boundary-accepting case passes. Mirrors the peer
8935        // `chart_keyword_shape_rejects_at_21_byte_boundary` substrate-
8936        // side pin, surfaced at the per-axis caller so the cap
8937        // propagates through validate end-to-end. Constructed as a
8938        // single all-`a` token so only the cap arm fires.
8939        let max_ok = "a".repeat(20);
8940        let c = caixa_with_etiquetas(vec![max_ok.as_str()]);
8941        c.validate_etiquetas().unwrap();
8942        let too_long = "a".repeat(21);
8943        let c = caixa_with_etiquetas(vec![too_long.as_str()]);
8944        let err = c.validate_etiquetas().unwrap_err();
8945        let ManifestError::EtiquetaInvalid { reason, .. } = err else {
8946            panic!("expected EtiquetaInvalid, got {err:?}");
8947        };
8948        assert!(reason.contains("20"), "got: {reason}");
8949        assert!(reason.contains("21"), "got: {reason}");
8950    }
8951
8952    #[test]
8953    fn validate_etiquetas_accepts_canonical_shaped_forms() {
8954        // Positive control sweep: every canonical-shaped tag from the
8955        // hello-rio / checkout-aplicacao / pangea-tatara-akeyless
8956        // example fixtures plus the substrate-fixed tags caixa-helm
8957        // unions in at chart render. Drift between this list and the
8958        // substrate-side `chart_keyword_shape_accepts_canonical_forms`
8959        // sweep surfaces here — one source of truth for the rule.
8960        let c = caixa_with_etiquetas(vec![
8961            "example",
8962            "aplicacao",
8963            "mesh",
8964            "ecommerce",
8965            "demo",
8966            "infrastructure",
8967            "aws",
8968            "akeyless",
8969            "pangea-native",
8970            "hello-world",
8971            "wasm",
8972            "rust",
8973            "tatara-lisp",
8974            "caixa-servico",
8975            "lareira",
8976        ]);
8977        c.validate_etiquetas().unwrap();
8978    }
8979
8980    // ── validate_autores — universal-axis maintainer shape ────────────
8981
8982    fn caixa_with_autores(autores: Vec<&str>) -> Caixa {
8983        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8984        c.autores = autores.into_iter().map(String::from).collect();
8985        c
8986    }
8987
8988    #[test]
8989    fn validate_autores_accepts_empty_list() {
8990        // The empty-list identity: `Caixa::template` emits `:autores ()`,
8991        // so the gate is non-disruptive against every existing manifest.
8992        let c = caixa_with_autores(vec![]);
8993        c.validate_autores().unwrap();
8994    }
8995
8996    #[test]
8997    fn validate_autores_accepts_canonical_forms() {
8998        // Positive control sweep: every canonical-shaped non-empty
8999        // distinct maintainer list passes — the hello-rio / checkout-
9000        // aplicacao fixtures' `:autores ("pleme-io")` shape, plus the
9001        // multi-author shape downstream packaging surfaces emit.
9002        let c = caixa_with_autores(vec!["pleme-io"]);
9003        c.validate_autores().unwrap();
9004        let c = caixa_with_autores(vec!["alice <alice@example.com>", "bob <bob@example.com>"]);
9005        c.validate_autores().unwrap();
9006    }
9007
9008    #[test]
9009    fn validate_autores_rejects_empty_entry() {
9010        // Canonical paste-from-blank-doc footgun. Without the gate the
9011        // empty entry rendered as `maintainers: [{name: "", email: null}]`
9012        // in `Chart.yaml`, a no-op maintainer the substrate cannot route
9013        // to.
9014        let c = caixa_with_autores(vec![""]);
9015        let err = c.validate_autores().unwrap_err();
9016        assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
9017    }
9018
9019    #[test]
9020    fn validate_autores_rejects_duplicate_entry() {
9021        // Canonical copy-paste-the-wrong-author footgun. Unlike the
9022        // `:etiquetas` peer (caixa-helm's `BTreeSet` collect silently
9023        // dedups the rendered `keywords:` array), the `maintainers:`
9024        // rendering has *no* dedup — duplicates stack verbatim. The
9025        // duplicate-arm names the offending author verbatim.
9026        let c = caixa_with_autores(vec!["pleme-io", "pleme-io"]);
9027        let err = c.validate_autores().unwrap_err();
9028        let ManifestError::AutorDuplicate { autor } = err else {
9029            panic!("expected AutorDuplicate, got {err:?}");
9030        };
9031        assert_eq!(autor, "pleme-io");
9032    }
9033
9034    #[test]
9035    fn validate_autores_empty_takes_precedence_over_duplicate() {
9036        // Empty-first cascade pin: `("" "pleme-io" "pleme-io")` surfaces
9037        // `AutorEmpty` not `AutorDuplicate` — the narrower structural
9038        // "this entry has no value" defect dominates the cross-entry
9039        // uniqueness diagnostic. Mirrors the peer empty-before-duplicate
9040        // cascades on `:etiquetas` (`EtiquetaEmpty` before
9041        // `EtiquetaDuplicate`, 360a499), `:caracteristicas`
9042        // (`CaracteristicaEmpty` before `CaracteristicaDuplicate`,
9043        // fc3b4d5), and `:membros :caixa` (`MembroCaixaEmpty` before
9044        // `MembroDuplicate`).
9045        let c = caixa_with_autores(vec!["", "pleme-io", "pleme-io"]);
9046        let err = c.validate_autores().unwrap_err();
9047        assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
9048    }
9049
9050    #[test]
9051    fn validate_autores_duplicate_reports_first_collision() {
9052        // First-collision pin: `("a" "b" "a" "b")` surfaces the `"a"`
9053        // duplicate (the lexicographically-earliest offending position
9054        // — the second `"a"` at index 2 collides with the first `"a"`
9055        // at index 0), not the later `"b"` collision at index 3,
9056        // peer with every other first-collision diagnostic posture on
9057        // this surface.
9058        let c = caixa_with_autores(vec!["a", "b", "a", "b"]);
9059        let err = c.validate_autores().unwrap_err();
9060        let ManifestError::AutorDuplicate { autor } = err else {
9061            panic!("expected AutorDuplicate, got {err:?}");
9062        };
9063        assert_eq!(autor, "a");
9064    }
9065
9066    #[test]
9067    fn validate_autores_case_sensitive() {
9068        // Case-sensitivity pin: `("Pleme-io" "pleme-io")` is two distinct
9069        // entries, mirroring the peer `:etiquetas` / `:membros :caixa`
9070        // / `:children :caixa` exact-string-match discipline.
9071        let c = caixa_with_autores(vec!["Pleme-io", "pleme-io"]);
9072        c.validate_autores().unwrap();
9073    }
9074
9075    #[test]
9076    fn validate_autores_diagnostic_carries_offending_author() {
9077        // Diagnostic-shape pin (peer with
9078        // `validate_etiquetas_diagnostic_carries_offending_tag`): the
9079        // error's Display surfaces the offending author verbatim, so a
9080        // `feira lint` run can render the diagnostic without re-parsing
9081        // and the author can grep their caixa.lisp for the offending
9082        // value.
9083        let c = caixa_with_autores(vec!["pleme-io", "pleme-io"]);
9084        let rendered = c.validate_autores().unwrap_err().to_string();
9085        assert!(
9086            rendered.contains(":autores"),
9087            "diagnostic must name the offending slot: {rendered}",
9088        );
9089        assert!(
9090            rendered.contains("pleme-io"),
9091            "diagnostic must quote the offending author: {rendered}",
9092        );
9093    }
9094
9095    #[test]
9096    fn validate_autores_rejects_leading_whitespace_entry() {
9097        // Canonical paste-from-aligned-doc footgun. Without the shape
9098        // gate `" pleme-io"` silently passed validate and landed as a
9099        // YAML plain-style scalar with leading whitespace in the
9100        // rendered Chart.yaml `maintainers:` array — every YAML 1.2
9101        // dumper trims leading whitespace from plain-style scalars, so
9102        // the authored space round-tripped inconsistently back through
9103        // `caixa.lisp`. Mirrors the peer
9104        // `validate_descricao_rejects_leading_whitespace`.
9105        let c = caixa_with_autores(vec![" pleme-io"]);
9106        let err = c.validate_autores().unwrap_err();
9107        let ManifestError::AutorInvalid { autor, reason } = err else {
9108            panic!("expected AutorInvalid, got {err:?}");
9109        };
9110        assert_eq!(autor, " pleme-io");
9111        assert!(reason.contains("whitespace"), "got: {reason}");
9112    }
9113
9114    #[test]
9115    fn validate_autores_rejects_trailing_whitespace_entry() {
9116        // Canonical paste-from-doc footgun.
9117        let c = caixa_with_autores(vec!["pleme-io "]);
9118        let err = c.validate_autores().unwrap_err();
9119        let ManifestError::AutorInvalid { autor, reason } = err else {
9120            panic!("expected AutorInvalid, got {err:?}");
9121        };
9122        assert_eq!(autor, "pleme-io ");
9123        assert!(reason.contains("whitespace"), "got: {reason}");
9124    }
9125
9126    #[test]
9127    fn validate_autores_rejects_embedded_newline_entry() {
9128        // Canonical paste-from-multiline-doc footgun — the author
9129        // pasted a multi-line block of author records into one
9130        // `:autores` entry instead of splitting into one entry per
9131        // author. Without the shape gate `"alice\nbob"` silently
9132        // passed validate and landed as a YAML-illegal multi-line
9133        // scalar in the rendered Chart.yaml `maintainers:` array.
9134        let c = caixa_with_autores(vec!["alice\nbob"]);
9135        let err = c.validate_autores().unwrap_err();
9136        let ManifestError::AutorInvalid { autor, reason } = err else {
9137            panic!("expected AutorInvalid, got {err:?}");
9138        };
9139        assert_eq!(autor, "alice\nbob");
9140        assert!(reason.contains("newline"), "got: {reason}");
9141    }
9142
9143    #[test]
9144    fn validate_autores_rejects_embedded_carriage_return_entry() {
9145        // Canonical paste-from-Windows-CRLF-doc footgun.
9146        let c = caixa_with_autores(vec!["alice\rbob"]);
9147        let err = c.validate_autores().unwrap_err();
9148        let ManifestError::AutorInvalid { autor, reason } = err else {
9149            panic!("expected AutorInvalid, got {err:?}");
9150        };
9151        assert_eq!(autor, "alice\rbob");
9152        assert!(reason.contains("carriage return"), "got: {reason}");
9153    }
9154
9155    #[test]
9156    fn validate_autores_rejects_embedded_tab_entry() {
9157        // Canonical tab-from-aligned-doc footgun.
9158        let c = caixa_with_autores(vec!["Pleme\tContributors"]);
9159        let err = c.validate_autores().unwrap_err();
9160        let ManifestError::AutorInvalid { autor, reason } = err else {
9161            panic!("expected AutorInvalid, got {err:?}");
9162        };
9163        assert_eq!(autor, "Pleme\tContributors");
9164        assert!(reason.contains("tab"), "got: {reason}");
9165    }
9166
9167    #[test]
9168    fn validate_autores_rejects_embedded_control_bytes_entry() {
9169        // Paste-from-binary-blob footguns: NUL, BEL, ESC, DEL all
9170        // surface the same control-byte arm.
9171        for entry in [
9172            "alice\x00bob",
9173            "alice\x07bob",
9174            "alice\x1bbob",
9175            "alice\x7fbob",
9176        ] {
9177            let c = caixa_with_autores(vec![entry]);
9178            let err = c.validate_autores().unwrap_err();
9179            let ManifestError::AutorInvalid { autor, reason } = err else {
9180                panic!("expected AutorInvalid for {entry:?}, got {err:?}");
9181            };
9182            assert_eq!(autor, entry);
9183            assert!(
9184                reason.contains("control character"),
9185                "{entry:?} reason: {reason}",
9186            );
9187        }
9188    }
9189
9190    #[test]
9191    fn validate_autores_accepts_unicode_entry() {
9192        // Unicode positive control: realistic maintainer names carry
9193        // Unicode (`François`, `日本語`, `naïve`). The predicate must
9194        // round-trip Unicode losslessly, peer with the
9195        // `chart_maintainer_name_shape_accepts_unicode` substrate-side
9196        // sweep.
9197        let c = caixa_with_autores(vec![
9198            "François Dupont",
9199            "日本語の名前",
9200            "naïve <naive@example.com>",
9201        ]);
9202        c.validate_autores().unwrap();
9203    }
9204
9205    #[test]
9206    fn validate_autores_empty_takes_precedence_over_shape() {
9207        // Per-entry empty-first cascade pin: an entry that is both
9208        // empty *and* shape-invalid surfaces `AutorEmpty` (the narrower
9209        // "this entry has no value" structural defect dominates the
9210        // broader shape-predicate diagnostic). The empty arm fires
9211        // before the shape predicate is consulted, mirroring the peer
9212        // `validate_repositorio_empty_takes_precedence_over_shape`
9213        // cascade on the universal `Option<String>` siblings — and now
9214        // established on the Vec<String> per-entry surface.
9215        let c = caixa_with_autores(vec![""]);
9216        let err = c.validate_autores().unwrap_err();
9217        assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
9218    }
9219
9220    #[test]
9221    fn validate_autores_shape_takes_precedence_over_duplicate() {
9222        // Per-entry shape-before-cross-entry-duplicate cascade pin: an
9223        // entry that is malformed surfaces `AutorInvalid` even when a
9224        // later entry would have collided on duplicate. The per-entry
9225        // shape arm fires inside the same loop iteration as the empty
9226        // arm, before the seen-set insert at end-of-iteration —
9227        // structural per-entry defects dominate the cross-entry
9228        // uniqueness diagnostic.
9229        let c = caixa_with_autores(vec!["alice\nbob", "alice\nbob"]);
9230        let err = c.validate_autores().unwrap_err();
9231        assert!(
9232            matches!(err, ManifestError::AutorInvalid { .. }),
9233            "got {err:?}",
9234        );
9235    }
9236
9237    #[test]
9238    fn validate_autores_invalid_diagnostic_names_offending_slot_and_value() {
9239        // Diagnostic-shape pin on the new shape arm (peer with
9240        // `validate_descricao_invalid_diagnostic_carries_offending_value`):
9241        // the rendered Display surfaces both the offending slot name
9242        // and the offending value verbatim, so a `feira lint` run
9243        // points the author at the exact `:autores` entry to fix.
9244        let c = caixa_with_autores(vec!["alice\nbob"]);
9245        let rendered = c.validate_autores().unwrap_err().to_string();
9246        assert!(
9247            rendered.contains(":autores"),
9248            "diagnostic must name the offending slot: {rendered}",
9249        );
9250        assert!(
9251            rendered.contains("alice\\nbob"),
9252            "diagnostic must quote the offending value (debug-escaped): {rendered}",
9253        );
9254    }
9255
9256    #[test]
9257    fn validate_autores_rejects_at_129_byte_boundary() {
9258        // The 128-byte cap pin — boundary-exceeding case rejected,
9259        // boundary-accepting case passes. Mirrors the peer
9260        // `chart_maintainer_name_shape_rejects_at_129_byte_boundary`
9261        // substrate-side pin, surfaced at the per-axis caller so the
9262        // cap propagates through validate end-to-end. Constructed as
9263        // a single all-`a` token so only the cap arm fires.
9264        let max_ok = "a".repeat(128);
9265        let c = caixa_with_autores(vec![max_ok.as_str()]);
9266        c.validate_autores().unwrap();
9267        let too_long = "a".repeat(129);
9268        let c = caixa_with_autores(vec![too_long.as_str()]);
9269        let err = c.validate_autores().unwrap_err();
9270        let ManifestError::AutorInvalid { reason, .. } = err else {
9271            panic!("expected AutorInvalid, got {err:?}");
9272        };
9273        assert!(reason.contains("128"), "got: {reason}");
9274        assert!(reason.contains("129"), "got: {reason}");
9275    }
9276
9277    // ── validate_repositorio — universal-axis git-repo-URL shape ──────
9278
9279    fn caixa_with_repositorio(repositorio: Option<&str>) -> Caixa {
9280        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9281        c.repositorio = repositorio.map(String::from);
9282        c
9283    }
9284
9285    #[test]
9286    fn validate_repositorio_accepts_none() {
9287        // The omit-the-slot identity: `:repositorio` is optional. The
9288        // gate is a no-op when the author didn't declare a value —
9289        // every caixa without a `:repositorio` line trivially passes,
9290        // and the substrate-side renderers fall back to their
9291        // documented placeholder (`caixa-helm`'s `home: None`,
9292        // `caixa-flux`'s `https://github.com/pleme-io/<nome>` derived
9293        // URL). Mirrors the peer `validate_restart_window_accepts_none`
9294        // posture on the other `Option<String>` Caixa slot.
9295        let c = caixa_with_repositorio(None);
9296        c.validate_repositorio().unwrap();
9297    }
9298
9299    #[test]
9300    fn validate_repositorio_accepts_canonical_forms() {
9301        // Positive control sweep across every documented `:repositorio`
9302        // authoring shape — the same union the shared
9303        // `crate::render::is_git_repo_url` predicate accepts and the
9304        // peer `:deps :fonte :repo` axis already routes through.
9305        // Covers the `github:` shorthand (the canonical pleme-io
9306        // convention used in the `:repositorio` field of every
9307        // manifest fixture across `caixa-helm` / `caixa-mesh` and the
9308        // `examples/`), the `https://…` URL the README quickstart uses,
9309        // the `ssh://`, `git://`, `git@host:path` scp-style SSH, and
9310        // `file://` URL schemes the shared predicate documents.
9311        for repo in [
9312            "github:pleme-io/hello-rio",
9313            "github:pleme-io/checkout",
9314            "https://github.com/pleme-io/hello-rio",
9315            "ssh://git@github.com/pleme-io/hello-rio.git",
9316            "git://github.com/pleme-io/hello-rio.git",
9317            "git@github.com:pleme-io/hello-rio.git",
9318            "file:///srv/pleme/hello-rio",
9319        ] {
9320            let c = caixa_with_repositorio(Some(repo));
9321            c.validate_repositorio()
9322                .unwrap_or_else(|err| panic!("canonical {repo:?} must pass: {err:?}"));
9323        }
9324    }
9325
9326    #[test]
9327    fn validate_repositorio_rejects_empty_some() {
9328        // Canonical paste-from-blank-doc footgun. The narrower
9329        // [`ManifestError::RepositorioEmpty`] arm fires before the
9330        // shape predicate is consulted, mirroring the empty-first
9331        // cascade every peer per-axis identity gate uses
9332        // (`NomeEmpty` → `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid`,
9333        // `FonteRepoEmpty` → `FonteRepoInvalid`). Without this gate
9334        // the empty `Some("")` silently passed the renderer's
9335        // `Option::unwrap_or_else(|| <fallback>)` (which only fires
9336        // on `None`) and landed as `home: ""` in `Chart.yaml` /
9337        // `url: ""` in the FluxCD `GitRepository`.
9338        let c = caixa_with_repositorio(Some(""));
9339        let err = c.validate_repositorio().unwrap_err();
9340        assert!(
9341            matches!(err, ManifestError::RepositorioEmpty),
9342            "got {err:?}",
9343        );
9344    }
9345
9346    #[test]
9347    fn validate_repositorio_rejects_whitespace() {
9348        // Paste-from-doc whitespace footgun. The shared
9349        // `is_git_repo_url` predicate refuses any whitespace byte; a
9350        // trailing space in a `:repositorio` value silently broke
9351        // `git clone '<value> '` at clone time. The diagnostic names
9352        // the offending value verbatim.
9353        let c = caixa_with_repositorio(Some("github:pleme-io/hello-rio "));
9354        let err = c.validate_repositorio().unwrap_err();
9355        let ManifestError::RepositorioInvalid { repositorio, .. } = err else {
9356            panic!("expected RepositorioInvalid, got {err:?}");
9357        };
9358        assert_eq!(repositorio, "github:pleme-io/hello-rio ");
9359    }
9360
9361    #[test]
9362    fn validate_repositorio_rejects_control_char() {
9363        // Paste-from-multiline-doc CRLF footgun — control characters
9364        // at the URL boundary are a class of subprocess-arg injection
9365        // and break git's URL parser at every porcelain entry point.
9366        let c = caixa_with_repositorio(Some("https://example.com/repo\n"));
9367        let err = c.validate_repositorio().unwrap_err();
9368        assert!(
9369            matches!(err, ManifestError::RepositorioInvalid { .. }),
9370            "got {err:?}",
9371        );
9372    }
9373
9374    #[test]
9375    fn validate_repositorio_rejects_leading_dash() {
9376        // Canonical CLI-argument-injection footgun: `git clone <repo>`
9377        // interprets a leading `-` as a CLI flag, so a
9378        // `-upload-pack=…` value escapes the subprocess argument
9379        // boundary. The shared predicate refuses every leading-`-`
9380        // shape at validate time.
9381        let c = caixa_with_repositorio(Some("-upload-pack=evil"));
9382        let err = c.validate_repositorio().unwrap_err();
9383        assert!(
9384            matches!(err, ManifestError::RepositorioInvalid { .. }),
9385            "got {err:?}",
9386        );
9387    }
9388
9389    #[test]
9390    fn validate_repositorio_rejects_missing_colon_separator() {
9391        // The bare `org/repo` ambiguity footgun — `git clone` reads
9392        // a no-`:` form as a relative filesystem path rather than the
9393        // GitHub-shorthand expansion the author probably intended.
9394        // The shared predicate refuses every shape without a `:`
9395        // separator.
9396        let c = caixa_with_repositorio(Some("pleme-io/hello-rio"));
9397        let err = c.validate_repositorio().unwrap_err();
9398        assert!(
9399            matches!(err, ManifestError::RepositorioInvalid { .. }),
9400            "got {err:?}",
9401        );
9402    }
9403
9404    #[test]
9405    fn validate_repositorio_rejects_fragment_anchor() {
9406        // Paste-from-browser-address-bar footgun on the
9407        // `:repositorio` axis — an author copies a GitHub permalink
9408        // to a README section / line-permalink and forgets to trim
9409        // the `#fragment` tail. The shared `is_git_repo_url`
9410        // predicate refuses the byte at the URL-grammar layer
9411        // (libcurl strips the fragment before opening the
9412        // transport, so the byte rides verbatim into the rendered
9413        // `Chart.yaml` `home:` and FluxCD `GitRepository` `url:`
9414        // fields but is silently dropped on the wire — two
9415        // manifest variants whose values differ only in their
9416        // fragment anchor lock to two distinct rendered artifacts
9417        // for the byte-identical clone, defeating the THEORY.md
9418        // §V.2 render-determinism contract on the `:repositorio`
9419        // axis the peer `:fonte :repo` axis already closes).
9420        let c = caixa_with_repositorio(Some("https://github.com/pleme-io/hello-rio#readme"));
9421        let err = c.validate_repositorio().unwrap_err();
9422        let ManifestError::RepositorioInvalid {
9423            repositorio,
9424            reason,
9425        } = err
9426        else {
9427            panic!("expected RepositorioInvalid, got {err:?}");
9428        };
9429        assert_eq!(repositorio, "https://github.com/pleme-io/hello-rio#readme");
9430        assert!(
9431            reason.contains("must not contain `#`"),
9432            "reason must surface the fragment-`#` arm, got {reason:?}"
9433        );
9434    }
9435
9436    #[test]
9437    fn validate_repositorio_rejects_query_string() {
9438        // Paste-from-browser-address-bar footgun on the
9439        // `:repositorio` axis (peer with the a68f818 fragment-`#`
9440        // arm on the same axis). An author copies a GitHub tab
9441        // deep-link out of the address bar and forgets to trim
9442        // the `?tab=…` query tail. The shared `is_git_repo_url`
9443        // predicate refuses the byte at the URL-grammar layer
9444        // (GitHub / GitLab / Bitbucket silently ignore the
9445        // `?query` tail and serve the same repo regardless, so
9446        // the byte rides verbatim into the rendered `Chart.yaml`
9447        // `home:` and FluxCD `GitRepository` `url:` fields but
9448        // is silently masked at the wire — two manifest variants
9449        // whose values differ only in their query tail lock to
9450        // two distinct rendered artifacts for the byte-identical
9451        // clone, defeating the THEORY.md §V.2 render-determinism
9452        // contract on the `:repositorio` axis the peer `:fonte
9453        // :repo` axis already closes).
9454        let c = caixa_with_repositorio(Some(
9455            "https://github.com/pleme-io/hello-rio?tab=readme-ov-file",
9456        ));
9457        let err = c.validate_repositorio().unwrap_err();
9458        let ManifestError::RepositorioInvalid {
9459            repositorio,
9460            reason,
9461        } = err
9462        else {
9463            panic!("expected RepositorioInvalid, got {err:?}");
9464        };
9465        assert_eq!(
9466            repositorio,
9467            "https://github.com/pleme-io/hello-rio?tab=readme-ov-file"
9468        );
9469        assert!(
9470            reason.contains("must not contain `?`"),
9471            "reason must surface the query-`?` arm, got {reason:?}"
9472        );
9473    }
9474
9475    #[test]
9476    fn validate_repositorio_rejects_embedded_backslash() {
9477        // Windows-file-path-confusion footgun on the `:repositorio`
9478        // axis (peer with the prior fragment-`#` / query-`?` arms on
9479        // the same axis, and peer with the new dep-level `:fonte :repo`
9480        // backslash arm on the URL-grammar trajectory). An author
9481        // pastes a Windows Explorer address-bar `file:///C:\Users\me\
9482        // hello-rio` into the `:repositorio` slot, expecting the
9483        // `lareira-<nome>` chart's `home:` field and the FluxCD
9484        // `GitRepository` `url:` field to render the canonical local
9485        // file-URI. The shared `is_git_repo_url` predicate refuses
9486        // the byte at the URL-grammar layer (libcurl silently
9487        // translates `\` → `/` on some platforms and refuses it on
9488        // others, so the byte rides verbatim into the rendered
9489        // artifacts but is silently rewritten or rejected at the wire
9490        // — two manifest variants whose values differ only in
9491        // backslash-vs-forward-slash lock to two distinct rendered
9492        // artifacts for the byte-identical clone, defeating the
9493        // THEORY.md §V.2 render-determinism contract on the
9494        // `:repositorio` axis the peer `:fonte :repo` axis already
9495        // closes).
9496        let c = caixa_with_repositorio(Some("file:///C:\\Users\\me\\hello-rio"));
9497        let err = c.validate_repositorio().unwrap_err();
9498        let ManifestError::RepositorioInvalid {
9499            repositorio,
9500            reason,
9501        } = err
9502        else {
9503            panic!("expected RepositorioInvalid, got {err:?}");
9504        };
9505        assert_eq!(repositorio, "file:///C:\\Users\\me\\hello-rio");
9506        assert!(
9507            reason.contains("must not contain `\\`"),
9508            "reason must surface the backslash-`\\` arm, got {reason:?}"
9509        );
9510    }
9511
9512    #[test]
9513    fn validate_repositorio_rejects_uri_template_placeholder() {
9514        // URI Template (RFC 6570) placeholder footgun on the
9515        // `:repositorio` axis (peer with the prior fragment-`#` /
9516        // query-`?` / backslash-`\` arms on the same axis, and peer
9517        // with the new dep-level `:fonte :repo` `{` / `}` arm on the
9518        // URL-grammar trajectory). An author pastes a quick-start
9519        // README snippet / OpenAPI `servers:` URL / Helm chart
9520        // `home:` template carrying unresolved `{org}` / `{repo}`
9521        // placeholders into the `:repositorio` slot, expecting the
9522        // substrate to resolve the placeholder downstream. The
9523        // shared `is_git_repo_url` predicate refuses the byte at the
9524        // URL-grammar layer (libcurl percent-encodes `{` / `}` to
9525        // `%7B` / `%7D` on the wire, so the byte round-trips
9526        // inconsistently between the rendered `Chart.yaml home:` /
9527        // FluxCD `GitRepository url:` and the resolver's `git clone`
9528        // invocation, defeating the THEORY.md §V.2 render-
9529        // determinism contract on the `:repositorio` axis the peer
9530        // `:fonte :repo` axis already closes; every git porcelain
9531        // entry-point additionally fetches a nonexistent literal-
9532        // `{placeholder}`-named path far from the source caixa.lisp).
9533        let c = caixa_with_repositorio(Some("https://github.com/{org}/hello-rio"));
9534        let err = c.validate_repositorio().unwrap_err();
9535        let ManifestError::RepositorioInvalid {
9536            repositorio,
9537            reason,
9538        } = err
9539        else {
9540            panic!("expected RepositorioInvalid, got {err:?}");
9541        };
9542        assert_eq!(repositorio, "https://github.com/{org}/hello-rio");
9543        assert!(
9544            reason.contains("must not contain `{`"),
9545            "reason must surface the open-brace `{{` arm, got {reason:?}"
9546        );
9547        assert!(
9548            reason.contains("URI Template") || reason.contains("RFC 6570"),
9549            "reason must name the RFC 6570 URI Template grammar, got {reason:?}"
9550        );
9551    }
9552
9553    #[test]
9554    fn validate_repositorio_empty_takes_precedence_over_shape() {
9555        // Empty-first cascade pin: the empty `Some("")` surfaces the
9556        // narrower `RepositorioEmpty` not the shape-predicate-wrapped
9557        // `RepositorioInvalid`, mirroring the peer
9558        // `NomeEmpty` → `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid`,
9559        // `FonteRepoEmpty` → `FonteRepoInvalid` cascades. The shared
9560        // `is_git_repo_url` predicate also rejects the empty input
9561        // (defensively, with its own `"must not be empty"` reason),
9562        // but the manifest-layer empty arm runs first to surface the
9563        // narrower diagnostic verbatim.
9564        let c = caixa_with_repositorio(Some(""));
9565        let err = c.validate_repositorio().unwrap_err();
9566        assert!(
9567            matches!(err, ManifestError::RepositorioEmpty),
9568            "got {err:?}",
9569        );
9570    }
9571
9572    #[test]
9573    fn validate_repositorio_diagnostic_carries_offending_value() {
9574        // Diagnostic-shape pin (peer with
9575        // `validate_autores_diagnostic_carries_offending_author`): the
9576        // error's Display surfaces the offending value + slot name
9577        // verbatim, so a `feira lint` run can render the diagnostic
9578        // without re-parsing and the author can grep their caixa.lisp
9579        // for the offending `:repositorio` value.
9580        let c = caixa_with_repositorio(Some("pleme-io/hello-rio"));
9581        let rendered = c.validate_repositorio().unwrap_err().to_string();
9582        assert!(
9583            rendered.contains(":repositorio"),
9584            "diagnostic must name the offending slot: {rendered}",
9585        );
9586        assert!(
9587            rendered.contains("pleme-io/hello-rio"),
9588            "diagnostic must quote the offending value: {rendered}",
9589        );
9590    }
9591
9592    // ── validate_descricao — universal-axis Chart.yaml description shape ──
9593
9594    fn caixa_with_descricao(descricao: Option<&str>) -> Caixa {
9595        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9596        c.descricao = descricao.map(String::from);
9597        c
9598    }
9599
9600    #[test]
9601    fn validate_descricao_accepts_none() {
9602        // The omit-the-slot identity: `:descricao` is optional. The
9603        // gate is a no-op when the author didn't declare a value —
9604        // every caixa without a `:descricao` line trivially passes,
9605        // and the substrate-side renderers fall back to their
9606        // documented `caixa.nome`-derived placeholder. Mirrors the
9607        // peer `validate_repositorio_accepts_none` posture on the
9608        // sibling `Option<String>` Caixa slot.
9609        let c = caixa_with_descricao(None);
9610        c.validate_descricao().unwrap();
9611    }
9612
9613    #[test]
9614    fn validate_descricao_accepts_canonical_summary() {
9615        // Positive control: the canonical pleme-io descricao shape —
9616        // a short free-form prose summary — passes the gate. Covers
9617        // the fixture shapes the `caixa-helm` / `caixa-flux` /
9618        // `caixa-mesh` test fixtures use (`"Canonical Rust→wasm32-
9619        // wasip2 caixa Servico."`, `"Checkout flow."`).
9620        for desc in [
9621            "Canonical Rust→wasm32-wasip2 caixa Servico.",
9622            "Checkout flow.",
9623            "AWS provider caixa for tatara-lisp",
9624            "FIXME — describe this caixa",
9625            "x",
9626        ] {
9627            let c = caixa_with_descricao(Some(desc));
9628            c.validate_descricao()
9629                .unwrap_or_else(|err| panic!("canonical {desc:?} must pass: {err:?}"));
9630        }
9631    }
9632
9633    #[test]
9634    fn validate_descricao_rejects_empty_some() {
9635        // Canonical paste-from-blank-doc footgun. Without this gate
9636        // the empty `Some("")` silently passed the renderer's
9637        // `Option::unwrap_or_else(|| <fallback>)` (which only fires
9638        // on `None`) and landed as `description: ""` in `Chart.yaml`
9639        // and a blank `README.md` header. Mirrors the peer
9640        // [`ManifestError::RepositorioEmpty`] empty-arm on the
9641        // sibling `Option<String>` Caixa slot.
9642        let c = caixa_with_descricao(Some(""));
9643        let err = c.validate_descricao().unwrap_err();
9644        assert!(matches!(err, ManifestError::DescricaoEmpty), "got {err:?}",);
9645    }
9646
9647    #[test]
9648    fn validate_descricao_rejects_leading_whitespace() {
9649        // Paste-from-aligned-doc footgun: a leading ASCII space the
9650        // bare empty-arm gate accepted, the shape predicate now
9651        // refuses. The diagnostic carries the offending value
9652        // verbatim (with the leading space preserved) so the author
9653        // can grep their caixa.lisp for the exact `:descricao` line
9654        // and fix the round-trip-inconsistent leading whitespace.
9655        // Mirrors the peer
9656        // `validate_licenca_rejects_leading_whitespace` arm on the
9657        // sibling `:licenca` axis.
9658        let c = caixa_with_descricao(Some(" Checkout flow."));
9659        let err = c.validate_descricao().unwrap_err();
9660        let ManifestError::DescricaoInvalid { descricao, reason } = err else {
9661            panic!("expected DescricaoInvalid, got {err:?}");
9662        };
9663        assert_eq!(descricao, " Checkout flow.");
9664        assert!(reason.contains("whitespace"), "got: {reason:?}");
9665    }
9666
9667    #[test]
9668    fn validate_descricao_rejects_trailing_whitespace() {
9669        // Paste-from-doc footgun: a trailing ASCII space the bare
9670        // empty-arm gate accepted, the shape predicate now refuses.
9671        let c = caixa_with_descricao(Some("Checkout flow. "));
9672        let err = c.validate_descricao().unwrap_err();
9673        let ManifestError::DescricaoInvalid { descricao, reason } = err else {
9674            panic!("expected DescricaoInvalid, got {err:?}");
9675        };
9676        assert_eq!(descricao, "Checkout flow. ");
9677        assert!(reason.contains("whitespace"), "got: {reason:?}");
9678    }
9679
9680    #[test]
9681    fn validate_descricao_rejects_embedded_newline() {
9682        // Paste-from-multiline-doc footgun: an embedded LF the bare
9683        // empty-arm gate accepted, the shape predicate now refuses.
9684        // Without this gate the embedded newline silently landed in
9685        // the rendered Chart.yaml as a multi-line YAML block scalar,
9686        // and every chart-aware UI (`helm list`, `helm search`,
9687        // Artifact Hub) renders the description in a single-line
9688        // column so the embedded newline is silently dropped at
9689        // every downstream consumer.
9690        let c = caixa_with_descricao(Some("Checkout\nflow."));
9691        let err = c.validate_descricao().unwrap_err();
9692        assert!(
9693            matches!(err, ManifestError::DescricaoInvalid { .. }),
9694            "got {err:?}",
9695        );
9696        assert!(err.to_string().contains("newline"), "got {err}");
9697    }
9698
9699    #[test]
9700    fn validate_descricao_rejects_embedded_carriage_return() {
9701        // Paste-from-Windows-CRLF-doc footgun.
9702        let c = caixa_with_descricao(Some("Checkout\rflow."));
9703        let err = c.validate_descricao().unwrap_err();
9704        assert!(
9705            matches!(err, ManifestError::DescricaoInvalid { .. }),
9706            "got {err:?}",
9707        );
9708        assert!(err.to_string().contains("carriage return"), "got {err}");
9709    }
9710
9711    #[test]
9712    fn validate_descricao_rejects_embedded_tab() {
9713        // Tab-from-aligned-doc footgun.
9714        let c = caixa_with_descricao(Some("Checkout\tflow."));
9715        let err = c.validate_descricao().unwrap_err();
9716        assert!(
9717            matches!(err, ManifestError::DescricaoInvalid { .. }),
9718            "got {err:?}",
9719        );
9720        assert!(err.to_string().contains("tab"), "got {err}");
9721    }
9722
9723    #[test]
9724    fn validate_descricao_rejects_embedded_control_bytes() {
9725        // Paste-from-binary-blob footgun: every other control byte
9726        // (NUL, BEL, ESC, DEL) is refused at validate time. Mirrors
9727        // the peer SPDX-expression control-byte arm.
9728        for s in [
9729            "Checkout\x00flow.",
9730            "Checkout\x07flow.",
9731            "Checkout\x1bflow.",
9732            "Checkout\x7fflow.",
9733        ] {
9734            let c = caixa_with_descricao(Some(s));
9735            let err = c.validate_descricao().unwrap_err();
9736            assert!(
9737                matches!(err, ManifestError::DescricaoInvalid { .. }),
9738                "{s:?} got {err:?}",
9739            );
9740            assert!(
9741                err.to_string().contains("control character"),
9742                "{s:?} got {err}",
9743            );
9744        }
9745    }
9746
9747    #[test]
9748    fn validate_descricao_accepts_unicode_prose() {
9749        // Positive control: Unicode prose is accepted — the
9750        // canonical fixtures carry `→` (U+2192) and `—` (U+2014),
9751        // and `Caixa::template`'s `"FIXME — describe this caixa"`
9752        // scaffold every `feira init` emits must continue to pass.
9753        for s in [
9754            "Canonical Rust→wasm32-wasip2 caixa Servico.",
9755            "FIXME — describe this caixa",
9756            "Caixa pour le projet tâche",
9757            "日本語の説明",
9758        ] {
9759            let c = caixa_with_descricao(Some(s));
9760            c.validate_descricao()
9761                .unwrap_or_else(|err| panic!("Unicode {s:?} must pass: {err:?}"));
9762        }
9763    }
9764
9765    #[test]
9766    fn validate_descricao_empty_takes_precedence_over_shape() {
9767        // Cascade pin: a `Some("")` surfaces the narrower
9768        // `DescricaoEmpty` arm, not the broader `DescricaoInvalid`
9769        // shape-predicate arm. Mirrors the peer
9770        // `validate_licenca_empty_takes_precedence_over_shape` pin
9771        // on the sibling `:licenca` axis.
9772        let c = caixa_with_descricao(Some(""));
9773        let err = c.validate_descricao().unwrap_err();
9774        assert!(matches!(err, ManifestError::DescricaoEmpty), "got {err:?}",);
9775    }
9776
9777    #[test]
9778    fn validate_descricao_invalid_diagnostic_carries_offending_value_and_slot() {
9779        // Diagnostic-shape pin: the error's Display surfaces both
9780        // the `:descricao` slot name and the offending value
9781        // verbatim, so a `feira lint` run can render the diagnostic
9782        // without re-parsing and the author can grep their caixa.lisp
9783        // for the offending `:descricao` line. Mirrors the peer
9784        // `validate_licenca_invalid_diagnostic_carries_offending_value_and_slot`
9785        // pin (ee2e888) on the sibling `:licenca` axis.
9786        // The `{descricao:?}` Debug format escapes embedded control
9787        // bytes; the quoted offending value surfaces as
9788        // `"Checkout\nflow."` (literal backslash-n) in the rendered
9789        // diagnostic. The author can grep their caixa.lisp for the
9790        // literal `Checkout` summary prefix.
9791        let c = caixa_with_descricao(Some("Checkout\nflow."));
9792        let rendered = c.validate_descricao().unwrap_err().to_string();
9793        assert!(
9794            rendered.contains(":descricao"),
9795            "diagnostic must name the offending slot: {rendered}",
9796        );
9797        assert!(
9798            rendered.contains("Checkout\\nflow."),
9799            "diagnostic must quote the offending value (debug-escaped): {rendered}",
9800        );
9801    }
9802
9803    #[test]
9804    fn validate_descricao_template_passes() {
9805        // Round-trip pin: the bare `Caixa::template` shape carries
9806        // `:descricao "FIXME — describe this caixa"` (a non-empty
9807        // sentinel), so the template-derived Caixa passes the gate by
9808        // construction. A future template-shape change that omits or
9809        // empties `:descricao` would surface here as a regression.
9810        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9811        c.validate_descricao().unwrap();
9812    }
9813
9814    #[test]
9815    fn validate_descricao_diagnostic_names_offending_slot() {
9816        // Diagnostic-shape pin (peer with
9817        // `validate_repositorio_diagnostic_carries_offending_value`):
9818        // the error's Display surfaces the `:descricao` slot name
9819        // verbatim, so a `feira lint` run can render the diagnostic
9820        // without re-parsing and the author can grep their caixa.lisp
9821        // for the offending `:descricao` line.
9822        let c = caixa_with_descricao(Some(""));
9823        let rendered = c.validate_descricao().unwrap_err().to_string();
9824        assert!(
9825            rendered.contains(":descricao"),
9826            "diagnostic must name the offending slot: {rendered}",
9827        );
9828    }
9829
9830    // ── validate_licenca — universal-axis chart README license shape ──
9831
9832    fn caixa_with_licenca(licenca: Option<&str>) -> Caixa {
9833        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9834        c.licenca = licenca.map(String::from);
9835        c
9836    }
9837
9838    #[test]
9839    fn validate_licenca_accepts_none() {
9840        // The omit-the-slot identity: `:licenca` is optional. The
9841        // gate is a no-op when the author didn't declare a value —
9842        // every caixa without a `:licenca` line trivially passes,
9843        // and the substrate-side `caixa-helm` renderer falls back to
9844        // the documented `"MIT"` placeholder. Mirrors the peer
9845        // `validate_descricao_accepts_none` posture on the sibling
9846        // `Option<String>` Caixa slot.
9847        let c = caixa_with_licenca(None);
9848        c.validate_licenca().unwrap();
9849    }
9850
9851    #[test]
9852    fn validate_licenca_accepts_canonical_expressions() {
9853        // Positive control: every canonical SPDX expression shape
9854        // pleme-io carries in its existing fixtures + the canonical
9855        // SPDX dual-license / with-exception / `+`-suffix / grouped /
9856        // user-defined-reference shapes all pass the gate. Covers
9857        // the single-license, `OR`-compound, `AND`-compound,
9858        // `WITH`-exception, parenthesis-grouped, `+`-suffix, and
9859        // `LicenseRef-` / `DocumentRef-:LicenseRef-` shapes — every
9860        // production the SPDX 2.1 expression grammar admits that
9861        // sits within the alphabet floor the
9862        // `is_spdx_expression_shape` predicate enforces.
9863        for lic in [
9864            "MIT",
9865            "Apache-2.0",
9866            "Apache-2.0 OR MIT",
9867            "Apache-2.0 AND MIT",
9868            "BSD-3-Clause",
9869            "MPL-2.0",
9870            "GPL-3.0-or-later",
9871            "GPL-2.0+",
9872            "Apache-2.0 WITH LLVM-exception",
9873            "(MIT OR Apache-2.0) AND BSD-3-Clause",
9874            "(MIT OR Apache-2.0) AND BSD-3-Clause AND ISC",
9875            "LicenseRef-MyLicense",
9876            "DocumentRef-spdx-tool:LicenseRef-MIT-Style",
9877            "x",
9878        ] {
9879            let c = caixa_with_licenca(Some(lic));
9880            c.validate_licenca()
9881                .unwrap_or_else(|err| panic!("canonical {lic:?} must pass: {err:?}"));
9882        }
9883    }
9884
9885    #[test]
9886    fn validate_licenca_rejects_trailing_whitespace() {
9887        // Paste-from-doc whitespace footgun. A trailing space in the
9888        // `:licenca` value would silently break a downstream SPDX
9889        // parser that splits on exact `AND` / `OR` / `WITH` keyword
9890        // boundaries. The shape predicate refuses every trailing
9891        // whitespace byte by construction. Peer with
9892        // `validate_repositorio_rejects_whitespace` and
9893        // `validate_edicao_rejects_trailing_whitespace`.
9894        let c = caixa_with_licenca(Some("MIT "));
9895        let err = c.validate_licenca().unwrap_err();
9896        let ManifestError::LicencaInvalid { licenca, .. } = err else {
9897            panic!("expected LicencaInvalid, got {err:?}");
9898        };
9899        assert_eq!(licenca, "MIT ");
9900    }
9901
9902    #[test]
9903    fn validate_licenca_rejects_leading_whitespace() {
9904        // Symmetric paste-from-doc whitespace footgun on the leading
9905        // boundary — the gate refuses every shape that starts with a
9906        // space byte by construction. Peer with
9907        // `validate_edicao_rejects_leading_whitespace`.
9908        let c = caixa_with_licenca(Some(" MIT"));
9909        let err = c.validate_licenca().unwrap_err();
9910        assert!(
9911            matches!(err, ManifestError::LicencaInvalid { .. }),
9912            "got {err:?}",
9913        );
9914    }
9915
9916    #[test]
9917    fn validate_licenca_rejects_control_char() {
9918        // Paste-from-multiline-doc CRLF footgun — control characters
9919        // at the value boundary land as a malformed line in the
9920        // rendered chart `README.md` `## License` section. Peer with
9921        // `validate_repositorio_rejects_control_char` and
9922        // `validate_edicao_rejects_control_char`.
9923        for lic in ["MIT\n", "MIT\r\n", "MIT\rApache-2.0"] {
9924            let c = caixa_with_licenca(Some(lic));
9925            let err = c.validate_licenca().unwrap_err();
9926            assert!(
9927                matches!(err, ManifestError::LicencaInvalid { .. }),
9928                "expected LicencaInvalid on {lic:?}, got {err:?}",
9929            );
9930        }
9931    }
9932
9933    #[test]
9934    fn validate_licenca_rejects_tab() {
9935        // Tab-from-aligned-doc footgun — SPDX expressions use a
9936        // single ASCII space between tokens; a tab breaks every
9937        // downstream SPDX parser that splits on exact `" "`
9938        // boundaries.
9939        let c = caixa_with_licenca(Some("MIT\tOR Apache-2.0"));
9940        let err = c.validate_licenca().unwrap_err();
9941        assert!(
9942            matches!(err, ManifestError::LicencaInvalid { .. }),
9943            "got {err:?}",
9944        );
9945    }
9946
9947    #[test]
9948    fn validate_licenca_rejects_non_ascii() {
9949        // Smart-quote / non-ASCII paste footgun — SPDX identifiers
9950        // are ASCII per the `idstring = 1*(ALPHA / DIGIT / "-" /
9951        // ".")` production. The shape predicate refuses every
9952        // non-ASCII byte by construction; peer with
9953        // `validate_edicao_rejects_non_ascii_lookalike`.
9954        for lic in ["MIT\u{a0}OR Apache-2.0", "MIT\u{2013}1.0", "Café-1.0"] {
9955            let c = caixa_with_licenca(Some(lic));
9956            let err = c.validate_licenca().unwrap_err();
9957            assert!(
9958                matches!(err, ManifestError::LicencaInvalid { .. }),
9959                "expected LicencaInvalid on {lic:?}, got {err:?}",
9960            );
9961        }
9962    }
9963
9964    #[test]
9965    fn validate_licenca_rejects_underscore() {
9966        // Underscore-instead-of-hyphen typo footgun — `Apache_2.0` /
9967        // `MIT_Style` / `BSD_3_Clause` are familiar shapes from
9968        // snake-case identifier conventions that don't apply to the
9969        // SPDX `idstring` grammar (which admits only `ALPHA / DIGIT /
9970        // "-" / "."`). The shape predicate refuses every underscore
9971        // byte by construction.
9972        for lic in ["Apache_2.0", "MIT_Style", "BSD_3_Clause"] {
9973            let c = caixa_with_licenca(Some(lic));
9974            let err = c.validate_licenca().unwrap_err();
9975            assert!(
9976                matches!(err, ManifestError::LicencaInvalid { .. }),
9977                "expected LicencaInvalid on {lic:?}, got {err:?}",
9978            );
9979        }
9980    }
9981
9982    #[test]
9983    fn validate_licenca_rejects_comma_separator() {
9984        // Comma-instead-of-`OR`-keyword colloquial idiom footgun —
9985        // SPDX expressions compose multiple licenses via `AND` / `OR`
9986        // keywords, not the comma separator. The shape predicate
9987        // refuses every comma byte by construction.
9988        for lic in ["MIT, Apache-2.0", "MIT,Apache-2.0"] {
9989            let c = caixa_with_licenca(Some(lic));
9990            let err = c.validate_licenca().unwrap_err();
9991            assert!(
9992                matches!(err, ManifestError::LicencaInvalid { .. }),
9993                "expected LicencaInvalid on {lic:?}, got {err:?}",
9994            );
9995        }
9996    }
9997
9998    #[test]
9999    fn validate_licenca_rejects_slash_dual_license() {
10000        // Slash-dual-license colloquial idiom footgun — the
10001        // `MIT/Apache-2.0` shape is common in Cargo's pre-SPDX
10002        // `package.license` field but non-SPDX; the SPDX equivalent
10003        // is `MIT OR Apache-2.0`. The shape predicate refuses every
10004        // forward-slash byte by construction.
10005        for lic in ["MIT/Apache-2.0", "MIT/BSD-3-Clause"] {
10006            let c = caixa_with_licenca(Some(lic));
10007            let err = c.validate_licenca().unwrap_err();
10008            assert!(
10009                matches!(err, ManifestError::LicencaInvalid { .. }),
10010                "expected LicencaInvalid on {lic:?}, got {err:?}",
10011            );
10012        }
10013    }
10014
10015    #[test]
10016    fn validate_licenca_rejects_semicolon_separator() {
10017        // Semicolon-list-separator confusion footgun — adjacent to
10018        // the comma-separator idiom, every list-separator-belongs-
10019        // to-list-grammar confusion lands here.
10020        let c = caixa_with_licenca(Some("MIT; Apache-2.0"));
10021        let err = c.validate_licenca().unwrap_err();
10022        assert!(
10023            matches!(err, ManifestError::LicencaInvalid { .. }),
10024            "got {err:?}",
10025        );
10026    }
10027
10028    #[test]
10029    fn validate_licenca_empty_takes_precedence_over_shape() {
10030        // Empty-first cascade pin: the empty `Some("")` surfaces the
10031        // narrower `LicencaEmpty` not the shape-predicate-wrapped
10032        // `LicencaInvalid`, mirroring the peer
10033        // `validate_edicao_empty_takes_precedence_over_shape` and
10034        // `validate_repositorio_empty_takes_precedence_over_shape`
10035        // (`RepositorioEmpty` → `RepositorioInvalid`), `NomeEmpty` →
10036        // `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid` cascades.
10037        // The shape predicate also refuses the empty input
10038        // (defensively — `"must not be empty"`), but the manifest-
10039        // layer empty arm runs first to surface the narrower
10040        // diagnostic verbatim.
10041        let c = caixa_with_licenca(Some(""));
10042        let err = c.validate_licenca().unwrap_err();
10043        assert!(matches!(err, ManifestError::LicencaEmpty), "got {err:?}",);
10044    }
10045
10046    #[test]
10047    fn validate_licenca_invalid_diagnostic_carries_offending_value() {
10048        // Diagnostic-shape pin on the shape-predicate arm (peer with
10049        // `validate_edicao_invalid_diagnostic_carries_offending_value`
10050        // and `validate_repositorio_diagnostic_carries_offending_value`):
10051        // the error's Display surfaces the offending value + slot
10052        // name verbatim, so a `feira lint` run can render the
10053        // diagnostic without re-parsing and the author can grep
10054        // their caixa.lisp for the offending `:licenca` value.
10055        let c = caixa_with_licenca(Some("Apache_2.0"));
10056        let rendered = c.validate_licenca().unwrap_err().to_string();
10057        assert!(
10058            rendered.contains(":licenca"),
10059            "diagnostic must name the offending slot: {rendered}",
10060        );
10061        assert!(
10062            rendered.contains("Apache_2.0"),
10063            "diagnostic must quote the offending value: {rendered}",
10064        );
10065    }
10066
10067    #[test]
10068    fn validate_licenca_rejects_empty_some() {
10069        // Canonical paste-from-blank-doc footgun. Without this gate
10070        // the empty `Some("")` silently passed the renderer's
10071        // `Option::unwrap_or_else(|| "MIT".into())` (which only
10072        // fires on `None`) and landed as a bare trailing period in
10073        // the rendered chart `README.md` `## License` section.
10074        // Mirrors the peer [`ManifestError::DescricaoEmpty`] empty-
10075        // arm on the sibling `Option<String>` Caixa slot.
10076        let c = caixa_with_licenca(Some(""));
10077        let err = c.validate_licenca().unwrap_err();
10078        assert!(matches!(err, ManifestError::LicencaEmpty), "got {err:?}",);
10079    }
10080
10081    #[test]
10082    fn validate_licenca_template_passes() {
10083        // Round-trip pin: the bare `Caixa::template` shape (whether
10084        // it carries `:licenca` or omits it) passes the gate by
10085        // construction. A future template-shape change that
10086        // introduced `(:licenca "")` would surface here as a
10087        // regression. Mirrors the peer
10088        // `validate_descricao_template_passes` pin.
10089        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10090        c.validate_licenca().unwrap();
10091    }
10092
10093    #[test]
10094    fn validate_licenca_diagnostic_names_offending_slot() {
10095        // Diagnostic-shape pin (peer with
10096        // `validate_descricao_diagnostic_names_offending_slot`):
10097        // the error's Display surfaces the `:licenca` slot name
10098        // verbatim, so a `feira lint` run can render the diagnostic
10099        // without re-parsing and the author can grep their caixa.lisp
10100        // for the offending `:licenca` line.
10101        let c = caixa_with_licenca(Some(""));
10102        let rendered = c.validate_licenca().unwrap_err().to_string();
10103        assert!(
10104            rendered.contains(":licenca"),
10105            "diagnostic must name the offending slot: {rendered}",
10106        );
10107    }
10108
10109    // ── Caixa::licenca — outer top-level Option<&str> scalar accessor ──
10110
10111    #[test]
10112    fn licenca_returns_licenca_byte_string_verbatim_across_permutations() {
10113        // The canonical per-`Caixa` `:licenca` SPDX-expression scalar
10114        // pin: [`Caixa::licenca`] must return the `:licenca` typed
10115        // byte-string verbatim as an `Option<&str>`, byte-equal to the
10116        // raw `self.licenca.as_deref()` access across every
10117        // representative value in the accept-set — `None` (the "omit
10118        // the slot to defer to the caixa-helm renderer's `MIT`
10119        // fallback" arm every existing fixture without a `:licenca`
10120        // line carries), `Some("")` (a past-the-guard sentinel that
10121        // pins the accessor doesn't perform a silent
10122        // `Some("") → None` collapse on the empty arm — validate
10123        // rejects `Some("")` through `LicencaEmpty` but the accessor
10124        // must ship the raw slot verbatim so a validate-time gate
10125        // regression surfaces at the caixa-helm emit boundary rather
10126        // than being silently absorbed into the fallback), `Some("MIT")`
10127        // (the canonical single-license shape every `feira init`
10128        // template scaffolds), `Some("Apache-2.0 OR MIT")` (the
10129        // canonical `OR`-compound shape the peer
10130        // `validate_licenca_accepts_canonical_expressions` positive
10131        // sweep exercises), `Some("(MIT OR Apache-2.0) AND
10132        // BSD-3-Clause")` (the canonical parenthesis-grouped shape),
10133        // `Some("MIT ")` / `Some(" MIT")` / `Some("MIT\n")` /
10134        // `Some("Apache_2.0")` / `Some("MIT,Apache-2.0")` (past-the-
10135        // guard sentinels — validate rejects each through
10136        // `LicencaInvalid` but the accessor must ship the raw slot
10137        // verbatim).
10138        //
10139        // First outer top-level [`Caixa`] `Option<&str>`-return scalar
10140        // accessor pin on the substrate primitive — opens the "outer
10141        // [`Caixa`] `Option<&str>` scalar" projection pattern the
10142        // sibling per-`Caixa` `:descricao` / `:repositorio` / `:edicao`
10143        // future lifts fold on. Sibling in shape to the peer per-`:placement`
10144        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
10145        // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
10146        // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
10147        // axes, extended onto the outer top-level [`Caixa`] universal-
10148        // axis surface. Pins against a future silent detour that
10149        // returned an owned `Option<String>` (which would type-check
10150        // but silently allocate on every accessor call, breaking the
10151        // zero-cost projection every peer sibling accessor carries), a
10152        // `Some("") → None` collapse (which would silently absorb the
10153        // `LicencaEmpty` refusal case at the accessor boundary and the
10154        // caixa-helm emit path would silently fall back to `"MIT"` on
10155        // a struct-literal `Caixa { licenca: Some(""), .. }`), or a
10156        // `None → Some("MIT")` collapse (which would silently reify
10157        // the caixa-helm renderer's `"MIT"` fallback at the accessor
10158        // boundary and every downstream consumer keying off the
10159        // `Option::is_none()` discriminator would lose the "author
10160        // omitted the slot" signal).
10161        for licenca in [
10162            None,
10163            Some(""),
10164            Some("MIT"),
10165            Some("Apache-2.0 OR MIT"),
10166            Some("(MIT OR Apache-2.0) AND BSD-3-Clause"),
10167            Some("MIT "),
10168            Some(" MIT"),
10169            Some("MIT\n"),
10170            Some("Apache_2.0"),
10171            Some("MIT,Apache-2.0"),
10172        ] {
10173            let c = caixa_with_licenca(licenca);
10174            assert_eq!(
10175                c.licenca(),
10176                licenca,
10177                "Caixa::licenca must return :licenca verbatim (got {:?}, \
10178                 expected {licenca:?})",
10179                c.licenca(),
10180            );
10181            assert_eq!(
10182                c.licenca(),
10183                c.licenca.as_deref(),
10184                "Caixa::licenca must byte-equal the raw \
10185                 `self.licenca.as_deref()` field access across every \
10186                 value in the Option<&str> accept-set",
10187            );
10188        }
10189    }
10190
10191    #[test]
10192    fn validate_licenca_empty_arm_routes_through_accessor() {
10193        // Composition pin: [`Caixa::validate_licenca`]'s empty-arm gate
10194        // must key off [`Caixa::licenca`], not the raw
10195        // `self.licenca.as_deref()` field access. Structurally: a
10196        // `Caixa { licenca: Some(""), .. }` must surface the
10197        // `LicencaEmpty` refusal exactly, and a
10198        // `Caixa { licenca: Some("MIT"), .. }` (the canonical
10199        // single-license form) must pass validate. The pair jointly
10200        // pins the accessor + validate-gate composition: any future
10201        // silent detour that had the accessor return `None` on the
10202        // empty arm (a `.filter(|s| !s.is_empty())` collapse) would
10203        // silently absorb the `LicencaEmpty` refusal at the accessor
10204        // boundary and the validate gate would accept a struct-literal
10205        // `Caixa { licenca: Some(""), .. }` — the composition pin
10206        // catches that at caixa-core build time.
10207        //
10208        // Peer of the per-`:politicas :circuit-breaker`
10209        // [`crate::aplicacao::CircuitBreaker::max_failures`] (3a74062)
10210        // accessor-composition pin
10211        // (`validate_politicas_max_failures_zero_floor_arm_routes_through_accessor`)
10212        // on the sibling per-M3-mesh-slot required-`u32` axis — same
10213        // "the validate / shape-gate predicate must route through the
10214        // substrate-primitive typed dispatch" discipline extended onto
10215        // the outer top-level [`Caixa`] universal-axis
10216        // `Option<&str>`-composition surface.
10217        let c = caixa_with_licenca(Some(""));
10218        assert!(
10219            matches!(c.validate_licenca(), Err(ManifestError::LicencaEmpty)),
10220            "validate_licenca must reject licenca == Some(\"\") with \
10221             LicencaEmpty — the accessor and the validate gate must \
10222             route through the same substrate-primitive typed dispatch \
10223             on the :licenca empty arm",
10224        );
10225        let c = caixa_with_licenca(Some("MIT"));
10226        assert!(
10227            c.validate_licenca().is_ok(),
10228            "validate_licenca must accept licenca == Some(\"MIT\") \
10229             (the canonical single-license SPDX shape)",
10230        );
10231    }
10232
10233    #[test]
10234    fn licenca_projects_option_str_by_borrow() {
10235        // The by-borrow pin: [`Caixa::licenca`] returns
10236        // `Option<&str>` by borrow — the `&str` borrows the underlying
10237        // `String` storage of the `Option<String>` slot and the
10238        // accessor must not allocate a fresh `String` on every call.
10239        // Peer of the per-`:placement`
10240        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
10241        // borrow pin on the peer per-M3-mesh-slot
10242        // `Option<&str>`-return axis, extended onto the outer top-
10243        // level [`Caixa`] universal-axis `Option<&str>` shape — the
10244        // accessor's returned `&str` must borrow from `&self` (the
10245        // returned reference's lifetime is tied to `&self`), and
10246        // calling the accessor twice on the same [`Caixa`] must yield
10247        // the same `Option<&str>` verbatim (idempotent, no side
10248        // effects on `&self`).
10249        //
10250        // Pins against a future silent detour that returned an owned
10251        // `Option<String>` (which would type-check but silently
10252        // allocate on every call, breaking the zero-cost projection
10253        // every peer sibling accessor carries), or a one-arm-only
10254        // accessor that returned a saturating value on some sentinel
10255        // input (breaking the pass-through invariant the sibling
10256        // required-scalar accessors carry).
10257        for licenca in [None, Some(""), Some("MIT"), Some("Apache-2.0 OR MIT")] {
10258            let c = caixa_with_licenca(licenca);
10259            let first = c.licenca();
10260            let second = c.licenca();
10261            assert_eq!(
10262                first, second,
10263                "Caixa::licenca must be idempotent — two successive \
10264                 calls on the same &self must return the same \
10265                 Option<&str>",
10266            );
10267            assert_eq!(
10268                first, licenca,
10269                "Caixa::licenca must return :licenca verbatim by \
10270                 borrow — got {first:?}, expected {licenca:?}",
10271            );
10272        }
10273    }
10274
10275    // ── Caixa::repositorio — outer top-level Option<&str> scalar accessor ──
10276
10277    #[test]
10278    fn repositorio_returns_repositorio_byte_string_verbatim_across_permutations() {
10279        // The canonical per-`Caixa` `:repositorio` git-repo-URL scalar
10280        // pin: [`Caixa::repositorio`] must return the `:repositorio`
10281        // typed byte-string verbatim as an `Option<&str>`, byte-equal
10282        // to the raw `self.repositorio.as_deref()` access across every
10283        // representative value in the accept-set — `None` (the "omit
10284        // the slot to defer to the per-renderer placeholder" arm every
10285        // existing fixture without a `:repositorio` line carries),
10286        // `Some("")` (a past-the-guard sentinel that pins the accessor
10287        // doesn't perform a silent `Some("") → None` collapse on the
10288        // empty arm — validate rejects `Some("")` through
10289        // `RepositorioEmpty` but the accessor must ship the raw slot
10290        // verbatim so a validate-time gate regression surfaces at the
10291        // caixa-helm / caixa-flux emit boundary rather than being
10292        // silently absorbed into the per-renderer fallback),
10293        // `Some("github:pleme-io/hello-rio")` (the canonical `github:`
10294        // shorthand every existing manifest fixture across
10295        // `caixa-helm` / `caixa-mesh` and the `examples/` uses),
10296        // `Some("https://github.com/pleme-io/checkout")` (the canonical
10297        // `https://` URL the README quickstart uses),
10298        // `Some("ssh://git@github.com/pleme-io/checkout.git")` /
10299        // `Some("git://github.com/pleme-io/checkout.git")` /
10300        // `Some("git@github.com:pleme-io/checkout.git")` /
10301        // `Some("file:///opt/mirrors/pleme-io/checkout")` (every non-
10302        // github scheme the shared `is_git_repo_url` predicate
10303        // documents), and five past-the-guard sentinels for the
10304        // `RepositorioInvalid` refusal cases (`Some("pleme-io/checkout")`
10305        // missing-colon, `Some("-upload-pack=evil")` leading-dash, /
10306        // `Some("github:pleme-io/checkout?ref=main")` query-string, /
10307        // `Some("github:pleme-io/checkout#main")` fragment-anchor, /
10308        // `Some("github:pleme-io/{tpl}")` URI-template-placeholder — the
10309        // sentinels pin the accessor doesn't silently absorb the
10310        // refusal cases into a fallback).
10311        //
10312        // Second outer top-level [`Caixa`] `Option<&str>`-return scalar
10313        // accessor pin on the substrate primitive — sibling of the peer
10314        // [`Caixa::licenca`] (6d5bc28) pin
10315        // (`licenca_returns_licenca_byte_string_verbatim_across_permutations`)
10316        // that opened the "outer [`Caixa`] `Option<&str>` scalar"
10317        // projection pin pattern this pin folds on. Sibling in shape to
10318        // the peer per-`:placement`
10319        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
10320        // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
10321        // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
10322        // axes, extended onto the outer top-level [`Caixa`] universal-
10323        // axis surface. Pins against a future silent detour that
10324        // returned an owned `Option<String>` (which would type-check
10325        // but silently allocate on every accessor call, breaking the
10326        // zero-cost projection every peer sibling accessor carries), a
10327        // `Some("") → None` collapse (which would silently absorb the
10328        // `RepositorioEmpty` refusal case at the accessor boundary and
10329        // the caixa-helm `Chart.yaml` `home:` fold would silently
10330        // render a `home: null` / omitted field on a struct-literal
10331        // `Caixa { repositorio: Some(""), .. }`), or a
10332        // `None → Some(<default>)` collapse (which would silently reify
10333        // the per-renderer fallback at the accessor boundary and every
10334        // downstream consumer keying off the `Option::is_none()`
10335        // discriminator would lose the "author omitted the slot"
10336        // signal).
10337        for repositorio in [
10338            None,
10339            Some(""),
10340            Some("github:pleme-io/hello-rio"),
10341            Some("https://github.com/pleme-io/checkout"),
10342            Some("ssh://git@github.com/pleme-io/checkout.git"),
10343            Some("git://github.com/pleme-io/checkout.git"),
10344            Some("git@github.com:pleme-io/checkout.git"),
10345            Some("file:///opt/mirrors/pleme-io/checkout"),
10346            Some("pleme-io/checkout"),
10347            Some("-upload-pack=evil"),
10348            Some("github:pleme-io/checkout?ref=main"),
10349            Some("github:pleme-io/checkout#main"),
10350            Some("github:pleme-io/{tpl}"),
10351        ] {
10352            let c = caixa_with_repositorio(repositorio);
10353            assert_eq!(
10354                c.repositorio(),
10355                repositorio,
10356                "Caixa::repositorio must return :repositorio verbatim \
10357                 (got {:?}, expected {repositorio:?})",
10358                c.repositorio(),
10359            );
10360            assert_eq!(
10361                c.repositorio(),
10362                c.repositorio.as_deref(),
10363                "Caixa::repositorio must byte-equal the raw \
10364                 `self.repositorio.as_deref()` field access across every \
10365                 value in the Option<&str> accept-set",
10366            );
10367        }
10368    }
10369
10370    #[test]
10371    fn validate_repositorio_empty_arm_routes_through_accessor() {
10372        // Composition pin: [`Caixa::validate_repositorio`]'s empty-arm
10373        // gate must key off [`Caixa::repositorio`], not the raw
10374        // `self.repositorio.as_deref()` field access. Structurally: a
10375        // `Caixa { repositorio: Some(""), .. }` must surface the
10376        // `RepositorioEmpty` refusal exactly, and a
10377        // `Caixa { repositorio: Some("github:pleme-io/hello-rio"), .. }`
10378        // (the canonical `github:` shorthand form) must pass validate.
10379        // The pair jointly pins the accessor + validate-gate
10380        // composition: any future silent detour that had the accessor
10381        // return `None` on the empty arm (a `.filter(|s| !s.is_empty())`
10382        // collapse) would silently absorb the `RepositorioEmpty` refusal
10383        // at the accessor boundary and the validate gate would accept a
10384        // struct-literal `Caixa { repositorio: Some(""), .. }` — the
10385        // composition pin catches that at caixa-core build time.
10386        //
10387        // Peer of the [`Caixa::licenca`] (6d5bc28)
10388        // `validate_licenca_empty_arm_routes_through_accessor`
10389        // composition pin on the sibling outer top-level [`Caixa`]
10390        // `Option<&str>` universal-axis surface — same "the validate /
10391        // shape-gate predicate must route through the substrate-
10392        // primitive typed dispatch" discipline extended onto the second
10393        // outer top-level [`Caixa`] universal-axis `Option<&str>`-
10394        // composition surface.
10395        let c = caixa_with_repositorio(Some(""));
10396        assert!(
10397            matches!(
10398                c.validate_repositorio(),
10399                Err(ManifestError::RepositorioEmpty),
10400            ),
10401            "validate_repositorio must reject repositorio == Some(\"\") \
10402             with RepositorioEmpty — the accessor and the validate gate \
10403             must route through the same substrate-primitive typed \
10404             dispatch on the :repositorio empty arm",
10405        );
10406        let c = caixa_with_repositorio(Some("github:pleme-io/hello-rio"));
10407        assert!(
10408            c.validate_repositorio().is_ok(),
10409            "validate_repositorio must accept repositorio == \
10410             Some(\"github:pleme-io/hello-rio\") (the canonical \
10411             `github:` shorthand git-repo-URL shape)",
10412        );
10413    }
10414
10415    #[test]
10416    fn repositorio_projects_option_str_by_borrow() {
10417        // The by-borrow pin: [`Caixa::repositorio`] returns
10418        // `Option<&str>` by borrow — the `&str` borrows the underlying
10419        // `String` storage of the `Option<String>` slot and the
10420        // accessor must not allocate a fresh `String` on every call.
10421        // Peer of the per-`:placement`
10422        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) and the
10423        // [`Caixa::licenca`] (6d5bc28) by-borrow pins on the peer
10424        // `Option<&str>`-return axes, extended onto the second outer
10425        // top-level [`Caixa`] universal-axis `Option<&str>` shape —
10426        // the accessor's returned `&str` must borrow from `&self` (the
10427        // returned reference's lifetime is tied to `&self`), and
10428        // calling the accessor twice on the same [`Caixa`] must yield
10429        // the same `Option<&str>` verbatim (idempotent, no side effects
10430        // on `&self`).
10431        //
10432        // Pins against a future silent detour that returned an owned
10433        // `Option<String>` (which would type-check but silently
10434        // allocate on every call, breaking the zero-cost projection
10435        // every peer sibling accessor carries), or a one-arm-only
10436        // accessor that returned a saturating value on some sentinel
10437        // input (breaking the pass-through invariant the sibling
10438        // required-scalar accessors carry).
10439        for repositorio in [
10440            None,
10441            Some(""),
10442            Some("github:pleme-io/hello-rio"),
10443            Some("https://github.com/pleme-io/checkout"),
10444        ] {
10445            let c = caixa_with_repositorio(repositorio);
10446            let first = c.repositorio();
10447            let second = c.repositorio();
10448            assert_eq!(
10449                first, second,
10450                "Caixa::repositorio must be idempotent — two successive \
10451                 calls on the same &self must return the same \
10452                 Option<&str>",
10453            );
10454            assert_eq!(
10455                first, repositorio,
10456                "Caixa::repositorio must return :repositorio verbatim by \
10457                 borrow — got {first:?}, expected {repositorio:?}",
10458            );
10459        }
10460    }
10461
10462    // ── Caixa::canonical_git_url — resolved-git-URL composer ──────────
10463
10464    #[test]
10465    fn canonical_git_url_returns_repositorio_verbatim_on_some_arm() {
10466        // Fail-before-pass-after pin: [`Caixa::canonical_git_url`] must
10467        // return the author-declared `:repositorio` byte-string verbatim
10468        // on the `Some` arm — no scheme rewrite, no trailing-slash
10469        // canonicalization, no `github:` → `https://github.com/`
10470        // desugaring. The resolved-URL composer is the projection of
10471        // the raw [`Caixa::repositorio`] `Option<&str>` accessor onto
10472        // the `String`-return arity every substrate-side field-fill
10473        // consumer keys off; on the `Some` arm the projection is
10474        // `str::to_owned` verbatim, so every accept-set value the
10475        // sibling `repositorio_returns_repositorio_byte_string_verbatim_
10476        // across_permutations` pin covers (`https://…`, `github:…`,
10477        // `ssh://…`, `git://…`, `git@…`, `file://…`, and the past-the-
10478        // guard sentinel `pleme-io/…`) must survive the accessor
10479        // byte-equal. Pins against a future silent detour that rewrote
10480        // the `github:` shorthand to the `https://github.com/` full URL
10481        // at the accessor boundary (which would silently split the
10482        // resolved-URL surface from the raw [`Caixa::repositorio`]
10483        // accessor's documented pass-through invariant), or a trailing-
10484        // slash normalization (which would silently break the
10485        // FluxCD `GitRepository` `spec.url` byte-exact match every
10486        // downstream consumer keys the source-controller reconcile off).
10487        for repositorio in [
10488            "github:pleme-io/hello-rio",
10489            "https://github.com/pleme-io/checkout",
10490            "ssh://git@github.com/pleme-io/checkout.git",
10491            "git://github.com/pleme-io/checkout.git",
10492            "git@github.com:pleme-io/checkout.git",
10493            "file:///opt/mirrors/pleme-io/checkout",
10494        ] {
10495            let c = caixa_with_repositorio(Some(repositorio));
10496            assert_eq!(
10497                c.canonical_git_url(),
10498                repositorio,
10499                "Caixa::canonical_git_url on the Some arm must return \
10500                 :repositorio verbatim (got {:?}, expected {repositorio:?})",
10501                c.canonical_git_url(),
10502            );
10503        }
10504    }
10505
10506    #[test]
10507    fn canonical_git_url_falls_back_to_pleme_org_url_on_none_arm() {
10508        // Fail-before-pass-after pin: [`Caixa::canonical_git_url`] on the
10509        // `None` arm must emit the substrate's canonical pleme-org github
10510        // URL derived from `caixa.nome()` — `https://github.com/<org>/
10511        // <nome>` with `<org>` bound to [`crate::DEFAULT_PLEME_GIT_ORG`]
10512        // and `<nome>` bound to the typed [`Caixa::nome`] accessor. This
10513        // is the exact byte-image of the prior inline
10514        // [`caixa-flux::ClusterBundleOpts::for_caixa`] `git_url`
10515        // composer at caixa-flux/src/lib.rs:2080 that every prior caller
10516        // re-derived open-coded. Pins against a future silent detour
10517        // that migrated the `<org>` segment to a different constant (a
10518        // fork rebranding that split off a new
10519        // `DEFAULT_PLEME_GIT_ORG_MIRROR` const the accessor would need
10520        // to migrate onto), a scheme change (`https://` → `git://` or
10521        // `ssh://`), or a per-`Caixa` `.canonical_git_url_prefix`
10522        // override (which would break the substrate-wide single-source-
10523        // of-truth guarantee this method encodes).
10524        let c = caixa_with_repositorio(None);
10525        let expected = format!(
10526            "https://github.com/{org}/{nome}",
10527            org = crate::DEFAULT_PLEME_GIT_ORG,
10528            nome = c.nome(),
10529        );
10530        assert_eq!(
10531            c.canonical_git_url(),
10532            expected,
10533            "Caixa::canonical_git_url on the None arm must fold through \
10534             the substrate's canonical pleme-org github URL fallback \
10535             `https://github.com/<DEFAULT_PLEME_GIT_ORG>/<nome>` — got \
10536             {:?}, expected {expected:?}",
10537            c.canonical_git_url(),
10538        );
10539    }
10540
10541    #[test]
10542    fn canonical_git_url_byte_matches_manual_composition() {
10543        // Byte-parity pin: [`Caixa::canonical_git_url`] must render
10544        // byte-identically to the manual open-coded
10545        // `caixa.repositorio().map(str::to_owned).unwrap_or_else(||
10546        //  format!("https://github.com/{org}/{nome}", ...))` composition
10547        // every prior substrate-side caller re-derived. Guards the
10548        // paired-site convergence just applied at caixa-flux's
10549        // [`ClusterBundleOpts::for_caixa`] `git_url` composer (which
10550        // now routes through this accessor): a future implementation of
10551        // this method that reordered the format arguments, swapped the
10552        // `<org>` constant for a different one, or interposed a
10553        // canonicalization pass on the `Some` arm surfaces here as a
10554        // caixa-core build-time test failure rather than as a downstream
10555        // FluxCD `GitRepository` reconcile mismatch far from this
10556        // method's source.
10557        for repositorio in [
10558            None,
10559            Some("github:pleme-io/hello-rio"),
10560            Some("https://github.com/pleme-io/checkout"),
10561            Some("ssh://git@github.com/pleme-io/checkout.git"),
10562        ] {
10563            let c = caixa_with_repositorio(repositorio);
10564            let manual = c.repositorio().map_or_else(
10565                || {
10566                    format!(
10567                        "https://github.com/{org}/{nome}",
10568                        org = crate::DEFAULT_PLEME_GIT_ORG,
10569                        nome = c.nome(),
10570                    )
10571                },
10572                str::to_owned,
10573            );
10574            assert_eq!(
10575                c.canonical_git_url(),
10576                manual,
10577                "Caixa::canonical_git_url must byte-equal the manual \
10578                 open-coded `repositorio().map(str::to_owned)\
10579                 .unwrap_or_else(|| format!(...))` composition across \
10580                 every representative :repositorio input — got {:?}, \
10581                 expected {manual:?}",
10582                c.canonical_git_url(),
10583            );
10584        }
10585    }
10586
10587    // ── Caixa::publish_tag — resolved-publish-tag composer ───────────
10588
10589    #[test]
10590    fn publish_tag_composes_prefix_and_versao_on_all_shapes() {
10591        // Fail-before-pass-after pin: [`Caixa::publish_tag`] must compose
10592        // [`crate::DEFAULT_PUBLISH_TAG_PREFIX`] against the caixa's typed
10593        // [`Caixa::versao`] byte-string across every SemVer-2 shape the
10594        // sibling [`validate_versao_accepts_canonical_forms`] positive-set
10595        // sweep documents — bare MAJOR.MINOR.PATCH, pre-release tags
10596        // (`-rc.1`), build metadata (`+build.42`), the combined form, and
10597        // the `0.0.0` boundary case. Every accept-set value the peer
10598        // validate gate lets through must survive the resolved-tag
10599        // projection byte-equal.
10600        for versao in [
10601            "0.1.0",
10602            "0.0.0",
10603            "1.0.0",
10604            "1.2.3-rc.1",
10605            "1.2.3+build.42",
10606            "1.2.3-rc.1+build.42",
10607        ] {
10608            let c = caixa_with_versao(versao);
10609            let expected = format!(
10610                "{prefix}{versao}",
10611                prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
10612            );
10613            assert_eq!(
10614                c.publish_tag(),
10615                expected,
10616                "Caixa::publish_tag must compose \
10617                 DEFAULT_PUBLISH_TAG_PREFIX ({prefix:?}) against \
10618                 :versao ({versao:?}) verbatim — got {got:?}, \
10619                 expected {expected:?}",
10620                prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
10621                got = c.publish_tag(),
10622            );
10623        }
10624    }
10625
10626    #[test]
10627    fn publish_tag_starts_with_default_publish_tag_prefix() {
10628        // Prefix-shape pin: every [`Caixa::publish_tag`] emission must
10629        // begin with the canonical [`crate::DEFAULT_PUBLISH_TAG_PREFIX`]
10630        // byte-string on every input, guarding a hypothetical future
10631        // implementation that migrated the prefix segment to an inline
10632        // literal (`"v"`) that would silently drift from any rebrand of
10633        // the lifted constant. Peer to the sibling caixa-flux
10634        // `cluster_bundle_default_git_tag_uses_lifted_caixa_core_prefix`
10635        // test which pins the same prefix invariant at the reader-side
10636        // `GitRefSpec::Tag` emit site.
10637        for versao in ["0.0.0", "0.1.0", "1.2.3-rc.1", "9.9.9+build.1"] {
10638            let c = caixa_with_versao(versao);
10639            let tag = c.publish_tag();
10640            assert!(
10641                tag.starts_with(crate::DEFAULT_PUBLISH_TAG_PREFIX),
10642                "Caixa::publish_tag emission {tag:?} must start with \
10643                 the lifted crate::DEFAULT_PUBLISH_TAG_PREFIX \
10644                 ({prefix:?})",
10645                prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
10646            );
10647        }
10648    }
10649
10650    #[test]
10651    fn publish_tag_byte_matches_manual_composition() {
10652        // Byte-parity pin: [`Caixa::publish_tag`] must render byte-
10653        // identically to the manual open-coded
10654        // `format!("{prefix}{versao}", prefix =
10655        //  caixa_core::DEFAULT_PUBLISH_TAG_PREFIX, versao =
10656        //  caixa.versao())` composition every prior substrate-side
10657        // caller re-derived. Guards the paired-site convergence just
10658        // applied at caixa-flux's [`ClusterBundleOpts::for_caixa`]
10659        // `git_ref` composer (which now routes through this accessor):
10660        // a future implementation of this method that reordered the
10661        // format arguments, swapped the `<prefix>` constant for a
10662        // different one, or interposed a canonicalization pass on the
10663        // `:versao` axis surfaces here as a caixa-core build-time test
10664        // failure rather than as a downstream FluxCD `GitRepository`
10665        // reconcile mismatch far from this method's source.
10666        for versao in [
10667            "0.1.0",
10668            "0.0.0",
10669            "1.2.3-rc.1",
10670            "1.2.3+build.42",
10671            "1.2.3-rc.1+build.42",
10672        ] {
10673            let c = caixa_with_versao(versao);
10674            let manual = format!(
10675                "{prefix}{versao}",
10676                prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
10677                versao = c.versao(),
10678            );
10679            assert_eq!(
10680                c.publish_tag(),
10681                manual,
10682                "Caixa::publish_tag must byte-equal the manual \
10683                 open-coded `format!(\"{{prefix}}{{versao}}\", ...)` \
10684                 composition across every representative :versao input \
10685                 — got {got:?}, expected {manual:?}",
10686                got = c.publish_tag(),
10687            );
10688        }
10689    }
10690
10691    // ── Caixa::descricao — outer top-level Option<&str> scalar accessor ──
10692
10693    #[test]
10694    fn descricao_returns_descricao_byte_string_verbatim_across_permutations() {
10695        // The canonical per-`Caixa` `:descricao` free-form-prose scalar
10696        // pin: [`Caixa::descricao`] must return the `:descricao` typed
10697        // byte-string verbatim as an `Option<&str>`, byte-equal to the
10698        // raw `self.descricao.as_deref()` access across every
10699        // representative value in the accept-set — `None` (the "omit
10700        // the slot to defer to the per-renderer `caixa.nome`-derived
10701        // fallback" arm every existing fixture without a `:descricao`
10702        // line carries), `Some("")` (a past-the-guard sentinel that
10703        // pins the accessor doesn't perform a silent `Some("") → None`
10704        // collapse on the empty arm — validate rejects `Some("")`
10705        // through `DescricaoEmpty` but the accessor must ship the raw
10706        // slot verbatim so a validate-time gate regression surfaces at
10707        // the caixa-helm / caixa-feira emit boundary rather than being
10708        // silently absorbed into the per-renderer `caixa.nome`-derived
10709        // fallback), `Some("Checkout flow.")` (the canonical one-line
10710        // prose descriptor the peer
10711        // `validate_descricao_accepts_canonical_value` positive sweep
10712        // exercises), `Some("Canonical Rust→wasm32-wasip2 caixa
10713        // Servico.")` (the multi-byte Unicode continuation-byte shape
10714        // the `hello-rio` fixture carries), `Some("→ — · ✓")` (a
10715        // multi-glyph Unicode shape the peer
10716        // `is_chart_description_shape` predicate accepts), and five
10717        // past-the-guard sentinels for the `DescricaoInvalid` refusal
10718        // cases (`Some(" Checkout flow.")` leading-whitespace,
10719        // `Some("Checkout flow. ")` trailing-whitespace,
10720        // `Some("Checkout\nflow.")` embedded-LF,
10721        // `Some("Checkout\tflow.")` embedded-TAB, and
10722        // `Some("Checkout\x00flow.")` embedded-NUL — the sentinels pin
10723        // the accessor doesn't silently absorb the refusal cases into
10724        // a fallback).
10725        //
10726        // Third outer top-level [`Caixa`] `Option<&str>`-return scalar
10727        // accessor pin on the substrate primitive — sibling of the peer
10728        // [`Caixa::licenca`] (6d5bc28) and [`Caixa::repositorio`]
10729        // (cc7332d) pins that opened the "outer [`Caixa`]
10730        // `Option<&str>` scalar" projection pin pattern this pin folds
10731        // on. Sibling in shape to the peer per-`:placement`
10732        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
10733        // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
10734        // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
10735        // axes, extended onto the outer top-level [`Caixa`] universal-
10736        // axis surface. Pins against a future silent detour that
10737        // returned an owned `Option<String>` (which would type-check
10738        // but silently allocate on every accessor call, breaking the
10739        // zero-cost projection every peer sibling accessor carries), a
10740        // `Some("") → None` collapse (which would silently absorb the
10741        // `DescricaoEmpty` refusal case at the accessor boundary and
10742        // the caixa-helm `Chart.yaml` `description:` fold would
10743        // silently render a `caixa.nome`-derived fallback on a
10744        // struct-literal `Caixa { descricao: Some(""), .. }`), or a
10745        // `None → Some(<default>)` collapse (which would silently
10746        // reify the per-renderer `caixa.nome`-derived fallback at the
10747        // accessor boundary and every downstream consumer keying off
10748        // the `Option::is_none()` discriminator would lose the "author
10749        // omitted the slot" signal).
10750        for descricao in [
10751            None,
10752            Some(""),
10753            Some("Checkout flow."),
10754            Some("Canonical Rust→wasm32-wasip2 caixa Servico."),
10755            Some("→ — · ✓"),
10756            Some(" Checkout flow."),
10757            Some("Checkout flow. "),
10758            Some("Checkout\nflow."),
10759            Some("Checkout\tflow."),
10760            Some("Checkout\x00flow."),
10761        ] {
10762            let c = caixa_with_descricao(descricao);
10763            assert_eq!(
10764                c.descricao(),
10765                descricao,
10766                "Caixa::descricao must return :descricao verbatim (got \
10767                 {:?}, expected {descricao:?})",
10768                c.descricao(),
10769            );
10770            assert_eq!(
10771                c.descricao(),
10772                c.descricao.as_deref(),
10773                "Caixa::descricao must byte-equal the raw \
10774                 `self.descricao.as_deref()` field access across every \
10775                 value in the Option<&str> accept-set",
10776            );
10777        }
10778    }
10779
10780    #[test]
10781    fn validate_descricao_empty_arm_routes_through_accessor() {
10782        // Composition pin: [`Caixa::validate_descricao`]'s empty-arm
10783        // gate must key off [`Caixa::descricao`], not the raw
10784        // `self.descricao.as_deref()` field access. Structurally: a
10785        // `Caixa { descricao: Some(""), .. }` must surface the
10786        // `DescricaoEmpty` refusal exactly, and a
10787        // `Caixa { descricao: Some("Checkout flow."), .. }` (the
10788        // canonical one-line-prose form) must pass validate. The pair
10789        // jointly pins the accessor + validate-gate composition: any
10790        // future silent detour that had the accessor return `None` on
10791        // the empty arm (a `.filter(|s| !s.is_empty())` collapse) would
10792        // silently absorb the `DescricaoEmpty` refusal at the accessor
10793        // boundary and the validate gate would accept a struct-literal
10794        // `Caixa { descricao: Some(""), .. }` — the composition pin
10795        // catches that at caixa-core build time.
10796        //
10797        // Peer of the [`Caixa::licenca`] (6d5bc28)
10798        // `validate_licenca_empty_arm_routes_through_accessor` and
10799        // [`Caixa::repositorio`] (cc7332d)
10800        // `validate_repositorio_empty_arm_routes_through_accessor`
10801        // composition pins on the sibling outer top-level [`Caixa`]
10802        // `Option<&str>` universal-axis surface — same "the validate /
10803        // shape-gate predicate must route through the substrate-
10804        // primitive typed dispatch" discipline extended onto the third
10805        // outer top-level [`Caixa`] universal-axis `Option<&str>`-
10806        // composition surface.
10807        let c = caixa_with_descricao(Some(""));
10808        assert!(
10809            matches!(c.validate_descricao(), Err(ManifestError::DescricaoEmpty),),
10810            "validate_descricao must reject descricao == Some(\"\") \
10811             with DescricaoEmpty — the accessor and the validate gate \
10812             must route through the same substrate-primitive typed \
10813             dispatch on the :descricao empty arm",
10814        );
10815        let c = caixa_with_descricao(Some("Checkout flow."));
10816        assert!(
10817            c.validate_descricao().is_ok(),
10818            "validate_descricao must accept descricao == \
10819             Some(\"Checkout flow.\") (the canonical one-line-prose \
10820             chart-description shape)",
10821        );
10822    }
10823
10824    #[test]
10825    fn descricao_projects_option_str_by_borrow() {
10826        // The by-borrow pin: [`Caixa::descricao`] returns
10827        // `Option<&str>` by borrow — the `&str` borrows the underlying
10828        // `String` storage of the `Option<String>` slot and the
10829        // accessor must not allocate a fresh `String` on every call.
10830        // Peer of the [`Caixa::licenca`] (6d5bc28) and
10831        // [`Caixa::repositorio`] (cc7332d) by-borrow pins on the peer
10832        // outer top-level [`Caixa`] `Option<&str>`-return axes, and of
10833        // the per-`:placement`
10834        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
10835        // borrow pin on the peer per-M3-mesh-slot `Option<&str>`-
10836        // return axis, extended onto the third outer top-level
10837        // [`Caixa`] universal-axis `Option<&str>` shape — the
10838        // accessor's returned `&str` must borrow from `&self` (the
10839        // returned reference's lifetime is tied to `&self`), and
10840        // calling the accessor twice on the same [`Caixa`] must yield
10841        // the same `Option<&str>` verbatim (idempotent, no side
10842        // effects on `&self`).
10843        //
10844        // Pins against a future silent detour that returned an owned
10845        // `Option<String>` (which would type-check but silently
10846        // allocate on every call, breaking the zero-cost projection
10847        // every peer sibling accessor carries), or a one-arm-only
10848        // accessor that returned a saturating value on some sentinel
10849        // input (breaking the pass-through invariant the sibling
10850        // required-scalar accessors carry).
10851        for descricao in [
10852            None,
10853            Some(""),
10854            Some("Checkout flow."),
10855            Some("Canonical Rust→wasm32-wasip2 caixa Servico."),
10856        ] {
10857            let c = caixa_with_descricao(descricao);
10858            let first = c.descricao();
10859            let second = c.descricao();
10860            assert_eq!(
10861                first, second,
10862                "Caixa::descricao must be idempotent — two successive \
10863                 calls on the same &self must return the same \
10864                 Option<&str>",
10865            );
10866            assert_eq!(
10867                first, descricao,
10868                "Caixa::descricao must return :descricao verbatim by \
10869                 borrow — got {first:?}, expected {descricao:?}",
10870            );
10871        }
10872    }
10873
10874    // ── validate_edicao — universal-axis language-edition shape ──
10875
10876    fn caixa_with_edicao(edicao: Option<&str>) -> Caixa {
10877        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10878        c.edicao = edicao.map(String::from);
10879        c
10880    }
10881
10882    #[test]
10883    fn validate_edicao_accepts_none() {
10884        // The omit-the-slot identity: `:edicao` is optional. The
10885        // gate is a no-op when the author didn't declare a value —
10886        // every caixa without an `:edicao` line trivially passes,
10887        // and the substrate-side build pipeline falls back to the
10888        // documented default edition. Mirrors the peer
10889        // `validate_licenca_accepts_none` posture on the sibling
10890        // `Option<String>` Caixa slot.
10891        let c = caixa_with_edicao(None);
10892        c.validate_edicao().unwrap();
10893    }
10894
10895    #[test]
10896    fn validate_edicao_accepts_canonical_value() {
10897        // Positive control: the canonical `"2026"` edition every
10898        // existing renderer-side fixture (`caixa-helm`, `caixa-flux`,
10899        // `caixa-mesh`) carries by construction passes the gate.
10900        // Future-introduced sibling editions (`"2027"`, `"2030"`,
10901        // `"2049"`) that match the same 4-digit ASCII decimal year
10902        // shape must also trivially pass — the structural shape
10903        // predicate accepts every well-formed year regardless of
10904        // whether the substrate yet understands the specific value
10905        // (a future known-edition allowlist tightens that).
10906        for ed in ["2026", "2027", "2030", "2049"] {
10907            let c = caixa_with_edicao(Some(ed));
10908            c.validate_edicao()
10909                .unwrap_or_else(|err| panic!("canonical {ed:?} must pass: {err:?}"));
10910        }
10911    }
10912
10913    #[test]
10914    fn validate_edicao_rejects_empty_some() {
10915        // Canonical paste-from-blank-doc footgun. Without this gate
10916        // the empty `Some("")` silently lands as `(:edicao "")` in
10917        // the rendered caixa.lisp and a future renderer-side
10918        // consumer's `Option::unwrap_or_else` (which only fires on
10919        // `None`) skips its fallback. Mirrors the peer
10920        // [`ManifestError::LicencaEmpty`] empty-arm on the sibling
10921        // `Option<String>` Caixa slot.
10922        let c = caixa_with_edicao(Some(""));
10923        let err = c.validate_edicao().unwrap_err();
10924        assert!(matches!(err, ManifestError::EdicaoEmpty), "got {err:?}",);
10925    }
10926
10927    #[test]
10928    fn validate_edicao_rejects_free_form_non_year() {
10929        // Free-form non-year footgun: the bare `"x"` / `"latest"` /
10930        // `"nightly"` shapes carry no operational meaning on the
10931        // substrate's build-time edition selector. Until this gate
10932        // landed the bare empty-arm check let every such value
10933        // through and broke far from the source caixa.lisp. Peer
10934        // with the shape-predicate cascade
10935        // `validate_repositorio_rejects_missing_colon_separator`
10936        // establishes past its own empty arm.
10937        for ed in ["x", "latest", "nightly", "stable"] {
10938            let c = caixa_with_edicao(Some(ed));
10939            let err = c.validate_edicao().unwrap_err();
10940            assert!(
10941                matches!(err, ManifestError::EdicaoInvalid { .. }),
10942                "expected EdicaoInvalid on {ed:?}, got {err:?}",
10943            );
10944        }
10945    }
10946
10947    #[test]
10948    fn validate_edicao_rejects_trailing_whitespace() {
10949        // Paste-from-doc whitespace footgun. A trailing space in
10950        // the `:edicao` value would silently break the substrate's
10951        // build-time edition match-table lookup at the rendered
10952        // artifact's edition-selector consumer. The shape predicate
10953        // refuses every whitespace byte by construction (any byte
10954        // outside `0-9` fails `is_ascii_digit`). Peer with
10955        // `validate_repositorio_rejects_whitespace`.
10956        let c = caixa_with_edicao(Some("2026 "));
10957        let err = c.validate_edicao().unwrap_err();
10958        let ManifestError::EdicaoInvalid { edicao, .. } = err else {
10959            panic!("expected EdicaoInvalid, got {err:?}");
10960        };
10961        assert_eq!(edicao, "2026 ");
10962    }
10963
10964    #[test]
10965    fn validate_edicao_rejects_leading_whitespace() {
10966        // Symmetric paste-from-doc whitespace footgun on the leading
10967        // boundary — the gate refuses every shape with a non-digit
10968        // byte by construction.
10969        let c = caixa_with_edicao(Some(" 2026"));
10970        let err = c.validate_edicao().unwrap_err();
10971        assert!(
10972            matches!(err, ManifestError::EdicaoInvalid { .. }),
10973            "got {err:?}",
10974        );
10975    }
10976
10977    #[test]
10978    fn validate_edicao_rejects_control_char() {
10979        // Paste-from-multiline-doc CRLF footgun — control characters
10980        // at the value boundary break the substrate's build-time
10981        // edition-selector parser. Peer with
10982        // `validate_repositorio_rejects_control_char`.
10983        let c = caixa_with_edicao(Some("2026\n"));
10984        let err = c.validate_edicao().unwrap_err();
10985        assert!(
10986            matches!(err, ManifestError::EdicaoInvalid { .. }),
10987            "got {err:?}",
10988        );
10989    }
10990
10991    #[test]
10992    fn validate_edicao_rejects_non_ascii_lookalike() {
10993        // Fullwidth-keyboard look-alike footgun — `"2026"` is
10994        // the U+FF12 U+FF10 U+FF12 U+FF16 sequence (CJK fullwidth
10995        // digits), 4 codepoints but 12 UTF-8 bytes; the substrate's
10996        // edition selector wants an ASCII year, and the gate
10997        // refuses every non-ASCII shape by construction (length in
10998        // bytes is 12 ≠ 4, *and* every byte falls outside
10999        // `is_ascii_digit`'s `0-9` range).
11000        let c = caixa_with_edicao(Some("2026"));
11001        let err = c.validate_edicao().unwrap_err();
11002        assert!(
11003            matches!(err, ManifestError::EdicaoInvalid { .. }),
11004            "got {err:?}",
11005        );
11006    }
11007
11008    #[test]
11009    fn validate_edicao_rejects_version_tag_prefix() {
11010        // Common version-tag idiom footgun — `"v2026"` / `"e2026"`
11011        // / `"r2026"` are familiar shapes from git-tag / Rust
11012        // edition / release-tag conventions that don't apply to
11013        // the year-shaped edition axis. The shape predicate refuses
11014        // every leading non-digit prefix.
11015        for ed in ["v2026", "e2026", "r2026"] {
11016            let c = caixa_with_edicao(Some(ed));
11017            let err = c.validate_edicao().unwrap_err();
11018            assert!(
11019                matches!(err, ManifestError::EdicaoInvalid { .. }),
11020                "expected EdicaoInvalid on {ed:?}, got {err:?}",
11021            );
11022        }
11023    }
11024
11025    #[test]
11026    fn validate_edicao_rejects_decimal_shape() {
11027        // Decimal-shaped pseudo-version footgun — `"2026.1"` /
11028        // `"2026.0"` are familiar shapes from semver / float
11029        // conventions that don't apply to the year-shaped edition
11030        // axis. The shape predicate refuses every non-digit byte
11031        // (`.` falls outside `is_ascii_digit`).
11032        for ed in ["2026.1", "2026.0", "2026.0.1"] {
11033            let c = caixa_with_edicao(Some(ed));
11034            let err = c.validate_edicao().unwrap_err();
11035            assert!(
11036                matches!(err, ManifestError::EdicaoInvalid { .. }),
11037                "expected EdicaoInvalid on {ed:?}, got {err:?}",
11038            );
11039        }
11040    }
11041
11042    #[test]
11043    fn validate_edicao_rejects_wrong_length_numeric() {
11044        // Wrong-length numeric footgun — `"26"` (truncated) /
11045        // `"202"` (truncated) / `"20260"` (extra digit) / `"00026"`
11046        // (zero-padded too wide) all parse as integers but don't
11047        // name a 4-digit year. The shape predicate refuses every
11048        // value whose length isn't exactly 4 bytes.
11049        for ed in ["26", "202", "20260", "00026", "9"] {
11050            let c = caixa_with_edicao(Some(ed));
11051            let err = c.validate_edicao().unwrap_err();
11052            assert!(
11053                matches!(err, ManifestError::EdicaoInvalid { .. }),
11054                "expected EdicaoInvalid on {ed:?}, got {err:?}",
11055            );
11056        }
11057    }
11058
11059    #[test]
11060    fn validate_edicao_empty_takes_precedence_over_shape() {
11061        // Empty-first cascade pin: the empty `Some("")` surfaces
11062        // the narrower `EdicaoEmpty` not the shape-predicate-
11063        // wrapped `EdicaoInvalid`, mirroring the peer
11064        // `validate_repositorio_empty_takes_precedence_over_shape`
11065        // (`RepositorioEmpty` → `RepositorioInvalid`),
11066        // `NomeEmpty` → `NomeInvalid`, `VersaoEmpty` →
11067        // `VersaoInvalid`, `FonteRepoEmpty` → `FonteRepoInvalid`
11068        // cascades. The shape predicate also refuses the empty
11069        // input (defensively — `s.len() != 4`), but the
11070        // manifest-layer empty arm runs first to surface the
11071        // narrower diagnostic verbatim.
11072        let c = caixa_with_edicao(Some(""));
11073        let err = c.validate_edicao().unwrap_err();
11074        assert!(matches!(err, ManifestError::EdicaoEmpty), "got {err:?}",);
11075    }
11076
11077    #[test]
11078    fn validate_edicao_template_passes() {
11079        // Round-trip pin: the bare `Caixa::template` shape (which
11080        // carries `:edicao "2026"` verbatim) passes the gate by
11081        // construction. A future template-shape change that
11082        // introduced `(:edicao "")` or a non-year value would
11083        // surface here as a regression. Mirrors the peer
11084        // `validate_licenca_template_passes` pin.
11085        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
11086        c.validate_edicao().unwrap();
11087    }
11088
11089    #[test]
11090    fn validate_edicao_diagnostic_names_offending_slot() {
11091        // Diagnostic-shape pin (peer with
11092        // `validate_licenca_diagnostic_names_offending_slot`): the
11093        // error's Display surfaces the `:edicao` slot name verbatim,
11094        // so a `feira lint` run can render the diagnostic without
11095        // re-parsing and the author can grep their caixa.lisp for
11096        // the offending `:edicao` line.
11097        let c = caixa_with_edicao(Some(""));
11098        let rendered = c.validate_edicao().unwrap_err().to_string();
11099        assert!(
11100            rendered.contains(":edicao"),
11101            "diagnostic must name the offending slot: {rendered}",
11102        );
11103    }
11104
11105    #[test]
11106    fn validate_edicao_invalid_diagnostic_carries_offending_value() {
11107        // Diagnostic-shape pin on the shape-predicate arm (peer
11108        // with `validate_repositorio_diagnostic_carries_offending_value`):
11109        // the error's Display surfaces the offending value + slot
11110        // name verbatim, so a `feira lint` run can render the
11111        // diagnostic without re-parsing and the author can grep
11112        // their caixa.lisp for the offending `:edicao` value.
11113        let c = caixa_with_edicao(Some("v2026"));
11114        let rendered = c.validate_edicao().unwrap_err().to_string();
11115        assert!(
11116            rendered.contains(":edicao"),
11117            "diagnostic must name the offending slot: {rendered}",
11118        );
11119        assert!(
11120            rendered.contains("v2026"),
11121            "diagnostic must quote the offending value: {rendered}",
11122        );
11123    }
11124
11125    // ── Caixa::edicao — outer top-level Option<&str> scalar accessor ──
11126
11127    #[test]
11128    fn edicao_returns_edicao_byte_string_verbatim_across_permutations() {
11129        // The canonical per-`Caixa` `:edicao` language-edition scalar
11130        // pin: [`Caixa::edicao`] must return the `:edicao` typed
11131        // byte-string verbatim as an `Option<&str>`, byte-equal to the
11132        // raw `self.edicao.as_deref()` access across every representative
11133        // value in the accept-set — `None` (the "omit the slot to defer
11134        // to the substrate's default edition" arm every existing
11135        // [`caixa-resolver`] fixture without an `:edicao` line carries),
11136        // `Some("")` (a past-the-guard sentinel that pins the accessor
11137        // doesn't perform a silent `Some("") → None` collapse on the
11138        // empty arm — validate rejects `Some("")` through `EdicaoEmpty`
11139        // but the accessor must ship the raw slot verbatim so a
11140        // validate-time gate regression surfaces at any future edition-
11141        // aware consumer's boundary rather than being silently absorbed
11142        // into the substrate's default edition), `Some("2026")` (the
11143        // canonical 4-digit-ASCII-decimal-year shape every `feira init`
11144        // template scaffolds via [`Caixa::template`] and every
11145        // renderer-side fixture at `caixa-helm/src/lib.rs:978` /
11146        // `caixa-flux/src/lib.rs:2319` / `caixa-mesh/src/lib.rs:3208`
11147        // carries by construction), `Some("2018")` / `Some("2021")` /
11148        // `Some("2024")` (canonical 4-digit-ASCII-decimal-year shapes
11149        // peer with Cargo's `[package] edition` grammar every future-
11150        // introduced sibling to `"2026"` will follow), and eight
11151        // past-the-guard sentinels for the `EdicaoInvalid` refusal cases
11152        // (`Some("2026 ")` trailing-whitespace, `Some(" 2026")` leading-
11153        // whitespace, `Some("2026\n")` embedded-LF, `Some("2026")`
11154        // fullwidth-non-ASCII-lookalike, `Some("v2026")` version-tag-
11155        // prefix, `Some("2026.1")` decimal-shape, `Some("26")` wrong-
11156        // length-numeric, `Some("latest")` free-form-non-year — the
11157        // sentinels pin the accessor doesn't silently absorb the
11158        // refusal cases into a substrate-default-edition fallback).
11159        //
11160        // Fourth and final outer top-level [`Caixa`] `Option<&str>`-
11161        // return scalar accessor pin on the substrate primitive —
11162        // sibling of the peer [`Caixa::licenca`] (6d5bc28),
11163        // [`Caixa::repositorio`] (cc7332d), and [`Caixa::descricao`]
11164        // (3f16e2f) pins that opened the "outer [`Caixa`]
11165        // `Option<&str>` scalar" projection pin pattern this pin folds
11166        // on. Sibling in shape to the peer per-`:placement`
11167        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
11168        // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
11169        // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
11170        // axes, extended onto the outer top-level [`Caixa`] universal-
11171        // axis surface's last unlifted `Option<String>` slot. Pins
11172        // against a future silent detour that returned an owned
11173        // `Option<String>` (which would type-check but silently
11174        // allocate on every accessor call, breaking the zero-cost
11175        // projection every peer sibling accessor carries), a
11176        // `Some("") → None` collapse (which would silently absorb the
11177        // `EdicaoEmpty` refusal case at the accessor boundary and any
11178        // future edition-aware consumer would silently fall back to
11179        // the substrate's default edition on a struct-literal
11180        // `Caixa { edicao: Some(""), .. }`), or a
11181        // `None → Some("2026")` collapse (which would silently reify
11182        // the substrate's default edition at the accessor boundary
11183        // and every downstream consumer keying off the
11184        // `Option::is_none()` discriminator would lose the "author
11185        // omitted the slot" signal).
11186        for edicao in [
11187            None,
11188            Some(""),
11189            Some("2026"),
11190            Some("2018"),
11191            Some("2021"),
11192            Some("2024"),
11193            Some("2026 "),
11194            Some(" 2026"),
11195            Some("2026\n"),
11196            Some("2026"),
11197            Some("v2026"),
11198            Some("2026.1"),
11199            Some("26"),
11200            Some("latest"),
11201        ] {
11202            let c = caixa_with_edicao(edicao);
11203            assert_eq!(
11204                c.edicao(),
11205                edicao,
11206                "Caixa::edicao must return :edicao verbatim (got {:?}, \
11207                 expected {edicao:?})",
11208                c.edicao(),
11209            );
11210            assert_eq!(
11211                c.edicao(),
11212                c.edicao.as_deref(),
11213                "Caixa::edicao must byte-equal the raw \
11214                 `self.edicao.as_deref()` field access across every \
11215                 value in the Option<&str> accept-set",
11216            );
11217        }
11218    }
11219
11220    #[test]
11221    fn validate_edicao_empty_arm_routes_through_accessor() {
11222        // Composition pin: [`Caixa::validate_edicao`]'s empty-arm gate
11223        // must key off [`Caixa::edicao`], not the raw
11224        // `self.edicao.as_deref()` field access. Structurally: a
11225        // `Caixa { edicao: Some(""), .. }` must surface the
11226        // `EdicaoEmpty` refusal exactly, and a
11227        // `Caixa { edicao: Some("2026"), .. }` (the canonical
11228        // 4-digit-ASCII-decimal-year form) must pass validate. The
11229        // pair jointly pins the accessor + validate-gate composition:
11230        // any future silent detour that had the accessor return `None`
11231        // on the empty arm (a `.filter(|s| !s.is_empty())` collapse)
11232        // would silently absorb the `EdicaoEmpty` refusal at the
11233        // accessor boundary and the validate gate would accept a
11234        // struct-literal `Caixa { edicao: Some(""), .. }` — the
11235        // composition pin catches that at caixa-core build time.
11236        //
11237        // Peer of the [`Caixa::licenca`] (6d5bc28)
11238        // `validate_licenca_empty_arm_routes_through_accessor`,
11239        // [`Caixa::repositorio`] (cc7332d)
11240        // `validate_repositorio_empty_arm_routes_through_accessor`,
11241        // and [`Caixa::descricao`] (3f16e2f)
11242        // `validate_descricao_empty_arm_routes_through_accessor`
11243        // composition pins on the sibling outer top-level [`Caixa`]
11244        // `Option<&str>` universal-axis surface — same "the validate /
11245        // shape-gate predicate must route through the substrate-
11246        // primitive typed dispatch" discipline extended onto the
11247        // fourth and final outer top-level [`Caixa`] universal-axis
11248        // `Option<&str>`-composition surface, closing the accessor-
11249        // composition family.
11250        let c = caixa_with_edicao(Some(""));
11251        assert!(
11252            matches!(c.validate_edicao(), Err(ManifestError::EdicaoEmpty)),
11253            "validate_edicao must reject edicao == Some(\"\") with \
11254             EdicaoEmpty — the accessor and the validate gate must \
11255             route through the same substrate-primitive typed dispatch \
11256             on the :edicao empty arm",
11257        );
11258        let c = caixa_with_edicao(Some("2026"));
11259        assert!(
11260            c.validate_edicao().is_ok(),
11261            "validate_edicao must accept edicao == Some(\"2026\") \
11262             (the canonical 4-digit-ASCII-decimal-year shape)",
11263        );
11264    }
11265
11266    #[test]
11267    fn edicao_projects_option_str_by_borrow() {
11268        // The by-borrow pin: [`Caixa::edicao`] returns
11269        // `Option<&str>` by borrow — the `&str` borrows the underlying
11270        // `String` storage of the `Option<String>` slot and the
11271        // accessor must not allocate a fresh `String` on every call.
11272        // Peer of the [`Caixa::licenca`] (6d5bc28),
11273        // [`Caixa::repositorio`] (cc7332d), and [`Caixa::descricao`]
11274        // (3f16e2f) by-borrow pins on the peer outer top-level
11275        // [`Caixa`] `Option<&str>`-return axes, and of the
11276        // per-`:placement`
11277        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
11278        // borrow pin on the peer per-M3-mesh-slot `Option<&str>`-
11279        // return axis, extended onto the fourth and final outer top-
11280        // level [`Caixa`] universal-axis `Option<&str>` shape — the
11281        // accessor's returned `&str` must borrow from `&self` (the
11282        // returned reference's lifetime is tied to `&self`), and
11283        // calling the accessor twice on the same [`Caixa`] must yield
11284        // the same `Option<&str>` verbatim (idempotent, no side
11285        // effects on `&self`).
11286        //
11287        // Pins against a future silent detour that returned an owned
11288        // `Option<String>` (which would type-check but silently
11289        // allocate on every call, breaking the zero-cost projection
11290        // every peer sibling accessor carries), or a one-arm-only
11291        // accessor that returned a saturating value on some sentinel
11292        // input (breaking the pass-through invariant the sibling
11293        // required-scalar accessors carry).
11294        for edicao in [None, Some(""), Some("2026"), Some("2018")] {
11295            let c = caixa_with_edicao(edicao);
11296            let first = c.edicao();
11297            let second = c.edicao();
11298            assert_eq!(
11299                first, second,
11300                "Caixa::edicao must be idempotent — two successive \
11301                 calls on the same &self must return the same \
11302                 Option<&str>",
11303            );
11304            assert_eq!(
11305                first, edicao,
11306                "Caixa::edicao must return :edicao verbatim by \
11307                 borrow — got {first:?}, expected {edicao:?}",
11308            );
11309        }
11310    }
11311
11312    #[test]
11313    fn nome_returns_nome_byte_string_verbatim_across_permutations() {
11314        // The canonical per-`Caixa` `:nome` universal-axis DNS-1123-
11315        // label caixa-identity scalar pin: [`Caixa::nome`] must return
11316        // the `:nome` typed `String` verbatim as `&str`, byte-equal to
11317        // the raw field access across every representative value in
11318        // the accept-set — the canonical `"demo"` template baseline
11319        // (the same `feira init`-scaffolded default the sibling
11320        // `validate_nome_accepts_canonical_template` positive-control
11321        // gate pins), plus every sibling per-typed-slot atom accessor's
11322        // canonical positive-arm byte-string (`"catalog"` per
11323        // [`crate::aplicacao::Membro::nome`], `"cart"` per the peer
11324        // per-`:contratos` `:de`, `"hello-rio"` per the canonical
11325        // `caixa-helm`/`caixa-flux` cross-crate integration-test
11326        // fixture, `"checkout"` per the M3 mesh-slot Aplicacao
11327        // canonical example), plus every past-the-guard sentinel for
11328        // the `NomeEmpty` / `NomeInvalid` / `NomeChartNameBudgetExceeded`
11329        // refusal cases (`""`, `"Bad_Name"`, `"a"` × 56 — 56 bytes fits
11330        // the bare DNS-1123 63-byte cap but overflows the joint
11331        // `lareira-<nome>` chart-name budget the sibling
11332        // [`Caixa::validate_nome_chart_name_budget`] gate closes on).
11333        //
11334        // The past-the-guard sentinels pin the accessor doesn't
11335        // silently absorb the refusal cases into a template-derived
11336        // fallback (a future `.nome().is_empty().then(|| "demo")`
11337        // collapse would silently absorb the `NomeEmpty` refusal at
11338        // the accessor boundary and the validate gate would accept a
11339        // struct-literal `Caixa { nome: "".into(), .. }` — the pin
11340        // catches that at caixa-core build time).
11341        //
11342        // First outer top-level [`Caixa`] `&str`-return required-
11343        // scalar accessor pin — opens the "outer [`Caixa`] `&str`
11344        // required-scalar" projection pattern the sibling per-`Caixa`
11345        // `:versao` future lift folds on. Sibling in shape to the peer
11346        // per-`:membros` [`crate::aplicacao::Membro::nome`] (4a32abf)
11347        // required-`String`-carry accessor pin on the sibling per-
11348        // sub-struct required-axis, extended onto the outer top-level
11349        // [`Caixa`] universal-axis required-`String`-carry axis.
11350        for nome in [
11351            "demo",
11352            "catalog",
11353            "cart",
11354            "hello-rio",
11355            "checkout",
11356            "",
11357            "Bad_Name",
11358            "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
11359        ] {
11360            let c = caixa_with_nome(nome);
11361            assert_eq!(
11362                c.nome(),
11363                nome,
11364                "Caixa::nome must return :nome verbatim (got {}, \
11365                 expected {nome})",
11366                c.nome(),
11367            );
11368            assert_eq!(
11369                c.nome(),
11370                c.nome.as_str(),
11371                "Caixa::nome must byte-equal the raw .nome field \
11372                 access across every value in the String accept-set",
11373            );
11374        }
11375    }
11376
11377    #[test]
11378    fn validate_nome_empty_arm_routes_through_accessor() {
11379        // Composition pin: [`Caixa::validate_nome`]'s empty-arm must
11380        // key off [`Caixa::nome`], not the raw `.nome` field access.
11381        // Structurally: a `Caixa { nome: "".into(), .. }` must surface
11382        // the `NomeEmpty` refusal exactly, and the canonical `"demo"`
11383        // template baseline (the peer positive-arm the sibling
11384        // `validate_nome_accepts_canonical_template` gate carves out)
11385        // must pass validate. The pair jointly pins the accessor +
11386        // validate-gate composition: any future silent detour that
11387        // had the accessor return a fresh `"demo"` on the empty arm
11388        // (a `.nome().is_empty().then(|| "demo")` fallback collapse)
11389        // would silently absorb the `NomeEmpty` refusal at the
11390        // accessor boundary and the validate gate would accept a
11391        // struct-literal `Caixa { nome: "".into(), .. }` — the
11392        // composition pin catches that at caixa-core build time.
11393        //
11394        // Peer of the sibling per-`Caixa`
11395        // `validate_licenca_empty_arm_routes_through_accessor` (6d5bc28)
11396        // / `validate_repositorio_empty_arm_routes_through_accessor`
11397        // (cc7332d) / `validate_descricao_empty_arm_routes_through_accessor`
11398        // (3f16e2f) / `validate_edicao_empty_arm_routes_through_accessor`
11399        // (2641cbd) composition pins on the sibling outer top-level
11400        // [`Caixa`] `Option<&str>` axes — same "the validate /
11401        // shape-gate predicate must route through the substrate-
11402        // primitive typed dispatch" discipline extended onto the peer
11403        // outer top-level [`Caixa`] required-`&str` composition axis.
11404        let c = caixa_with_nome("");
11405        assert!(
11406            matches!(c.validate_nome(), Err(ManifestError::NomeEmpty)),
11407            "validate_nome must reject nome == \"\" with NomeEmpty — \
11408             the accessor and the validate gate must route through the \
11409             same substrate-primitive typed dispatch on the :nome \
11410             empty-arm",
11411        );
11412        let c = caixa_with_nome("demo");
11413        assert!(
11414            c.validate_nome().is_ok(),
11415            "validate_nome must accept nome == \"demo\" (the canonical \
11416             DNS-1123-label template baseline)",
11417        );
11418    }
11419
11420    #[test]
11421    fn nome_projects_str_by_borrow() {
11422        // The by-borrow pin: [`Caixa::nome`] returns `&str` by borrow
11423        // — the `&str` borrows the underlying `String` storage of the
11424        // required `nome` slot and the accessor must not allocate a
11425        // fresh `String` on every call. Peer of the [`Caixa::licenca`]
11426        // (6d5bc28) / [`Caixa::repositorio`] (cc7332d) /
11427        // [`Caixa::descricao`] (3f16e2f) / [`Caixa::edicao`] (2641cbd)
11428        // by-borrow pins on the peer outer top-level [`Caixa`]
11429        // `Option<&str>`-return axes, extended onto the first outer
11430        // top-level [`Caixa`] required-`&str`-return axis — the
11431        // accessor's returned `&str` must borrow from `&self` (the
11432        // returned reference's lifetime is tied to `&self`), and
11433        // calling the accessor twice on the same [`Caixa`] must yield
11434        // the same `&str` verbatim (idempotent, no side effects on
11435        // `&self`).
11436        //
11437        // Pins against a future silent detour that returned an owned
11438        // `String` (which would type-check but silently allocate on
11439        // every call, breaking the zero-cost projection every peer
11440        // sibling accessor carries), an accidental
11441        // `.nome.to_lowercase()` detour that returned a fresh
11442        // allocation through an already-DNS-1123-lowercase-only
11443        // string (breaking a future `const fn` regression), or a
11444        // one-arm-only accessor that returned a canonicalized value
11445        // on some sentinel input (breaking the pass-through invariant
11446        // the sibling required-scalar accessors carry).
11447        for nome in ["demo", "catalog", "hello-rio", "checkout"] {
11448            let c = caixa_with_nome(nome);
11449            let first = c.nome();
11450            let second = c.nome();
11451            assert_eq!(
11452                first, second,
11453                "Caixa::nome must be idempotent — two successive calls \
11454                 on the same &self must return the same &str",
11455            );
11456            assert_eq!(
11457                first, nome,
11458                "Caixa::nome must return :nome verbatim by borrow — \
11459                 got {first}, expected {nome}",
11460            );
11461        }
11462    }
11463
11464    #[test]
11465    fn versao_returns_versao_byte_string_verbatim_across_permutations() {
11466        // The canonical per-`Caixa` `:versao` universal-axis SemVer-2
11467        // pinned-version scalar pin: [`Caixa::versao`] must return the
11468        // `:versao` typed `String` verbatim as `&str`, byte-equal to the
11469        // raw `.versao` field access across every representative value
11470        // in the accept-set — the canonical `"0.1.0"` template baseline
11471        // (the same `feira init`-scaffolded default the sibling
11472        // `validate_versao_accepts_canonical_template` positive-control
11473        // gate pins), plus every canonical SemVer-2 shape the sibling
11474        // `validate_versao_accepts_canonical_forms` positive-arm sweep
11475        // covers (`"0.0.0"`, `"1.0.0"`, `"0.2.0-rc.1"`,
11476        // `"1.0.0-alpha.0"`, `"1.0.0+build.42"`, `"1.0.0-rc.1+build.42"`,
11477        // `"10.20.30"`), plus every past-the-guard sentinel for the
11478        // `VersaoEmpty` / `VersaoInvalid` refusal cases (`""` the empty
11479        // arm, `"v0.1.0"` the git-tag-shape-leak footgun, `"0.1"` the
11480        // missing-patch footgun, `"^0.1"` the requirement-shape-leak
11481        // footgun, `"0.1.0.0"` the four-part-Java-convention footgun,
11482        // `"latest"` the docker-tag-shape footgun — the sentinels pin
11483        // the accessor doesn't silently absorb the refusal cases into a
11484        // template-derived fallback like `"0.1.0"`).
11485        //
11486        // The past-the-guard sentinels pin the accessor doesn't silently
11487        // absorb the refusal cases into a template-derived fallback (a
11488        // future `.versao().is_empty().then(|| "0.1.0")` collapse would
11489        // silently absorb the `VersaoEmpty` refusal at the accessor
11490        // boundary and the validate gate would accept a struct-literal
11491        // `Caixa { versao: "".into(), .. }` — the pin catches that at
11492        // caixa-core build time).
11493        //
11494        // Second outer top-level [`Caixa`] `&str`-return required-scalar
11495        // accessor pin — folds on the "outer [`Caixa`] `&str` required-
11496        // scalar" projection pattern the sibling per-`Caixa`
11497        // [`Caixa::nome`] (e6b7d97) opened. Sibling in shape to the peer
11498        // per-`:membros` [`crate::aplicacao::Membro::versao_requirement`]
11499        // (4127bb6) / per-`:children`
11500        // [`crate::supervisor::ChildSpec::versao_requirement`] (2c053c8)
11501        // / per-`:upgrade-from`
11502        // [`crate::UpgradeFromEntry::prior_versao`] (75d27a8) per-sub-
11503        // struct `:versao`-shaped `&str`-return accessor pins on the
11504        // sibling per-typed-slot version-carrier axes, extended onto the
11505        // second outer top-level [`Caixa`] universal-axis required-
11506        // `String`-carry axis so the two universal-axis identity-
11507        // carrying scalars every `defcaixa` form supplies (`:nome` +
11508        // `:versao`) share the same "one typed dispatch per axis" pin
11509        // discipline.
11510        for versao in [
11511            "0.1.0",
11512            "0.0.0",
11513            "1.0.0",
11514            "0.2.0-rc.1",
11515            "1.0.0-alpha.0",
11516            "1.0.0+build.42",
11517            "1.0.0-rc.1+build.42",
11518            "10.20.30",
11519            "",
11520            "v0.1.0",
11521            "0.1",
11522            "^0.1",
11523            "0.1.0.0",
11524            "latest",
11525        ] {
11526            let c = caixa_with_versao(versao);
11527            assert_eq!(
11528                c.versao(),
11529                versao,
11530                "Caixa::versao must return :versao verbatim (got {}, \
11531                 expected {versao})",
11532                c.versao(),
11533            );
11534            assert_eq!(
11535                c.versao(),
11536                c.versao.as_str(),
11537                "Caixa::versao must byte-equal the raw .versao field \
11538                 access across every value in the String accept-set",
11539            );
11540        }
11541    }
11542
11543    #[test]
11544    fn validate_versao_empty_arm_routes_through_accessor() {
11545        // Composition pin: [`Caixa::validate_versao`]'s empty-arm gate
11546        // must key off [`Caixa::versao`], not the raw `.versao` field
11547        // access. Structurally: a `Caixa { versao: "".into(), .. }` must
11548        // surface the `VersaoEmpty` refusal exactly, and the canonical
11549        // `"0.1.0"` template baseline (the peer positive-arm the sibling
11550        // `validate_versao_accepts_canonical_template` gate carves out)
11551        // must pass validate. The pair jointly pins the accessor +
11552        // validate-gate composition: any future silent detour that had
11553        // the accessor return a fresh `"0.1.0"` on the empty arm
11554        // (a `.versao().is_empty().then(|| "0.1.0")` fallback collapse)
11555        // would silently absorb the `VersaoEmpty` refusal at the
11556        // accessor boundary and the validate gate would accept a
11557        // struct-literal `Caixa { versao: "".into(), .. }` — the
11558        // composition pin catches that at caixa-core build time.
11559        //
11560        // Peer of the sibling per-`Caixa`
11561        // `validate_nome_empty_arm_routes_through_accessor` (e6b7d97)
11562        // composition pin on the sibling outer top-level [`Caixa`]
11563        // required-`&str` universal-axis surface — same "the validate /
11564        // shape-gate predicate must route through the substrate-
11565        // primitive typed dispatch" discipline extended onto the peer
11566        // outer top-level [`Caixa`] required-`&str` universal-axis
11567        // pinned-version composition axis, closing the second
11568        // coordinate of the "one canonical typed dispatch per per-Caixa
11569        // required-`&str` universal-axis" discipline.
11570        let c = caixa_with_versao("");
11571        assert!(
11572            matches!(c.validate_versao(), Err(ManifestError::VersaoEmpty)),
11573            "validate_versao must reject versao == \"\" with VersaoEmpty — \
11574             the accessor and the validate gate must route through the \
11575             same substrate-primitive typed dispatch on the :versao \
11576             empty-arm",
11577        );
11578        let c = caixa_with_versao("0.1.0");
11579        assert!(
11580            c.validate_versao().is_ok(),
11581            "validate_versao must accept versao == \"0.1.0\" (the \
11582             canonical SemVer-2 template baseline)",
11583        );
11584    }
11585
11586    #[test]
11587    fn versao_projects_str_by_borrow() {
11588        // The by-borrow pin: [`Caixa::versao`] returns `&str` by borrow
11589        // — the `&str` borrows the underlying `String` storage of the
11590        // required `versao` slot and the accessor must not allocate a
11591        // fresh `String` on every call. Peer of the [`Caixa::nome`]
11592        // (e6b7d97) by-borrow pin on the sibling outer top-level
11593        // [`Caixa`] required-`&str`-return axis, extended onto the
11594        // second outer top-level [`Caixa`] required-`&str`-return
11595        // universal-axis pinned-version surface — the accessor's
11596        // returned `&str` must borrow from `&self` (the returned
11597        // reference's lifetime is tied to `&self`), and calling the
11598        // accessor twice on the same [`Caixa`] must yield the same
11599        // `&str` verbatim (idempotent, no side effects on `&self`).
11600        //
11601        // Pins against a future silent detour that returned an owned
11602        // `String` (which would type-check but silently allocate on
11603        // every call, breaking the zero-cost projection every peer
11604        // sibling accessor carries), an accidental
11605        // `semver::Version::parse(&self.versao).unwrap().to_string()`
11606        // detour that returned a canonicalized fresh allocation through
11607        // an already-canonical byte-string (breaking a future `const fn`
11608        // regression and silently absorbing the `VersaoInvalid` refusal
11609        // at the accessor boundary), or a one-arm-only accessor that
11610        // returned a canonicalized value on some sentinel input
11611        // (breaking the pass-through invariant the sibling required-
11612        // scalar accessors carry).
11613        for versao in ["0.1.0", "1.0.0", "0.2.0-rc.1", "1.0.0+build.42"] {
11614            let c = caixa_with_versao(versao);
11615            let first = c.versao();
11616            let second = c.versao();
11617            assert_eq!(
11618                first, second,
11619                "Caixa::versao must be idempotent — two successive \
11620                 calls on the same &self must return the same &str",
11621            );
11622            assert_eq!(
11623                first, versao,
11624                "Caixa::versao must return :versao verbatim by borrow \
11625                 — got {first}, expected {versao}",
11626            );
11627        }
11628    }
11629
11630    fn caixa_with_kind(kind: CaixaKind) -> Caixa {
11631        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
11632        c.kind = kind;
11633        c
11634    }
11635
11636    #[test]
11637    fn kind_returns_kind_variant_verbatim_across_permutations() {
11638        // The canonical per-`Caixa` `:kind` universal-axis closed-set-
11639        // enum discriminant pin: [`Caixa::kind`] must return the `:kind`
11640        // typed [`CaixaKind`] variant verbatim by `Copy`, byte-equal to
11641        // the raw `.kind` field access across every variant in the
11642        // closed accept-set (`Biblioteca` — the library kind that
11643        // exports lisp forms; `Binario` — the nix-built executable kind
11644        // under `exe/`; `Servico` — the wasm-component daemon kind
11645        // under `servicos/`; `Supervisor` — the OTP-shaped hierarchical
11646        // reconciliation kind; `Aplicacao` — the M3 typed-mesh
11647        // composition kind).
11648        //
11649        // Pins against a future silent detour that re-derived the kind
11650        // from a peer axis (an accidental fallback to
11651        // `if !servicos.is_empty() { Servico } else if
11652        // !membros.is_empty() { Aplicacao } else { Biblioteca }`
11653        // collapse that read the code-surface / mesh-slot columns into
11654        // the kind discriminator), a variant remap the operator
11655        // authors on one consumer without the other, or a stale-derive
11656        // detour that substituted [`CaixaKind::Biblioteca`] as the
11657        // default when the field held any other variant (which would
11658        // silently collapse the distinction between "author explicitly
11659        // declared `:kind Servico`" and "author declared any other
11660        // kind" every downstream renderer-dispatch site depends on).
11661        //
11662        // First outer top-level [`Caixa`] `Copy`-return required-enum-
11663        // discriminant accessor pin — opens the "outer [`Caixa`]
11664        // `Copy`-return required-discriminant" projection pattern.
11665        // Sibling in shape to the peer per-`:supervisor`
11666        // [`crate::supervisor::SupervisorSpec::estrategia`] (eafb619),
11667        // per-`:placement` [`crate::aplicacao::Placement::estrategia`]
11668        // (921fe1b), and per-`:children`
11669        // [`crate::supervisor::ChildSpec::restart`] (dfb4a81)
11670        // `Copy`-return closed-set-enum discriminant accessor pins on
11671        // the sibling nested-spec typed-slot discriminator axes,
11672        // extended here to the outer top-level [`Caixa`] universal-
11673        // axis surface.
11674        for kind in [
11675            CaixaKind::Biblioteca,
11676            CaixaKind::Binario,
11677            CaixaKind::Servico,
11678            CaixaKind::Supervisor,
11679            CaixaKind::Aplicacao,
11680        ] {
11681            let c = caixa_with_kind(kind);
11682            assert_eq!(
11683                c.kind(),
11684                kind,
11685                "Caixa::kind must return :kind verbatim (got {:?}, \
11686                 expected {kind:?})",
11687                c.kind(),
11688            );
11689            assert_eq!(
11690                c.kind(),
11691                c.kind,
11692                "Caixa::kind accessor and .kind field access must \
11693                 byte-equal — the accessor is the substrate-primitive \
11694                 typed dispatch every downstream kind-gate consumer \
11695                 must route through",
11696            );
11697        }
11698    }
11699
11700    #[test]
11701    fn require_kind_reads_through_lifted_kind_accessor() {
11702        // Two-consumer coherence pin: the [`crate::render::require_kind`]
11703        // entry-gate predicate (the canonical two-line
11704        // `require_kind(caixa, Servico)?` prelude every per-Servico /
11705        // per-Aplicacao renderer runs at its entry-point) and the
11706        // sibling [`crate::render::KindMismatch`] error carrier's
11707        // `actual:` field (which names the offending caixa's variant
11708        // in the diagnostic) must both key off the lifted accessor, so
11709        // any future rebrand on the typed slot's reader shape lands at
11710        // exactly one place. Pins the two-site coherence by exercising
11711        // every off-diagonal `(actual, expected)` pair across the
11712        // closed accept-set — the `KindMismatch { actual, expected }`
11713        // surfaced on the mismatch arm must byte-equal the pair the
11714        // accessor returns for each side.
11715        //
11716        // Peer of the sibling per-`:placement`
11717        // `validate_placement_reads_through_lifted_estrategia_accessor`
11718        // (921fe1b) two-arm consumer-coherence pin on the M3 mesh-slot
11719        // `Copy`-return discriminant axis — same "the entry-gate
11720        // predicate and the error carrier's `actual:` field must route
11721        // through the substrate-primitive typed dispatch" discipline
11722        // extended onto the outer top-level [`Caixa`] universal-axis
11723        // discriminant surface.
11724        for expected in [
11725            CaixaKind::Biblioteca,
11726            CaixaKind::Binario,
11727            CaixaKind::Servico,
11728            CaixaKind::Supervisor,
11729            CaixaKind::Aplicacao,
11730        ] {
11731            for actual in [
11732                CaixaKind::Biblioteca,
11733                CaixaKind::Binario,
11734                CaixaKind::Servico,
11735                CaixaKind::Supervisor,
11736                CaixaKind::Aplicacao,
11737            ] {
11738                let c = caixa_with_kind(actual);
11739                let result = crate::render::require_kind(&c, expected);
11740                if expected == actual {
11741                    assert!(
11742                        result.is_ok(),
11743                        "require_kind must accept when actual == expected \
11744                         (actual={actual:?}, expected={expected:?})",
11745                    );
11746                } else {
11747                    let err = result.expect_err("require_kind must reject when actual != expected");
11748                    assert_eq!(
11749                        err.actual,
11750                        c.kind(),
11751                        "KindMismatch.actual must byte-equal Caixa::kind() \
11752                         — the error carrier's `actual:` field reads \
11753                         through the lifted accessor",
11754                    );
11755                    assert_eq!(
11756                        err.expected, expected,
11757                        "KindMismatch.expected must byte-equal the \
11758                         expected variant passed to require_kind",
11759                    );
11760                }
11761            }
11762        }
11763    }
11764
11765    #[test]
11766    fn aplicacao_view_kind_gate_routes_through_accessor() {
11767        // Composition pin: [`Caixa::aplicacao_view`]'s kind-gate arm
11768        // must key off [`Caixa::kind`], not the raw `.kind` field
11769        // access. Structurally: a `Caixa { kind: X, .. }` for any
11770        // non-`Aplicacao` variant must fold to `None` on the
11771        // `aplicacao_view` composer (the "kind mismatch → no typed
11772        // view" contract every downstream Aplicacao consumer keys off
11773        // via `?`), and a `Caixa { kind: Aplicacao, .. }` must fold to
11774        // `Some(_)`. The pair jointly pins the accessor + view-gate
11775        // composition: any future silent detour that had the accessor
11776        // return a fresh [`CaixaKind::Aplicacao`] on some sentinel
11777        // input would silently absorb the kind-mismatch case at the
11778        // accessor boundary and every per-Aplicacao renderer would
11779        // silently render a non-Aplicacao caixa's mesh slots — the
11780        // composition pin catches that at caixa-core build time.
11781        //
11782        // Peer of the sibling per-`Caixa`
11783        // `validate_nome_empty_arm_routes_through_accessor` (e6b7d97) /
11784        // `validate_versao_empty_arm_routes_through_accessor` (20c0539)
11785        // composition pins on the sibling outer top-level [`Caixa`]
11786        // required-`&str` universal-axis surfaces — same "the
11787        // composer / validate gate must route through the substrate-
11788        // primitive typed dispatch" discipline extended onto the
11789        // outer top-level [`Caixa`] `Copy`-return required-
11790        // discriminant composition axis.
11791        for kind in [
11792            CaixaKind::Biblioteca,
11793            CaixaKind::Binario,
11794            CaixaKind::Servico,
11795            CaixaKind::Supervisor,
11796        ] {
11797            let c = caixa_with_kind(kind);
11798            assert!(
11799                c.aplicacao_view().is_none(),
11800                "aplicacao_view must return None on non-Aplicacao \
11801                 kind {kind:?} — the composer's kind-gate must route \
11802                 through Caixa::kind()",
11803            );
11804        }
11805        let c = caixa_with_kind(CaixaKind::Aplicacao);
11806        assert!(
11807            c.aplicacao_view().is_some(),
11808            "aplicacao_view must return Some on kind Aplicacao — \
11809             the composer's kind-gate must accept the matching arm \
11810             through Caixa::kind()",
11811        );
11812    }
11813
11814    #[test]
11815    fn supervisor_view_kind_gate_routes_through_accessor() {
11816        // Composition pin (mirror of the sibling
11817        // `aplicacao_view_kind_gate_routes_through_accessor` on the
11818        // second `_view` composer): [`Caixa::supervisor_view`]'s kind-
11819        // gate arm must key off [`Caixa::kind`], not the raw `.kind`
11820        // field access. A `Caixa { kind: X, .. }` for any non-
11821        // `Supervisor` variant must fold to `None` on the
11822        // `supervisor_view` composer, and a `Caixa { kind:
11823        // Supervisor, .. }` must fold to `Some(_)`. Same peer
11824        // composition pin discipline on the second `_view` composer
11825        // axis.
11826        for kind in [
11827            CaixaKind::Biblioteca,
11828            CaixaKind::Binario,
11829            CaixaKind::Servico,
11830            CaixaKind::Aplicacao,
11831        ] {
11832            let c = caixa_with_kind(kind);
11833            assert!(
11834                c.supervisor_view().is_none(),
11835                "supervisor_view must return None on non-Supervisor \
11836                 kind {kind:?} — the composer's kind-gate must route \
11837                 through Caixa::kind()",
11838            );
11839        }
11840        let mut c = caixa_with_kind(CaixaKind::Supervisor);
11841        // A Supervisor caixa needs a strategy + at least one child to
11842        // fold to a Some(_) that also validates; the composer itself
11843        // requires only the kind arm, so bare kind flip is enough to
11844        // pin the `Some(_)` return, but we populate the minimum
11845        // supervisor shape so a future strengthening of the composer
11846        // to reject an empty spec doesn't false-positive this pin.
11847        c.estrategia = Some(crate::supervisor::RestartStrategy::OneForOne);
11848        c.children = vec![crate::supervisor::ChildSpec {
11849            caixa: "child".into(),
11850            versao: "^0.1".into(),
11851            restart: crate::supervisor::RestartPolicy::Permanent,
11852        }];
11853        assert!(
11854            c.supervisor_view().is_some(),
11855            "supervisor_view must return Some on kind Supervisor — \
11856             the composer's kind-gate must accept the matching arm \
11857             through Caixa::kind()",
11858        );
11859    }
11860
11861    #[test]
11862    fn kind_projects_by_copy() {
11863        // The by-`Copy` pin: [`Caixa::kind`] returns a fresh
11864        // [`CaixaKind`] by `Copy` — the accessor must not borrow from
11865        // `&self` (the returned value is owned, `Copy`-projected from
11866        // the underlying [`CaixaKind`] storage; two calls on the same
11867        // [`Caixa`] must yield byte-equal values). Peer of the peer
11868        // per-`:placement` `Placement::estrategia` / per-`:supervisor`
11869        // `SupervisorSpec::estrategia` / per-`:children`
11870        // `ChildSpec::restart` `Copy`-return discriminant accessor
11871        // pins on the sibling nested-spec typed-slot discriminator
11872        // axes, extended onto the first outer top-level [`Caixa`]
11873        // required-`Copy`-return axis — pins against a future silent
11874        // detour that returned `&CaixaKind` (which would type-check
11875        // but silently constrain every consumer's callsite to a
11876        // borrow-shaped dispatch, breaking the zero-cost `Copy`
11877        // projection every peer sibling accessor carries).
11878        for kind in [
11879            CaixaKind::Biblioteca,
11880            CaixaKind::Binario,
11881            CaixaKind::Servico,
11882            CaixaKind::Supervisor,
11883            CaixaKind::Aplicacao,
11884        ] {
11885            let c = caixa_with_kind(kind);
11886            let first: CaixaKind = c.kind();
11887            let second: CaixaKind = c.kind();
11888            assert_eq!(
11889                first, second,
11890                "Caixa::kind must be idempotent — two successive \
11891                 calls on the same &self must return the same \
11892                 CaixaKind variant",
11893            );
11894            assert_eq!(
11895                first, kind,
11896                "Caixa::kind must return :kind verbatim by Copy — \
11897                 got {first:?}, expected {kind:?}",
11898            );
11899        }
11900    }
11901
11902    // ── Caixa::autores — outer top-level &[T] slice accessor ──────────
11903
11904    #[test]
11905    fn autores_returns_autores_slice_verbatim_across_permutations() {
11906        // The canonical per-`Caixa` `:autores` universal-axis maintainer-
11907        // name-list slice pin: [`Caixa::autores`] must return the
11908        // `:autores` typed [`Vec<String>`] list verbatim as a
11909        // `&[String]`, byte-equal to the raw `self.autores.as_slice()`
11910        // access across every representative value in the accept-set —
11911        // `[]` (the "no maintainers declared" arm every existing
11912        // fixture without an `:autores` line carries), `[""]` (a past-
11913        // the-guard sentinel that pins the accessor doesn't perform a
11914        // silent `[""] → []` collapse on the empty-entry arm — validate
11915        // rejects `[""]` through `AutorEmpty` but the accessor must
11916        // ship the raw slot verbatim so a validate-time gate regression
11917        // surfaces at the caixa-helm emit boundary rather than being
11918        // silently absorbed into a maintainer-drop), `["pleme-io"]` (the
11919        // canonical single-maintainer form every `feira init` template
11920        // scaffolds), `["alice", "bob"]` (a canonical multi-maintainer
11921        // form), `["alice <alice@example.com>", "bob <bob@example.com>"]`
11922        // (the canonical RFC-5322 `<name> <email>` form the
11923        // `is_chart_maintainer_name_shape` predicate accepts), and
11924        // `["pleme-io", "pleme-io"]` (a past-the-guard duplicate
11925        // sentinel — validate rejects through `AutorDuplicate` but the
11926        // accessor must ship the raw slot verbatim).
11927        //
11928        // First outer top-level [`Caixa`] `&[T]`-return slice accessor
11929        // pin on the substrate primitive — opens the "outer [`Caixa`]
11930        // `&[T]` slice" projection pattern the sibling per-`Caixa`
11931        // `:etiquetas` / `:deps` / `:deps-dev` / `:exe` / `:bibliotecas`
11932        // / `:servicos` / `:upgrade-from` / `:children` future lifts
11933        // fold on. Sibling in shape to the peer per-`:supervisor`
11934        // [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
11935        // per-`:placement` [`crate::aplicacao::Placement::clusters`]
11936        // (a6e18d7), per-`:membros`
11937        // [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
11938        // per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
11939        // (0dcc926), and per-`:upgrade-from :instructions`
11940        // [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
11941        // `&[T]`-return slice accessor pins on the sibling per-M2 /
11942        // per-M3 typed-slot list axes, extended onto the outer top-
11943        // level [`Caixa`] universal-axis surface. Pins against a future
11944        // silent detour that returned an owned `Vec<String>` (which
11945        // would type-check but silently clone on every accessor call,
11946        // breaking the zero-cost projection every peer sibling slice
11947        // accessor carries), a `[""] → []` collapse (which would
11948        // silently absorb the `AutorEmpty` refusal case at the accessor
11949        // boundary), or a `["a", "a"] → ["a"]` dedup collapse (which
11950        // would silently absorb the `AutorDuplicate` refusal case at
11951        // the accessor boundary and the caixa-helm `maintainers:` fold
11952        // would silently render a dedupped list on a struct-literal
11953        // `Caixa { autores: vec!["a".into(), "a".into()], .. }`).
11954        for autores in [
11955            vec![],
11956            vec![""],
11957            vec!["pleme-io"],
11958            vec!["alice", "bob"],
11959            vec!["alice <alice@example.com>", "bob <bob@example.com>"],
11960            vec!["pleme-io", "pleme-io"],
11961        ] {
11962            let c = caixa_with_autores(autores.clone());
11963            let expected: Vec<String> = autores.iter().map(|s| (*s).to_string()).collect();
11964            assert_eq!(
11965                c.autores(),
11966                expected.as_slice(),
11967                "Caixa::autores must return :autores verbatim (got {:?}, \
11968                 expected {expected:?})",
11969                c.autores(),
11970            );
11971            assert_eq!(
11972                c.autores(),
11973                c.autores.as_slice(),
11974                "Caixa::autores must byte-equal the raw \
11975                 `self.autores.as_slice()` field access across every \
11976                 value in the Vec<String> accept-set",
11977            );
11978        }
11979    }
11980
11981    #[test]
11982    fn validate_autores_empty_entry_arm_routes_through_accessor() {
11983        // Composition pin: [`Caixa::validate_autores`]'s per-entry
11984        // empty-arm gate must key off [`Caixa::autores`], not the raw
11985        // `&self.autores` field-borrow walk. Structurally: a
11986        // `Caixa { autores: vec!["".into()], .. }` must surface the
11987        // `AutorEmpty` refusal exactly, and a
11988        // `Caixa { autores: vec!["pleme-io".into()], .. }` (the
11989        // canonical single-maintainer form) must pass validate. The
11990        // pair jointly pins the accessor + validate-gate composition:
11991        // any future silent detour that had the accessor return an
11992        // empty slice on the `[""]` arm (a
11993        // `.iter().filter(|s| !s.is_empty()).collect()` collapse)
11994        // would silently absorb the `AutorEmpty` refusal at the
11995        // accessor boundary and the validate gate would accept a
11996        // struct-literal `Caixa { autores: vec!["".into()], .. }` —
11997        // the composition pin catches that at caixa-core build time.
11998        //
11999        // Peer of the per-`Caixa` [`Caixa::validate_licenca`] (6d5bc28)
12000        // accessor-composition pin
12001        // (`validate_licenca_empty_arm_routes_through_accessor`) on the
12002        // sibling `Option<&str>`-composition axis and the
12003        // per-`:politicas :circuit-breaker`
12004        // [`crate::aplicacao::CircuitBreaker::max_failures`] (3a74062)
12005        // accessor-composition pin
12006        // (`validate_politicas_max_failures_zero_floor_arm_routes_through_accessor`)
12007        // on the sibling required-`u32`-composition axis — same "the
12008        // validate / shape-gate predicate must route through the
12009        // substrate-primitive typed dispatch" discipline extended onto
12010        // the outer top-level [`Caixa`] universal-axis `&[T]`-
12011        // composition surface.
12012        let c = caixa_with_autores(vec![""]);
12013        assert!(
12014            matches!(c.validate_autores(), Err(ManifestError::AutorEmpty)),
12015            "validate_autores must reject autores == vec![\"\"] with \
12016             AutorEmpty — the accessor and the validate gate must \
12017             route through the same substrate-primitive typed dispatch \
12018             on the :autores per-entry empty arm",
12019        );
12020        let c = caixa_with_autores(vec!["pleme-io"]);
12021        assert!(
12022            c.validate_autores().is_ok(),
12023            "validate_autores must accept autores == vec![\"pleme-io\"] \
12024             (the canonical single-maintainer shape every `feira init` \
12025             template scaffolds)",
12026        );
12027    }
12028
12029    #[test]
12030    fn autores_projects_slice_by_borrow() {
12031        // The by-borrow pin: [`Caixa::autores`] returns `&[String]` by
12032        // borrow — the returned slice borrows the underlying
12033        // `Vec<String>` storage of the `:autores` slot and the
12034        // accessor must not clone the backing `Vec` on every call.
12035        // Peer of the per-`:membros`
12036        // [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36) /
12037        // per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
12038        // (0dcc926) / per-`:placement`
12039        // [`crate::aplicacao::Placement::clusters`] (a6e18d7) /
12040        // per-`:supervisor` [`crate::supervisor::SupervisorSpec::children`]
12041        // (bc92bce) by-borrow pins on the sibling per-M2 / per-M3
12042        // typed-slot `&[T]`-return axes, extended onto the outer top-
12043        // level [`Caixa`] universal-axis `&[String]` shape — the
12044        // accessor's returned slice must borrow from `&self` (the
12045        // returned reference's lifetime is tied to `&self`), and
12046        // calling the accessor twice on the same [`Caixa`] must yield
12047        // slices that are pointer-equal (the underlying byte-buffer is
12048        // the storage `Vec`'s allocation, not a fresh copy) as well as
12049        // value-equal (idempotent, no side effects on `&self`).
12050        //
12051        // Pins against a future silent detour that returned an owned
12052        // `Vec<String>` (which would type-check but silently clone on
12053        // every call, breaking the zero-cost projection every peer
12054        // sibling slice accessor carries), a `&Vec<String>` return
12055        // (which would leak the backing `Vec`'s grow/push/reserve
12056        // surface no downstream consumer reaches for), or a one-arm-
12057        // only accessor that returned a saturating value on some
12058        // sentinel input (breaking the pass-through invariant the
12059        // sibling slice accessors carry).
12060        for autores in [
12061            vec![],
12062            vec!["pleme-io"],
12063            vec!["alice", "bob"],
12064            vec!["pleme-io", "pleme-io"],
12065        ] {
12066            let c = caixa_with_autores(autores.clone());
12067            let expected: Vec<String> = autores.iter().map(|s| (*s).to_string()).collect();
12068            let first = c.autores();
12069            let second = c.autores();
12070            assert_eq!(
12071                first, second,
12072                "Caixa::autores must be idempotent — two successive \
12073                 calls on the same &self must return the same \
12074                 &[String]",
12075            );
12076            assert_eq!(
12077                first.as_ptr(),
12078                second.as_ptr(),
12079                "Caixa::autores must borrow the underlying Vec<String> \
12080                 storage — two successive calls must return slices \
12081                 with the same backing pointer (a fresh Vec<String> \
12082                 clone would change the pointer on every call)",
12083            );
12084            assert_eq!(
12085                first,
12086                expected.as_slice(),
12087                "Caixa::autores must return :autores verbatim by \
12088                 borrow — got {first:?}, expected {expected:?}",
12089            );
12090        }
12091    }
12092
12093    // ── Caixa::etiquetas — outer top-level &[T] slice accessor ────────
12094
12095    #[test]
12096    fn etiquetas_returns_etiquetas_slice_verbatim_across_permutations() {
12097        // The canonical per-`Caixa` `:etiquetas` universal-axis
12098        // registry-search-tag-list slice pin: [`Caixa::etiquetas`] must
12099        // return the `:etiquetas` typed [`Vec<String>`] list verbatim
12100        // as a `&[String]`, byte-equal to the raw
12101        // `self.etiquetas.as_slice()` access across every representative
12102        // value in the accept-set — `[]` (the "no tags declared" arm
12103        // every existing fixture without an `:etiquetas` line carries),
12104        // `[""]` (a past-the-guard sentinel that pins the accessor
12105        // doesn't perform a silent `[""] → []` collapse on the empty-
12106        // entry arm — validate rejects `[""]` through `EtiquetaEmpty`
12107        // but the accessor must ship the raw slot verbatim so a
12108        // validate-time gate regression surfaces at the caixa-helm emit
12109        // boundary rather than being silently absorbed into a keyword-
12110        // drop), `["demo"]` (the canonical single-tag form every
12111        // `feira init` template scaffolds), `["example", "aplicacao",
12112        // "mesh", "ecommerce", "demo"]` (the canonical multi-tag form
12113        // the checkout-aplicacao fixture emits), and `["demo", "demo"]`
12114        // (a past-the-guard duplicate sentinel — validate rejects
12115        // through `EtiquetaDuplicate` but the accessor must ship the
12116        // raw slot verbatim so the caixa-helm `BTreeSet::collect` dedup
12117        // at chart-render time isn't silently promoted into the
12118        // accessor boundary and struct-literal
12119        // `Caixa { etiquetas: vec!["demo".into(), "demo".into()], .. }`
12120        // fixtures continue to expose the duplicate at the accessor).
12121        //
12122        // Second outer top-level [`Caixa`] `&[T]`-return slice accessor
12123        // pin on the substrate primitive — folds on the "outer
12124        // [`Caixa`] `&[T]` slice" projection pattern
12125        // `autores_returns_autores_slice_verbatim_across_permutations`
12126        // (b5d813f) opened, sibling in shape and idiom. Pins against a
12127        // future silent detour that returned an owned `Vec<String>`
12128        // (which would type-check but silently clone on every accessor
12129        // call, breaking the zero-cost projection every peer sibling
12130        // slice accessor carries), a `[""] → []` collapse (which would
12131        // silently absorb the `EtiquetaEmpty` refusal case at the
12132        // accessor boundary), or a `["a", "a"] → ["a"]` dedup collapse
12133        // (which would silently absorb the `EtiquetaDuplicate` refusal
12134        // case at the accessor boundary — the caixa-helm chart-render
12135        // `BTreeSet::collect` dedup is downstream of the accessor and
12136        // must not be silently promoted into it).
12137        for etiquetas in [
12138            vec![],
12139            vec![""],
12140            vec!["demo"],
12141            vec!["example", "aplicacao", "mesh", "ecommerce", "demo"],
12142            vec!["demo", "demo"],
12143        ] {
12144            let c = caixa_with_etiquetas(etiquetas.clone());
12145            let expected: Vec<String> = etiquetas.iter().map(|s| (*s).to_string()).collect();
12146            assert_eq!(
12147                c.etiquetas(),
12148                expected.as_slice(),
12149                "Caixa::etiquetas must return :etiquetas verbatim (got \
12150                 {:?}, expected {expected:?})",
12151                c.etiquetas(),
12152            );
12153            assert_eq!(
12154                c.etiquetas(),
12155                c.etiquetas.as_slice(),
12156                "Caixa::etiquetas must byte-equal the raw \
12157                 `self.etiquetas.as_slice()` field access across every \
12158                 value in the Vec<String> accept-set",
12159            );
12160        }
12161    }
12162
12163    #[test]
12164    fn validate_etiquetas_empty_entry_arm_routes_through_accessor() {
12165        // Composition pin: [`Caixa::validate_etiquetas`]'s per-entry
12166        // empty-arm gate must key off [`Caixa::etiquetas`], not the raw
12167        // `&self.etiquetas` field-borrow walk. Structurally: a
12168        // `Caixa { etiquetas: vec!["".into()], .. }` must surface the
12169        // `EtiquetaEmpty` refusal exactly, and a
12170        // `Caixa { etiquetas: vec!["demo".into()], .. }` (the canonical
12171        // single-tag form) must pass validate. The pair jointly pins
12172        // the accessor + validate-gate composition: any future silent
12173        // detour that had the accessor return an empty slice on the
12174        // `[""]` arm (a
12175        // `.iter().filter(|s| !s.is_empty()).collect()` collapse) would
12176        // silently absorb the `EtiquetaEmpty` refusal at the accessor
12177        // boundary and the validate gate would accept a struct-literal
12178        // `Caixa { etiquetas: vec!["".into()], .. }` — the composition
12179        // pin catches that at caixa-core build time.
12180        //
12181        // Peer of the per-`Caixa` `validate_autores_empty_arm_routes_
12182        // through_accessor` (b5d813f) accessor-composition pin on the
12183        // sibling `&[T]`-composition axis — same "the validate / shape-
12184        // gate predicate must route through the substrate-primitive
12185        // typed dispatch" discipline extended onto the sibling outer
12186        // top-level [`Caixa`] `&[T]`-composition surface.
12187        let c = caixa_with_etiquetas(vec![""]);
12188        assert!(
12189            matches!(c.validate_etiquetas(), Err(ManifestError::EtiquetaEmpty)),
12190            "validate_etiquetas must reject etiquetas == vec![\"\"] \
12191             with EtiquetaEmpty — the accessor and the validate gate \
12192             must route through the same substrate-primitive typed \
12193             dispatch on the :etiquetas per-entry empty arm",
12194        );
12195        let c = caixa_with_etiquetas(vec!["demo"]);
12196        assert!(
12197            c.validate_etiquetas().is_ok(),
12198            "validate_etiquetas must accept etiquetas == vec![\"demo\"] \
12199             (the canonical single-tag shape every `feira init` \
12200             template scaffolds)",
12201        );
12202    }
12203
12204    #[test]
12205    fn etiquetas_projects_slice_by_borrow() {
12206        // The by-borrow pin: [`Caixa::etiquetas`] returns `&[String]`
12207        // by borrow — the returned slice borrows the underlying
12208        // `Vec<String>` storage of the `:etiquetas` slot and the
12209        // accessor must not clone the backing `Vec` on every call.
12210        // Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
12211        // (b5d813f) by-borrow pin on the sibling outer top-level
12212        // [`Caixa`] `&[String]`-return axis — the accessor's returned
12213        // slice must borrow from `&self` (the returned reference's
12214        // lifetime is tied to `&self`), and calling the accessor twice
12215        // on the same [`Caixa`] must yield slices that are pointer-
12216        // equal (the underlying byte-buffer is the storage `Vec`'s
12217        // allocation, not a fresh copy) as well as value-equal
12218        // (idempotent, no side effects on `&self`).
12219        //
12220        // Pins against a future silent detour that returned an owned
12221        // `Vec<String>` (which would type-check but silently clone on
12222        // every call, breaking the zero-cost projection every peer
12223        // sibling slice accessor carries), a `&Vec<String>` return
12224        // (which would leak the backing `Vec`'s grow/push/reserve
12225        // surface no downstream consumer reaches for), or a one-arm-
12226        // only accessor that returned a saturating value on some
12227        // sentinel input (breaking the pass-through invariant the
12228        // sibling slice accessors carry).
12229        for etiquetas in [
12230            vec![],
12231            vec!["demo"],
12232            vec!["example", "aplicacao", "mesh"],
12233            vec!["demo", "demo"],
12234        ] {
12235            let c = caixa_with_etiquetas(etiquetas.clone());
12236            let expected: Vec<String> = etiquetas.iter().map(|s| (*s).to_string()).collect();
12237            let first = c.etiquetas();
12238            let second = c.etiquetas();
12239            assert_eq!(
12240                first, second,
12241                "Caixa::etiquetas must be idempotent — two successive \
12242                 calls on the same &self must return the same \
12243                 &[String]",
12244            );
12245            assert_eq!(
12246                first.as_ptr(),
12247                second.as_ptr(),
12248                "Caixa::etiquetas must borrow the underlying \
12249                 Vec<String> storage — two successive calls must \
12250                 return slices with the same backing pointer (a fresh \
12251                 Vec<String> clone would change the pointer on every \
12252                 call)",
12253            );
12254            assert_eq!(
12255                first,
12256                expected.as_slice(),
12257                "Caixa::etiquetas must return :etiquetas verbatim by \
12258                 borrow — got {first:?}, expected {expected:?}",
12259            );
12260        }
12261    }
12262
12263    // ── Caixa::bibliotecas — outer top-level &[T] slice accessor ──────
12264
12265    #[test]
12266    fn bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations() {
12267        // The canonical per-`Caixa` `:bibliotecas` universal-axis
12268        // library-source-path-list slice pin: [`Caixa::bibliotecas`]
12269        // must return the `:bibliotecas` typed [`Vec<String>`] list
12270        // verbatim as a `&[String]`, byte-equal to the raw
12271        // `self.bibliotecas.as_slice()` access across every
12272        // representative value in the accept-set — `[]` (the "no
12273        // libraries declared" arm every `:kind` other than `Biblioteca`
12274        // + every `Biblioteca` relying on the canonical
12275        // `lib/<nome>.lisp` implicit-default path carries; the
12276        // layout's [`crate::LayoutInvariants`] `MissingLib` arm-gate
12277        // fires exactly on this empty-slot + `Biblioteca`-kind
12278        // combination), `[""]` (a past-the-guard sentinel that pins
12279        // the accessor doesn't perform a silent `[""] → []` collapse
12280        // on the empty-entry arm — validate rejects `[""]` through
12281        // `CodePathEmpty { slot: ":bibliotecas" }` but the accessor
12282        // must ship the raw slot verbatim so a validate-time gate
12283        // regression surfaces at the `feira build` phase-1 parse
12284        // boundary rather than being silently absorbed into a
12285        // library-drop), `["lib/demo.lisp"]` (the canonical single-
12286        // entry form `Caixa::template` scaffolds and every `feira init`
12287        // template emits), `["lib/demo.lisp", "lib/helpers.lisp"]`
12288        // (the canonical multi-library form the
12289        // `validate_code_paths_accepts_explicit_relative_paths_on_
12290        // every_slot` fixture emits), and `["lib/foo.lisp",
12291        // "lib/foo.lisp"]` (a past-the-guard duplicate sentinel —
12292        // validate rejects through `CodePathDuplicate { slot:
12293        // ":bibliotecas" }` per the per-slot set-not-multiset gate,
12294        // but the accessor must ship the raw slot verbatim so the
12295        // `feira build` `for entry in caixa.bibliotecas()` parse walk
12296        // sees the duplicate at the accessor boundary and struct-
12297        // literal `Caixa { bibliotecas: vec!["lib/foo.lisp".into(),
12298        // "lib/foo.lisp".into()], .. }` fixtures continue to expose
12299        // the duplicate at the accessor).
12300        //
12301        // Third outer top-level [`Caixa`] `&[T]`-return slice accessor
12302        // pin on the substrate primitive — folds on the "outer
12303        // [`Caixa`] `&[T]` slice" projection pattern
12304        // `autores_returns_autores_slice_verbatim_across_permutations`
12305        // (b5d813f) opened and
12306        // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
12307        // (78c7d3c) folded on, sibling in shape and idiom. Pins
12308        // against a future silent detour that returned an owned
12309        // `Vec<String>` (which would type-check but silently clone on
12310        // every accessor call, breaking the zero-cost projection
12311        // every peer sibling slice accessor carries), a `[""] → []`
12312        // collapse (which would silently absorb the `CodePathEmpty`
12313        // refusal case at the accessor boundary), or a `["lib/foo.lisp",
12314        // "lib/foo.lisp"] → ["lib/foo.lisp"]` dedup collapse (which
12315        // would silently absorb the `CodePathDuplicate` refusal case
12316        // at the accessor boundary — the per-slot set-not-multiset
12317        // gate is downstream of the accessor and must not be silently
12318        // promoted into it).
12319        for bibliotecas in [
12320            vec![],
12321            vec![""],
12322            vec!["lib/demo.lisp"],
12323            vec!["lib/demo.lisp", "lib/helpers.lisp"],
12324            vec!["lib/foo.lisp", "lib/foo.lisp"],
12325        ] {
12326            let c = caixa_with_code_paths(bibliotecas.clone(), vec![], vec![]);
12327            let expected: Vec<String> = bibliotecas.iter().map(|s| (*s).to_string()).collect();
12328            assert_eq!(
12329                c.bibliotecas(),
12330                expected.as_slice(),
12331                "Caixa::bibliotecas must return :bibliotecas verbatim \
12332                 (got {:?}, expected {expected:?})",
12333                c.bibliotecas(),
12334            );
12335            assert_eq!(
12336                c.bibliotecas(),
12337                c.bibliotecas.as_slice(),
12338                "Caixa::bibliotecas must byte-equal the raw \
12339                 `self.bibliotecas.as_slice()` field access across \
12340                 every value in the Vec<String> accept-set",
12341            );
12342        }
12343    }
12344
12345    #[test]
12346    fn validate_code_paths_bibliotecas_empty_arm_routes_through_accessor() {
12347        // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
12348        // empty-arm gate on the `:bibliotecas` slot must key off
12349        // [`Caixa::bibliotecas`], not a divergent raw
12350        // `&self.bibliotecas` field-borrow walk. Structurally: a
12351        // `Caixa { bibliotecas: vec!["".into()], .. }` must surface
12352        // the `CodePathEmpty { slot: ":bibliotecas" }` refusal
12353        // exactly, and a `Caixa { bibliotecas: vec!["lib/demo.lisp".
12354        // into()], .. }` (the canonical single-library form
12355        // `Caixa::template` scaffolds) must pass validate. The pair
12356        // jointly pins the accessor + validate-gate composition: any
12357        // future silent detour that had the accessor return an empty
12358        // slice on the `[""]` arm (a `.iter().filter(|s|
12359        // !s.is_empty()).collect()` collapse) would silently absorb
12360        // the `CodePathEmpty` refusal at the accessor boundary and
12361        // the validate gate would accept a struct-literal
12362        // `Caixa { bibliotecas: vec!["".into()], .. }` — the
12363        // composition pin catches that at caixa-core build time.
12364        //
12365        // Peer of the per-`Caixa` `validate_autores_empty_arm_routes_
12366        // through_accessor` (b5d813f) and
12367        // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
12368        // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
12369        // composition axes — same "the validate / shape-gate
12370        // predicate must route through the substrate-primitive typed
12371        // dispatch" discipline extended onto the sibling outer top-
12372        // level [`Caixa`] `&[T]`-composition surface. Nominally the
12373        // in-tree `validate_code_paths` production body still keys
12374        // off the internal `[(":bibliotecas", &self.bibliotecas,
12375        // CodePathFileType::LispSource), (":exe", &self.exe, ..),
12376        // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
12377        // (the tuple's homogeneous slice-typed shape blocks a per-
12378        // element accessor swap in isolation — a future companion
12379        // lift for `:exe` and `:servicos` on the same outer-`Caixa`
12380        // `&[T]` slice-accessor axis closes that tuple onto the
12381        // triple of typed dispatches as a unit); the composition pin
12382        // catches any future accessor-side silent filter drop against
12383        // that eventual tuple-closure regardless of whether the
12384        // `:bibliotecas` slot is threaded through the accessor or the
12385        // raw field access at the tuple's construction site.
12386        let c = caixa_with_code_paths(vec![""], vec![], vec![]);
12387        assert!(
12388            matches!(
12389                c.validate_code_paths(),
12390                Err(ManifestError::CodePathEmpty {
12391                    slot: ":bibliotecas"
12392                })
12393            ),
12394            "validate_code_paths must reject bibliotecas == vec![\"\"] \
12395             with CodePathEmpty {{ slot: \":bibliotecas\" }} — the \
12396             accessor and the validate gate must route through the \
12397             same substrate-primitive typed dispatch on the \
12398             :bibliotecas per-entry empty arm",
12399        );
12400        let c = caixa_with_code_paths(vec!["lib/demo.lisp"], vec![], vec![]);
12401        assert!(
12402            c.validate_code_paths().is_ok(),
12403            "validate_code_paths must accept bibliotecas == \
12404             vec![\"lib/demo.lisp\"] (the canonical single-library \
12405             shape every `feira init` template scaffolds)",
12406        );
12407    }
12408
12409    #[test]
12410    fn bibliotecas_projects_slice_by_borrow() {
12411        // The by-borrow pin: [`Caixa::bibliotecas`] returns
12412        // `&[String]` by borrow — the returned slice borrows the
12413        // underlying `Vec<String>` storage of the `:bibliotecas` slot
12414        // and the accessor must not clone the backing `Vec` on every
12415        // call. Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
12416        // (b5d813f) and `etiquetas_projects_slice_by_borrow` (78c7d3c)
12417        // by-borrow pins on the sibling outer top-level [`Caixa`]
12418        // `&[String]`-return axes — the accessor's returned slice
12419        // must borrow from `&self` (the returned reference's lifetime
12420        // is tied to `&self`), and calling the accessor twice on the
12421        // same [`Caixa`] must yield slices that are pointer-equal
12422        // (the underlying byte-buffer is the storage `Vec`'s
12423        // allocation, not a fresh copy) as well as value-equal
12424        // (idempotent, no side effects on `&self`).
12425        //
12426        // Pins against a future silent detour that returned an owned
12427        // `Vec<String>` (which would type-check but silently clone on
12428        // every call, breaking the zero-cost projection every peer
12429        // sibling slice accessor carries), a `&Vec<String>` return
12430        // (which would leak the backing `Vec`'s grow/push/reserve
12431        // surface no downstream consumer reaches for), or a one-arm-
12432        // only accessor that returned a saturating value on some
12433        // sentinel input (breaking the pass-through invariant the
12434        // sibling slice accessors carry).
12435        for bibliotecas in [
12436            vec![],
12437            vec!["lib/demo.lisp"],
12438            vec!["lib/demo.lisp", "lib/helpers.lisp"],
12439            vec!["lib/foo.lisp", "lib/foo.lisp"],
12440        ] {
12441            let c = caixa_with_code_paths(bibliotecas.clone(), vec![], vec![]);
12442            let expected: Vec<String> = bibliotecas.iter().map(|s| (*s).to_string()).collect();
12443            let first = c.bibliotecas();
12444            let second = c.bibliotecas();
12445            assert_eq!(
12446                first, second,
12447                "Caixa::bibliotecas must be idempotent — two \
12448                 successive calls on the same &self must return the \
12449                 same &[String]",
12450            );
12451            assert_eq!(
12452                first.as_ptr(),
12453                second.as_ptr(),
12454                "Caixa::bibliotecas must borrow the underlying \
12455                 Vec<String> storage — two successive calls must \
12456                 return slices with the same backing pointer (a \
12457                 fresh Vec<String> clone would change the pointer on \
12458                 every call)",
12459            );
12460            assert_eq!(
12461                first,
12462                expected.as_slice(),
12463                "Caixa::bibliotecas must return :bibliotecas verbatim \
12464                 by borrow — got {first:?}, expected {expected:?}",
12465            );
12466        }
12467    }
12468
12469    // ── Caixa::exe — outer top-level &[T] slice accessor ──────────────
12470
12471    #[test]
12472    fn exe_returns_exe_slice_verbatim_across_permutations() {
12473        // The canonical per-`Caixa` `:exe` universal-axis
12474        // nix-built-executable-entry-path-list slice pin: [`Caixa::exe`]
12475        // must return the `:exe` typed [`Vec<String>`] list verbatim as
12476        // a `&[String]`, byte-equal to the raw `self.exe.as_slice()`
12477        // access across every representative value in the accept-set —
12478        // `[]` (the "no executable declared" arm every `:kind` other
12479        // than `Binario` carries; the layout's [`crate::LayoutInvariants`]
12480        // `BinarioWithoutExe` arm-gate fires exactly on this empty-slot
12481        // + `Binario`-kind combination), `[""]` (a past-the-guard
12482        // sentinel that pins the accessor doesn't perform a silent
12483        // `[""] → []` collapse on the empty-entry arm — validate rejects
12484        // `[""]` through `CodePathEmpty { slot: ":exe" }` but the
12485        // accessor must ship the raw slot verbatim so a validate-time
12486        // gate regression surfaces at the layout / `feira nix` boundary
12487        // rather than being silently absorbed into an executable-drop),
12488        // `["exe/cli"]` (the canonical single-entry Binario form every
12489        // in-tree `caixa_with_code_paths` positive control uses),
12490        // `["exe/cli", "exe/serve"]` (the canonical multi-executable
12491        // form the `validate_code_paths_accepts_explicit_relative_paths_
12492        // on_every_slot` fixture emits), and `["exe/cli", "exe/cli"]`
12493        // (a past-the-guard duplicate sentinel — validate rejects
12494        // through `CodePathDuplicate { slot: ":exe" }` per the per-slot
12495        // set-not-multiset gate, but the accessor must ship the raw
12496        // slot verbatim so struct-literal `Caixa { exe: vec!["exe/cli".
12497        // into(), "exe/cli".into()], .. }` fixtures continue to expose
12498        // the duplicate at the accessor).
12499        //
12500        // Fourth outer top-level [`Caixa`] `&[T]`-return slice accessor
12501        // pin on the substrate primitive — folds on the "outer
12502        // [`Caixa`] `&[T]` slice" projection pattern
12503        // `autores_returns_autores_slice_verbatim_across_permutations`
12504        // (b5d813f) opened,
12505        // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
12506        // (78c7d3c) folded on, and
12507        // `bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
12508        // (8a36c23) closed the universal-axis text-tag family of.
12509        // Opens the outer-`Caixa` foreign-code-slot `&[T]` sub-family
12510        // the sibling `:servicos` future lift closes onto. Pins against
12511        // a future silent detour that returned an owned `Vec<String>`
12512        // (which would type-check but silently clone on every accessor
12513        // call, breaking the zero-cost projection every peer sibling
12514        // slice accessor carries), a `[""] → []` collapse (which would
12515        // silently absorb the `CodePathEmpty` refusal case at the
12516        // accessor boundary), or an `["exe/cli", "exe/cli"] →
12517        // ["exe/cli"]` dedup collapse (which would silently absorb the
12518        // `CodePathDuplicate` refusal case at the accessor boundary —
12519        // the per-slot set-not-multiset gate is downstream of the
12520        // accessor and must not be silently promoted into it).
12521        for exe in [
12522            vec![],
12523            vec![""],
12524            vec!["exe/cli"],
12525            vec!["exe/cli", "exe/serve"],
12526            vec!["exe/cli", "exe/cli"],
12527        ] {
12528            let c = caixa_with_code_paths(vec![], exe.clone(), vec![]);
12529            let expected: Vec<String> = exe.iter().map(|s| (*s).to_string()).collect();
12530            assert_eq!(
12531                c.exe(),
12532                expected.as_slice(),
12533                "Caixa::exe must return :exe verbatim (got {:?}, \
12534                 expected {expected:?})",
12535                c.exe(),
12536            );
12537            assert_eq!(
12538                c.exe(),
12539                c.exe.as_slice(),
12540                "Caixa::exe must byte-equal the raw \
12541                 `self.exe.as_slice()` field access across every value \
12542                 in the Vec<String> accept-set",
12543            );
12544        }
12545    }
12546
12547    #[test]
12548    fn validate_code_paths_exe_empty_arm_routes_through_accessor() {
12549        // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
12550        // empty-arm gate on the `:exe` slot must key off
12551        // [`Caixa::exe`], not a divergent raw `&self.exe` field-borrow
12552        // walk. Structurally: a `Caixa { exe: vec!["".into()], .. }`
12553        // must surface the `CodePathEmpty { slot: ":exe" }` refusal
12554        // exactly, and a `Caixa { exe: vec!["exe/cli".into()], .. }`
12555        // (the canonical single-executable form every in-tree
12556        // `caixa_with_code_paths` positive control uses) must pass
12557        // validate. The pair jointly pins the accessor + validate-gate
12558        // composition: any future silent detour that had the accessor
12559        // return an empty slice on the `[""]` arm (a
12560        // `.iter().filter(|s| !s.is_empty()).collect()` collapse) would
12561        // silently absorb the `CodePathEmpty` refusal at the accessor
12562        // boundary and the validate gate would accept a struct-literal
12563        // `Caixa { exe: vec!["".into()], .. }` — the composition pin
12564        // catches that at caixa-core build time.
12565        //
12566        // Peer of the per-`Caixa`
12567        // `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
12568        // (8a36c23), `validate_autores_empty_arm_routes_through_accessor`
12569        // (b5d813f), and
12570        // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
12571        // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
12572        // composition axes — same "the validate / shape-gate predicate
12573        // must route through the substrate-primitive typed dispatch"
12574        // discipline extended onto the sibling outer top-level [`Caixa`]
12575        // `&[T]`-composition surface. Nominally the in-tree
12576        // `validate_code_paths` production body still keys off the
12577        // internal `[(":bibliotecas", &self.bibliotecas,
12578        // CodePathFileType::LispSource), (":exe", &self.exe, ..),
12579        // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
12580        // (the tuple's homogeneous slice-typed shape blocks a per-
12581        // element accessor swap in isolation — a future companion lift
12582        // for `:servicos` on the same outer-`Caixa` `&[T]` slice-
12583        // accessor axis closes that tuple onto the triple of typed
12584        // dispatches as a unit); the composition pin catches any future
12585        // accessor-side silent filter drop against that eventual tuple-
12586        // closure regardless of whether the `:exe` slot is threaded
12587        // through the accessor or the raw field access at the tuple's
12588        // construction site.
12589        let c = caixa_with_code_paths(vec![], vec![""], vec![]);
12590        assert!(
12591            matches!(
12592                c.validate_code_paths(),
12593                Err(ManifestError::CodePathEmpty { slot: ":exe" })
12594            ),
12595            "validate_code_paths must reject exe == vec![\"\"] \
12596             with CodePathEmpty {{ slot: \":exe\" }} — the \
12597             accessor and the validate gate must route through the \
12598             same substrate-primitive typed dispatch on the \
12599             :exe per-entry empty arm",
12600        );
12601        let c = caixa_with_code_paths(vec![], vec!["exe/cli"], vec![]);
12602        assert!(
12603            c.validate_code_paths().is_ok(),
12604            "validate_code_paths must accept exe == vec![\"exe/cli\"] \
12605             (the canonical single-executable shape every in-tree \
12606             `caixa_with_code_paths` positive control uses)",
12607        );
12608    }
12609
12610    #[test]
12611    fn exe_projects_slice_by_borrow() {
12612        // The by-borrow pin: [`Caixa::exe`] returns `&[String]` by
12613        // borrow — the returned slice borrows the underlying
12614        // `Vec<String>` storage of the `:exe` slot and the accessor
12615        // must not clone the backing `Vec` on every call. Peer of the
12616        // per-`Caixa` `autores_projects_slice_by_borrow` (b5d813f),
12617        // `etiquetas_projects_slice_by_borrow` (78c7d3c), and
12618        // `bibliotecas_projects_slice_by_borrow` (8a36c23) by-borrow
12619        // pins on the sibling outer top-level [`Caixa`] `&[String]`-
12620        // return axes — the accessor's returned slice must borrow from
12621        // `&self` (the returned reference's lifetime is tied to
12622        // `&self`), and calling the accessor twice on the same
12623        // [`Caixa`] must yield slices that are pointer-equal (the
12624        // underlying byte-buffer is the storage `Vec`'s allocation,
12625        // not a fresh copy) as well as value-equal (idempotent, no
12626        // side effects on `&self`).
12627        //
12628        // Pins against a future silent detour that returned an owned
12629        // `Vec<String>` (which would type-check but silently clone on
12630        // every call, breaking the zero-cost projection every peer
12631        // sibling slice accessor carries), a `&Vec<String>` return
12632        // (which would leak the backing `Vec`'s grow/push/reserve
12633        // surface no downstream consumer reaches for), or a one-arm-
12634        // only accessor that returned a saturating value on some
12635        // sentinel input (breaking the pass-through invariant the
12636        // sibling slice accessors carry).
12637        for exe in [
12638            vec![],
12639            vec!["exe/cli"],
12640            vec!["exe/cli", "exe/serve"],
12641            vec!["exe/cli", "exe/cli"],
12642        ] {
12643            let c = caixa_with_code_paths(vec![], exe.clone(), vec![]);
12644            let expected: Vec<String> = exe.iter().map(|s| (*s).to_string()).collect();
12645            let first = c.exe();
12646            let second = c.exe();
12647            assert_eq!(
12648                first, second,
12649                "Caixa::exe must be idempotent — two successive calls \
12650                 on the same &self must return the same &[String]",
12651            );
12652            assert_eq!(
12653                first.as_ptr(),
12654                second.as_ptr(),
12655                "Caixa::exe must borrow the underlying Vec<String> \
12656                 storage — two successive calls must return slices \
12657                 with the same backing pointer (a fresh Vec<String> \
12658                 clone would change the pointer on every call)",
12659            );
12660            assert_eq!(
12661                first,
12662                expected.as_slice(),
12663                "Caixa::exe must return :exe verbatim by borrow — \
12664                 got {first:?}, expected {expected:?}",
12665            );
12666        }
12667    }
12668
12669    // ── Caixa::servicos — outer top-level &[T] slice accessor ─────────
12670
12671    #[test]
12672    fn servicos_returns_servicos_slice_verbatim_across_permutations() {
12673        // The canonical per-`Caixa` `:servicos` universal-axis
12674        // ComputeUnit-CR-YAML-entry-path-list slice pin:
12675        // [`Caixa::servicos`] must return the `:servicos` typed
12676        // [`Vec<String>`] list verbatim as a `&[String]`, byte-equal to
12677        // the raw `self.servicos.as_slice()` access across every
12678        // representative value in the accept-set — `[]` (the "no
12679        // ComputeUnit-CR declared" arm every `:kind` other than
12680        // `Servico` carries; the layout's [`crate::LayoutInvariants`]
12681        // `ServicoWithoutServicos` arm-gate fires exactly on this
12682        // empty-slot + `Servico`-kind combination), `[""]` (a past-the-
12683        // guard sentinel that pins the accessor doesn't perform a
12684        // silent `[""] → []` collapse on the empty-entry arm — validate
12685        // rejects `[""]` through `CodePathEmpty { slot: ":servicos" }`
12686        // but the accessor must ship the raw slot verbatim so a
12687        // validate-time gate regression surfaces at the layout /
12688        // per-Servico renderer boundary rather than being silently
12689        // absorbed into a component-drop),
12690        // `["servicos/demo.computeunit.yaml"]` (the canonical
12691        // singleton V0-shape every in-tree `caixa_with_code_paths`
12692        // positive control uses; the same shape
12693        // [`crate::require_single_servico`] admits),
12694        // `["servicos/a.computeunit.yaml", "servicos/b.computeunit.
12695        // yaml"]` (a past-the-guard `len != 1` sentinel — the V0
12696        // singularity gate rejects through `ServicoCountMismatch
12697        // { count: 2 }` but the accessor must ship the raw slot
12698        // verbatim so struct-literal `Caixa { servicos: vec![...,
12699        // ...], .. }` fixtures continue to expose the count at the
12700        // accessor), and `["servicos/a.computeunit.yaml",
12701        // "servicos/a.computeunit.yaml"]` (a past-the-guard duplicate
12702        // sentinel — validate rejects through
12703        // `CodePathDuplicate { slot: ":servicos" }` per the per-slot
12704        // set-not-multiset gate, but the accessor must ship the raw
12705        // slot verbatim so struct-literal fixtures continue to expose
12706        // the duplicate at the accessor).
12707        //
12708        // Fifth and final outer top-level [`Caixa`] `&[T]`-return
12709        // slice accessor pin on the substrate primitive — folds on the
12710        // "outer [`Caixa`] `&[T]` slice" projection pattern
12711        // `autores_returns_autores_slice_verbatim_across_permutations`
12712        // (b5d813f) opened,
12713        // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
12714        // (78c7d3c) folded on,
12715        // `bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
12716        // (8a36c23) closed the universal-axis text-tag family of, and
12717        // `exe_returns_exe_slice_verbatim_across_permutations`
12718        // (65d9527) opened the foreign-code-slot sub-family of. Closes
12719        // the outer-`Caixa` foreign-code-slot `&[T]` sub-family — the
12720        // trio of code-surface list slots (`:bibliotecas` + `:exe` +
12721        // `:servicos`) now each carries a substrate-canonical slice
12722        // accessor. Pins against a future silent detour that returned
12723        // an owned `Vec<String>` (which would type-check but silently
12724        // clone on every accessor call, breaking the zero-cost
12725        // projection every peer sibling slice accessor carries), a
12726        // `[""] → []` collapse (which would silently absorb the
12727        // `CodePathEmpty` refusal case at the accessor boundary), an
12728        // `[a, a] → [a]` dedup collapse (which would silently absorb
12729        // the `CodePathDuplicate` refusal case at the accessor
12730        // boundary — the per-slot set-not-multiset gate is downstream
12731        // of the accessor and must not be silently promoted into it),
12732        // or a `[a, b] → [a]` singleton collapse (which would silently
12733        // absorb the V0 `ServicoCountMismatch` refusal case at the
12734        // accessor boundary — the V0 singularity gate is downstream of
12735        // the accessor and must not be silently promoted into it).
12736        for servicos in [
12737            vec![],
12738            vec![""],
12739            vec!["servicos/demo.computeunit.yaml"],
12740            vec!["servicos/a.computeunit.yaml", "servicos/b.computeunit.yaml"],
12741            vec!["servicos/a.computeunit.yaml", "servicos/a.computeunit.yaml"],
12742        ] {
12743            let c = caixa_with_code_paths(vec![], vec![], servicos.clone());
12744            let expected: Vec<String> = servicos.iter().map(|s| (*s).to_string()).collect();
12745            assert_eq!(
12746                c.servicos(),
12747                expected.as_slice(),
12748                "Caixa::servicos must return :servicos verbatim (got \
12749                 {:?}, expected {expected:?})",
12750                c.servicos(),
12751            );
12752            assert_eq!(
12753                c.servicos(),
12754                c.servicos.as_slice(),
12755                "Caixa::servicos must byte-equal the raw \
12756                 `self.servicos.as_slice()` field access across every \
12757                 value in the Vec<String> accept-set",
12758            );
12759        }
12760    }
12761
12762    #[test]
12763    fn validate_code_paths_servicos_empty_arm_routes_through_accessor() {
12764        // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
12765        // empty-arm gate on the `:servicos` slot must key off
12766        // [`Caixa::servicos`], not a divergent raw `&self.servicos`
12767        // field-borrow walk. Structurally: a `Caixa { servicos:
12768        // vec!["".into()], .. }` must surface the `CodePathEmpty
12769        // { slot: ":servicos" }` refusal exactly, and a `Caixa
12770        // { servicos: vec!["servicos/demo.computeunit.yaml".into()],
12771        // .. }` (the canonical singleton V0-shape every in-tree
12772        // `caixa_with_code_paths` positive control uses) must pass
12773        // validate. The pair jointly pins the accessor + validate-gate
12774        // composition: any future silent detour that had the accessor
12775        // return an empty slice on the `[""]` arm (a `.iter().filter
12776        // (|s| !s.is_empty()).collect()` collapse) would silently
12777        // absorb the `CodePathEmpty` refusal at the accessor boundary
12778        // and the validate gate would accept a struct-literal
12779        // `Caixa { servicos: vec!["".into()], .. }` — the composition
12780        // pin catches that at caixa-core build time.
12781        //
12782        // Peer of the per-`Caixa`
12783        // `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
12784        // (8a36c23), `validate_code_paths_exe_empty_arm_routes_through_accessor`
12785        // (65d9527), `validate_autores_empty_arm_routes_through_accessor`
12786        // (b5d813f), and
12787        // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
12788        // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
12789        // composition axes — same "the validate / shape-gate predicate
12790        // must route through the substrate-primitive typed dispatch"
12791        // discipline extended onto the sibling outer top-level
12792        // [`Caixa`] `&[T]`-composition surface, closing the trio of
12793        // code-surface accessor-composition pins on the same axis.
12794        // Nominally the in-tree `validate_code_paths` production body
12795        // still keys off the internal
12796        // `[(":bibliotecas", &self.bibliotecas,
12797        // CodePathFileType::LispSource), (":exe", &self.exe, ..),
12798        // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
12799        // (the tuple's homogeneous `&Vec<String>`-typed shape blocks a
12800        // per-element accessor swap in isolation — a future companion
12801        // lift promotes the tuple's element type to `&[String]` and
12802        // threads the triple of typed dispatches through as a unit);
12803        // the composition pin catches any future accessor-side silent
12804        // filter drop against that eventual tuple-closure regardless
12805        // of whether the `:servicos` slot is threaded through the
12806        // accessor or the raw field access at the tuple's construction
12807        // site.
12808        let c = caixa_with_code_paths(vec![], vec![], vec![""]);
12809        assert!(
12810            matches!(
12811                c.validate_code_paths(),
12812                Err(ManifestError::CodePathEmpty { slot: ":servicos" })
12813            ),
12814            "validate_code_paths must reject servicos == vec![\"\"] \
12815             with CodePathEmpty {{ slot: \":servicos\" }} — the \
12816             accessor and the validate gate must route through the \
12817             same substrate-primitive typed dispatch on the \
12818             :servicos per-entry empty arm",
12819        );
12820        let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/demo.computeunit.yaml"]);
12821        assert!(
12822            c.validate_code_paths().is_ok(),
12823            "validate_code_paths must accept servicos == \
12824             vec![\"servicos/demo.computeunit.yaml\"] (the canonical \
12825             singleton V0-shape every in-tree `caixa_with_code_paths` \
12826             positive control uses)",
12827        );
12828    }
12829
12830    #[test]
12831    fn servicos_projects_slice_by_borrow() {
12832        // The by-borrow pin: [`Caixa::servicos`] returns `&[String]` by
12833        // borrow — the returned slice borrows the underlying
12834        // `Vec<String>` storage of the `:servicos` slot and the
12835        // accessor must not clone the backing `Vec` on every call.
12836        // Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
12837        // (b5d813f), `etiquetas_projects_slice_by_borrow` (78c7d3c),
12838        // `bibliotecas_projects_slice_by_borrow` (8a36c23), and
12839        // `exe_projects_slice_by_borrow` (65d9527) by-borrow pins on
12840        // the sibling outer top-level [`Caixa`] `&[String]`-return
12841        // axes — the accessor's returned slice must borrow from
12842        // `&self` (the returned reference's lifetime is tied to
12843        // `&self`), and calling the accessor twice on the same
12844        // [`Caixa`] must yield slices that are pointer-equal (the
12845        // underlying byte-buffer is the storage `Vec`'s allocation,
12846        // not a fresh copy) as well as value-equal (idempotent, no
12847        // side effects on `&self`).
12848        //
12849        // Pins against a future silent detour that returned an owned
12850        // `Vec<String>` (which would type-check but silently clone on
12851        // every call, breaking the zero-cost projection every peer
12852        // sibling slice accessor carries), a `&Vec<String>` return
12853        // (which would leak the backing `Vec`'s grow/push/reserve
12854        // surface no downstream consumer reaches for), or a one-arm-
12855        // only accessor that returned a saturating value on some
12856        // sentinel input (breaking the pass-through invariant the
12857        // sibling slice accessors carry).
12858        for servicos in [
12859            vec![],
12860            vec!["servicos/demo.computeunit.yaml"],
12861            vec!["servicos/a.computeunit.yaml", "servicos/b.computeunit.yaml"],
12862            vec!["servicos/a.computeunit.yaml", "servicos/a.computeunit.yaml"],
12863        ] {
12864            let c = caixa_with_code_paths(vec![], vec![], servicos.clone());
12865            let expected: Vec<String> = servicos.iter().map(|s| (*s).to_string()).collect();
12866            let first = c.servicos();
12867            let second = c.servicos();
12868            assert_eq!(
12869                first, second,
12870                "Caixa::servicos must be idempotent — two successive \
12871                 calls on the same &self must return the same &[String]",
12872            );
12873            assert_eq!(
12874                first.as_ptr(),
12875                second.as_ptr(),
12876                "Caixa::servicos must borrow the underlying \
12877                 Vec<String> storage — two successive calls must \
12878                 return slices with the same backing pointer (a fresh \
12879                 Vec<String> clone would change the pointer on every \
12880                 call)",
12881            );
12882            assert_eq!(
12883                first,
12884                expected.as_slice(),
12885                "Caixa::servicos must return :servicos verbatim by \
12886                 borrow — got {first:?}, expected {expected:?}",
12887            );
12888        }
12889    }
12890
12891    // ── Caixa::deps — outer top-level &[Dep] slice accessor ───────────
12892
12893    fn caixa_with_deps(deps: Vec<Dep>) -> Caixa {
12894        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12895        c.deps = deps;
12896        c
12897    }
12898
12899    #[test]
12900    fn deps_returns_deps_slice_verbatim_across_permutations() {
12901        // The canonical per-`Caixa` `:deps` universal-axis runtime-
12902        // dependency-declaration-list slice pin: [`Caixa::deps`] must
12903        // return the `:deps` typed [`Vec<Dep>`] list verbatim as a
12904        // `&[Dep]`, element-equal to the raw `self.deps.as_slice()`
12905        // access across every representative value in the accept-set —
12906        // `[]` (the "no runtime deps declared" arm every existing
12907        // fixture without a `:deps` line carries; the
12908        // [`Caixa::template`] scaffold emits `:deps ()`), a canonical
12909        // single-entry list (the shape most consumer caixas carry), a
12910        // canonical two-entry list (the multi-dep runtime closure), and
12911        // two past-the-guard sentinels — a `[""]`-`:nome` entry
12912        // ([`Self::validate_deps`] rejects through `NomeEmpty` /
12913        // `NomeInvalid` but the accessor must ship the raw slot
12914        // verbatim) and a `[a, a]` duplicate (validate rejects through
12915        // `DuplicateNome { list: ":deps" }` but the accessor must ship
12916        // the raw slot verbatim so struct-literal fixtures continue to
12917        // expose the duplicate at the accessor).
12918        //
12919        // First outer top-level [`Caixa`] `&[Dep]`-return slice accessor
12920        // pin on the substrate primitive — opens the outer-`Caixa`
12921        // dependency-slot `&[Dep]` sub-family the sibling `:deps-dev`
12922        // future lift closes on. Peer of the closed outer-`Caixa`
12923        // foreign-code-slot `&[String]` sub-family
12924        // (`bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
12925        // 8a36c23, `exe_returns_exe_slice_verbatim_across_permutations`
12926        // 65d9527, `servicos_returns_servicos_slice_verbatim_across_permutations`
12927        // 611f78b) and the outer-`Caixa` universal-axis text-tag family
12928        // (`autores_returns_autores_slice_verbatim_across_permutations`
12929        // b5d813f, `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
12930        // 78c7d3c) — extends the "outer [`Caixa`] `&[T]` slice"
12931        // projection pattern onto a novel element-type axis (`Dep`
12932        // composite vs the prior sibling family's `String` scalar).
12933        // Pins against a future silent detour that returned an owned
12934        // `Vec<Dep>` (which would type-check but silently clone on every
12935        // accessor call, breaking the zero-cost projection every peer
12936        // sibling slice accessor carries), a `[""] → []` collapse (which
12937        // would silently absorb the `NomeEmpty` refusal case at the
12938        // accessor boundary), or a `[a, a] → [a]` dedup collapse (which
12939        // would silently absorb the `DuplicateNome` refusal case at the
12940        // accessor boundary).
12941        for deps in [
12942            vec![],
12943            vec![Dep::simple("", "^0.1")],
12944            vec![Dep::simple("caixa-teia", "^0.1")],
12945            vec![
12946                Dep::simple("caixa-teia", "^0.1"),
12947                Dep::simple("caixa-core", "^0.1"),
12948            ],
12949            vec![
12950                Dep::simple("caixa-teia", "^0.1"),
12951                Dep::simple("caixa-teia", "^0.2"),
12952            ],
12953        ] {
12954            let c = caixa_with_deps(deps.clone());
12955            assert_eq!(
12956                c.deps(),
12957                deps.as_slice(),
12958                "Caixa::deps must return :deps verbatim (got {:?}, \
12959                 expected {deps:?})",
12960                c.deps(),
12961            );
12962            assert_eq!(
12963                c.deps(),
12964                c.deps.as_slice(),
12965                "Caixa::deps must element-equal the raw \
12966                 `self.deps.as_slice()` field access across every \
12967                 value in the Vec<Dep> accept-set",
12968            );
12969        }
12970    }
12971
12972    #[test]
12973    fn validate_deps_duplicate_arm_routes_through_accessor() {
12974        // Composition pin: [`Caixa::validate_deps`]'s within-`:deps`
12975        // duplicate-`:nome` gate must key off [`Caixa::deps`], not the
12976        // raw `&self.deps` field-borrow walk. Structurally: a `Caixa
12977        // { deps: vec![Dep::simple("d", "^0.1"), Dep::simple("d",
12978        // "^0.2")], .. }` must surface the `DuplicateNome { list:
12979        // ":deps" }` refusal exactly, and a `Caixa { deps: vec![
12980        // Dep::simple("d", "^0.1")], .. }` (the canonical single-entry
12981        // form) must pass validate. The pair jointly pins the accessor +
12982        // validate-gate composition: any future silent detour that had
12983        // the accessor return a dedupped slice on the `[a, a]` arm (a
12984        // `.iter().unique_by(|d| d.nome.as_str()).collect()` collapse)
12985        // would silently absorb the `DuplicateNome` refusal at the
12986        // accessor boundary and the validate gate would accept a
12987        // struct-literal `Caixa` carrying the drift — the composition
12988        // pin catches that at caixa-core build time.
12989        //
12990        // Peer of the per-`Caixa`
12991        // `validate_autores_empty_entry_arm_routes_through_accessor`
12992        // (b5d813f), `validate_etiquetas_empty_entry_arm_routes_through_accessor`
12993        // (78c7d3c), `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
12994        // (8a36c23), `validate_code_paths_exe_empty_arm_routes_through_accessor`
12995        // (65d9527), and `validate_code_paths_servicos_empty_arm_routes_through_accessor`
12996        // (611f78b) accessor-composition pins on the sibling `&[T]`-
12997        // composition axes — same "the validate gate must route through
12998        // the substrate-primitive typed dispatch" discipline extended
12999        // onto the sibling outer top-level [`Caixa`] `&[Dep]`-
13000        // composition surface, opening the outer-`Caixa` dependency-slot
13001        // arm of the composition-pin family.
13002        let c = caixa_with_deps(vec![Dep::simple("d", "^0.1"), Dep::simple("d", "^0.2")]);
13003        let err = c.validate_deps().unwrap_err();
13004        assert!(
13005            matches!(
13006                err,
13007                DepError::DuplicateNome { ref nome, list } if nome == "d"
13008                    && list == crate::render::DEP_AUTHOR_KEY_DEPS
13009            ),
13010            "validate_deps must reject deps == \
13011             vec![Dep(\"d\",\"^0.1\"), Dep(\"d\",\"^0.2\")] with \
13012             DuplicateNome {{ nome: \"d\", list: \":deps\" }} — the \
13013             accessor and the validate gate must route through the \
13014             same substrate-primitive typed dispatch on the :deps \
13015             within-list duplicate arm (got {err:?})",
13016        );
13017        let c = caixa_with_deps(vec![Dep::simple("d", "^0.1")]);
13018        assert!(
13019            c.validate_deps().is_ok(),
13020            "validate_deps must accept deps == vec![Dep(\"d\",\"^0.1\")] \
13021             (the canonical single-entry form)",
13022        );
13023    }
13024
13025    #[test]
13026    fn deps_projects_slice_by_borrow() {
13027        // The by-borrow pin: [`Caixa::deps`] returns `&[Dep]` by borrow
13028        // — the returned slice borrows the underlying `Vec<Dep>` storage
13029        // of the `:deps` slot and the accessor must not clone the
13030        // backing `Vec` on every call. Peer of the per-`Caixa`
13031        // `autores_projects_slice_by_borrow` (b5d813f),
13032        // `etiquetas_projects_slice_by_borrow` (78c7d3c),
13033        // `bibliotecas_projects_slice_by_borrow` (8a36c23),
13034        // `exe_projects_slice_by_borrow` (65d9527), and
13035        // `servicos_projects_slice_by_borrow` (611f78b) by-borrow pins
13036        // on the sibling outer top-level [`Caixa`] `&[String]`-return
13037        // axes — the accessor's returned slice must borrow from `&self`
13038        // (the returned reference's lifetime is tied to `&self`), and
13039        // calling the accessor twice on the same [`Caixa`] must yield
13040        // slices that are pointer-equal (the underlying byte-buffer is
13041        // the storage `Vec`'s allocation, not a fresh copy) as well as
13042        // value-equal (idempotent, no side effects on `&self`).
13043        //
13044        // Pins against a future silent detour that returned an owned
13045        // `Vec<Dep>` (which would type-check but silently clone on
13046        // every call), a `&Vec<Dep>` return (which would leak the
13047        // backing `Vec`'s grow/push/reserve surface no downstream
13048        // consumer reaches for), or a one-arm-only accessor that
13049        // returned a saturating value on some sentinel input.
13050        for deps in [
13051            vec![],
13052            vec![Dep::simple("caixa-teia", "^0.1")],
13053            vec![
13054                Dep::simple("caixa-teia", "^0.1"),
13055                Dep::simple("caixa-core", "^0.1"),
13056            ],
13057        ] {
13058            let c = caixa_with_deps(deps.clone());
13059            let first = c.deps();
13060            let second = c.deps();
13061            assert_eq!(
13062                first, second,
13063                "Caixa::deps must be idempotent — two successive calls \
13064                 on the same &self must return the same &[Dep]",
13065            );
13066            assert_eq!(
13067                first.as_ptr(),
13068                second.as_ptr(),
13069                "Caixa::deps must borrow the underlying Vec<Dep> \
13070                 storage — two successive calls must return slices \
13071                 with the same backing pointer (a fresh Vec<Dep> clone \
13072                 would change the pointer on every call)",
13073            );
13074            assert_eq!(
13075                first,
13076                deps.as_slice(),
13077                "Caixa::deps must return :deps verbatim by borrow — \
13078                 got {first:?}, expected {deps:?}",
13079            );
13080        }
13081    }
13082
13083    // ── Caixa::deps_dev — outer top-level &[Dep] slice accessor ──────
13084
13085    fn caixa_with_deps_dev(deps_dev: Vec<Dep>) -> Caixa {
13086        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
13087        c.deps_dev = deps_dev;
13088        c
13089    }
13090
13091    #[test]
13092    fn deps_dev_returns_deps_dev_slice_verbatim_across_permutations() {
13093        // The canonical per-`Caixa` `:deps-dev` universal-axis dev-only-
13094        // dependency-declaration-list slice pin: [`Caixa::deps_dev`]
13095        // must return the `:deps-dev` typed [`Vec<Dep>`] list verbatim as
13096        // a `&[Dep]`, element-equal to the raw `self.deps_dev.as_slice()`
13097        // access across every representative value in the accept-set —
13098        // `[]` (the "no dev deps declared" arm every existing fixture
13099        // without a `:deps-dev` line carries; the [`Caixa::template`]
13100        // scaffold emits `:deps-dev ()`), a canonical single-entry list
13101        // (the shape most consumer caixas carry — a `tatara-check` dev
13102        // pin), a canonical two-entry list (the multi-dev-dep closure),
13103        // and two past-the-guard sentinels — a `[""]`-`:nome` entry
13104        // ([`Self::validate_deps`] rejects through `NomeEmpty` /
13105        // `NomeInvalid` but the accessor must ship the raw slot
13106        // verbatim) and a `[a, a]` duplicate (validate rejects through
13107        // `DuplicateNome { list: ":deps-dev" }` but the accessor must
13108        // ship the raw slot verbatim so struct-literal fixtures continue
13109        // to expose the duplicate at the accessor).
13110        //
13111        // Second outer top-level [`Caixa`] `&[Dep]`-return slice-accessor
13112        // pin on the substrate primitive — closes the outer-`Caixa`
13113        // dependency-slot `&[Dep]` sub-family the sibling
13114        // `deps_returns_deps_slice_verbatim_across_permutations`
13115        // (ad34b4e) opened on. Folds the "outer [`Caixa`] `&[Dep]`
13116        // slice" projection pattern onto the sibling dev-dep axis —
13117        // pins against a future silent detour that returned an owned
13118        // `Vec<Dep>` (which would type-check but silently clone on every
13119        // accessor call, breaking the zero-cost projection every peer
13120        // sibling slice accessor carries), a `[""] → []` collapse (which
13121        // would silently absorb the `NomeEmpty` refusal case at the
13122        // accessor boundary), or a `[a, a] → [a]` dedup collapse (which
13123        // would silently absorb the `DuplicateNome` refusal case at the
13124        // accessor boundary).
13125        for deps_dev in [
13126            vec![],
13127            vec![Dep::simple("", "^0.1")],
13128            vec![Dep::simple("tatara-check", "^0.1")],
13129            vec![
13130                Dep::simple("tatara-check", "^0.1"),
13131                Dep::simple("caixa-lint", "^0.1"),
13132            ],
13133            vec![
13134                Dep::simple("tatara-check", "^0.1"),
13135                Dep::simple("tatara-check", "^0.2"),
13136            ],
13137        ] {
13138            let c = caixa_with_deps_dev(deps_dev.clone());
13139            assert_eq!(
13140                c.deps_dev(),
13141                deps_dev.as_slice(),
13142                "Caixa::deps_dev must return :deps-dev verbatim (got \
13143                 {:?}, expected {deps_dev:?})",
13144                c.deps_dev(),
13145            );
13146            assert_eq!(
13147                c.deps_dev(),
13148                c.deps_dev.as_slice(),
13149                "Caixa::deps_dev must element-equal the raw \
13150                 `self.deps_dev.as_slice()` field access across every \
13151                 value in the Vec<Dep> accept-set",
13152            );
13153        }
13154    }
13155
13156    #[test]
13157    fn validate_deps_duplicate_deps_dev_arm_routes_through_accessor() {
13158        // Composition pin: [`Caixa::validate_deps`]'s within-`:deps-dev`
13159        // duplicate-`:nome` gate must key off [`Caixa::deps_dev`], not
13160        // the raw `&self.deps_dev` field-borrow walk. Structurally: a
13161        // `Caixa { deps_dev: vec![Dep::simple("d", "^0.1"),
13162        // Dep::simple("d", "^0.2")], .. }` must surface the
13163        // `DuplicateNome { list: ":deps-dev" }` refusal exactly, and a
13164        // `Caixa { deps_dev: vec![Dep::simple("d", "^0.1")], .. }` (the
13165        // canonical single-entry form) must pass validate. The pair
13166        // jointly pins the accessor + validate-gate composition: any
13167        // future silent detour that had the accessor return a dedupped
13168        // slice on the `[a, a]` arm (a
13169        // `.iter().unique_by(|d| d.nome.as_str()).collect()` collapse)
13170        // would silently absorb the `DuplicateNome` refusal at the
13171        // accessor boundary and the validate gate would accept a
13172        // struct-literal `Caixa` carrying the drift — the composition
13173        // pin catches that at caixa-core build time.
13174        //
13175        // Peer of `validate_deps_duplicate_arm_routes_through_accessor`
13176        // (ad34b4e) on the sibling `:deps` axis — same "the validate
13177        // gate must route through the substrate-primitive typed
13178        // dispatch" discipline folded onto the sibling `:deps-dev`
13179        // axis, closing the two-list dep-graph composition-pin family.
13180        // The `:deps-dev` diagnostic must carry the
13181        // `DEP_AUTHOR_KEY_DEPS_DEV` list-tag (not
13182        // `DEP_AUTHOR_KEY_DEPS`) so the emitted error names the
13183        // offending list unambiguously.
13184        let c = caixa_with_deps_dev(vec![Dep::simple("d", "^0.1"), Dep::simple("d", "^0.2")]);
13185        let err = c.validate_deps().unwrap_err();
13186        assert!(
13187            matches!(
13188                err,
13189                DepError::DuplicateNome { ref nome, list } if nome == "d"
13190                    && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
13191            ),
13192            "validate_deps must reject deps_dev == \
13193             vec![Dep(\"d\",\"^0.1\"), Dep(\"d\",\"^0.2\")] with \
13194             DuplicateNome {{ nome: \"d\", list: \":deps-dev\" }} — the \
13195             accessor and the validate gate must route through the \
13196             same substrate-primitive typed dispatch on the :deps-dev \
13197             within-list duplicate arm (got {err:?})",
13198        );
13199        let c = caixa_with_deps_dev(vec![Dep::simple("d", "^0.1")]);
13200        assert!(
13201            c.validate_deps().is_ok(),
13202            "validate_deps must accept deps_dev == \
13203             vec![Dep(\"d\",\"^0.1\")] (the canonical single-entry form)",
13204        );
13205    }
13206
13207    #[test]
13208    fn deps_dev_projects_slice_by_borrow() {
13209        // The by-borrow pin: [`Caixa::deps_dev`] returns `&[Dep]` by
13210        // borrow — the returned slice borrows the underlying `Vec<Dep>`
13211        // storage of the `:deps-dev` slot and the accessor must not
13212        // clone the backing `Vec` on every call. Peer of
13213        // `deps_projects_slice_by_borrow` (ad34b4e) on the sibling
13214        // `:deps` axis, and of the per-`Caixa`
13215        // `autores_projects_slice_by_borrow` (b5d813f),
13216        // `etiquetas_projects_slice_by_borrow` (78c7d3c),
13217        // `bibliotecas_projects_slice_by_borrow` (8a36c23),
13218        // `exe_projects_slice_by_borrow` (65d9527), and
13219        // `servicos_projects_slice_by_borrow` (611f78b) by-borrow pins
13220        // on the sibling outer top-level [`Caixa`] `&[String]`-return
13221        // axes — the accessor's returned slice must borrow from `&self`
13222        // (the returned reference's lifetime is tied to `&self`), and
13223        // calling the accessor twice on the same [`Caixa`] must yield
13224        // slices that are pointer-equal (the underlying byte-buffer is
13225        // the storage `Vec`'s allocation, not a fresh copy) as well as
13226        // value-equal (idempotent, no side effects on `&self`).
13227        //
13228        // Pins against a future silent detour that returned an owned
13229        // `Vec<Dep>` (which would type-check but silently clone on
13230        // every call), a `&Vec<Dep>` return (which would leak the
13231        // backing `Vec`'s grow/push/reserve surface no downstream
13232        // consumer reaches for), or a one-arm-only accessor that
13233        // returned a saturating value on some sentinel input.
13234        for deps_dev in [
13235            vec![],
13236            vec![Dep::simple("tatara-check", "^0.1")],
13237            vec![
13238                Dep::simple("tatara-check", "^0.1"),
13239                Dep::simple("caixa-lint", "^0.1"),
13240            ],
13241        ] {
13242            let c = caixa_with_deps_dev(deps_dev.clone());
13243            let first = c.deps_dev();
13244            let second = c.deps_dev();
13245            assert_eq!(
13246                first, second,
13247                "Caixa::deps_dev must be idempotent — two successive \
13248                 calls on the same &self must return the same &[Dep]",
13249            );
13250            assert_eq!(
13251                first.as_ptr(),
13252                second.as_ptr(),
13253                "Caixa::deps_dev must borrow the underlying Vec<Dep> \
13254                 storage — two successive calls must return slices \
13255                 with the same backing pointer (a fresh Vec<Dep> clone \
13256                 would change the pointer on every call)",
13257            );
13258            assert_eq!(
13259                first,
13260                deps_dev.as_slice(),
13261                "Caixa::deps_dev must return :deps-dev verbatim by \
13262                 borrow — got {first:?}, expected {deps_dev:?}",
13263            );
13264        }
13265    }
13266
13267    // ── Caixa::limits — outer top-level Option<&LimitsSpec> composite-reference accessor ──
13268
13269    fn caixa_with_limits(limits: Option<crate::LimitsSpec>) -> Caixa {
13270        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
13271        c.limits = limits;
13272        c
13273    }
13274
13275    #[test]
13276    fn limits_returns_limits_option_ref_verbatim_across_permutations() {
13277        // The canonical per-`Caixa` `:limits` M2 typed-slot outer-
13278        // composite optional-composite-reference-shape pin:
13279        // [`Caixa::limits`] must return the `:limits` typed
13280        // `Option<LimitsSpec>` verbatim as an `Option<&LimitsSpec>`
13281        // reference over the same backing storage the raw
13282        // `self.limits.as_ref()` field access borrows from, byte-equal
13283        // across every representative fixture in the accept-set — the
13284        // author-omitted `None` shape (the "engine-default applies"
13285        // partition every downstream Servico M2 overlay emitter treats
13286        // as "emit nothing"), the empty-composite `Some(LimitsSpec {
13287        // .. default })` shape ([`LimitsSpec::is_empty`] holds — every
13288        // per-axis cap is `None`, so the peer M2 overlay emitter's
13289        // `.is_empty()`-gated projection still emits nothing but the
13290        // outer presence-bit is `Some`, so [`Caixa::declared_servico_slots`]
13291        // still pushes the `M2_AUTHOR_KEY_LIMITS` label), a single-axis
13292        // fixture (only `:memory` set — the canonical shape most
13293        // memory-heavy Servicos carry), and a fully-populated composite
13294        // (every per-axis cap set — the canonical shape a
13295        // sandboxed-by-default Servico carries).
13296        //
13297        // Pins against a future silent detour that returned a fresh-
13298        // cloned [`LimitsSpec`] copy (which would type-check via the
13299        // `Clone` impl but silently break every downstream caller that
13300        // relied on the reference sharing the composite's backing
13301        // identity), a reference to an operator-resolved overlay (the
13302        // future per-cluster `:limits-overrides` slot — its resolution
13303        // must land at exactly this accessor body, not silently divert
13304        // the raw slot away from a second consumer), a
13305        // `None` → `Some(LimitsSpec::default)` cluster-default
13306        // projection (which would collapse the load-bearing
13307        // "author-omitted `:limits` ⇒ engine-default applies" partition
13308        // the peer [`crate::render::servico_m2_overlay`] emitter and
13309        // the peer [`Caixa::declared_servico_slots`] enumerator both
13310        // read), or an axis-shuffled projection (a future detour that
13311        // swapped `memory` and `fuel` through the accessor would
13312        // silently split the paired [`crate::StandardLayout::verify`]
13313        // per-`:limits` shape gate's traversal input from the peer
13314        // `servico_m2_overlay` emitter's projection input).
13315        //
13316        // First outer top-level [`Caixa`] `Option<&Composite>`-return
13317        // composite-reference accessor pin on the substrate primitive
13318        // — opens the outer-`Caixa` `Option<&Composite>` composite-
13319        // reference projection pattern the sibling `:behavior`
13320        // [`crate::BehaviorSpec`] / `:politicas`
13321        // [`crate::aplicacao::MeshPolicy`] / `:placement`
13322        // [`crate::aplicacao::Placement`] / `:entrada`
13323        // [`crate::aplicacao::Entrada`] future outer-composite lifts
13324        // fold on. Peer of the closed M3 outer-composite family the
13325        // sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
13326        // [`crate::AplicacaoSpec::placement`] (9abb8f0) /
13327        // [`crate::AplicacaoSpec::entrada`] (d32111c) composite-
13328        // reference accessor pins already carry on the outer
13329        // [`crate::AplicacaoSpec`] altitude — extends the outer-
13330        // accessor byte-equal-projection discipline onto the outer
13331        // top-level [`Caixa`] M2 Servico-runtime slot altitude.
13332        use crate::LimitsSpec;
13333        use std::time::Duration;
13334        let fixtures: Vec<Option<LimitsSpec>> = vec![
13335            None,
13336            Some(LimitsSpec::default()),
13337            Some(LimitsSpec {
13338                memory: Some(64 * 1024 * 1024),
13339                ..Default::default()
13340            }),
13341            Some(LimitsSpec {
13342                memory: Some(64 * 1024 * 1024),
13343                fuel: Some(1_000_000),
13344                wall_clock: Some(Duration::from_secs(30)),
13345                cpu: Some(500),
13346            }),
13347        ];
13348        for limits in fixtures {
13349            let c = caixa_with_limits(limits.clone());
13350            assert_eq!(
13351                c.limits(),
13352                limits.as_ref(),
13353                "Caixa::limits must return :limits verbatim (got {:?}, \
13354                 expected {:?})",
13355                c.limits(),
13356                limits.as_ref(),
13357            );
13358            match (c.limits(), c.limits.as_ref()) {
13359                (Some(a), Some(b)) => assert!(
13360                    std::ptr::eq(a, b),
13361                    "Caixa::limits accessor and self.limits.as_ref() \
13362                     field access must borrow the same backing storage \
13363                     — the accessor is the substrate-primitive typed \
13364                     dispatch every downstream Servico-M2-overlay \
13365                     composite consumer must route through, and a \
13366                     reference-identity split would silently break \
13367                     every consumer that relied on the borrow sharing \
13368                     the composite's storage",
13369                ),
13370                (None, None) => {}
13371                _ => panic!(
13372                    "Caixa::limits presence bit must byte-equal \
13373                     self.limits.is_some() — a presence-bit drift would \
13374                     silently split the paired StandardLayout::verify \
13375                     per-`:limits` shape gate's traversal head from \
13376                     the peer render::servico_m2_overlay M2 overlay \
13377                     emitter's traversal head from the peer \
13378                     Caixa::declared_servico_slots M2 declared-slot \
13379                     enumerator's presence probe",
13380                ),
13381            }
13382            assert_eq!(
13383                c.limits().is_some(),
13384                c.limits.is_some(),
13385                "Caixa::limits().is_some() must byte-equal \
13386                 self.limits.is_some() — a presence-bit drift would \
13387                 silently split every downstream Option<&LimitsSpec> \
13388                 consumer's partition on the engine-default arm",
13389            );
13390        }
13391    }
13392
13393    #[test]
13394    fn declared_servico_slots_limits_arm_routes_through_accessor() {
13395        // Composition pin: [`Caixa::declared_servico_slots`]'s
13396        // `:limits` presence-probe arm must key off [`Caixa::limits`],
13397        // not the raw `self.limits.is_some()` field-probe. Structurally:
13398        // a `Caixa { limits: Some(LimitsSpec::default()), .. }` must
13399        // still push `M2_AUTHOR_KEY_LIMITS` onto the declared-slot list
13400        // (the presence bit is `Some`, so the M2 kind-coherence gate
13401        // must surface the slot as "declared" even when every per-axis
13402        // cap is unset), and a `Caixa { limits: None, .. }` must NOT
13403        // push the label (the "author omitted the slot entirely"
13404        // partition). The pair jointly pins the accessor + declared-
13405        // slot enumerator composition: any future silent detour that
13406        // had the accessor collapse `Some(LimitsSpec::default())` to
13407        // `None` (a `.filter(|l| !l.is_empty())` projection) would
13408        // silently absorb the "declared but empty" arm at the
13409        // accessor boundary and the [`crate::LayoutError::ServicoSlotsOnNonServico`]
13410        // kind-coherence gate would silently accept a
13411        // struct-literal `Caixa` carrying the drift.
13412        //
13413        // Peer of the sibling per-`Caixa`
13414        // `validate_deps_duplicate_arm_routes_through_accessor` (ad34b4e)
13415        // and `validate_deps_duplicate_deps_dev_arm_routes_through_accessor`
13416        // (f7fd81e) accessor-composition pins on the sibling `:deps` /
13417        // `:deps-dev` outer-`&[Dep]`-composition axes — same "the
13418        // enumerator gate must route through the substrate-primitive
13419        // typed dispatch" discipline extended onto the outer top-level
13420        // [`Caixa`] `Option<&LimitsSpec>`-composition surface, opening
13421        // the outer-`Caixa` M2 Servico-runtime-slot arm of the
13422        // composition-pin family.
13423        use crate::LimitsSpec;
13424        let c = caixa_with_limits(Some(LimitsSpec::default()));
13425        let slots = c.declared_servico_slots();
13426        assert!(
13427            slots.contains(&crate::render::M2_AUTHOR_KEY_LIMITS),
13428            "declared_servico_slots must push M2_AUTHOR_KEY_LIMITS \
13429             when `:limits` is Some (even for LimitsSpec::default()) \
13430             — the accessor and the enumerator gate must route through \
13431             the same substrate-primitive typed dispatch on the outer \
13432             :limits presence bit (got slots={slots:?})",
13433        );
13434        let c = caixa_with_limits(None);
13435        let slots = c.declared_servico_slots();
13436        assert!(
13437            !slots.contains(&crate::render::M2_AUTHOR_KEY_LIMITS),
13438            "declared_servico_slots must NOT push M2_AUTHOR_KEY_LIMITS \
13439             when `:limits` is None — the author-omitted arm must \
13440             route through the accessor's None-return unchanged (got \
13441             slots={slots:?})",
13442        );
13443    }
13444
13445    #[test]
13446    fn servico_m2_overlay_limits_arm_routes_through_accessor() {
13447        // Composition pin: [`crate::render::servico_m2_overlay`]'s
13448        // per-`:limits` M2 overlay emit arm must key off
13449        // [`Caixa::limits`], not the raw `&caixa.limits` field-borrow.
13450        // Structurally: a `Caixa { limits: Some(LimitsSpec { memory:
13451        // Some(64 MiB), .. default }), .. }` must surface the
13452        // `M2_KEY_LIMITS` key with the per-axis
13453        // `memory: "64MiB"` sub-mapping in the overlay, a `Caixa {
13454        // limits: Some(LimitsSpec::default()), .. }` must omit the
13455        // key entirely (the `.is_empty()`-gated inner arm elides an
13456        // empty composite even when the outer presence bit is `Some`),
13457        // and a `Caixa { limits: None, .. }` must also omit the key
13458        // (the "author omitted the slot entirely" partition). The
13459        // three-fixture family jointly pins the accessor + M2 overlay
13460        // emitter composition: any future silent detour that had the
13461        // accessor return a fresh-cloned copy on the `Some` arm (a
13462        // `LimitsSpec::clone()` projection) would silently break the
13463        // reference-identity pin the peer per-axis
13464        // `serde_yaml::to_value(limits)` projection reads from.
13465        use crate::LimitsSpec;
13466        use crate::render::{M2_KEY_LIMITS, servico_m2_overlay};
13467        let c = caixa_with_limits(Some(LimitsSpec {
13468            memory: Some(64 * 1024 * 1024),
13469            ..Default::default()
13470        }));
13471        let overlay = servico_m2_overlay(&c).unwrap();
13472        assert!(
13473            overlay.contains_key(M2_KEY_LIMITS),
13474            "servico_m2_overlay must surface M2_KEY_LIMITS when \
13475             `:limits` carries a non-empty composite — the accessor \
13476             and the M2 overlay emitter must route through the same \
13477             substrate-primitive typed dispatch on the outer :limits \
13478             composite (got overlay={overlay:?})",
13479        );
13480        let c = caixa_with_limits(Some(LimitsSpec::default()));
13481        let overlay = servico_m2_overlay(&c).unwrap();
13482        assert!(
13483            !overlay.contains_key(M2_KEY_LIMITS),
13484            "servico_m2_overlay must omit M2_KEY_LIMITS when \
13485             `:limits` is Some(LimitsSpec::default()) — the empty \
13486             composite's `.is_empty()`-gated inner arm must elide \
13487             the key regardless of the outer presence bit (got \
13488             overlay={overlay:?})",
13489        );
13490        let c = caixa_with_limits(None);
13491        let overlay = servico_m2_overlay(&c).unwrap();
13492        assert!(
13493            !overlay.contains_key(M2_KEY_LIMITS),
13494            "servico_m2_overlay must omit M2_KEY_LIMITS when \
13495             `:limits` is None — the author-omitted arm must route \
13496             through the accessor's None-return unchanged (got \
13497             overlay={overlay:?})",
13498        );
13499    }
13500
13501    #[test]
13502    fn limits_projects_option_ref_by_borrow() {
13503        // The by-borrow pin: [`Caixa::limits`] returns
13504        // `Option<&LimitsSpec>` by borrow — the returned reference
13505        // borrows the underlying `Option<LimitsSpec>` storage of the
13506        // `:limits` slot and the accessor must not clone the backing
13507        // composite on every call. Peer of the sibling
13508        // `deps_projects_slice_by_borrow` (ad34b4e) /
13509        // `deps_dev_projects_slice_by_borrow` (f7fd81e) by-borrow pins
13510        // on the outer top-level [`Caixa`] `&[Dep]`-return axes —
13511        // extended here to the outer [`Caixa`] `Option<&Composite>`-
13512        // return axis: the accessor's returned reference must borrow
13513        // from `&self` (the returned reference's lifetime is tied to
13514        // `&self`), and calling the accessor twice on the same
13515        // [`Caixa`] must yield references that are pointer-equal (the
13516        // underlying byte-buffer is the storage `LimitsSpec`'s
13517        // allocation, not a fresh copy) as well as value-equal
13518        // (idempotent, no side effects on `&self`).
13519        //
13520        // Pins against a future silent detour that returned an owned
13521        // `LimitsSpec` (which would type-check via the `Clone` impl
13522        // but silently clone on every call), a `&LimitsSpec` panic-
13523        // return on the `None` arm (which would collapse the load-
13524        // bearing `Option` presence-bit into a runtime panic), or a
13525        // one-arm-only accessor that returned a saturating composite
13526        // on some sentinel input.
13527        use crate::LimitsSpec;
13528        use std::time::Duration;
13529        for limits in [
13530            Some(LimitsSpec::default()),
13531            Some(LimitsSpec {
13532                memory: Some(64 * 1024 * 1024),
13533                fuel: Some(1_000_000),
13534                wall_clock: Some(Duration::from_secs(30)),
13535                cpu: Some(500),
13536            }),
13537        ] {
13538            let c = caixa_with_limits(limits.clone());
13539            let first = c.limits().unwrap();
13540            let second = c.limits().unwrap();
13541            assert_eq!(
13542                first, second,
13543                "Caixa::limits must be idempotent — two successive \
13544                 calls on the same &self must return the same \
13545                 &LimitsSpec",
13546            );
13547            assert!(
13548                std::ptr::eq(first, second),
13549                "Caixa::limits must borrow the underlying \
13550                 Option<LimitsSpec> storage — two successive calls \
13551                 must return references with the same backing pointer \
13552                 (a fresh LimitsSpec clone would change the pointer \
13553                 on every call)",
13554            );
13555            assert_eq!(
13556                Some(first),
13557                limits.as_ref(),
13558                "Caixa::limits must return :limits verbatim by borrow \
13559                 — got {first:?}, expected {:?}",
13560                limits.as_ref(),
13561            );
13562        }
13563        let c = caixa_with_limits(None);
13564        assert!(
13565            c.limits().is_none(),
13566            "Caixa::limits must return None when :limits is absent — \
13567             the author-omitted arm must project through the \
13568             accessor's Option::None unchanged",
13569        );
13570    }
13571
13572    // ── Caixa::behavior — outer top-level Option<&BehaviorSpec> composite-reference accessor ──
13573
13574    fn caixa_with_behavior(behavior: Option<crate::BehaviorSpec>) -> Caixa {
13575        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
13576        c.behavior = behavior;
13577        c
13578    }
13579
13580    #[test]
13581    fn behavior_returns_behavior_option_ref_verbatim_across_permutations() {
13582        // The canonical per-`Caixa` `:behavior` M2 typed-slot outer-
13583        // composite optional-composite-reference-shape pin:
13584        // [`Caixa::behavior`] must return the `:behavior` typed
13585        // `Option<BehaviorSpec>` verbatim as an `Option<&BehaviorSpec>`
13586        // reference over the same backing storage the raw
13587        // `self.behavior.as_ref()` field access borrows from, byte-equal
13588        // across every representative fixture in the accept-set — the
13589        // author-omitted `None` shape (the "runtime-default applies"
13590        // partition every downstream Servico M2 overlay emitter treats
13591        // as "emit nothing"), the empty-composite `Some(BehaviorSpec {
13592        // .. default })` shape ([`BehaviorSpec::is_empty`] holds —
13593        // every per-callback path is `None`, so the peer M2 overlay
13594        // emitter's `.is_empty()`-gated projection still emits nothing
13595        // but the outer presence-bit is `Some`, so
13596        // [`Caixa::declared_servico_slots`] still pushes the
13597        // `M2_AUTHOR_KEY_BEHAVIOR` label), a single-callback fixture
13598        // (only `:on-state-change` set — the canonical shape a caixa
13599        // that only wires the hot-upgrade migration path carries), and
13600        // a fully-populated composite (every per-callback path set —
13601        // the canonical shape a fully-instrumented gen_server-shaped
13602        // Servico carries).
13603        //
13604        // Peer of the sibling
13605        // `limits_returns_limits_option_ref_verbatim_across_permutations`
13606        // (b2bd9d7) opening fixture-family + reference-identity +
13607        // presence-bit tetrad pin on the outer top-level [`Caixa`]
13608        // `Option<&Composite>`-return sub-family — extended here to the
13609        // second axis of that sub-family so both of the currently-lifted
13610        // M2 Servico-runtime `Option<&Composite>` slots (`:limits` /
13611        // `:behavior`) carry the same "byte-equal, borrow-shared,
13612        // presence-bit-preserved" outer-accessor discipline.
13613        //
13614        // Pins against a future silent detour that returned a fresh-
13615        // cloned [`crate::BehaviorSpec`] copy (which would type-check
13616        // via the `Clone` impl but silently break every downstream
13617        // caller that relied on the reference sharing the composite's
13618        // backing identity), a reference to an operator-resolved
13619        // overlay (a future per-cluster `:behavior-overrides` slot —
13620        // its resolution must land at exactly this accessor body, not
13621        // silently divert the raw slot away from a second consumer), a
13622        // `None` → `Some(BehaviorSpec::default)` cluster-default
13623        // projection (which would collapse the load-bearing
13624        // "author-omitted `:behavior` ⇒ runtime-default applies"
13625        // partition the peer [`crate::render::servico_m2_overlay`]
13626        // emitter, the peer [`Caixa::declared_servico_slots`]
13627        // enumerator, and the cross-slot
13628        // [`crate::upgrade::validate_upgrade_from_against_behavior`]
13629        // gate all read), or a callback-shuffled projection (a future
13630        // detour that swapped `on_init` and `on_terminate` through the
13631        // accessor would silently split the paired
13632        // [`crate::StandardLayout::verify`] per-`:behavior` shape gate's
13633        // traversal input from the peer `servico_m2_overlay` emitter's
13634        // projection input from the cross-slot `:state-change`
13635        // composition gate's traversal input).
13636        use crate::BehaviorSpec;
13637        use std::path::PathBuf;
13638        let fixtures: Vec<Option<BehaviorSpec>> = vec![
13639            None,
13640            Some(BehaviorSpec::default()),
13641            Some(BehaviorSpec {
13642                on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
13643                ..Default::default()
13644            }),
13645            Some(BehaviorSpec {
13646                on_init: Some(PathBuf::from("lib/init.lisp")),
13647                on_call: Some(PathBuf::from("lib/handlers.lisp")),
13648                on_cast: Some(PathBuf::from("lib/handlers.lisp")),
13649                on_info: Some(PathBuf::from("lib/handlers.lisp")),
13650                on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
13651                on_terminate: Some(PathBuf::from("lib/cleanup.lisp")),
13652            }),
13653        ];
13654        for behavior in fixtures {
13655            let c = caixa_with_behavior(behavior.clone());
13656            assert_eq!(
13657                c.behavior(),
13658                behavior.as_ref(),
13659                "Caixa::behavior must return :behavior verbatim (got \
13660                 {:?}, expected {:?})",
13661                c.behavior(),
13662                behavior.as_ref(),
13663            );
13664            match (c.behavior(), c.behavior.as_ref()) {
13665                (Some(a), Some(b)) => assert!(
13666                    std::ptr::eq(a, b),
13667                    "Caixa::behavior accessor and self.behavior.as_ref() \
13668                     field access must borrow the same backing storage \
13669                     — the accessor is the substrate-primitive typed \
13670                     dispatch every downstream Servico-M2-overlay \
13671                     composite consumer must route through, and a \
13672                     reference-identity split would silently break \
13673                     every consumer that relied on the borrow sharing \
13674                     the composite's storage",
13675                ),
13676                (None, None) => {}
13677                _ => panic!(
13678                    "Caixa::behavior presence bit must byte-equal \
13679                     self.behavior.is_some() — a presence-bit drift \
13680                     would silently split the paired \
13681                     StandardLayout::verify per-`:behavior` shape \
13682                     gate's traversal head from the peer \
13683                     render::servico_m2_overlay M2 overlay emitter's \
13684                     traversal head from the cross-slot \
13685                     validate_upgrade_from_against_behavior \
13686                     composition gate's traversal head from the peer \
13687                     Caixa::declared_servico_slots M2 declared-slot \
13688                     enumerator's presence probe",
13689                ),
13690            }
13691            assert_eq!(
13692                c.behavior().is_some(),
13693                c.behavior.is_some(),
13694                "Caixa::behavior().is_some() must byte-equal \
13695                 self.behavior.is_some() — a presence-bit drift would \
13696                 silently split every downstream Option<&BehaviorSpec> \
13697                 consumer's partition on the runtime-default arm",
13698            );
13699        }
13700    }
13701
13702    #[test]
13703    fn declared_servico_slots_behavior_arm_routes_through_accessor() {
13704        // Composition pin: [`Caixa::declared_servico_slots`]'s
13705        // `:behavior` presence-probe arm must key off
13706        // [`Caixa::behavior`], not the raw `self.behavior.is_some()`
13707        // field-probe. Structurally: a `Caixa { behavior:
13708        // Some(BehaviorSpec::default()), .. }` must still push
13709        // `M2_AUTHOR_KEY_BEHAVIOR` onto the declared-slot list (the
13710        // presence bit is `Some`, so the M2 kind-coherence gate must
13711        // surface the slot as "declared" even when every per-callback
13712        // path is unset), and a `Caixa { behavior: None, .. }` must
13713        // NOT push the label (the "author omitted the slot entirely"
13714        // partition). The pair jointly pins the accessor + declared-
13715        // slot enumerator composition: any future silent detour that
13716        // had the accessor collapse `Some(BehaviorSpec::default())`
13717        // to `None` (a `.filter(|b| !b.is_empty())` projection) would
13718        // silently absorb the "declared but empty" arm at the
13719        // accessor boundary and the
13720        // [`crate::LayoutError::ServicoSlotsOnNonServico`]
13721        // kind-coherence gate would silently accept a struct-literal
13722        // `Caixa` carrying the drift.
13723        //
13724        // Peer of the sibling
13725        // `declared_servico_slots_limits_arm_routes_through_accessor`
13726        // (b2bd9d7) composition pin on the sibling `:limits` outer-
13727        // `Option<&LimitsSpec>` arm of the same
13728        // [`Caixa::declared_servico_slots`] M2 declared-slot
13729        // enumerator's traversal — same "the enumerator gate must
13730        // route through the substrate-primitive typed dispatch"
13731        // discipline extended onto the outer top-level [`Caixa`]
13732        // `Option<&BehaviorSpec>`-composition surface.
13733        use crate::BehaviorSpec;
13734        let c = caixa_with_behavior(Some(BehaviorSpec::default()));
13735        let slots = c.declared_servico_slots();
13736        assert!(
13737            slots.contains(&crate::render::M2_AUTHOR_KEY_BEHAVIOR),
13738            "declared_servico_slots must push M2_AUTHOR_KEY_BEHAVIOR \
13739             when `:behavior` is Some (even for BehaviorSpec::default()) \
13740             — the accessor and the enumerator gate must route through \
13741             the same substrate-primitive typed dispatch on the outer \
13742             :behavior presence bit (got slots={slots:?})",
13743        );
13744        let c = caixa_with_behavior(None);
13745        let slots = c.declared_servico_slots();
13746        assert!(
13747            !slots.contains(&crate::render::M2_AUTHOR_KEY_BEHAVIOR),
13748            "declared_servico_slots must NOT push M2_AUTHOR_KEY_BEHAVIOR \
13749             when `:behavior` is None — the author-omitted arm must \
13750             route through the accessor's None-return unchanged (got \
13751             slots={slots:?})",
13752        );
13753    }
13754
13755    #[test]
13756    fn servico_m2_overlay_behavior_arm_routes_through_accessor() {
13757        // Composition pin: [`crate::render::servico_m2_overlay`]'s
13758        // per-`:behavior` M2 overlay emit arm must key off
13759        // [`Caixa::behavior`], not the raw `&caixa.behavior`
13760        // field-borrow. Structurally: a `Caixa { behavior:
13761        // Some(BehaviorSpec { on_state_change: Some(...), .. default
13762        // }), .. }` must surface the `M2_KEY_BEHAVIOR` key with the
13763        // per-callback `onStateChange` sub-mapping in the overlay, a
13764        // `Caixa { behavior: Some(BehaviorSpec::default()), .. }`
13765        // must omit the key entirely (the `.is_empty()`-gated inner
13766        // arm elides an empty composite even when the outer presence
13767        // bit is `Some`), and a `Caixa { behavior: None, .. }` must
13768        // also omit the key (the "author omitted the slot entirely"
13769        // partition). The three-fixture family jointly pins the
13770        // accessor + M2 overlay emitter composition: any future
13771        // silent detour that had the accessor return a fresh-cloned
13772        // copy on the `Some` arm (a `BehaviorSpec::clone()`
13773        // projection) would silently break the reference-identity
13774        // pin the peer per-callback `serde_yaml::to_value(behavior)`
13775        // projection reads from.
13776        //
13777        // Peer of the sibling
13778        // `servico_m2_overlay_limits_arm_routes_through_accessor`
13779        // (b2bd9d7) composition pin on the sibling `:limits` outer-
13780        // `Option<&LimitsSpec>` arm of the same
13781        // [`crate::render::servico_m2_overlay`] M2 overlay emitter's
13782        // traversal — same "the emitter must route through the
13783        // substrate-primitive typed dispatch on the outer composite"
13784        // discipline extended onto the outer top-level [`Caixa`]
13785        // `Option<&BehaviorSpec>`-composition surface.
13786        use crate::BehaviorSpec;
13787        use crate::render::{M2_KEY_BEHAVIOR, servico_m2_overlay};
13788        use std::path::PathBuf;
13789        let c = caixa_with_behavior(Some(BehaviorSpec {
13790            on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
13791            ..Default::default()
13792        }));
13793        let overlay = servico_m2_overlay(&c).unwrap();
13794        assert!(
13795            overlay.contains_key(M2_KEY_BEHAVIOR),
13796            "servico_m2_overlay must surface M2_KEY_BEHAVIOR when \
13797             `:behavior` carries a non-empty composite — the accessor \
13798             and the M2 overlay emitter must route through the same \
13799             substrate-primitive typed dispatch on the outer :behavior \
13800             composite (got overlay={overlay:?})",
13801        );
13802        let c = caixa_with_behavior(Some(BehaviorSpec::default()));
13803        let overlay = servico_m2_overlay(&c).unwrap();
13804        assert!(
13805            !overlay.contains_key(M2_KEY_BEHAVIOR),
13806            "servico_m2_overlay must omit M2_KEY_BEHAVIOR when \
13807             `:behavior` is Some(BehaviorSpec::default()) — the empty \
13808             composite's `.is_empty()`-gated inner arm must elide the \
13809             key regardless of the outer presence bit (got \
13810             overlay={overlay:?})",
13811        );
13812        let c = caixa_with_behavior(None);
13813        let overlay = servico_m2_overlay(&c).unwrap();
13814        assert!(
13815            !overlay.contains_key(M2_KEY_BEHAVIOR),
13816            "servico_m2_overlay must omit M2_KEY_BEHAVIOR when \
13817             `:behavior` is None — the author-omitted arm must route \
13818             through the accessor's None-return unchanged (got \
13819             overlay={overlay:?})",
13820        );
13821    }
13822
13823    #[test]
13824    fn behavior_projects_option_ref_by_borrow() {
13825        // The by-borrow pin: [`Caixa::behavior`] returns
13826        // `Option<&BehaviorSpec>` by borrow — the returned reference
13827        // borrows the underlying `Option<BehaviorSpec>` storage of the
13828        // `:behavior` slot and the accessor must not clone the backing
13829        // composite on every call. Peer of the sibling
13830        // `limits_projects_option_ref_by_borrow` (b2bd9d7) by-borrow
13831        // pin on the outer top-level [`Caixa`] `Option<&Composite>`-
13832        // return sub-family — extended here to the second axis of the
13833        // same sub-family: the accessor's returned reference must
13834        // borrow from `&self` (the returned reference's lifetime is
13835        // tied to `&self`), and calling the accessor twice on the same
13836        // [`Caixa`] must yield references that are pointer-equal (the
13837        // underlying byte-buffer is the storage `BehaviorSpec`'s
13838        // allocation, not a fresh copy) as well as value-equal
13839        // (idempotent, no side effects on `&self`).
13840        //
13841        // Pins against a future silent detour that returned an owned
13842        // `BehaviorSpec` (which would type-check via the `Clone` impl
13843        // but silently clone on every call), a `&BehaviorSpec` panic-
13844        // return on the `None` arm (which would collapse the load-
13845        // bearing `Option` presence-bit into a runtime panic), or a
13846        // one-arm-only accessor that returned a saturating composite
13847        // on some sentinel input.
13848        use crate::BehaviorSpec;
13849        use std::path::PathBuf;
13850        for behavior in [
13851            Some(BehaviorSpec::default()),
13852            Some(BehaviorSpec {
13853                on_init: Some(PathBuf::from("lib/init.lisp")),
13854                on_call: Some(PathBuf::from("lib/handlers.lisp")),
13855                on_cast: Some(PathBuf::from("lib/handlers.lisp")),
13856                on_info: Some(PathBuf::from("lib/handlers.lisp")),
13857                on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
13858                on_terminate: Some(PathBuf::from("lib/cleanup.lisp")),
13859            }),
13860        ] {
13861            let c = caixa_with_behavior(behavior.clone());
13862            let first = c.behavior().unwrap();
13863            let second = c.behavior().unwrap();
13864            assert_eq!(
13865                first, second,
13866                "Caixa::behavior must be idempotent — two successive \
13867                 calls on the same &self must return the same \
13868                 &BehaviorSpec",
13869            );
13870            assert!(
13871                std::ptr::eq(first, second),
13872                "Caixa::behavior must borrow the underlying \
13873                 Option<BehaviorSpec> storage — two successive calls \
13874                 must return references with the same backing pointer \
13875                 (a fresh BehaviorSpec clone would change the pointer \
13876                 on every call)",
13877            );
13878            assert_eq!(
13879                Some(first),
13880                behavior.as_ref(),
13881                "Caixa::behavior must return :behavior verbatim by \
13882                 borrow — got {first:?}, expected {:?}",
13883                behavior.as_ref(),
13884            );
13885        }
13886        let c = caixa_with_behavior(None);
13887        assert!(
13888            c.behavior().is_none(),
13889            "Caixa::behavior must return None when :behavior is absent \
13890             — the author-omitted arm must project through the \
13891             accessor's Option::None unchanged",
13892        );
13893    }
13894
13895    // ── Caixa::politicas — outer top-level Option<&MeshPolicy> composite-reference accessor ──
13896
13897    fn caixa_aplicacao_with_politicas(politicas: Option<crate::aplicacao::MeshPolicy>) -> Caixa {
13898        use crate::aplicacao::{Membro, WitContract};
13899        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
13900        c.kind = CaixaKind::Aplicacao;
13901        c.membros = vec![Membro {
13902            caixa: "a".into(),
13903            versao: "^0.1".into(),
13904        }];
13905        c.contratos = vec![WitContract {
13906            de: "a".into(),
13907            para: "a".into(),
13908            wit: "wasi:http/proxy".into(),
13909            endpoint: Some("/x".into()),
13910            subject: None,
13911            slot: None,
13912        }];
13913        c.politicas = politicas;
13914        c
13915    }
13916
13917    #[test]
13918    fn politicas_returns_politicas_option_ref_verbatim_across_permutations() {
13919        // The canonical per-`Caixa` `:politicas` M3 mesh-slot outer-
13920        // composite optional-composite-reference-shape pin:
13921        // [`Caixa::politicas`] must return the `:politicas` typed
13922        // `Option<MeshPolicy>` verbatim as an `Option<&MeshPolicy>`
13923        // reference over the same backing storage the raw
13924        // `self.politicas.as_ref()` field access borrows from,
13925        // byte-equal across every representative fixture in the
13926        // accept-set — the author-omitted `None` shape (the "cluster-
13927        // default applies" partition every downstream mesh-artifact
13928        // emitter treats as "emit no `:politicas` overlay"), the
13929        // empty-composite `Some(MeshPolicy { .. default })` shape
13930        // ([`crate::aplicacao::MeshPolicy::is_empty`] holds — every
13931        // per-axis mesh-policy scalar is `None`, so the peer inner
13932        // [`crate::AplicacaoSpec::politicas`] `.is_empty()`-gated
13933        // caixa-mesh overlay elides every per-axis emit but the outer
13934        // presence-bit is `Some`, so [`Caixa::declared_mesh_slots`]
13935        // still pushes the `M3_AUTHOR_KEY_POLITICAS` label), a
13936        // single-axis fixture (only `:timeout` set — the canonical
13937        // shape a latency-sensitive Aplicacao carries), and a
13938        // fully-populated composite (every per-axis mesh-policy
13939        // scalar set — the canonical shape a fully-governed
13940        // Aplicacao carries).
13941        //
13942        // Pins against a future silent detour that returned a fresh-
13943        // cloned [`crate::aplicacao::MeshPolicy`] copy (which would
13944        // type-check via the `Clone` impl but silently break every
13945        // downstream caller that relied on the reference sharing the
13946        // composite's backing identity), a reference to an operator-
13947        // resolved overlay (the future per-cluster
13948        // `:politicas-overrides` slot — its resolution must land at
13949        // exactly this accessor body, not silently divert the raw
13950        // slot away from the peer [`Caixa::declared_mesh_slots`]
13951        // enumerator's presence probe), a
13952        // `None` → `Some(MeshPolicy::default)` cluster-default
13953        // projection (which would collapse the load-bearing
13954        // "author-omitted `:politicas` ⇒ cluster-default applies"
13955        // partition the peer [`Caixa::declared_mesh_slots`]
13956        // enumerator and the peer [`Caixa::aplicacao_view`]
13957        // Aplicacao-composition seed both read), or an axis-shuffled
13958        // projection (a future detour that swapped `timeout` and
13959        // `retries` through the accessor would silently split the
13960        // paired [`Caixa::aplicacao_view`] seed's fold input from the
13961        // sibling M3 mesh-artifact emitter's projection input).
13962        //
13963        // Third outer top-level [`Caixa`] `Option<&Composite>`-return
13964        // composite-reference accessor pin on the substrate primitive
13965        // — peer of the sibling
13966        // `limits_returns_limits_option_ref_verbatim_across_permutations`
13967        // (b2bd9d7) and
13968        // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
13969        // (35d8b52) opening tetrad pins on the outer top-level
13970        // [`Caixa`] `Option<&Composite>`-return sub-family — extended
13971        // here to the first of the three M3 mesh-slot axes so the
13972        // opening third of the outer `Option<&Composite>` sub-family
13973        // carries the same "byte-equal, borrow-shared, presence-bit-
13974        // preserved" outer-accessor discipline.
13975        use crate::aplicacao::{CircuitBreaker, MeshPolicy, RateLimit};
13976        use std::time::Duration;
13977        let fixtures: Vec<Option<MeshPolicy>> = vec![
13978            None,
13979            Some(MeshPolicy::default()),
13980            Some(MeshPolicy {
13981                timeout: Some(Duration::from_secs(30)),
13982                ..Default::default()
13983            }),
13984            Some(MeshPolicy {
13985                timeout: Some(Duration::from_secs(30)),
13986                retries: Some(3),
13987                circuit_breaker: Some(CircuitBreaker {
13988                    max_failures: 5,
13989                    window: Duration::from_secs(60),
13990                }),
13991                mtls_required: Some(true),
13992                rate_limit: Some(RateLimit {
13993                    rate: 100,
13994                    window: Duration::from_secs(1),
13995                }),
13996            }),
13997        ];
13998        for politicas in fixtures {
13999            let c = caixa_aplicacao_with_politicas(politicas.clone());
14000            assert_eq!(
14001                c.politicas(),
14002                politicas.as_ref(),
14003                "Caixa::politicas must return :politicas verbatim (got \
14004                 {:?}, expected {:?})",
14005                c.politicas(),
14006                politicas.as_ref(),
14007            );
14008            match (c.politicas(), c.politicas.as_ref()) {
14009                (Some(a), Some(b)) => assert!(
14010                    std::ptr::eq(a, b),
14011                    "Caixa::politicas accessor and self.politicas.as_ref() \
14012                     field access must borrow the same backing storage \
14013                     — the accessor is the substrate-primitive typed \
14014                     dispatch every downstream Aplicacao-mesh-overlay \
14015                     composite consumer must route through, and a \
14016                     reference-identity split would silently break \
14017                     every consumer that relied on the borrow sharing \
14018                     the composite's storage",
14019                ),
14020                (None, None) => {}
14021                _ => panic!(
14022                    "Caixa::politicas presence bit must byte-equal \
14023                     self.politicas.is_some() — a presence-bit drift \
14024                     would silently split the paired \
14025                     Caixa::aplicacao_view Aplicacao-composition seed's \
14026                     traversal head from the peer \
14027                     Caixa::declared_mesh_slots M3 declared-slot \
14028                     enumerator's presence probe",
14029                ),
14030            }
14031            assert_eq!(
14032                c.politicas().is_some(),
14033                c.politicas.is_some(),
14034                "Caixa::politicas().is_some() must byte-equal \
14035                 self.politicas.is_some() — a presence-bit drift would \
14036                 silently split every downstream Option<&MeshPolicy> \
14037                 consumer's partition on the cluster-default arm",
14038            );
14039        }
14040    }
14041
14042    #[test]
14043    fn declared_mesh_slots_politicas_arm_routes_through_accessor() {
14044        // Composition pin: [`Caixa::declared_mesh_slots`]'s
14045        // `:politicas` presence-probe arm must key off
14046        // [`Caixa::politicas`], not the raw `self.politicas.is_some()`
14047        // field-probe. Structurally: a `Caixa { politicas:
14048        // Some(MeshPolicy::default()), .. }` must still push
14049        // `M3_AUTHOR_KEY_POLITICAS` onto the declared-slot list (the
14050        // presence bit is `Some`, so the M3 kind-coherence gate must
14051        // surface the slot as "declared" even when every per-axis
14052        // scalar is unset), and a `Caixa { politicas: None, .. }` must
14053        // NOT push the label (the "author omitted the slot entirely"
14054        // partition). The pair jointly pins the accessor + declared-
14055        // slot enumerator composition: any future silent detour that
14056        // had the accessor collapse `Some(MeshPolicy::default())` to
14057        // `None` (a `.filter(|p| !p.is_empty())` projection) would
14058        // silently absorb the "declared but empty" arm at the
14059        // accessor boundary and the
14060        // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
14061        // coherence gate would silently accept a struct-literal
14062        // `Caixa` carrying the drift.
14063        //
14064        // Peer of the sibling
14065        // `declared_servico_slots_limits_arm_routes_through_accessor`
14066        // (b2bd9d7) and
14067        // `declared_servico_slots_behavior_arm_routes_through_accessor`
14068        // (35d8b52) composition pins on the sibling `:limits` /
14069        // `:behavior` outer-`Option<&Composite>` arms of the peer
14070        // [`Caixa::declared_servico_slots`] M2 declared-slot
14071        // enumerator's traversal — same "the enumerator gate must
14072        // route through the substrate-primitive typed dispatch"
14073        // discipline extended onto the outer top-level [`Caixa`] M3
14074        // mesh-slot family so the [`Caixa::declared_mesh_slots`]
14075        // enumerator carries the same routing invariant as its M2
14076        // sibling.
14077        use crate::aplicacao::MeshPolicy;
14078        let c = caixa_aplicacao_with_politicas(Some(MeshPolicy::default()));
14079        let slots = c.declared_mesh_slots();
14080        assert!(
14081            slots.contains(&crate::render::M3_AUTHOR_KEY_POLITICAS),
14082            "declared_mesh_slots must push M3_AUTHOR_KEY_POLITICAS \
14083             when `:politicas` is Some (even for MeshPolicy::default()) \
14084             — the accessor and the enumerator gate must route through \
14085             the same substrate-primitive typed dispatch on the outer \
14086             :politicas presence bit (got slots={slots:?})",
14087        );
14088        let c = caixa_aplicacao_with_politicas(None);
14089        let slots = c.declared_mesh_slots();
14090        assert!(
14091            !slots.contains(&crate::render::M3_AUTHOR_KEY_POLITICAS),
14092            "declared_mesh_slots must NOT push M3_AUTHOR_KEY_POLITICAS \
14093             when `:politicas` is None — the author-omitted arm must \
14094             route through the accessor's None-return unchanged (got \
14095             slots={slots:?})",
14096        );
14097    }
14098
14099    #[test]
14100    fn aplicacao_view_politicas_arm_folds_through_accessor() {
14101        // Composition pin: [`Caixa::aplicacao_view`]'s per-`:politicas`
14102        // Aplicacao-composition seed must fold through
14103        // [`Caixa::politicas`], not the raw
14104        // `self.politicas.clone().unwrap_or_default()` field-borrow.
14105        // Structurally: a `Caixa { politicas: Some(MeshPolicy {
14106        // timeout: Some(30s), .. default }), kind: Aplicacao, .. }`
14107        // must surface a projected [`crate::AplicacaoSpec`] whose
14108        // `politicas().timeout()` field byte-equals the outer
14109        // composite's `timeout` scalar (the fold must project the
14110        // authored composite verbatim), a `Caixa { politicas:
14111        // Some(MeshPolicy::default()), kind: Aplicacao, .. }` must
14112        // surface an [`crate::AplicacaoSpec`] whose `politicas()`
14113        // byte-equals [`crate::aplicacao::MeshPolicy::default`] (the
14114        // fold's empty-composite arm collapses to the same default the
14115        // author-omitted arm does), and a `Caixa { politicas: None,
14116        // kind: Aplicacao, .. }` must surface an
14117        // [`crate::AplicacaoSpec`] whose `politicas()` byte-equals
14118        // [`crate::aplicacao::MeshPolicy::default`] (the "author
14119        // omitted the slot entirely" arm folds through the
14120        // `unwrap_or_default` onto the cluster-default). The triad
14121        // jointly pins the accessor + Aplicacao-composition seed
14122        // composition: any future silent detour that had the accessor
14123        // divert the raw slot away from the seed's fold (an operator-
14124        // resolved overlay's default-fold arm silently differing from
14125        // the raw slot's default-fold arm) would silently split the
14126        // build-time mesh-artifact emission gate from the caixa-mesh
14127        // renderer's Aplicacao-view input at the composition boundary.
14128        use crate::aplicacao::MeshPolicy;
14129        use std::time::Duration;
14130        let c = caixa_aplicacao_with_politicas(Some(MeshPolicy {
14131            timeout: Some(Duration::from_secs(30)),
14132            ..Default::default()
14133        }));
14134        let view = c.aplicacao_view().unwrap();
14135        assert_eq!(
14136            view.politicas().timeout(),
14137            Some(Duration::from_secs(30)),
14138            "Caixa::aplicacao_view must fold the authored :politicas \
14139             :timeout scalar through the accessor verbatim onto the \
14140             projected AplicacaoSpec — a future silent detour at the \
14141             seed's fold arm would surface here as a projected-scalar \
14142             drift (got {:?})",
14143            view.politicas().timeout(),
14144        );
14145        let c = caixa_aplicacao_with_politicas(Some(MeshPolicy::default()));
14146        let view = c.aplicacao_view().unwrap();
14147        assert_eq!(
14148            view.politicas(),
14149            &MeshPolicy::default(),
14150            "Caixa::aplicacao_view must fold Some(MeshPolicy::default()) \
14151             through the accessor onto MeshPolicy::default — the empty- \
14152             composite arm collapses to the same default the author- \
14153             omitted arm does (got {:?})",
14154            view.politicas(),
14155        );
14156        let c = caixa_aplicacao_with_politicas(None);
14157        let view = c.aplicacao_view().unwrap();
14158        assert_eq!(
14159            view.politicas(),
14160            &MeshPolicy::default(),
14161            "Caixa::aplicacao_view must fold None through the accessor's \
14162             unwrap_or_default onto MeshPolicy::default — the author- \
14163             omitted arm must route through the accessor's None-return \
14164             unchanged (got {:?})",
14165            view.politicas(),
14166        );
14167    }
14168
14169    #[test]
14170    fn politicas_projects_option_ref_by_borrow() {
14171        // The by-borrow pin: [`Caixa::politicas`] returns
14172        // `Option<&MeshPolicy>` by borrow — the returned reference
14173        // borrows the underlying `Option<MeshPolicy>` storage of the
14174        // `:politicas` slot and the accessor must not clone the
14175        // backing composite on every call. Peer of the sibling
14176        // `limits_projects_option_ref_by_borrow` (b2bd9d7) and
14177        // `behavior_projects_option_ref_by_borrow` (35d8b52) by-borrow
14178        // pins on the outer top-level [`Caixa`]
14179        // `Option<&Composite>`-return sub-family — extended here to
14180        // the third axis of the same sub-family: the accessor's
14181        // returned reference must borrow from `&self` (the returned
14182        // reference's lifetime is tied to `&self`), and calling the
14183        // accessor twice on the same [`Caixa`] must yield references
14184        // that are pointer-equal (the underlying byte-buffer is the
14185        // storage `MeshPolicy`'s allocation, not a fresh copy) as
14186        // well as value-equal (idempotent, no side effects on
14187        // `&self`).
14188        //
14189        // Pins against a future silent detour that returned an owned
14190        // `MeshPolicy` (which would type-check via the `Clone` impl
14191        // but silently clone on every call), a `&MeshPolicy` panic-
14192        // return on the `None` arm (which would collapse the load-
14193        // bearing `Option` presence-bit into a runtime panic), or a
14194        // one-arm-only accessor that returned a saturating composite
14195        // on some sentinel input.
14196        use crate::aplicacao::{CircuitBreaker, MeshPolicy, RateLimit};
14197        use std::time::Duration;
14198        for politicas in [
14199            Some(MeshPolicy::default()),
14200            Some(MeshPolicy {
14201                timeout: Some(Duration::from_secs(30)),
14202                retries: Some(3),
14203                circuit_breaker: Some(CircuitBreaker {
14204                    max_failures: 5,
14205                    window: Duration::from_secs(60),
14206                }),
14207                mtls_required: Some(true),
14208                rate_limit: Some(RateLimit {
14209                    rate: 100,
14210                    window: Duration::from_secs(1),
14211                }),
14212            }),
14213        ] {
14214            let c = caixa_aplicacao_with_politicas(politicas.clone());
14215            let first = c.politicas().unwrap();
14216            let second = c.politicas().unwrap();
14217            assert_eq!(
14218                first, second,
14219                "Caixa::politicas must be idempotent — two successive \
14220                 calls on the same &self must return the same \
14221                 &MeshPolicy",
14222            );
14223            assert!(
14224                std::ptr::eq(first, second),
14225                "Caixa::politicas must borrow the underlying \
14226                 Option<MeshPolicy> storage — two successive calls \
14227                 must return references with the same backing pointer \
14228                 (a fresh MeshPolicy clone would change the pointer on \
14229                 every call)",
14230            );
14231            assert_eq!(
14232                Some(first),
14233                politicas.as_ref(),
14234                "Caixa::politicas must return :politicas verbatim by \
14235                 borrow — got {first:?}, expected {:?}",
14236                politicas.as_ref(),
14237            );
14238        }
14239        let c = caixa_aplicacao_with_politicas(None);
14240        assert!(
14241            c.politicas().is_none(),
14242            "Caixa::politicas must return None when :politicas is \
14243             absent — the author-omitted arm must project through the \
14244             accessor's Option::None unchanged",
14245        );
14246    }
14247
14248    // ── Caixa::placement — outer top-level Option<&Placement> composite-reference accessor ──
14249
14250    fn caixa_aplicacao_with_placement(placement: Option<crate::aplicacao::Placement>) -> Caixa {
14251        use crate::aplicacao::{Membro, WitContract};
14252        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
14253        c.kind = CaixaKind::Aplicacao;
14254        c.membros = vec![Membro {
14255            caixa: "a".into(),
14256            versao: "^0.1".into(),
14257        }];
14258        c.contratos = vec![WitContract {
14259            de: "a".into(),
14260            para: "a".into(),
14261            wit: "wasi:http/proxy".into(),
14262            endpoint: Some("/x".into()),
14263            subject: None,
14264            slot: None,
14265        }];
14266        c.placement = placement;
14267        c
14268    }
14269
14270    #[test]
14271    fn placement_returns_placement_option_ref_verbatim_across_permutations() {
14272        // The canonical per-`Caixa` `:placement` M3 mesh-slot outer-
14273        // composite optional-composite-reference-shape pin:
14274        // [`Caixa::placement`] must return the `:placement` typed
14275        // `Option<Placement>` verbatim as an `Option<&Placement>`
14276        // reference over the same backing storage the raw
14277        // `self.placement.as_ref()` field access borrows from,
14278        // byte-equal across every representative fixture in the
14279        // accept-set — the author-omitted `None` shape (the
14280        // "cluster-default applies" partition every downstream mesh-
14281        // artifact emitter treats as "emit no `:placement` overlay"),
14282        // the empty-composite `Some(Placement { .. default })` shape
14283        // (`estrategia: SingleNode`, empty clusters, no shard-key /
14284        // affinity — the outer presence-bit is `Some` so
14285        // [`Caixa::declared_mesh_slots`] still pushes the
14286        // `M3_AUTHOR_KEY_PLACEMENT` label), a single-axis
14287        // `Replicated`-on-two-clusters fixture (the canonical shape a
14288        // stateless HTTP Aplicacao carries), and a fully-populated
14289        // `Sharded`-with-shard-key-and-affinity fixture (the canonical
14290        // shape a stateful Akka-style cluster-sharding Aplicacao
14291        // carries).
14292        //
14293        // Pins against a future silent detour that returned a fresh-
14294        // cloned [`crate::aplicacao::Placement`] copy (which would
14295        // type-check via the `Clone` impl but silently break every
14296        // downstream caller that relied on the reference sharing the
14297        // composite's backing identity), a reference to an operator-
14298        // resolved overlay (the future per-cluster
14299        // `:placement-overrides` slot — its resolution must land at
14300        // exactly this accessor body, not silently divert the raw
14301        // slot away from the peer [`Caixa::declared_mesh_slots`]
14302        // enumerator's presence probe), a `None` →
14303        // `Some(Placement::default)` cluster-default projection (which
14304        // would collapse the load-bearing "author-omitted `:placement`
14305        // ⇒ cluster-default applies" partition the peer
14306        // [`Caixa::declared_mesh_slots`] enumerator and the peer
14307        // [`Caixa::aplicacao_view`] Aplicacao-composition seed both
14308        // read), or an axis-shuffled projection (a future detour that
14309        // swapped `clusters` and `affinity` through the accessor would
14310        // silently split the paired [`Caixa::aplicacao_view`] seed's
14311        // fold input from the sibling M3 mesh-artifact emitter's
14312        // projection input).
14313        //
14314        // Fourth outer top-level [`Caixa`] `Option<&Composite>`-return
14315        // composite-reference accessor pin on the substrate primitive
14316        // — peer of the sibling
14317        // `limits_returns_limits_option_ref_verbatim_across_permutations`
14318        // (b2bd9d7),
14319        // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
14320        // (35d8b52), and
14321        // `politicas_returns_politicas_option_ref_verbatim_across_permutations`
14322        // (5d23d29) opening triad pins on the outer top-level
14323        // [`Caixa`] `Option<&Composite>`-return sub-family — extended
14324        // here to the second of the three M3 mesh-slot axes so the
14325        // opening four-fifths of the outer `Option<&Composite>` sub-
14326        // family carries the same "byte-equal, borrow-shared,
14327        // presence-bit-preserved" outer-accessor discipline.
14328        use crate::aplicacao::{Placement, PlacementStrategy};
14329        let fixtures: Vec<Option<Placement>> = vec![
14330            None,
14331            Some(Placement::default()),
14332            Some(Placement {
14333                estrategia: PlacementStrategy::Replicated,
14334                clusters: vec!["rio".into(), "sao-paulo".into()],
14335                affinity: None,
14336                shard_key: None,
14337            }),
14338            Some(Placement {
14339                estrategia: PlacementStrategy::Sharded,
14340                clusters: vec!["rio".into(), "sao-paulo".into(), "brasilia".into()],
14341                affinity: Some("data-locality".into()),
14342                shard_key: Some("$tenantId".into()),
14343            }),
14344        ];
14345        for placement in fixtures {
14346            let c = caixa_aplicacao_with_placement(placement.clone());
14347            assert_eq!(
14348                c.placement(),
14349                placement.as_ref(),
14350                "Caixa::placement must return :placement verbatim (got \
14351                 {:?}, expected {:?})",
14352                c.placement(),
14353                placement.as_ref(),
14354            );
14355            match (c.placement(), c.placement.as_ref()) {
14356                (Some(a), Some(b)) => assert!(
14357                    std::ptr::eq(a, b),
14358                    "Caixa::placement accessor and self.placement.as_ref() \
14359                     field access must borrow the same backing storage \
14360                     — the accessor is the substrate-primitive typed \
14361                     dispatch every downstream Aplicacao-distribution- \
14362                     overlay composite consumer must route through, and \
14363                     a reference-identity split would silently break \
14364                     every consumer that relied on the borrow sharing \
14365                     the composite's storage",
14366                ),
14367                (None, None) => {}
14368                _ => panic!(
14369                    "Caixa::placement presence bit must byte-equal \
14370                     self.placement.is_some() — a presence-bit drift \
14371                     would silently split the paired \
14372                     Caixa::aplicacao_view Aplicacao-composition seed's \
14373                     traversal head from the peer \
14374                     Caixa::declared_mesh_slots M3 declared-slot \
14375                     enumerator's presence probe",
14376                ),
14377            }
14378            assert_eq!(
14379                c.placement().is_some(),
14380                c.placement.is_some(),
14381                "Caixa::placement().is_some() must byte-equal \
14382                 self.placement.is_some() — a presence-bit drift would \
14383                 silently split every downstream Option<&Placement> \
14384                 consumer's partition on the cluster-default arm",
14385            );
14386        }
14387    }
14388
14389    #[test]
14390    fn declared_mesh_slots_placement_arm_routes_through_accessor() {
14391        // Composition pin: [`Caixa::declared_mesh_slots`]'s
14392        // `:placement` presence-probe arm must key off
14393        // [`Caixa::placement`], not the raw `self.placement.is_some()`
14394        // field-probe. Structurally: a `Caixa { placement:
14395        // Some(Placement::default()), .. }` must still push
14396        // `M3_AUTHOR_KEY_PLACEMENT` onto the declared-slot list (the
14397        // presence bit is `Some`, so the M3 kind-coherence gate must
14398        // surface the slot as "declared" even when every per-axis
14399        // scalar defers to the cluster-default arm), and a `Caixa {
14400        // placement: None, .. }` must NOT push the label (the "author
14401        // omitted the slot entirely" partition). The pair jointly pins
14402        // the accessor + declared-slot enumerator composition: any
14403        // future silent detour that had the accessor collapse
14404        // `Some(Placement::default())` to `None` (a `.filter(|p|
14405        // p.clusters().is_empty().not())` projection) would silently
14406        // absorb the "declared but empty" arm at the accessor boundary
14407        // and the [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
14408        // kind-coherence gate would silently accept a struct-literal
14409        // `Caixa` carrying the drift.
14410        //
14411        // Peer of the sibling
14412        // `declared_servico_slots_limits_arm_routes_through_accessor`
14413        // (b2bd9d7),
14414        // `declared_servico_slots_behavior_arm_routes_through_accessor`
14415        // (35d8b52), and
14416        // `declared_mesh_slots_politicas_arm_routes_through_accessor`
14417        // (5d23d29) composition pins on the sibling `:limits` /
14418        // `:behavior` / `:politicas` outer-`Option<&Composite>` arms
14419        // — same "the enumerator gate must route through the
14420        // substrate-primitive typed dispatch" discipline extended onto
14421        // the second of the three M3 mesh-slot axes so the
14422        // [`Caixa::declared_mesh_slots`] enumerator carries the same
14423        // routing invariant on the `:placement` arm as the peer
14424        // `:politicas` arm.
14425        use crate::aplicacao::Placement;
14426        let c = caixa_aplicacao_with_placement(Some(Placement::default()));
14427        let slots = c.declared_mesh_slots();
14428        assert!(
14429            slots.contains(&crate::render::M3_AUTHOR_KEY_PLACEMENT),
14430            "declared_mesh_slots must push M3_AUTHOR_KEY_PLACEMENT \
14431             when `:placement` is Some (even for Placement::default()) \
14432             — the accessor and the enumerator gate must route through \
14433             the same substrate-primitive typed dispatch on the outer \
14434             :placement presence bit (got slots={slots:?})",
14435        );
14436        let c = caixa_aplicacao_with_placement(None);
14437        let slots = c.declared_mesh_slots();
14438        assert!(
14439            !slots.contains(&crate::render::M3_AUTHOR_KEY_PLACEMENT),
14440            "declared_mesh_slots must NOT push M3_AUTHOR_KEY_PLACEMENT \
14441             when `:placement` is None — the author-omitted arm must \
14442             route through the accessor's None-return unchanged (got \
14443             slots={slots:?})",
14444        );
14445    }
14446
14447    #[test]
14448    fn aplicacao_view_placement_arm_folds_through_accessor() {
14449        // Composition pin: [`Caixa::aplicacao_view`]'s per-`:placement`
14450        // Aplicacao-composition seed must fold through
14451        // [`Caixa::placement`], not the raw
14452        // `self.placement.clone().unwrap_or_default()` field-borrow.
14453        // Structurally: a `Caixa { placement: Some(Placement {
14454        // estrategia: Replicated, clusters: ["rio"], .. default }),
14455        // kind: Aplicacao, .. }` must surface a projected
14456        // [`crate::AplicacaoSpec`] whose `placement().estrategia()` +
14457        // `placement().clusters()` byte-equal the outer composite's
14458        // authored values (the fold must project the authored
14459        // composite verbatim), a `Caixa { placement:
14460        // Some(Placement::default()), kind: Aplicacao, .. }` must
14461        // surface an [`crate::AplicacaoSpec`] whose `placement()`
14462        // byte-equals [`crate::aplicacao::Placement::default`] (the
14463        // fold's empty-composite arm collapses to the same default
14464        // the author-omitted arm does), and a `Caixa { placement:
14465        // None, kind: Aplicacao, .. }` must surface an
14466        // [`crate::AplicacaoSpec`] whose `placement()` byte-equals
14467        // [`crate::aplicacao::Placement::default`] (the "author
14468        // omitted the slot entirely" arm folds through the
14469        // `unwrap_or_default` onto the cluster-default). The triad
14470        // jointly pins the accessor + Aplicacao-composition seed
14471        // composition: any future silent detour that had the accessor
14472        // divert the raw slot away from the seed's fold (an operator-
14473        // resolved overlay's default-fold arm silently differing from
14474        // the raw slot's default-fold arm) would silently split the
14475        // build-time distribution-artifact emission gate from the
14476        // caixa-mesh renderer's Aplicacao-view input at the
14477        // composition boundary.
14478        use crate::aplicacao::{Placement, PlacementStrategy};
14479        let c = caixa_aplicacao_with_placement(Some(Placement {
14480            estrategia: PlacementStrategy::Replicated,
14481            clusters: vec!["rio".into()],
14482            affinity: None,
14483            shard_key: None,
14484        }));
14485        let view = c.aplicacao_view().unwrap();
14486        assert_eq!(
14487            view.placement().estrategia(),
14488            PlacementStrategy::Replicated,
14489            "Caixa::aplicacao_view must fold the authored :placement \
14490             :estrategia scalar through the accessor verbatim onto the \
14491             projected AplicacaoSpec — a future silent detour at the \
14492             seed's fold arm would surface here as a projected-scalar \
14493             drift (got {:?})",
14494            view.placement().estrategia(),
14495        );
14496        assert_eq!(
14497            view.placement().clusters(),
14498            &["rio"],
14499            "Caixa::aplicacao_view must fold the authored :placement \
14500             :clusters list through the accessor verbatim onto the \
14501             projected AplicacaoSpec — a future silent detour at the \
14502             seed's fold arm would surface here as a projected-list \
14503             drift (got {:?})",
14504            view.placement().clusters(),
14505        );
14506        let c = caixa_aplicacao_with_placement(Some(Placement::default()));
14507        let view = c.aplicacao_view().unwrap();
14508        assert_eq!(
14509            view.placement(),
14510            &Placement::default(),
14511            "Caixa::aplicacao_view must fold Some(Placement::default()) \
14512             through the accessor onto Placement::default — the empty- \
14513             composite arm collapses to the same default the author- \
14514             omitted arm does (got {:?})",
14515            view.placement(),
14516        );
14517        let c = caixa_aplicacao_with_placement(None);
14518        let view = c.aplicacao_view().unwrap();
14519        assert_eq!(
14520            view.placement(),
14521            &Placement::default(),
14522            "Caixa::aplicacao_view must fold None through the accessor's \
14523             unwrap_or_default onto Placement::default — the author- \
14524             omitted arm must route through the accessor's None-return \
14525             unchanged (got {:?})",
14526            view.placement(),
14527        );
14528    }
14529
14530    #[test]
14531    fn placement_projects_option_ref_by_borrow() {
14532        // The by-borrow pin: [`Caixa::placement`] returns
14533        // `Option<&Placement>` by borrow — the returned reference
14534        // borrows the underlying `Option<Placement>` storage of the
14535        // `:placement` slot and the accessor must not clone the
14536        // backing composite on every call. Peer of the sibling
14537        // `limits_projects_option_ref_by_borrow` (b2bd9d7),
14538        // `behavior_projects_option_ref_by_borrow` (35d8b52), and
14539        // `politicas_projects_option_ref_by_borrow` (5d23d29) by-borrow
14540        // pins on the outer top-level [`Caixa`]
14541        // `Option<&Composite>`-return sub-family — extended here to
14542        // the fourth axis of the same sub-family: the accessor's
14543        // returned reference must borrow from `&self` (the returned
14544        // reference's lifetime is tied to `&self`), and calling the
14545        // accessor twice on the same [`Caixa`] must yield references
14546        // that are pointer-equal (the underlying byte-buffer is the
14547        // storage `Placement`'s allocation, not a fresh copy) as well
14548        // as value-equal (idempotent, no side effects on `&self`).
14549        //
14550        // Pins against a future silent detour that returned an owned
14551        // `Placement` (which would type-check via the `Clone` impl
14552        // but silently clone on every call), a `&Placement` panic-
14553        // return on the `None` arm (which would collapse the load-
14554        // bearing `Option` presence-bit into a runtime panic), or a
14555        // one-arm-only accessor that returned a saturating composite
14556        // on some sentinel input.
14557        use crate::aplicacao::{Placement, PlacementStrategy};
14558        for placement in [
14559            Some(Placement::default()),
14560            Some(Placement {
14561                estrategia: PlacementStrategy::Sharded,
14562                clusters: vec!["rio".into(), "sao-paulo".into()],
14563                affinity: Some("data-locality".into()),
14564                shard_key: Some("$tenantId".into()),
14565            }),
14566        ] {
14567            let c = caixa_aplicacao_with_placement(placement.clone());
14568            let first = c.placement().unwrap();
14569            let second = c.placement().unwrap();
14570            assert_eq!(
14571                first, second,
14572                "Caixa::placement must be idempotent — two successive \
14573                 calls on the same &self must return the same \
14574                 &Placement",
14575            );
14576            assert!(
14577                std::ptr::eq(first, second),
14578                "Caixa::placement must borrow the underlying \
14579                 Option<Placement> storage — two successive calls \
14580                 must return references with the same backing pointer \
14581                 (a fresh Placement clone would change the pointer on \
14582                 every call)",
14583            );
14584            assert_eq!(
14585                Some(first),
14586                placement.as_ref(),
14587                "Caixa::placement must return :placement verbatim by \
14588                 borrow — got {first:?}, expected {:?}",
14589                placement.as_ref(),
14590            );
14591        }
14592        let c = caixa_aplicacao_with_placement(None);
14593        assert!(
14594            c.placement().is_none(),
14595            "Caixa::placement must return None when :placement is \
14596             absent — the author-omitted arm must project through the \
14597             accessor's Option::None unchanged",
14598        );
14599    }
14600
14601    // ── Caixa::entrada — outer top-level Option<&Entrada> composite-reference accessor ──
14602
14603    fn caixa_aplicacao_with_entrada(entrada: Option<crate::aplicacao::Entrada>) -> Caixa {
14604        use crate::aplicacao::{Membro, WitContract};
14605        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
14606        c.kind = CaixaKind::Aplicacao;
14607        c.membros = vec![Membro {
14608            caixa: "a".into(),
14609            versao: "^0.1".into(),
14610        }];
14611        c.contratos = vec![WitContract {
14612            de: "a".into(),
14613            para: "a".into(),
14614            wit: "wasi:http/proxy".into(),
14615            endpoint: Some("/x".into()),
14616            subject: None,
14617            slot: None,
14618        }];
14619        c.entrada = entrada;
14620        c
14621    }
14622
14623    #[test]
14624    fn entrada_returns_entrada_option_ref_verbatim_across_permutations() {
14625        // The canonical per-`Caixa` `:entrada` M3 mesh-slot outer-
14626        // composite optional-composite-reference-shape pin:
14627        // [`Caixa::entrada`] must return the `:entrada` typed
14628        // `Option<Entrada>` verbatim as an `Option<&Entrada>`
14629        // reference over the same backing storage the raw
14630        // `self.entrada.as_ref()` field access borrows from,
14631        // byte-equal across every representative fixture in the
14632        // accept-set — the author-omitted `None` shape (the
14633        // "cluster-internal Aplicacao" partition every downstream
14634        // Gateway-API emitter treats as "emit no listener + no
14635        // HTTPRoute"), a bare-`host`/`para` minimum-composite fixture
14636        // (empty `paths` — the resolved-paths fallback the peer
14637        // [`crate::aplicacao::Entrada::resolved_paths`] cascade folds
14638        // onto the substrate catch-all), and a fully-populated
14639        // multi-path-with-non-default-port fixture (the canonical
14640        // shape a public HTTP Aplicacao carries).
14641        //
14642        // Pins against a future silent detour that returned a fresh-
14643        // cloned [`crate::aplicacao::Entrada`] copy (which would
14644        // type-check via the `Clone` impl but silently break every
14645        // downstream caller that relied on the reference sharing the
14646        // composite's backing identity), a reference to an operator-
14647        // resolved overlay (the future per-cluster
14648        // `:entrada-overrides` slot — its resolution must land at
14649        // exactly this accessor body, not silently divert the raw
14650        // slot away from the peer [`Caixa::declared_mesh_slots`]
14651        // enumerator's presence probe), or an axis-shuffled projection
14652        // (a future detour that swapped `host` and `para` through the
14653        // accessor would silently split the paired
14654        // [`Caixa::aplicacao_view`] seed's forward input from the
14655        // sibling M3 gateway-artifact emitter's projection input).
14656        //
14657        // Fifth and final outer top-level [`Caixa`]
14658        // `Option<&Composite>`-return composite-reference accessor pin
14659        // on the substrate primitive — peer of the sibling
14660        // `limits_returns_limits_option_ref_verbatim_across_permutations`
14661        // (b2bd9d7),
14662        // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
14663        // (35d8b52),
14664        // `politicas_returns_politicas_option_ref_verbatim_across_permutations`
14665        // (5d23d29), and
14666        // `placement_returns_placement_option_ref_verbatim_across_permutations`
14667        // (4fb8074) opening tetrad pins on the outer top-level
14668        // [`Caixa`] `Option<&Composite>`-return sub-family — extended
14669        // here to the third and final M3 mesh-slot axis so the closed
14670        // outer `Option<&Composite>` sub-family carries the same
14671        // "byte-equal, borrow-shared, presence-bit-preserved" outer-
14672        // accessor discipline across all five arms.
14673        use crate::aplicacao::Entrada;
14674        let fixtures: Vec<Option<Entrada>> = vec![
14675            None,
14676            Some(Entrada {
14677                host: "checkout.quero.cloud".into(),
14678                para: "gateway".into(),
14679                paths: Vec::new(),
14680                port: crate::DEFAULT_SERVICO_PORT,
14681            }),
14682            Some(Entrada {
14683                host: "api.pleme.io".into(),
14684                para: "public-api".into(),
14685                paths: vec!["/v1".into(), "/v2".into()],
14686                port: 8080,
14687            }),
14688        ];
14689        for entrada in fixtures {
14690            let c = caixa_aplicacao_with_entrada(entrada.clone());
14691            assert_eq!(
14692                c.entrada(),
14693                entrada.as_ref(),
14694                "Caixa::entrada must return :entrada verbatim (got \
14695                 {:?}, expected {:?})",
14696                c.entrada(),
14697                entrada.as_ref(),
14698            );
14699            match (c.entrada(), c.entrada.as_ref()) {
14700                (Some(a), Some(b)) => assert!(
14701                    std::ptr::eq(a, b),
14702                    "Caixa::entrada accessor and self.entrada.as_ref() \
14703                     field access must borrow the same backing storage \
14704                     — the accessor is the substrate-primitive typed \
14705                     dispatch every downstream Aplicacao-external- \
14706                     gateway composite consumer must route through, and \
14707                     a reference-identity split would silently break \
14708                     every consumer that relied on the borrow sharing \
14709                     the composite's storage",
14710                ),
14711                (None, None) => {}
14712                _ => panic!(
14713                    "Caixa::entrada presence bit must byte-equal \
14714                     self.entrada.is_some() — a presence-bit drift \
14715                     would silently split the paired \
14716                     Caixa::aplicacao_view Aplicacao-composition seed's \
14717                     traversal head from the peer \
14718                     Caixa::declared_mesh_slots M3 declared-slot \
14719                     enumerator's presence probe",
14720                ),
14721            }
14722            assert_eq!(
14723                c.entrada().is_some(),
14724                c.entrada.is_some(),
14725                "Caixa::entrada().is_some() must byte-equal \
14726                 self.entrada.is_some() — a presence-bit drift would \
14727                 silently split every downstream Option<&Entrada> \
14728                 consumer's partition on the cluster-internal arm",
14729            );
14730        }
14731    }
14732
14733    #[test]
14734    fn declared_mesh_slots_entrada_arm_routes_through_accessor() {
14735        // Composition pin: [`Caixa::declared_mesh_slots`]'s `:entrada`
14736        // presence-probe arm must key off [`Caixa::entrada`], not the
14737        // raw `self.entrada.is_some()` field-probe. Structurally: a
14738        // `Caixa { entrada: Some(Entrada { host: "...", para: "...",
14739        // paths: [], port: DEFAULT_SERVICO_PORT }), .. }` must push
14740        // `M3_AUTHOR_KEY_ENTRADA` onto the declared-slot list (the
14741        // presence bit is `Some`, so the M3 kind-coherence gate must
14742        // surface the slot as "declared" even when every per-axis
14743        // scalar defers to the substrate catch-all / default port),
14744        // and a `Caixa { entrada: None, .. }` must NOT push the label
14745        // (the "author omitted the slot entirely" partition). The pair
14746        // jointly pins the accessor + declared-slot enumerator
14747        // composition: any future silent detour that had the accessor
14748        // collapse `Some(Entrada { paths: [], .. })` to `None` (a
14749        // `.filter(|e| !e.paths.is_empty())` projection) would silently
14750        // absorb the "declared but empty-paths" arm at the accessor
14751        // boundary and the
14752        // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
14753        // coherence gate would silently accept a struct-literal
14754        // `Caixa` carrying the drift.
14755        //
14756        // Peer of the sibling
14757        // `declared_servico_slots_limits_arm_routes_through_accessor`
14758        // (b2bd9d7),
14759        // `declared_servico_slots_behavior_arm_routes_through_accessor`
14760        // (35d8b52),
14761        // `declared_mesh_slots_politicas_arm_routes_through_accessor`
14762        // (5d23d29), and
14763        // `declared_mesh_slots_placement_arm_routes_through_accessor`
14764        // (4fb8074) composition pins on the sibling `:limits` /
14765        // `:behavior` / `:politicas` / `:placement` outer-
14766        // `Option<&Composite>` arms — same "the enumerator gate must
14767        // route through the substrate-primitive typed dispatch"
14768        // discipline extended onto the third and final M3 mesh-slot
14769        // axis so the [`Caixa::declared_mesh_slots`] enumerator now
14770        // carries the routing invariant on every M3 mesh-slot arm.
14771        use crate::aplicacao::Entrada;
14772        let c = caixa_aplicacao_with_entrada(Some(Entrada {
14773            host: "checkout.quero.cloud".into(),
14774            para: "gateway".into(),
14775            paths: Vec::new(),
14776            port: crate::DEFAULT_SERVICO_PORT,
14777        }));
14778        let slots = c.declared_mesh_slots();
14779        assert!(
14780            slots.contains(&crate::render::M3_AUTHOR_KEY_ENTRADA),
14781            "declared_mesh_slots must push M3_AUTHOR_KEY_ENTRADA when \
14782             `:entrada` is Some (even for empty-paths / default-port) \
14783             — the accessor and the enumerator gate must route through \
14784             the same substrate-primitive typed dispatch on the outer \
14785             :entrada presence bit (got slots={slots:?})",
14786        );
14787        let c = caixa_aplicacao_with_entrada(None);
14788        let slots = c.declared_mesh_slots();
14789        assert!(
14790            !slots.contains(&crate::render::M3_AUTHOR_KEY_ENTRADA),
14791            "declared_mesh_slots must NOT push M3_AUTHOR_KEY_ENTRADA \
14792             when `:entrada` is None — the author-omitted arm must \
14793             route through the accessor's None-return unchanged (got \
14794             slots={slots:?})",
14795        );
14796    }
14797
14798    #[test]
14799    fn aplicacao_view_entrada_arm_folds_through_accessor() {
14800        // Composition pin: [`Caixa::aplicacao_view`]'s per-`:entrada`
14801        // Aplicacao-composition seed must fold through
14802        // [`Caixa::entrada`], not the raw `self.entrada.clone()` field-
14803        // borrow. Structurally: a `Caixa { entrada: Some(Entrada {
14804        // host: "api.pleme.io", para: "public-api", paths: ["/v1"],
14805        // port: 8080 }), kind: Aplicacao, .. }` must surface a projected
14806        // [`crate::AplicacaoSpec`] whose `entrada().unwrap()` byte-
14807        // equals the outer composite's authored value (the fold must
14808        // project the authored composite verbatim), and a `Caixa {
14809        // entrada: None, kind: Aplicacao, .. }` must surface an
14810        // [`crate::AplicacaoSpec`] whose `entrada()` is `None` (the
14811        // "author omitted the slot entirely" arm folds through the
14812        // accessor's `Option::cloned` onto the same `None` presence
14813        // bit — unlike the peer `:politicas` / `:placement` arms
14814        // `:entrada` has no cluster-default fold, the omitted arm
14815        // stays omitted). The pair jointly pins the accessor +
14816        // Aplicacao-composition seed composition: any future silent
14817        // detour that had the accessor divert the raw slot away from
14818        // the seed's fold (an operator-resolved overlay's forward arm
14819        // silently differing from the raw slot's forward arm) would
14820        // silently split the build-time gateway-artifact emission gate
14821        // from the caixa-mesh renderer's Aplicacao-view input at the
14822        // composition boundary.
14823        use crate::aplicacao::Entrada;
14824        let authored = Entrada {
14825            host: "api.pleme.io".into(),
14826            para: "public-api".into(),
14827            paths: vec!["/v1".into()],
14828            port: 8080,
14829        };
14830        let c = caixa_aplicacao_with_entrada(Some(authored.clone()));
14831        let view = c.aplicacao_view().unwrap();
14832        assert_eq!(
14833            view.entrada(),
14834            Some(&authored),
14835            "Caixa::aplicacao_view must fold the authored :entrada \
14836             composite through the accessor verbatim onto the \
14837             projected AplicacaoSpec — a future silent detour at the \
14838             seed's fold arm would surface here as a projected- \
14839             composite drift (got {:?})",
14840            view.entrada(),
14841        );
14842        let c = caixa_aplicacao_with_entrada(None);
14843        let view = c.aplicacao_view().unwrap();
14844        assert!(
14845            view.entrada().is_none(),
14846            "Caixa::aplicacao_view must fold None through the \
14847             accessor's Option::cloned onto None — the author- \
14848             omitted arm must route through the accessor's None-return \
14849             unchanged (got {:?})",
14850            view.entrada(),
14851        );
14852    }
14853
14854    #[test]
14855    fn entrada_projects_option_ref_by_borrow() {
14856        // The by-borrow pin: [`Caixa::entrada`] returns
14857        // `Option<&Entrada>` by borrow — the returned reference
14858        // borrows the underlying `Option<Entrada>` storage of the
14859        // `:entrada` slot and the accessor must not clone the backing
14860        // composite on every call. Peer of the sibling
14861        // `limits_projects_option_ref_by_borrow` (b2bd9d7),
14862        // `behavior_projects_option_ref_by_borrow` (35d8b52),
14863        // `politicas_projects_option_ref_by_borrow` (5d23d29), and
14864        // `placement_projects_option_ref_by_borrow` (4fb8074) by-
14865        // borrow pins on the outer top-level [`Caixa`]
14866        // `Option<&Composite>`-return sub-family — extended here to
14867        // the fifth and final axis of the same sub-family, closing
14868        // the discipline: the accessor's returned reference must
14869        // borrow from `&self` (the returned reference's lifetime is
14870        // tied to `&self`), and calling the accessor twice on the
14871        // same [`Caixa`] must yield references that are pointer-equal
14872        // (the underlying byte-buffer is the storage `Entrada`'s
14873        // allocation, not a fresh copy) as well as value-equal
14874        // (idempotent, no side effects on `&self`).
14875        //
14876        // Pins against a future silent detour that returned an owned
14877        // `Entrada` (which would type-check via the `Clone` impl but
14878        // silently clone on every call), a `&Entrada` panic-return on
14879        // the `None` arm (which would collapse the load-bearing
14880        // `Option` presence-bit into a runtime panic), or a one-arm-
14881        // only accessor that returned a saturating composite on some
14882        // sentinel input.
14883        use crate::aplicacao::Entrada;
14884        for entrada in [
14885            Some(Entrada {
14886                host: "checkout.quero.cloud".into(),
14887                para: "gateway".into(),
14888                paths: Vec::new(),
14889                port: crate::DEFAULT_SERVICO_PORT,
14890            }),
14891            Some(Entrada {
14892                host: "api.pleme.io".into(),
14893                para: "public-api".into(),
14894                paths: vec!["/v1".into(), "/v2".into()],
14895                port: 8080,
14896            }),
14897        ] {
14898            let c = caixa_aplicacao_with_entrada(entrada.clone());
14899            let first = c.entrada().unwrap();
14900            let second = c.entrada().unwrap();
14901            assert_eq!(
14902                first, second,
14903                "Caixa::entrada must be idempotent — two successive \
14904                 calls on the same &self must return the same &Entrada",
14905            );
14906            assert!(
14907                std::ptr::eq(first, second),
14908                "Caixa::entrada must borrow the underlying \
14909                 Option<Entrada> storage — two successive calls must \
14910                 return references with the same backing pointer (a \
14911                 fresh Entrada clone would change the pointer on every \
14912                 call)",
14913            );
14914            assert_eq!(
14915                Some(first),
14916                entrada.as_ref(),
14917                "Caixa::entrada must return :entrada verbatim by \
14918                 borrow — got {first:?}, expected {:?}",
14919                entrada.as_ref(),
14920            );
14921        }
14922        let c = caixa_aplicacao_with_entrada(None);
14923        assert!(
14924            c.entrada().is_none(),
14925            "Caixa::entrada must return None when :entrada is absent \
14926             — the author-omitted arm must project through the \
14927             accessor's Option::None unchanged",
14928        );
14929    }
14930
14931    // ── Caixa::estrategia — outer top-level Option<RestartStrategy> flat-spread supervisor-tree accessor ──
14932
14933    fn caixa_with_estrategia(estrategia: Option<crate::supervisor::RestartStrategy>) -> Caixa {
14934        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
14935        c.estrategia = estrategia;
14936        c
14937    }
14938
14939    #[test]
14940    fn estrategia_returns_estrategia_option_verbatim_across_permutations() {
14941        // The canonical per-`Caixa` `:estrategia` M2 supervisor-tree-slot
14942        // flat-spread `Option<RestartStrategy>`-return `Copy`-composite-
14943        // enum-arm scalar shape pin: [`Caixa::estrategia`] must return
14944        // the `:estrategia` typed `Option<crate::supervisor::RestartStrategy>`
14945        // verbatim as an `Option<RestartStrategy>` `Copy`-projected value
14946        // over the same discriminant the raw `self.estrategia` field
14947        // access carries, byte-equal across every representative fixture
14948        // in the accept-set — the author-omitted `None` shape (the
14949        // "defer to [`RestartStrategy::default`] through the
14950        // [`Self::supervisor_view`] `unwrap_or_default()` fold" partition
14951        // every non-`Supervisor`-kind `defcaixa` carries by
14952        // `#[serde(default)]`), and each of the four closed-set variants
14953        // [`RestartStrategy::OneForOne`] / [`RestartStrategy::OneForAll`]
14954        // / [`RestartStrategy::RestForOne`] /
14955        // [`RestartStrategy::SimpleOneForOne`] the author-declared arm
14956        // partitions on.
14957        //
14958        // Pins against a future silent detour that re-derived the
14959        // strategy from a peer axis (an accidental fallback to
14960        // `if children.is_empty() { SimpleOneForOne } else { OneForOne }`
14961        // collapse that read the outer `:children` list-length axis into
14962        // the strategy discriminator at the accessor boundary), a
14963        // stale-derive detour that substituted [`RestartStrategy::default`]
14964        // when the outer `Option` held `None` (which would silently
14965        // collapse the load-bearing "author explicitly declared
14966        // `:estrategia OneForOne`" vs "author omitted the slot and
14967        // inherited the default" partition the [`Self::declared_supervisor_slots`]
14968        // presence-probe reads — the enumerator gate would still push
14969        // `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA` on the omitted arm, silently
14970        // splitting the paired [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
14971        // kind-coherence gate's traversal head from the
14972        // [`Self::supervisor_view`] `unwrap_or_default()` fold's
14973        // composition head), a reference to an operator-resolved overlay
14974        // (the future per-cluster `:estrategia-overrides` slot — its
14975        // resolution must land at exactly this accessor body, not
14976        // silently divert the raw slot away from a second consumer), or
14977        // an axis-remap projection (a future detour that mapped
14978        // `OneForAll` through the accessor onto `OneForOne` would
14979        // silently split every downstream sibling-restart-strategy
14980        // consumer's per-arm fan-out).
14981        //
14982        // First outer top-level [`Caixa`] `Option<Copy>`-return
14983        // supervisor-tree-slot flat-spread accessor pin on the substrate
14984        // primitive — opens the outer-`Caixa` `Option<Copy>` flat-spread
14985        // projection pattern the sibling per-`Caixa` `:max-restarts` /
14986        // `:restart-window` future outer-scalar pins fold on. Peer of
14987        // the inner-altitude
14988        // `supervisor_spec_estrategia_returns_estrategia_verbatim_across_permutations`
14989        // (eafb619) pin on the post-composition [`SupervisorSpec`]
14990        // altitude — same "the substrate-primitive accessor must byte-
14991        // equal the raw field access verbatim across every author-
14992        // declared value" discipline extended onto the pre-composition
14993        // outer author-surface [`Caixa`] altitude. Peer of the closed
14994        // outer-`Caixa` `Option<&Composite>` composite-reference family
14995        // the sibling `limits` / `behavior` / `politicas` / `placement` /
14996        // `entrada`
14997        // `..._returns_..._option_ref_verbatim_across_permutations` pins
14998        // already carry on the outer `Option<&Composite>` altitude.
14999        use crate::supervisor::RestartStrategy;
15000        let fixtures: Vec<Option<RestartStrategy>> = vec![
15001            None,
15002            Some(RestartStrategy::OneForOne),
15003            Some(RestartStrategy::OneForAll),
15004            Some(RestartStrategy::RestForOne),
15005            Some(RestartStrategy::SimpleOneForOne),
15006        ];
15007        for estrategia in fixtures {
15008            let c = caixa_with_estrategia(estrategia);
15009            assert_eq!(
15010                c.estrategia(),
15011                estrategia,
15012                "Caixa::estrategia must return :estrategia verbatim (got \
15013                 {:?}, expected {:?})",
15014                c.estrategia(),
15015                estrategia,
15016            );
15017            assert_eq!(
15018                c.estrategia(),
15019                c.estrategia,
15020                "Caixa::estrategia accessor and self.estrategia field \
15021                 access must byte-equal — the accessor is the substrate-\
15022                 primitive typed dispatch every downstream supervisor-\
15023                 tree flat-spread consumer must route through, and a \
15024                 discriminant split would silently break every consumer \
15025                 that relied on the accessor sharing the field's own \
15026                 Option<Copy> shape",
15027            );
15028            assert_eq!(
15029                c.estrategia().is_some(),
15030                c.estrategia.is_some(),
15031                "Caixa::estrategia().is_some() must byte-equal \
15032                 self.estrategia.is_some() — a presence-bit drift would \
15033                 silently split the paired Caixa::declared_supervisor_slots \
15034                 presence-probe arm from the Caixa::supervisor_view \
15035                 unwrap_or_default() fold's composition input",
15036            );
15037        }
15038    }
15039
15040    #[test]
15041    fn declared_supervisor_slots_estrategia_arm_routes_through_accessor() {
15042        // Composition pin: [`Caixa::declared_supervisor_slots`]'s
15043        // `:estrategia` presence-probe arm must key off
15044        // [`Caixa::estrategia`], not the raw `self.estrategia.is_some()`
15045        // field-probe. Structurally: every `Caixa { estrategia:
15046        // Some(RestartStrategy::_), .. }` variant must push
15047        // `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA` onto the declared-slot list
15048        // (the presence bit is `Some` for every closed-set variant, so
15049        // the M2 supervisor-tree kind-coherence gate must surface the
15050        // slot as "declared" regardless of which variant the author
15051        // picked), and a `Caixa { estrategia: None, .. }` must NOT push
15052        // the label (the "author omitted the slot entirely, deferring
15053        // to [`RestartStrategy::default`] through the supervisor_view
15054        // fold" partition). The pair jointly pins the accessor +
15055        // declared-slot enumerator composition: any future silent detour
15056        // that had the accessor collapse `Some(RestartStrategy::default())`
15057        // to `None` (a `.filter(|e| *e != RestartStrategy::default())`
15058        // projection) would silently absorb the "declared but default-
15059        // valued" arm at the accessor boundary and the
15060        // [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
15061        // coherence gate would silently accept a struct-literal `Caixa`
15062        // carrying the drift.
15063        //
15064        // Peer of the sibling per-`Caixa`
15065        // `declared_servico_slots_limits_arm_routes_through_accessor`
15066        // (b2bd9d7) accessor-composition pin on the sibling outer-`Caixa`
15067        // `Option<&LimitsSpec>` composition axis — same "the enumerator
15068        // gate must route through the substrate-primitive typed
15069        // dispatch" discipline extended onto the flat-spread M2
15070        // supervisor-tree `Option<RestartStrategy>`-composition surface,
15071        // opening the outer-`Caixa` supervisor-tree-slot arm of the
15072        // composition-pin family.
15073        use crate::supervisor::RestartStrategy;
15074        for estrategia in [
15075            RestartStrategy::OneForOne,
15076            RestartStrategy::OneForAll,
15077            RestartStrategy::RestForOne,
15078            RestartStrategy::SimpleOneForOne,
15079        ] {
15080            let c = caixa_with_estrategia(Some(estrategia));
15081            let slots = c.declared_supervisor_slots();
15082            assert!(
15083                slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA),
15084                "declared_supervisor_slots must push \
15085                 SUPERVISOR_AUTHOR_KEY_ESTRATEGIA when `:estrategia` is \
15086                 Some({estrategia:?}) — the accessor and the enumerator \
15087                 gate must route through the same substrate-primitive \
15088                 typed dispatch on the outer :estrategia presence bit \
15089                 (got slots={slots:?})",
15090            );
15091        }
15092        let c = caixa_with_estrategia(None);
15093        let slots = c.declared_supervisor_slots();
15094        assert!(
15095            !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA),
15096            "declared_supervisor_slots must NOT push \
15097             SUPERVISOR_AUTHOR_KEY_ESTRATEGIA when `:estrategia` is None \
15098             — the author-omitted arm must route through the accessor's \
15099             None-return unchanged (got slots={slots:?})",
15100        );
15101    }
15102
15103    #[test]
15104    fn supervisor_view_estrategia_arm_routes_through_accessor() {
15105        // Composition pin: [`Caixa::supervisor_view`]'s per-`:estrategia`
15106        // [`SupervisorSpec`] construction arm must key off
15107        // [`Caixa::estrategia`]'s `unwrap_or_default()` fold, not the raw
15108        // `self.estrategia.unwrap_or_default()` field-fold. Structurally:
15109        // for every `:kind Supervisor` `Caixa` carrying an author-
15110        // declared `Some(RestartStrategy::_)` variant, the composed
15111        // [`SupervisorSpec`]'s `.estrategia` field must byte-equal the
15112        // outer accessor's declared variant unchanged; and for a
15113        // `:kind Supervisor` `Caixa` carrying `None`, the composed
15114        // [`SupervisorSpec`]'s `.estrategia` field must byte-equal
15115        // [`RestartStrategy::default`] (the [`RestartStrategy::OneForOne`]
15116        // arm the flat-spread `unwrap_or_default()` fold projects to on
15117        // the author-omitted arm — this is the *composition* between the
15118        // outer `Option<RestartStrategy>` accessor's presence-bit
15119        // surface and the inner post-composition non-`Option`
15120        // [`SupervisorSpec::estrategia`] altitude). The pair jointly
15121        // pins the accessor + supervisor_view composition: any future
15122        // silent detour that had the accessor promote `None` to
15123        // `Some(RestartStrategy::default())` (a `.or_else(|| Some(RestartStrategy::default()))`
15124        // projection) would silently collapse the two arms into one at
15125        // the accessor boundary and the [`Self::declared_supervisor_slots`]
15126        // presence probe would silently drift from the composition site.
15127        //
15128        // Peer of the sibling M2 supervisor-slot post-composition
15129        // `validate_reads_through_lifted_estrategia_accessor` (eafb619)
15130        // pin on the [`SupervisorSpec::validate`] altitude — this pin
15131        // extends that inner-altitude accessor-routing discipline onto
15132        // the pre-composition outer author-surface [`Caixa`] altitude,
15133        // pinning the composition edge between the flat-spread outer
15134        // `Option<RestartStrategy>` and the composed [`SupervisorSpec`]
15135        // `RestartStrategy` axes.
15136        use crate::CaixaKind;
15137        use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
15138        for estrategia in [
15139            RestartStrategy::OneForOne,
15140            RestartStrategy::OneForAll,
15141            RestartStrategy::RestForOne,
15142            RestartStrategy::SimpleOneForOne,
15143        ] {
15144            let mut c = caixa_with_estrategia(Some(estrategia));
15145            c.kind = CaixaKind::Supervisor;
15146            // Route the `SimpleOneForOne ↔ non-SimpleOneForOne` fixture-
15147            // shape partition through the [`gen_platform::IsVariant`]
15148            // derive-generated
15149            // [`RestartStrategy::is_simple_one_for_one`] predicate rather
15150            // than the raw `matches!(estrategia, RestartStrategy::
15151            // SimpleOneForOne)` open-coded pattern-match — same closed-
15152            // set-typed-enum arm-discriminator dispatch discipline the
15153            // sibling [`crate::upgrade::UpgradeInstruction::is_restart`]
15154            // convergence (915a934) extended onto its two paired positive
15155            // / negated `matches!` sites and the peer
15156            // [`crate::aplicacao::PlacementStrategy`] `IsVariant`-derived
15157            // predicate convergence (766ec63) extended onto the M3 mesh-
15158            // slot per-`:placement` distribution-strategy discriminator
15159            // axis. See the sibling `supervisor::tests::
15160            // round_trip_all_strategies` and
15161            // `supervisor::tests::supervisor_spec_estrategia_returns_estrategia_verbatim_across_permutations`
15162            // fixtures — the three sites (all test-only,
15163            // acknowledged in 915a934's Prior-commits footnote as the
15164            // outstanding follow-up) now consult one typed dispatch on
15165            // the substrate primitive.
15166            c.children = if estrategia.is_simple_one_for_one() {
15167                Vec::new()
15168            } else {
15169                vec![ChildSpec {
15170                    caixa: "worker".into(),
15171                    versao: "^0.1".into(),
15172                    restart: RestartPolicy::Permanent,
15173                }]
15174            };
15175            let view = c.supervisor_view().expect(
15176                "supervisor_view must materialize a SupervisorSpec for a \
15177                 :kind Supervisor Caixa carrying a Some(:estrategia) slot",
15178            );
15179            assert_eq!(
15180                view.estrategia(),
15181                c.estrategia().unwrap(),
15182                "supervisor_view must carry the outer Caixa::estrategia() \
15183                 declared variant onto the composed SupervisorSpec.estrategia \
15184                 field verbatim on the Some arm (got {:?}, expected {:?})",
15185                view.estrategia(),
15186                c.estrategia().unwrap(),
15187            );
15188        }
15189        // The author-omitted arm: outer `None` → composed
15190        // `RestartStrategy::default()` through the flat-spread
15191        // `unwrap_or_default()` fold.
15192        let mut c = caixa_with_estrategia(None);
15193        c.kind = CaixaKind::Supervisor;
15194        // Populate children so the sibling supervisor slots are coherent
15195        // for the [`Self::supervisor_view`] projection; the `:estrategia`
15196        // arm still defers to [`RestartStrategy::default`] on the
15197        // author-omitted arm even when the sibling slots carry values.
15198        c.children = vec![ChildSpec {
15199            caixa: "worker".into(),
15200            versao: "^0.1".into(),
15201            restart: RestartPolicy::Permanent,
15202        }];
15203        let view = c.supervisor_view().expect(
15204            "supervisor_view must materialize a SupervisorSpec for a \
15205             :kind Supervisor Caixa carrying a None `:estrategia` slot",
15206        );
15207        assert_eq!(
15208            view.estrategia(),
15209            RestartStrategy::default(),
15210            "supervisor_view must project the outer Caixa::estrategia() \
15211             None arm onto RestartStrategy::default() through the flat-\
15212             spread unwrap_or_default() fold (got {:?}, expected {:?})",
15213            view.estrategia(),
15214            RestartStrategy::default(),
15215        );
15216        assert!(
15217            c.estrategia().is_none(),
15218            "Caixa::estrategia() must remain None on the author-omitted \
15219             arm — the supervisor_view fold must not mutate the outer \
15220             flat-spread presence bit",
15221        );
15222    }
15223
15224    #[test]
15225    fn estrategia_projects_option_by_copy() {
15226        // The by-`Copy` pin: [`Caixa::estrategia`] returns
15227        // `Option<RestartStrategy>` by value (`RestartStrategy: Copy`) —
15228        // the accessor does not borrow `&self` past the call (no
15229        // lifetime on the return type), and calling the accessor twice
15230        // on the same [`Caixa`] must yield discriminant-equal values
15231        // (idempotent, no side effects on `&self`). Peer of the sibling
15232        // outer-`Caixa` `Option<&Composite>` by-borrow
15233        // `limits_projects_option_ref_by_borrow` (b2bd9d7) /
15234        // `behavior_projects_option_ref_by_borrow` (35d8b52) /
15235        // `politicas_projects_option_ref_by_borrow` (5d23d29) /
15236        // `placement_projects_option_ref_by_borrow` (4fb8074) /
15237        // `entrada_projects_option_ref_by_borrow` (e4128e4) by-borrow
15238        // pins on the outer-`Caixa` `Option<&Composite>`-return axes —
15239        // extended here to the outer-`Caixa` `Option<Copy>`-return
15240        // flat-spread axis. The `Copy` discipline replaces the pointer-
15241        // equality claim the by-borrow siblings pin (a fresh `Copy` of a
15242        // `Copy` discriminant is definitionally the same discriminant, so
15243        // the axis reduces to discriminant equality).
15244        //
15245        // Pins against a future silent detour that returned a fresh
15246        // `Option<&RestartStrategy>` (which would type-check but silently
15247        // introduce a borrow of `&self` past the call, collapsing the
15248        // load-bearing "no lifetime on the return type" `Copy` projection
15249        // the flat-spread axis's `Option<Copy>` shape carries), a stale-
15250        // read side effect that flipped the outer discriminant on
15251        // successive calls, or an axis-remap projection that returned a
15252        // different variant than the field storage.
15253        use crate::supervisor::RestartStrategy;
15254        for estrategia in [
15255            Some(RestartStrategy::OneForOne),
15256            Some(RestartStrategy::OneForAll),
15257            Some(RestartStrategy::RestForOne),
15258            Some(RestartStrategy::SimpleOneForOne),
15259        ] {
15260            let c = caixa_with_estrategia(estrategia);
15261            let first = c.estrategia();
15262            let second = c.estrategia();
15263            assert_eq!(
15264                first, second,
15265                "Caixa::estrategia must be idempotent — two successive \
15266                 calls on the same &self must return the same \
15267                 Option<RestartStrategy>",
15268            );
15269            assert_eq!(
15270                first, estrategia,
15271                "Caixa::estrategia must return :estrategia verbatim by \
15272                 Copy — got {first:?}, expected {estrategia:?}",
15273            );
15274        }
15275        let c = caixa_with_estrategia(None);
15276        assert!(
15277            c.estrategia().is_none(),
15278            "Caixa::estrategia must return None when :estrategia is \
15279             absent — the author-omitted arm must project through the \
15280             accessor's Option::None unchanged",
15281        );
15282    }
15283
15284    // ── Caixa::max_restarts / Caixa::restart_window —
15285    //    outer top-level M2 supervisor-tree-slot flat-spread accessors
15286    //    (Option<u32> / Option<&str>) folding on the ed04d3c
15287    //    Caixa::estrategia Option<Copy> sub-family ─────────────────────
15288
15289    fn caixa_with_max_restarts(max_restarts: Option<u32>) -> Caixa {
15290        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15291        c.max_restarts = max_restarts;
15292        c
15293    }
15294
15295    fn caixa_supervisor_with_max_restarts_and_window(
15296        max_restarts: Option<u32>,
15297        restart_window: Option<&str>,
15298    ) -> Caixa {
15299        use crate::CaixaKind;
15300        use crate::supervisor::{ChildSpec, RestartPolicy};
15301        let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
15302        c.kind = CaixaKind::Supervisor;
15303        c.max_restarts = max_restarts;
15304        c.restart_window = restart_window.map(str::to_string);
15305        c.children = vec![ChildSpec {
15306            caixa: "worker".into(),
15307            versao: "^0.1".into(),
15308            restart: RestartPolicy::Permanent,
15309        }];
15310        c
15311    }
15312
15313    #[test]
15314    fn max_restarts_returns_max_restarts_option_verbatim_across_permutations() {
15315        // Value-shape pin: [`Caixa::max_restarts`] returns the
15316        // `:max-restarts` typed `Option<u32>` verbatim, `Copy`-projected
15317        // from the typed slot's own storage, byte-equal across the
15318        // author-omitted `None` arm (the "defer to the
15319        // [`Self::supervisor_view`] `unwrap_or(5)` OTP-canonical
15320        // `{intensity, 5, 60}` default" partition every
15321        // non-`Supervisor`-kind caixa carries by `#[serde(default)]`)
15322        // and each of the representative fixtures in the accept-set —
15323        // `0` (the zero-floor arm the peer
15324        // [`crate::supervisor::SupervisorSpec::validate`]
15325        // [`crate::SupervisorError::ZeroMaxRestarts`] gate refuses on
15326        // the post-composition altitude — the accessor must ship the
15327        // raw slot verbatim so struct-literal fixtures continue to
15328        // expose the zero at the accessor boundary), the OTP-canonical
15329        // `5` default (`{intensity, 5, 60}` worker-supervisor from
15330        // Learn You Some Erlang), `1000` (the
15331        // [`SUPERVISOR_MAX_RESTARTS_MAX`] cap the peer post-composition
15332        // upper-bound gate accepts on the boundary), `u32::MAX` (a
15333        // past-the-cap sentinel that the substrate-primitive accessor
15334        // must still ship verbatim). Second outer top-level
15335        // [`Caixa`] `Option<Copy>`-return supervisor-tree flat-spread
15336        // pin — folds on the sibling
15337        // `estrategia_returns_estrategia_option_verbatim_across_permutations`
15338        // (ed04d3c) pin's `Option<Copy>` shape, extending the sub-family
15339        // onto the sibling `Option<u32>` restart-budget-count arm.
15340        let fixtures: Vec<Option<u32>> = vec![None, Some(0), Some(5), Some(1000), Some(u32::MAX)];
15341        for max_restarts in fixtures {
15342            let c = caixa_with_max_restarts(max_restarts);
15343            assert_eq!(
15344                c.max_restarts(),
15345                max_restarts,
15346                "Caixa::max_restarts must return :max-restarts verbatim \
15347                 (got {:?}, expected {max_restarts:?})",
15348                c.max_restarts(),
15349            );
15350            assert_eq!(
15351                c.max_restarts(),
15352                c.max_restarts,
15353                "Caixa::max_restarts accessor and self.max_restarts \
15354                 field access must byte-equal — a presence-bit or count \
15355                 drift would silently split the paired \
15356                 Caixa::declared_supervisor_slots presence-probe arm \
15357                 from the Caixa::supervisor_view unwrap_or(5) fold's \
15358                 composition input",
15359            );
15360        }
15361    }
15362
15363    #[test]
15364    fn max_restarts_projects_option_by_copy() {
15365        // The by-`Copy` pin: [`Caixa::max_restarts`] returns
15366        // `Option<u32>` by value (`u32: Copy`) — the accessor does not
15367        // borrow `&self` past the call (no lifetime on the return type),
15368        // and calling the accessor twice on the same [`Caixa`] must
15369        // yield equal values (idempotent, no side effects). Peer of the
15370        // sibling `estrategia_projects_option_by_copy` (ed04d3c) pin on
15371        // the outer-`Caixa` `Option<Copy>`-return flat-spread axis.
15372        for max_restarts in [Some(0u32), Some(5), Some(1000), Some(u32::MAX), None] {
15373            let c = caixa_with_max_restarts(max_restarts);
15374            let first = c.max_restarts();
15375            let second = c.max_restarts();
15376            assert_eq!(
15377                first, second,
15378                "Caixa::max_restarts must be idempotent — two successive \
15379                 calls on the same &self must return the same Option<u32>",
15380            );
15381            assert_eq!(
15382                first, max_restarts,
15383                "Caixa::max_restarts must return :max-restarts verbatim \
15384                 by Copy — got {first:?}, expected {max_restarts:?}",
15385            );
15386        }
15387    }
15388
15389    #[test]
15390    fn declared_supervisor_slots_max_restarts_arm_routes_through_accessor() {
15391        // Composition pin: [`Caixa::declared_supervisor_slots`]'s
15392        // `:max-restarts` presence-probe arm must key off
15393        // [`Caixa::max_restarts`], not the raw
15394        // `self.max_restarts.is_some()` field-probe. Structurally: every
15395        // `Caixa { max_restarts: Some(_), .. }` variant must push
15396        // `SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS` onto the declared-slot
15397        // list (the presence bit is `Some` for every representative
15398        // count, so the M2 kind-coherence gate must surface the slot as
15399        // "declared"), and a `Caixa { max_restarts: None, .. }` must
15400        // NOT push the label. Peer of the sibling
15401        // `declared_supervisor_slots_estrategia_arm_routes_through_accessor`
15402        // (ed04d3c) composition pin — same routing-through-accessor
15403        // discipline extended onto the sibling flat-spread `Option<u32>`
15404        // arm.
15405        for max_restarts in [0u32, 5, 1000, u32::MAX] {
15406            let c = caixa_with_max_restarts(Some(max_restarts));
15407            let slots = c.declared_supervisor_slots();
15408            assert!(
15409                slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS),
15410                "declared_supervisor_slots must push \
15411                 SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS when `:max-restarts` \
15412                 is Some({max_restarts}) — the accessor and the \
15413                 enumerator gate must route through the same \
15414                 substrate-primitive typed dispatch on the outer \
15415                 :max-restarts presence bit (got slots={slots:?})",
15416            );
15417        }
15418        let c = caixa_with_max_restarts(None);
15419        let slots = c.declared_supervisor_slots();
15420        assert!(
15421            !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS),
15422            "declared_supervisor_slots must NOT push \
15423             SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS when `:max-restarts` is \
15424             None — the author-omitted arm must route through the \
15425             accessor's None-return unchanged (got slots={slots:?})",
15426        );
15427    }
15428
15429    #[test]
15430    fn supervisor_view_max_restarts_arm_routes_through_accessor() {
15431        // Composition pin: [`Caixa::supervisor_view`]'s per-`:max-restarts`
15432        // [`SupervisorSpec`] construction arm must key off
15433        // [`Caixa::max_restarts`]'s `unwrap_or(5)` fold, not the raw
15434        // `self.max_restarts.unwrap_or(5)` field-fold. Structurally: for
15435        // every `:kind Supervisor` `Caixa` carrying an author-declared
15436        // `Some(n)`, the composed [`SupervisorSpec`]'s `.max_restarts()`
15437        // must byte-equal `n`; and for a `:kind Supervisor` `Caixa`
15438        // carrying `None`, the composed [`SupervisorSpec`]'s
15439        // `.max_restarts()` must byte-equal the OTP-canonical `5`. Peer
15440        // of the sibling
15441        // `supervisor_view_estrategia_arm_routes_through_accessor`
15442        // (ed04d3c) composition pin.
15443        for max_restarts in [1u32, 5, 1000] {
15444            let c = caixa_supervisor_with_max_restarts_and_window(Some(max_restarts), None);
15445            let view = c.supervisor_view().expect(
15446                "supervisor_view must materialize a SupervisorSpec for a \
15447                 :kind Supervisor Caixa carrying a Some(:max-restarts)",
15448            );
15449            assert_eq!(
15450                view.max_restarts(),
15451                max_restarts,
15452                "supervisor_view must carry the outer \
15453                 Caixa::max_restarts() Some arm onto the composed \
15454                 SupervisorSpec.max_restarts field verbatim (got {}, \
15455                 expected {max_restarts})",
15456                view.max_restarts(),
15457            );
15458        }
15459        let c = caixa_supervisor_with_max_restarts_and_window(None, None);
15460        let view = c.supervisor_view().expect(
15461            "supervisor_view must materialize a SupervisorSpec for a \
15462             :kind Supervisor Caixa carrying a None :max-restarts",
15463        );
15464        assert_eq!(
15465            view.max_restarts(),
15466            5,
15467            "supervisor_view must project the outer \
15468             Caixa::max_restarts() None arm onto the OTP-canonical \
15469             {{intensity, 5, 60}} default (5) through the flat-spread \
15470             unwrap_or(5) fold (got {})",
15471            view.max_restarts(),
15472        );
15473        assert!(
15474            c.max_restarts().is_none(),
15475            "Caixa::max_restarts() must remain None on the author-\
15476             omitted arm — the supervisor_view fold must not mutate \
15477             the outer flat-spread presence bit",
15478        );
15479    }
15480
15481    #[test]
15482    fn restart_window_returns_restart_window_option_verbatim_across_permutations() {
15483        // Value-shape pin: [`Caixa::restart_window`] returns the
15484        // `:restart-window` typed `Option<String>` verbatim as an
15485        // `Option<&str>`, borrowed from the typed slot's own storage,
15486        // byte-equal across the author-omitted `None` arm and each of
15487        // the representative fixtures in the accept-set — the canonical
15488        // `"60s"` from `{intensity, 5, 60}`, the sibling
15489        // canonical-magnitude forms (`"5m"` / `"1h"` / `"500ms"` / `"30"`
15490        // / `"0s"`) the shared codec's positive-set sweep pin covers,
15491        // plus a past-the-guard sentinel (`"1.5s"` — the fractional-
15492        // seconds drift the sibling [`Self::validate_restart_window`]
15493        // gate refuses; the accessor must ship the raw slot verbatim
15494        // so struct-literal fixtures continue to expose the drift at
15495        // the accessor boundary). Third outer top-level [`Caixa`]
15496        // supervisor-tree flat-spread pin — extends the sub-family onto
15497        // the sibling `Option<&str>` raw-duration-string arm.
15498        for window in [
15499            None,
15500            Some("60s"),
15501            Some("5m"),
15502            Some("1h"),
15503            Some("500ms"),
15504            Some("1.5s"),
15505            Some(""),
15506        ] {
15507            let c = caixa_with_restart_window(window);
15508            assert_eq!(
15509                c.restart_window(),
15510                window,
15511                "Caixa::restart_window must return :restart-window \
15512                 verbatim as Option<&str> (got {:?}, expected {window:?})",
15513                c.restart_window(),
15514            );
15515            assert_eq!(
15516                c.restart_window(),
15517                c.restart_window.as_deref(),
15518                "Caixa::restart_window accessor and \
15519                 self.restart_window.as_deref() field access must \
15520                 byte-equal — a byte-level drift would silently split \
15521                 the paired Caixa::declared_supervisor_slots \
15522                 presence-probe arm from the \
15523                 Caixa::validate_restart_window shared-codec gate and \
15524                 the Caixa::supervisor_view soft-swallowing fold",
15525            );
15526        }
15527    }
15528
15529    #[test]
15530    fn restart_window_projects_slice_by_borrow() {
15531        // The by-borrow pin: [`Caixa::restart_window`] returns
15532        // `Option<&str>` by borrow — the returned string slice borrows
15533        // the underlying `Option<String>` storage of the `:restart-window`
15534        // slot and the accessor must not clone on every call. Peer of
15535        // the sibling outer top-level [`Caixa`] `Option<&str>`-return
15536        // by-borrow pins on the universal-axis scalar family
15537        // (`licenca_projects_option_ref_by_borrow` /
15538        // `descricao_projects_option_ref_by_borrow` and siblings) —
15539        // extended onto the M2 supervisor-tree flat-spread
15540        // `Option<&str>` raw-duration-string axis.
15541        for window in [None, Some("60s"), Some("5m"), Some("")] {
15542            let c = caixa_with_restart_window(window);
15543            let first = c.restart_window();
15544            let second = c.restart_window();
15545            assert_eq!(
15546                first, second,
15547                "Caixa::restart_window must be idempotent — two \
15548                 successive calls on the same &self must return the \
15549                 same Option<&str>",
15550            );
15551            if let (Some(a), Some(b)) = (first, second) {
15552                assert_eq!(
15553                    a.as_ptr(),
15554                    b.as_ptr(),
15555                    "Caixa::restart_window must borrow the underlying \
15556                     String storage — two successive Some-arm calls must \
15557                     return slices with the same backing pointer (a fresh \
15558                     String clone would change the pointer on every call)",
15559                );
15560            }
15561            assert_eq!(
15562                first, window,
15563                "Caixa::restart_window must return :restart-window \
15564                 verbatim by borrow — got {first:?}, expected {window:?}",
15565            );
15566        }
15567    }
15568
15569    #[test]
15570    fn declared_supervisor_slots_restart_window_arm_routes_through_accessor() {
15571        // Composition pin: [`Caixa::declared_supervisor_slots`]'s
15572        // `:restart-window` presence-probe arm must key off
15573        // [`Caixa::restart_window`], not the raw
15574        // `self.restart_window.is_some()` field-probe. Structurally:
15575        // every `Caixa { restart_window: Some(_), .. }` must push
15576        // `SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW` onto the declared-slot
15577        // list, and a `Caixa { restart_window: None, .. }` must NOT
15578        // push the label. Peer of the sibling
15579        // `declared_supervisor_slots_max_restarts_arm_routes_through_accessor`
15580        // routing pin.
15581        for window in ["60s", "5m", "1h", "500ms", "1.5s", ""] {
15582            let c = caixa_with_restart_window(Some(window));
15583            let slots = c.declared_supervisor_slots();
15584            assert!(
15585                slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW),
15586                "declared_supervisor_slots must push \
15587                 SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW when \
15588                 `:restart-window` is Some({window:?}) — the accessor \
15589                 and the enumerator gate must route through the same \
15590                 substrate-primitive typed dispatch on the outer \
15591                 :restart-window presence bit (got slots={slots:?})",
15592            );
15593        }
15594        let c = caixa_with_restart_window(None);
15595        let slots = c.declared_supervisor_slots();
15596        assert!(
15597            !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW),
15598            "declared_supervisor_slots must NOT push \
15599             SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW when `:restart-window` \
15600             is None — the author-omitted arm must route through the \
15601             accessor's None-return unchanged (got slots={slots:?})",
15602        );
15603    }
15604
15605    #[test]
15606    fn validate_restart_window_arm_routes_through_accessor() {
15607        // Composition pin: [`Caixa::validate_restart_window`]'s
15608        // shared-codec fold arm must key off [`Caixa::restart_window`],
15609        // not the raw `self.restart_window.as_deref()` field-projection.
15610        // Structurally: (1) `None` → `Ok(())` (the "omit the slot to
15611        // express no reset" canonical shape); (2) a canonical `Some`
15612        // arm (`"60s"`) → `Ok(())`; (3) a codec-rejected `Some` arm
15613        // (`"1.5s"`) → `Err(RestartWindowMalformed { restart_window,
15614        // .. })` carrying the offending raw string verbatim. The three
15615        // arms jointly pin that the validator's raw-string binding is
15616        // the accessor's return, not a peer projection — any future
15617        // silent detour that had the accessor collapse `Some("")` to
15618        // `None` would silently absorb the empty-after-trim refusal
15619        // case at the accessor boundary.
15620        caixa_with_restart_window(None)
15621            .validate_restart_window()
15622            .expect("None :restart-window must validate through the accessor");
15623        caixa_with_restart_window(Some("60s"))
15624            .validate_restart_window()
15625            .expect("canonical :restart-window \"60s\" must validate through the accessor");
15626        let err = caixa_with_restart_window(Some("1.5s"))
15627            .validate_restart_window()
15628            .expect_err("fractional-seconds :restart-window must fail through the accessor");
15629        assert!(
15630            matches!(
15631                err,
15632                ManifestError::RestartWindowMalformed { ref restart_window, .. }
15633                    if restart_window == "1.5s"
15634            ),
15635            "validator must carry the offending raw string verbatim \
15636             from the accessor's borrowed &str (got {err:?})",
15637        );
15638    }
15639
15640    #[test]
15641    fn supervisor_view_restart_window_arm_routes_through_accessor() {
15642        // Composition pin: [`Caixa::supervisor_view`]'s
15643        // per-`:restart-window` [`SupervisorSpec`] construction arm
15644        // must key off [`Caixa::restart_window`]'s soft-swallowing
15645        // `.and_then(|s| duration_codec::parse(s).ok())` fold, not the
15646        // raw `self.restart_window.as_deref().and_then(…)` field-fold.
15647        // Structurally: (1) `None` → `SupervisorSpec.restart_window ==
15648        // None` (the "never reset" sentinel); (2) canonical `Some("60s")`
15649        // → `SupervisorSpec.restart_window == Some(Duration::from_secs(60))`
15650        // (the shared codec's canonical parse); (3) codec-rejected
15651        // `Some("1.5s")` → `SupervisorSpec.restart_window == None`
15652        // (the soft-swallow preserving the view's best-effort shape).
15653        let c = caixa_supervisor_with_max_restarts_and_window(None, None);
15654        let view = c.supervisor_view().expect("Supervisor kind has a view");
15655        assert_eq!(
15656            view.restart_window(),
15657            None,
15658            "supervisor_view must project outer None :restart-window \
15659             onto None on the composed SupervisorSpec (never-reset \
15660             sentinel) through the accessor's None-return unchanged",
15661        );
15662
15663        let c = caixa_supervisor_with_max_restarts_and_window(None, Some("60s"));
15664        let view = c.supervisor_view().expect("Supervisor kind has a view");
15665        assert_eq!(
15666            view.restart_window(),
15667            Some(std::time::Duration::from_secs(60)),
15668            "supervisor_view must fold outer Some(\"60s\") through the \
15669             shared duration_codec into Duration::from_secs(60) on the \
15670             composed SupervisorSpec (accessor's Some(&str) → codec \
15671             parse → Some(Duration))",
15672        );
15673
15674        let c = caixa_supervisor_with_max_restarts_and_window(None, Some("1.5s"));
15675        let view = c.supervisor_view().expect("Supervisor kind has a view");
15676        assert_eq!(
15677            view.restart_window(),
15678            None,
15679            "supervisor_view must soft-swallow the shared-codec parse \
15680             failure to None (the view's best-effort shape the sibling \
15681             manifest-level validate_restart_window surfaces as \
15682             RestartWindowMalformed); the accessor's raw-string return \
15683             is the single input every downstream consumer keys off",
15684        );
15685    }
15686
15687    // ── Caixa::upgrade_from — outer top-level &[UpgradeFromEntry] composite-slice accessor ──
15688
15689    fn caixa_with_upgrade_from(upgrade_from: Vec<crate::upgrade::UpgradeFromEntry>) -> Caixa {
15690        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15691        c.upgrade_from = upgrade_from;
15692        c
15693    }
15694
15695    #[test]
15696    fn upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations() {
15697        // The canonical per-`Caixa` `:upgrade-from` M2 typed-slot
15698        // outer-composite `&[UpgradeFromEntry]`-return slice-shape
15699        // pin: [`Caixa::upgrade_from`] must return the `:upgrade-from`
15700        // typed `Vec<UpgradeFromEntry>` verbatim as a
15701        // `&[UpgradeFromEntry]` slice-view over the same backing
15702        // buffer the raw `self.upgrade_from.as_slice()` field access
15703        // borrows from, element-equal across every representative
15704        // fixture in the accept-set — `[]` (the "no hot-upgrade path
15705        // declared" arm every `defcaixa` without an `:upgrade-from`
15706        // block carries; `#[serde(default)]` folds an omitted slot
15707        // onto `Vec::new()`), a canonical single-entry `Restart`
15708        // fixture (the shape most Servicos carry — a single prior
15709        // version with the fallback strategy), a canonical multi-
15710        // entry list carrying every typed instruction variant
15711        // (`LoadModule` / `StateChange` / `SoftPurge` / `Purge` /
15712        // `Restart`), and a past-the-guard sentinel — a duplicate-
15713        // `:from` `[(0.1.0, Restart), (0.1.0, Restart)]` entry pair
15714        // ([`crate::upgrade::validate_upgrade_from`] rejects through
15715        // `DuplicateFrom { from: "0.1.0" }` but the accessor must
15716        // ship the raw slot verbatim so struct-literal fixtures
15717        // continue to expose the duplicate at the accessor boundary).
15718        //
15719        // Pins against a future silent detour that returned an owned
15720        // `Vec<UpgradeFromEntry>` (which would type-check but silently
15721        // clone on every accessor call, breaking the zero-cost
15722        // projection every peer sibling slice accessor carries), a
15723        // `[dup, dup] → [dup]` dedup collapse (which would silently
15724        // absorb the `DuplicateFrom` refusal case at the accessor
15725        // boundary and the [`crate::StandardLayout::verify`] cross-
15726        // entry gate would silently accept a struct-literal `Caixa`
15727        // carrying the drift), a reference to an operator-resolved
15728        // overlay (the future per-cluster `:upgrade-overrides` slot
15729        // — its resolution must land at exactly this accessor body,
15730        // not silently divert the raw slot away from a second
15731        // consumer), or an axis-shuffled projection (a future detour
15732        // that reordered entries through the accessor would silently
15733        // split the paired [`crate::StandardLayout::verify`] per-
15734        // `:upgrade-from` shape gate's traversal input from the peer
15735        // [`crate::render::servico_m2_overlay`] emitter's projection
15736        // input, since the operator's hot-upgrade dispatch matches
15737        // per-`:from` and axis reordering would silently split the
15738        // per-entry script-path existence probe's iteration order
15739        // from the M2 overlay emitter's serialized-entry order).
15740        //
15741        // First outer top-level [`Caixa`] `&[Composite]`-return
15742        // slice accessor pin on the substrate primitive for M2 / M3
15743        // typed-slot vec-carry axes — opens the outer-`Caixa`
15744        // `&[Composite]` composite-slice projection pattern the
15745        // sibling `:children` [`crate::supervisor::ChildSpec`] /
15746        // `:membros` [`crate::aplicacao::Membro`] / `:contratos`
15747        // [`crate::aplicacao::WitContract`] future outer-composite-
15748        // slice pins fold on. Peer of the closed outer-`Caixa`
15749        // scalar `Option<&Composite>` composite-reference family the
15750        // sibling `limits` / `behavior` / `politicas` / `placement`
15751        // / `entrada` `..._returns_..._option_ref_verbatim_across_
15752        // permutations` pins closed (b2bd9d7 → e4128e4) — extends
15753        // the "byte-equal, borrow-shared" outer-accessor discipline
15754        // onto the outer-`Caixa` `&[Composite]` vec-carry altitude.
15755        use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
15756        let fixtures: Vec<Vec<UpgradeFromEntry>> = vec![
15757            vec![],
15758            vec![UpgradeFromEntry {
15759                from: "0.0.1".into(),
15760                instructions: vec![UpgradeInstruction::Restart],
15761            }],
15762            vec![
15763                UpgradeFromEntry {
15764                    from: "0.0.1".into(),
15765                    instructions: vec![
15766                        UpgradeInstruction::LoadModule {
15767                            module: "demo".into(),
15768                        },
15769                        UpgradeInstruction::SoftPurge {
15770                            module: "demo".into(),
15771                        },
15772                    ],
15773                },
15774                UpgradeFromEntry {
15775                    from: "0.0.2".into(),
15776                    instructions: vec![
15777                        UpgradeInstruction::StateChange {
15778                            script: "servicos/upgrade.lisp".into(),
15779                        },
15780                        UpgradeInstruction::Purge {
15781                            module: "demo".into(),
15782                        },
15783                        UpgradeInstruction::Restart,
15784                    ],
15785                },
15786            ],
15787            vec![
15788                UpgradeFromEntry {
15789                    from: "0.1.0".into(),
15790                    instructions: vec![UpgradeInstruction::Restart],
15791                },
15792                UpgradeFromEntry {
15793                    from: "0.1.0".into(),
15794                    instructions: vec![UpgradeInstruction::Restart],
15795                },
15796            ],
15797        ];
15798        for upgrade_from in fixtures {
15799            let c = caixa_with_upgrade_from(upgrade_from.clone());
15800            assert_eq!(
15801                c.upgrade_from(),
15802                upgrade_from.as_slice(),
15803                "Caixa::upgrade_from must return :upgrade-from \
15804                 verbatim (got {:?}, expected {upgrade_from:?})",
15805                c.upgrade_from(),
15806            );
15807            assert_eq!(
15808                c.upgrade_from(),
15809                c.upgrade_from.as_slice(),
15810                "Caixa::upgrade_from must element-equal the raw \
15811                 `self.upgrade_from.as_slice()` field access across \
15812                 every value in the Vec<UpgradeFromEntry> accept-set",
15813            );
15814            assert_eq!(
15815                c.upgrade_from().is_empty(),
15816                c.upgrade_from.is_empty(),
15817                "Caixa::upgrade_from().is_empty() must byte-equal \
15818                 self.upgrade_from.is_empty() — a presence-bit drift \
15819                 would silently split the paired \
15820                 Caixa::declared_servico_slots M2 declared-slot \
15821                 enumerator's presence probe from the peer \
15822                 crate::render::servico_m2_overlay M2 overlay \
15823                 emitter's presence gate",
15824            );
15825        }
15826    }
15827
15828    #[test]
15829    fn declared_servico_slots_upgrade_from_arm_routes_through_accessor() {
15830        // Composition pin: [`Caixa::declared_servico_slots`]'s
15831        // `:upgrade-from` presence-probe arm must key off
15832        // [`Caixa::upgrade_from`], not the raw
15833        // `self.upgrade_from.is_empty()` field-probe. Structurally: a
15834        // `Caixa { upgrade_from: vec![UpgradeFromEntry { from: "0.0.1",
15835        // instructions: vec![Restart] }], .. }` must push
15836        // `M2_AUTHOR_KEY_UPGRADE_FROM` onto the declared-slot list
15837        // (the presence bit is non-empty, so the M2 kind-coherence
15838        // gate must surface the slot as "declared"), and a `Caixa {
15839        // upgrade_from: vec![], .. }` must NOT push the label (the
15840        // "author omitted the slot entirely" arm — the empty-slice
15841        // partition the serde-default folds onto). The pair jointly
15842        // pins the accessor + declared-slot enumerator composition:
15843        // any future silent detour that had the accessor collapse
15844        // `[Restart]` to `[]` (a `.filter(|e| !e.instructions.
15845        // is_empty())` projection) would silently absorb the
15846        // "declared but degenerate" arm at the accessor boundary and
15847        // the [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-
15848        // coherence gate would silently accept a struct-literal
15849        // `Caixa` carrying the drift.
15850        //
15851        // Peer of the sibling
15852        // `declared_servico_slots_limits_arm_routes_through_accessor`
15853        // (b2bd9d7) and
15854        // `declared_servico_slots_behavior_arm_routes_through_accessor`
15855        // (35d8b52) composition pins on the sibling `:limits` /
15856        // `:behavior` outer-`Option<&Composite>` arms — same "the
15857        // enumerator gate must route through the substrate-primitive
15858        // typed dispatch" discipline extended onto the third M2
15859        // Servico-runtime slot axis, closing the enumerator's routing
15860        // invariant on every M2 arm.
15861        use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
15862        let c = caixa_with_upgrade_from(vec![UpgradeFromEntry {
15863            from: "0.0.1".into(),
15864            instructions: vec![UpgradeInstruction::Restart],
15865        }]);
15866        let slots = c.declared_servico_slots();
15867        assert!(
15868            slots.contains(&crate::render::M2_AUTHOR_KEY_UPGRADE_FROM),
15869            "declared_servico_slots must push \
15870             M2_AUTHOR_KEY_UPGRADE_FROM when `:upgrade-from` is \
15871             non-empty — the accessor and the enumerator gate must \
15872             route through the same substrate-primitive typed \
15873             dispatch on the outer :upgrade-from presence bit (got \
15874             slots={slots:?})",
15875        );
15876        let c = caixa_with_upgrade_from(vec![]);
15877        let slots = c.declared_servico_slots();
15878        assert!(
15879            !slots.contains(&crate::render::M2_AUTHOR_KEY_UPGRADE_FROM),
15880            "declared_servico_slots must NOT push \
15881             M2_AUTHOR_KEY_UPGRADE_FROM when `:upgrade-from` is \
15882             empty — the author-omitted arm must route through the \
15883             accessor's empty-slice return unchanged (got \
15884             slots={slots:?})",
15885        );
15886    }
15887
15888    #[test]
15889    fn servico_m2_overlay_upgrade_from_arm_routes_through_accessor() {
15890        // Composition pin: [`crate::render::servico_m2_overlay`]'s
15891        // per-`:upgrade-from` M2 overlay emit arm must key off
15892        // [`Caixa::upgrade_from`], not the raw
15893        // `!caixa.upgrade_from.is_empty()` presence gate + the
15894        // `serde_yaml::to_value(&caixa.upgrade_from)` projection.
15895        // Structurally: a `Caixa { upgrade_from: vec![UpgradeFromEntry
15896        // { from: "0.0.1", instructions: vec![Restart] }], .. }` must
15897        // surface the `M2_KEY_UPGRADE_FROM` key with a per-entry
15898        // sequence in the overlay (the emitter fans onto the serde
15899        // slice-serialization), and a `Caixa { upgrade_from: vec![],
15900        // .. }` must omit the key entirely (the empty-slice
15901        // partition — the `!.is_empty()` outer gate elides the key
15902        // when the author omitted the slot). The pair jointly pins
15903        // the accessor + M2 overlay emitter composition: any future
15904        // silent detour that had the accessor return a fresh-cloned
15905        // `Vec<UpgradeFromEntry>` copy would silently break the
15906        // reference-identity pin the peer per-entry
15907        // `serde_yaml::to_value(caixa.upgrade_from())` projection
15908        // reads from — the projection would clone once per accessor
15909        // call instead of borrowing the storage buffer verbatim.
15910        //
15911        // Peer of the sibling
15912        // `servico_m2_overlay_limits_arm_routes_through_accessor`
15913        // (b2bd9d7) and
15914        // `servico_m2_overlay_behavior_arm_routes_through_accessor`
15915        // (35d8b52) composition pins on the sibling `:limits` /
15916        // `:behavior` outer-`Option<&Composite>` arms — same "the
15917        // M2 overlay emitter must route through the substrate-
15918        // primitive typed dispatch" discipline extended onto the
15919        // third M2 Servico-runtime slot axis, closing the overlay
15920        // emitter's routing invariant on every M2 arm.
15921        use crate::render::{M2_KEY_UPGRADE_FROM, servico_m2_overlay};
15922        use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
15923        let c = caixa_with_upgrade_from(vec![UpgradeFromEntry {
15924            from: "0.0.1".into(),
15925            instructions: vec![UpgradeInstruction::Restart],
15926        }]);
15927        let overlay = servico_m2_overlay(&c).unwrap();
15928        assert!(
15929            overlay.contains_key(M2_KEY_UPGRADE_FROM),
15930            "servico_m2_overlay must surface M2_KEY_UPGRADE_FROM when \
15931             `:upgrade-from` is non-empty — the accessor and the M2 \
15932             overlay emitter must route through the same substrate- \
15933             primitive typed dispatch on the outer :upgrade-from \
15934             slice (got overlay={overlay:?})",
15935        );
15936        let c = caixa_with_upgrade_from(vec![]);
15937        let overlay = servico_m2_overlay(&c).unwrap();
15938        assert!(
15939            !overlay.contains_key(M2_KEY_UPGRADE_FROM),
15940            "servico_m2_overlay must omit M2_KEY_UPGRADE_FROM when \
15941             `:upgrade-from` is empty — the empty-slice partition \
15942             must route through the accessor's empty-slice return \
15943             unchanged (got overlay={overlay:?})",
15944        );
15945    }
15946
15947    #[test]
15948    fn upgrade_from_projects_slice_by_borrow() {
15949        // The by-borrow pin: [`Caixa::upgrade_from`] returns
15950        // `&[UpgradeFromEntry]` by borrow — the returned slice
15951        // borrows the underlying `Vec<UpgradeFromEntry>` storage of
15952        // the `:upgrade-from` slot and the accessor must not clone
15953        // the backing `Vec` on every call. Peer of the sibling
15954        // outer top-level [`Caixa`] `&[T]`-return by-borrow pins
15955        // (`autores_projects_slice_by_borrow` b5d813f,
15956        // `etiquetas_projects_slice_by_borrow` 78c7d3c,
15957        // `bibliotecas_projects_slice_by_borrow` 8a36c23,
15958        // `exe_projects_slice_by_borrow` 65d9527,
15959        // `servicos_projects_slice_by_borrow` 611f78b,
15960        // `deps_projects_slice_by_borrow` ad34b4e,
15961        // `deps_dev_projects_slice_by_borrow` f7fd81e) on the
15962        // sibling outer top-level [`Caixa`] scalar-element `&[T]`
15963        // axes — extended here to the first outer-`Caixa`
15964        // composite-element `&[Composite]` axis: the accessor's
15965        // returned slice must borrow from `&self` (the returned
15966        // reference's lifetime is tied to `&self`), and calling the
15967        // accessor twice on the same [`Caixa`] must yield slices
15968        // that are pointer-equal (the underlying byte-buffer is the
15969        // storage `Vec`'s allocation, not a fresh copy) as well as
15970        // value-equal (idempotent, no side effects on `&self`).
15971        //
15972        // Pins against a future silent detour that returned an owned
15973        // `Vec<UpgradeFromEntry>` (which would type-check but
15974        // silently clone on every call), a `&Vec<UpgradeFromEntry>`
15975        // return (which would leak the backing `Vec`'s
15976        // grow/push/reserve surface no downstream consumer reaches
15977        // for), or a one-arm-only accessor that returned a
15978        // saturating value on some sentinel input.
15979        use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
15980        for upgrade_from in [
15981            vec![],
15982            vec![UpgradeFromEntry {
15983                from: "0.0.1".into(),
15984                instructions: vec![UpgradeInstruction::Restart],
15985            }],
15986            vec![
15987                UpgradeFromEntry {
15988                    from: "0.0.1".into(),
15989                    instructions: vec![UpgradeInstruction::Restart],
15990                },
15991                UpgradeFromEntry {
15992                    from: "0.0.2".into(),
15993                    instructions: vec![UpgradeInstruction::SoftPurge {
15994                        module: "demo".into(),
15995                    }],
15996                },
15997            ],
15998        ] {
15999            let c = caixa_with_upgrade_from(upgrade_from.clone());
16000            let first = c.upgrade_from();
16001            let second = c.upgrade_from();
16002            assert_eq!(
16003                first, second,
16004                "Caixa::upgrade_from must be idempotent — two \
16005                 successive calls on the same &self must return the \
16006                 same &[UpgradeFromEntry]",
16007            );
16008            assert_eq!(
16009                first.as_ptr(),
16010                second.as_ptr(),
16011                "Caixa::upgrade_from must borrow the underlying \
16012                 Vec<UpgradeFromEntry> storage — two successive calls \
16013                 must return slices with the same backing pointer (a \
16014                 fresh Vec<UpgradeFromEntry> clone would change the \
16015                 pointer on every call)",
16016            );
16017            assert_eq!(
16018                first,
16019                upgrade_from.as_slice(),
16020                "Caixa::upgrade_from must return :upgrade-from \
16021                 verbatim by borrow — got {first:?}, expected \
16022                 {upgrade_from:?}",
16023            );
16024        }
16025    }
16026
16027    // ── Caixa::children — outer top-level &[ChildSpec] composite-slice accessor ──
16028
16029    fn caixa_with_children(children: Vec<crate::supervisor::ChildSpec>) -> Caixa {
16030        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16031        c.children = children;
16032        c
16033    }
16034
16035    #[test]
16036    fn children_returns_children_slice_verbatim_across_permutations() {
16037        // The canonical per-`Caixa` `:children` M2 supervisor-tree-slot
16038        // outer-composite `&[ChildSpec]`-return slice-shape pin:
16039        // [`Caixa::children`] must return the `:children` typed
16040        // `Vec<ChildSpec>` verbatim as a `&[ChildSpec]` slice-view over
16041        // the same backing buffer the raw `self.children.as_slice()`
16042        // field access borrows from, element-equal across every
16043        // representative fixture in the accept-set — `[]` (the "no
16044        // static children declared" arm every non-`Supervisor`-kind
16045        // `defcaixa` carries by `#[serde(default)]` and every
16046        // `SimpleOneForOne` supervisor carries by cross-slot refusal),
16047        // a canonical single-child `Permanent` fixture (the shape
16048        // most `OneForOne` supervisors carry — a single long-running
16049        // worker child), a canonical multi-child list carrying every
16050        // typed restart-policy variant (`Permanent` / `Transient` /
16051        // `Temporary`), and a past-the-guard sentinel — a duplicate
16052        // `:caixa` `[("w", ...), ("w", ...)]` entry pair
16053        // ([`crate::SupervisorSpec::validate`] rejects through
16054        // `DuplicateChildNome { nome: "w" }` but the accessor must
16055        // ship the raw slot verbatim so struct-literal fixtures
16056        // continue to expose the duplicate at the accessor boundary).
16057        //
16058        // Pins against a future silent detour that returned an owned
16059        // `Vec<ChildSpec>` (which would type-check but silently clone
16060        // on every accessor call, breaking the zero-cost projection
16061        // every peer sibling slice accessor carries), a `[dup, dup] →
16062        // [dup]` dedup collapse (which would silently absorb the
16063        // `DuplicateChildNome` refusal case at the accessor boundary
16064        // and the [`crate::StandardLayout::verify`] cross-child gate
16065        // would silently accept a struct-literal `Caixa` carrying the
16066        // drift), a reference to an operator-resolved overlay (the
16067        // future per-cluster `:children-overrides` slot — its
16068        // resolution must land at exactly this accessor body, not
16069        // silently divert the raw slot away from a second consumer),
16070        // or an axis-shuffled projection (a future detour that
16071        // reordered children through the accessor would silently
16072        // split the paired [`crate::StandardLayout::verify`] per-
16073        // supervisor gate's traversal input from the peer
16074        // [`Self::supervisor_view`] fold-in path's clone-order input,
16075        // since the OTP `RestForOne` restart strategy dispatches on
16076        // declared child order and axis reordering would silently
16077        // split the operator's per-cluster restart-fan-out order
16078        // from the caixa.lisp source-order).
16079        //
16080        // Second outer top-level [`Caixa`] `&[Composite]`-return slice
16081        // accessor pin on the substrate primitive for M2 / M3 typed-
16082        // slot vec-carry axes — folds on the outer-`Caixa`
16083        // `&[Composite]` composite-slice sub-family the sibling
16084        // `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
16085        // (2a1f907) pin opened, peer at the outer altitude of the
16086        // closed inner-`SupervisorSpec` `SupervisorSpec::children`
16087        // (bc92bce) accessor on the same OTP-supervisor static-child-
16088        // list axis.
16089        use crate::supervisor::{ChildSpec, RestartPolicy};
16090        let fixtures: Vec<Vec<ChildSpec>> = vec![
16091            vec![],
16092            vec![ChildSpec {
16093                caixa: "worker".into(),
16094                versao: "^0.1".into(),
16095                restart: RestartPolicy::Permanent,
16096            }],
16097            vec![
16098                ChildSpec {
16099                    caixa: "worker-a".into(),
16100                    versao: "^0.1".into(),
16101                    restart: RestartPolicy::Permanent,
16102                },
16103                ChildSpec {
16104                    caixa: "worker-b".into(),
16105                    versao: "^0.1".into(),
16106                    restart: RestartPolicy::Transient,
16107                },
16108                ChildSpec {
16109                    caixa: "worker-c".into(),
16110                    versao: "^0.1".into(),
16111                    restart: RestartPolicy::Temporary,
16112                },
16113            ],
16114            vec![
16115                ChildSpec {
16116                    caixa: "w".into(),
16117                    versao: "^0.1".into(),
16118                    restart: RestartPolicy::Permanent,
16119                },
16120                ChildSpec {
16121                    caixa: "w".into(),
16122                    versao: "^0.1".into(),
16123                    restart: RestartPolicy::Permanent,
16124                },
16125            ],
16126        ];
16127        for children in fixtures {
16128            let c = caixa_with_children(children.clone());
16129            assert_eq!(
16130                c.children(),
16131                children.as_slice(),
16132                "Caixa::children must return :children verbatim \
16133                 (got {:?}, expected {children:?})",
16134                c.children(),
16135            );
16136            assert_eq!(
16137                c.children(),
16138                c.children.as_slice(),
16139                "Caixa::children must element-equal the raw \
16140                 `self.children.as_slice()` field access across \
16141                 every value in the Vec<ChildSpec> accept-set",
16142            );
16143            assert_eq!(
16144                c.children().is_empty(),
16145                c.children.is_empty(),
16146                "Caixa::children().is_empty() must byte-equal \
16147                 self.children.is_empty() — a presence-bit drift \
16148                 would silently split the paired \
16149                 Caixa::declared_supervisor_slots supervisor-tree \
16150                 declared-slot enumerator's presence probe from the \
16151                 peer Caixa::supervisor_view typed-view composer's \
16152                 fold-in path",
16153            );
16154        }
16155    }
16156
16157    #[test]
16158    fn declared_supervisor_slots_children_arm_routes_through_accessor() {
16159        // Composition pin: [`Caixa::declared_supervisor_slots`]'s
16160        // `:children` presence-probe arm must key off
16161        // [`Caixa::children`], not the raw
16162        // `!self.children.is_empty()` field-probe. Structurally: a
16163        // `Caixa { children: vec![ChildSpec { caixa: "w", versao:
16164        // "^0.1", restart: Permanent }], .. }` must push
16165        // `SUPERVISOR_AUTHOR_KEY_CHILDREN` onto the declared-slot list
16166        // (the presence bit is non-empty, so the supervisor-tree
16167        // kind-coherence gate must surface the slot as "declared"),
16168        // and a `Caixa { children: vec![], .. }` must NOT push the
16169        // label (the "author omitted the slot entirely" arm — the
16170        // empty-slice partition the serde-default folds onto). The
16171        // pair jointly pins the accessor + declared-slot enumerator
16172        // composition: any future silent detour that had the accessor
16173        // collapse `[Permanent]` to `[]` (a `.filter(|c| c.nome() !=
16174        // "__reserved__")` projection) would silently absorb the
16175        // "declared but degenerate" arm at the accessor boundary and
16176        // the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
16177        // kind-coherence gate would silently accept a struct-literal
16178        // `Caixa` carrying the drift.
16179        //
16180        // Peer of the sibling
16181        // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
16182        // (2a1f907) on the M2 `:upgrade-from` composite-slice arm —
16183        // same "the enumerator gate must route through the substrate-
16184        // primitive typed dispatch" discipline extended onto the
16185        // supervisor-tree `:children` composite-slice arm.
16186        use crate::supervisor::{ChildSpec, RestartPolicy};
16187        let c = caixa_with_children(vec![ChildSpec {
16188            caixa: "w".into(),
16189            versao: "^0.1".into(),
16190            restart: RestartPolicy::Permanent,
16191        }]);
16192        let slots = c.declared_supervisor_slots();
16193        assert!(
16194            slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN),
16195            "declared_supervisor_slots must push \
16196             SUPERVISOR_AUTHOR_KEY_CHILDREN when `:children` is \
16197             non-empty — the accessor and the enumerator gate must \
16198             route through the same substrate-primitive typed \
16199             dispatch on the outer :children presence bit (got \
16200             slots={slots:?})",
16201        );
16202        let c = caixa_with_children(vec![]);
16203        let slots = c.declared_supervisor_slots();
16204        assert!(
16205            !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN),
16206            "declared_supervisor_slots must NOT push \
16207             SUPERVISOR_AUTHOR_KEY_CHILDREN when `:children` is \
16208             empty — the author-omitted arm must route through the \
16209             accessor's empty-slice return unchanged (got \
16210             slots={slots:?})",
16211        );
16212    }
16213
16214    #[test]
16215    fn supervisor_view_children_arm_routes_through_accessor() {
16216        // Composition pin: [`Caixa::supervisor_view`]'s per-`:children`
16217        // fold-in arm must key off [`Caixa::children`], not the raw
16218        // `self.children.clone()` field-clone. Structurally: a `Caixa {
16219        // kind: Supervisor, estrategia: Some(OneForOne), children:
16220        // vec![ChildSpec { caixa: "w", .. }], .. }` must fold the
16221        // per-child list through the accessor into the typed
16222        // [`SupervisorSpec`] view's `children` field verbatim — every
16223        // entry the accessor surfaces must land in the view's
16224        // `children` slot in the same order. The pair jointly pins the
16225        // accessor + view-composer composition: any future silent
16226        // detour that had the accessor return a fresh-cloned
16227        // `Vec<ChildSpec>` copy would silently break the reference-
16228        // identity pin the peer `supervisor_view` fold-in path reads
16229        // from — the fold would clone once more per accessor call
16230        // instead of borrowing the storage buffer verbatim once.
16231        //
16232        // Peer of the sibling
16233        // `supervisor_view_kind_gate_routes_through_accessor` (35d8b52-
16234        // family) composition pin on the peer kind-gate arm — same
16235        // "the view composer must route through the substrate-
16236        // primitive typed dispatch" discipline extended onto the
16237        // per-`:children` fold-in arm, closing the supervisor-view
16238        // composer's routing invariant on the composite-slice input.
16239        use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
16240        let mut c = caixa_with_children(vec![
16241            ChildSpec {
16242                caixa: "worker-a".into(),
16243                versao: "^0.1".into(),
16244                restart: RestartPolicy::Permanent,
16245            },
16246            ChildSpec {
16247                caixa: "worker-b".into(),
16248                versao: "^0.1".into(),
16249                restart: RestartPolicy::Transient,
16250            },
16251        ]);
16252        c.kind = crate::CaixaKind::Supervisor;
16253        c.estrategia = Some(RestartStrategy::OneForOne);
16254        let view = c
16255            .supervisor_view()
16256            .expect("Supervisor kind must produce a supervisor_view");
16257        assert_eq!(
16258            view.children(),
16259            c.children(),
16260            "supervisor_view must fold Caixa::children verbatim into \
16261             SupervisorSpec::children — the accessor and the view \
16262             composer must route through the same substrate-primitive \
16263             typed dispatch on the outer :children slice (got view \
16264             children={:?}, expected {:?})",
16265            view.children(),
16266            c.children(),
16267        );
16268    }
16269
16270    #[test]
16271    fn children_projects_slice_by_borrow() {
16272        // The by-borrow pin: [`Caixa::children`] returns
16273        // `&[ChildSpec]` by borrow — the returned slice borrows the
16274        // underlying `Vec<ChildSpec>` storage of the `:children` slot
16275        // and the accessor must not clone the backing `Vec` on every
16276        // call. Peer of the sibling outer top-level [`Caixa`]
16277        // `&[T]`-return by-borrow pins (`autores_projects_slice_by_borrow`
16278        // b5d813f, `etiquetas_projects_slice_by_borrow` 78c7d3c,
16279        // `bibliotecas_projects_slice_by_borrow` 8a36c23,
16280        // `exe_projects_slice_by_borrow` 65d9527,
16281        // `servicos_projects_slice_by_borrow` 611f78b,
16282        // `deps_projects_slice_by_borrow` ad34b4e,
16283        // `deps_dev_projects_slice_by_borrow` f7fd81e,
16284        // `upgrade_from_projects_slice_by_borrow` 2a1f907) on the
16285        // sibling outer top-level [`Caixa`] scalar-element and
16286        // composite-element `&[T]` axes — folds on the outer-`Caixa`
16287        // composite-element `&[Composite]` axis: the accessor's
16288        // returned slice must borrow from `&self` (the returned
16289        // reference's lifetime is tied to `&self`), and calling the
16290        // accessor twice on the same [`Caixa`] must yield slices
16291        // that are pointer-equal (the underlying byte-buffer is the
16292        // storage `Vec`'s allocation, not a fresh copy) as well as
16293        // value-equal (idempotent, no side effects on `&self`).
16294        //
16295        // Pins against a future silent detour that returned an owned
16296        // `Vec<ChildSpec>` (which would type-check but silently clone
16297        // on every call), a `&Vec<ChildSpec>` return (which would leak
16298        // the backing `Vec`'s grow/push/reserve surface no downstream
16299        // consumer reaches for), or a one-arm-only accessor that
16300        // returned a saturating value on some sentinel input.
16301        use crate::supervisor::{ChildSpec, RestartPolicy};
16302        for children in [
16303            vec![],
16304            vec![ChildSpec {
16305                caixa: "w".into(),
16306                versao: "^0.1".into(),
16307                restart: RestartPolicy::Permanent,
16308            }],
16309            vec![
16310                ChildSpec {
16311                    caixa: "worker-a".into(),
16312                    versao: "^0.1".into(),
16313                    restart: RestartPolicy::Permanent,
16314                },
16315                ChildSpec {
16316                    caixa: "worker-b".into(),
16317                    versao: "^0.1".into(),
16318                    restart: RestartPolicy::Transient,
16319                },
16320            ],
16321        ] {
16322            let c = caixa_with_children(children.clone());
16323            let first = c.children();
16324            let second = c.children();
16325            assert_eq!(
16326                first, second,
16327                "Caixa::children must be idempotent — two successive \
16328                 calls on the same &self must return the same \
16329                 &[ChildSpec]",
16330            );
16331            assert_eq!(
16332                first.as_ptr(),
16333                second.as_ptr(),
16334                "Caixa::children must borrow the underlying \
16335                 Vec<ChildSpec> storage — two successive calls must \
16336                 return slices with the same backing pointer (a fresh \
16337                 Vec<ChildSpec> clone would change the pointer on \
16338                 every call)",
16339            );
16340            assert_eq!(
16341                first,
16342                children.as_slice(),
16343                "Caixa::children must return :children verbatim by \
16344                 borrow — got {first:?}, expected {children:?}",
16345            );
16346        }
16347    }
16348
16349    // ── Caixa::membros — outer top-level &[Membro] composite-slice accessor ──
16350
16351    fn caixa_aplicacao_with_membros(membros: Vec<crate::aplicacao::Membro>) -> Caixa {
16352        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16353        c.kind = CaixaKind::Aplicacao;
16354        c.membros = membros;
16355        c
16356    }
16357
16358    #[test]
16359    fn membros_returns_membros_slice_verbatim_across_permutations() {
16360        // The canonical per-`Caixa` `:membros` M3 mesh-slot outer-
16361        // composite `&[Membro]`-return slice-shape pin:
16362        // [`Caixa::membros`] must return the `:membros` typed
16363        // `Vec<Membro>` verbatim as a `&[Membro]` slice-view over the
16364        // same backing buffer the raw `self.membros.as_slice()` field
16365        // access borrows from, element-equal across every
16366        // representative fixture in the accept-set — `[]` (the "no
16367        // members declared" arm every non-`Aplicacao`-kind `defcaixa`
16368        // carries by `#[serde(default)]` and every partially-authored
16369        // Aplicacao carries before the
16370        // [`crate::AplicacaoError::MembrosEmpty`] gate fires), a
16371        // canonical single-member fixture (the shape a minimal
16372        // Aplicacao carries — one Servico wrapping one contained
16373        // computation), a canonical multi-member list carrying three
16374        // distinct entries (the canonical checkout-shape Aplicacao —
16375        // cart / pricing / auth — every canonical example carries), and
16376        // a past-the-guard sentinel — a duplicate `:caixa`
16377        // `[("cart", ...), ("cart", ...)]` entry pair
16378        // ([`crate::AplicacaoSpec::validate`] rejects through
16379        // `DuplicateMembro { nome: "cart" }` but the accessor must ship
16380        // the raw slot verbatim so struct-literal fixtures continue to
16381        // expose the duplicate at the accessor boundary).
16382        //
16383        // Pins against a future silent detour that returned an owned
16384        // `Vec<Membro>` (which would type-check but silently clone on
16385        // every accessor call, breaking the zero-cost projection every
16386        // peer sibling slice accessor carries), a `[dup, dup] → [dup]`
16387        // dedup collapse (which would silently absorb the
16388        // `DuplicateMembro` refusal case at the accessor boundary and
16389        // the [`crate::StandardLayout::verify`] cross-member gate would
16390        // silently accept a struct-literal `Caixa` carrying the drift),
16391        // a reference to an operator-resolved overlay (the future per-
16392        // cluster `:membros-overrides` slot — its resolution must land
16393        // at exactly this accessor body, not silently divert the raw
16394        // slot away from a second consumer), or an axis-shuffled
16395        // projection (a future detour that reordered members through
16396        // the accessor would silently split the paired
16397        // [`crate::StandardLayout::verify`] per-Aplicacao gate's
16398        // traversal input from the peer [`Self::aplicacao_view`] fold-
16399        // in path's clone-order input, since the canonical `:contratos`
16400        // `:de`/`:para` and `:entrada :para` cross-slot refusal probes
16401        // read the member set through the same slice).
16402        //
16403        // Third outer top-level [`Caixa`] `&[Composite]`-return slice
16404        // accessor pin on the substrate primitive for M2 / M3 typed-
16405        // slot vec-carry axes — opens the outer-`Caixa` M3 mesh-slot
16406        // arm of the `&[Composite]` composite-slice sub-family the
16407        // sibling M2 `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
16408        // (2a1f907) and
16409        // `children_returns_children_slice_verbatim_across_permutations`
16410        // (c17b51e) pins opened, peer at the outer altitude of the
16411        // closed inner-[`crate::AplicacaoSpec::membros`] (6c77e36)
16412        // accessor on the same MESH-COMPOSITION per-Aplicacao member-
16413        // list axis.
16414        use crate::aplicacao::Membro;
16415        let fixtures: Vec<Vec<Membro>> = vec![
16416            vec![],
16417            vec![Membro {
16418                caixa: "cart".into(),
16419                versao: "^0.1".into(),
16420            }],
16421            vec![
16422                Membro {
16423                    caixa: "cart".into(),
16424                    versao: "^0.1".into(),
16425                },
16426                Membro {
16427                    caixa: "pricing".into(),
16428                    versao: "^0.2".into(),
16429                },
16430                Membro {
16431                    caixa: "auth".into(),
16432                    versao: "^1.0".into(),
16433                },
16434            ],
16435            vec![
16436                Membro {
16437                    caixa: "cart".into(),
16438                    versao: "^0.1".into(),
16439                },
16440                Membro {
16441                    caixa: "cart".into(),
16442                    versao: "^0.1".into(),
16443                },
16444            ],
16445        ];
16446        for membros in fixtures {
16447            let c = caixa_aplicacao_with_membros(membros.clone());
16448            assert_eq!(
16449                c.membros(),
16450                membros.as_slice(),
16451                "Caixa::membros must return :membros verbatim \
16452                 (got {:?}, expected {membros:?})",
16453                c.membros(),
16454            );
16455            assert_eq!(
16456                c.membros(),
16457                c.membros.as_slice(),
16458                "Caixa::membros must element-equal the raw \
16459                 `self.membros.as_slice()` field access across every \
16460                 value in the Vec<Membro> accept-set",
16461            );
16462            assert_eq!(
16463                c.membros().is_empty(),
16464                c.membros.is_empty(),
16465                "Caixa::membros().is_empty() must byte-equal \
16466                 self.membros.is_empty() — a presence-bit drift would \
16467                 silently split the paired Caixa::declared_mesh_slots \
16468                 mesh declared-slot enumerator's presence probe from \
16469                 the peer Caixa::aplicacao_view typed-view composer's \
16470                 fold-in path",
16471            );
16472        }
16473    }
16474
16475    #[test]
16476    fn declared_mesh_slots_membros_arm_routes_through_accessor() {
16477        // Composition pin: [`Caixa::declared_mesh_slots`]'s `:membros`
16478        // presence-probe arm must key off [`Caixa::membros`], not the
16479        // raw `!self.membros.is_empty()` field-probe. Structurally: a
16480        // `Caixa { membros: vec![Membro { caixa: "cart", versao:
16481        // "^0.1" }], .. }` must push `M3_AUTHOR_KEY_MEMBROS` onto the
16482        // declared-slot list (the presence bit is non-empty, so the
16483        // mesh kind-coherence gate must surface the slot as
16484        // "declared"), and a `Caixa { membros: vec![], .. }` must NOT
16485        // push the label (the "author omitted the slot entirely" arm
16486        // — the empty-slice partition the serde-default folds onto).
16487        // The pair jointly pins the accessor + declared-slot
16488        // enumerator composition: any future silent detour that had
16489        // the accessor collapse `[Membro { .. }]` to `[]` (a
16490        // `.filter(|m| m.nome() != "__reserved__")` projection) would
16491        // silently absorb the "declared but degenerate" arm at the
16492        // accessor boundary and the
16493        // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
16494        // coherence gate would silently accept a struct-literal
16495        // `Caixa` carrying the drift.
16496        //
16497        // Peer of the sibling
16498        // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
16499        // (2a1f907) and
16500        // `declared_supervisor_slots_children_arm_routes_through_accessor`
16501        // (c17b51e) composition pins on the M2 `:upgrade-from` /
16502        // `:children` composite-slice arms — same "the enumerator gate
16503        // must route through the substrate-primitive typed dispatch"
16504        // discipline extended onto the M3 `:membros` composite-slice
16505        // arm, opening the M3 arm of the declared-slot enumerator's
16506        // routing invariant.
16507        use crate::aplicacao::Membro;
16508        let c = caixa_aplicacao_with_membros(vec![Membro {
16509            caixa: "cart".into(),
16510            versao: "^0.1".into(),
16511        }]);
16512        let slots = c.declared_mesh_slots();
16513        assert!(
16514            slots.contains(&crate::render::M3_AUTHOR_KEY_MEMBROS),
16515            "declared_mesh_slots must push M3_AUTHOR_KEY_MEMBROS when \
16516             `:membros` is non-empty — the accessor and the enumerator \
16517             gate must route through the same substrate-primitive \
16518             typed dispatch on the outer :membros presence bit (got \
16519             slots={slots:?})",
16520        );
16521        let c = caixa_aplicacao_with_membros(vec![]);
16522        let slots = c.declared_mesh_slots();
16523        assert!(
16524            !slots.contains(&crate::render::M3_AUTHOR_KEY_MEMBROS),
16525            "declared_mesh_slots must NOT push M3_AUTHOR_KEY_MEMBROS \
16526             when `:membros` is empty — the author-omitted arm must \
16527             route through the accessor's empty-slice return unchanged \
16528             (got slots={slots:?})",
16529        );
16530    }
16531
16532    #[test]
16533    fn aplicacao_view_membros_arm_routes_through_accessor() {
16534        // Composition pin: [`Caixa::aplicacao_view`]'s per-`:membros`
16535        // fold-in arm must key off [`Caixa::membros`], not the raw
16536        // `self.membros.clone()` field-clone. Structurally: a `Caixa {
16537        // kind: Aplicacao, membros: vec![Membro { caixa: "cart", .. },
16538        // Membro { caixa: "pricing", .. }], .. }` must fold the per-
16539        // member list through the accessor into the typed
16540        // [`crate::AplicacaoSpec`] view's `membros` slot verbatim —
16541        // every entry the accessor surfaces must land in the view's
16542        // `membros` slot in the same order. The pair jointly pins the
16543        // accessor + view-composer composition: any future silent
16544        // detour that had the accessor return a fresh-cloned
16545        // `Vec<Membro>` copy would silently break the reference-
16546        // identity pin the peer `aplicacao_view` fold-in path reads
16547        // from — the fold would clone once more per accessor call
16548        // instead of borrowing the storage buffer verbatim once.
16549        //
16550        // Peer of the sibling
16551        // `aplicacao_view_politicas_arm_folds_through_accessor`
16552        // (5d23d29) /
16553        // `aplicacao_view_placement_arm_folds_through_accessor`
16554        // (4fb8074) /
16555        // `aplicacao_view_entrada_arm_folds_through_accessor` (e4128e4)
16556        // composition pins on the M3 `:politicas` / `:placement` /
16557        // `:entrada` outer-`Option<&Composite>` arms — extended here to
16558        // the M3 `:membros` outer-`&[Composite]` composite-slice arm,
16559        // closing the aplicacao-view composer's routing invariant on
16560        // the composite-slice input.
16561        use crate::aplicacao::Membro;
16562        let c = caixa_aplicacao_with_membros(vec![
16563            Membro {
16564                caixa: "cart".into(),
16565                versao: "^0.1".into(),
16566            },
16567            Membro {
16568                caixa: "pricing".into(),
16569                versao: "^0.2".into(),
16570            },
16571        ]);
16572        let view = c
16573            .aplicacao_view()
16574            .expect("Aplicacao kind must produce an aplicacao_view");
16575        assert_eq!(
16576            view.membros(),
16577            c.membros(),
16578            "aplicacao_view must fold Caixa::membros verbatim into \
16579             AplicacaoSpec::membros — the accessor and the view \
16580             composer must route through the same substrate-primitive \
16581             typed dispatch on the outer :membros slice (got view \
16582             membros={:?}, expected {:?})",
16583            view.membros(),
16584            c.membros(),
16585        );
16586    }
16587
16588    #[test]
16589    fn membros_projects_slice_by_borrow() {
16590        // The by-borrow pin: [`Caixa::membros`] returns `&[Membro]` by
16591        // borrow — the returned slice borrows the underlying
16592        // `Vec<Membro>` storage of the `:membros` slot and the
16593        // accessor must not clone the backing `Vec` on every call.
16594        // Peer of the sibling outer top-level [`Caixa`] `&[T]`-return
16595        // by-borrow pins (`autores_projects_slice_by_borrow` b5d813f,
16596        // `etiquetas_projects_slice_by_borrow` 78c7d3c,
16597        // `bibliotecas_projects_slice_by_borrow` 8a36c23,
16598        // `exe_projects_slice_by_borrow` 65d9527,
16599        // `servicos_projects_slice_by_borrow` 611f78b,
16600        // `deps_projects_slice_by_borrow` ad34b4e,
16601        // `deps_dev_projects_slice_by_borrow` f7fd81e,
16602        // `upgrade_from_projects_slice_by_borrow` 2a1f907,
16603        // `children_projects_slice_by_borrow` c17b51e) on the sibling
16604        // outer top-level [`Caixa`] scalar-element and composite-
16605        // element `&[T]` axes — folds on the outer-`Caixa` M3 mesh-
16606        // slot composite-element `&[Composite]` axis: the accessor's
16607        // returned slice must borrow from `&self` (the returned
16608        // reference's lifetime is tied to `&self`), and calling the
16609        // accessor twice on the same [`Caixa`] must yield slices that
16610        // are pointer-equal (the underlying byte-buffer is the storage
16611        // `Vec`'s allocation, not a fresh copy) as well as value-equal
16612        // (idempotent, no side effects on `&self`).
16613        //
16614        // Pins against a future silent detour that returned an owned
16615        // `Vec<Membro>` (which would type-check but silently clone on
16616        // every call), a `&Vec<Membro>` return (which would leak the
16617        // backing `Vec`'s grow/push/reserve surface no downstream
16618        // consumer reaches for), or a one-arm-only accessor that
16619        // returned a saturating value on some sentinel input.
16620        use crate::aplicacao::Membro;
16621        for membros in [
16622            vec![],
16623            vec![Membro {
16624                caixa: "cart".into(),
16625                versao: "^0.1".into(),
16626            }],
16627            vec![
16628                Membro {
16629                    caixa: "cart".into(),
16630                    versao: "^0.1".into(),
16631                },
16632                Membro {
16633                    caixa: "pricing".into(),
16634                    versao: "^0.2".into(),
16635                },
16636            ],
16637        ] {
16638            let c = caixa_aplicacao_with_membros(membros.clone());
16639            let first = c.membros();
16640            let second = c.membros();
16641            assert_eq!(
16642                first, second,
16643                "Caixa::membros must be idempotent — two successive \
16644                 calls on the same &self must return the same &[Membro]",
16645            );
16646            assert_eq!(
16647                first.as_ptr(),
16648                second.as_ptr(),
16649                "Caixa::membros must borrow the underlying Vec<Membro> \
16650                 storage — two successive calls must return slices with \
16651                 the same backing pointer (a fresh Vec<Membro> clone \
16652                 would change the pointer on every call)",
16653            );
16654            assert_eq!(
16655                first,
16656                membros.as_slice(),
16657                "Caixa::membros must return :membros verbatim by borrow \
16658                 — got {first:?}, expected {membros:?}",
16659            );
16660        }
16661    }
16662
16663    // ── Caixa::contratos — outer top-level &[WitContract] composite-slice accessor ──
16664
16665    fn caixa_aplicacao_with_contratos(contratos: Vec<crate::aplicacao::WitContract>) -> Caixa {
16666        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16667        c.kind = CaixaKind::Aplicacao;
16668        c.contratos = contratos;
16669        c
16670    }
16671
16672    fn contrato_http_for_test(
16673        de: &str,
16674        para: &str,
16675        endpoint: &str,
16676    ) -> crate::aplicacao::WitContract {
16677        crate::aplicacao::WitContract {
16678            de: de.into(),
16679            para: para.into(),
16680            wit: "wasi:http/proxy".into(),
16681            endpoint: Some(endpoint.into()),
16682            subject: None,
16683            slot: None,
16684        }
16685    }
16686
16687    #[test]
16688    fn contratos_returns_contratos_slice_verbatim_across_permutations() {
16689        // The canonical per-`Caixa` `:contratos` M3 mesh-slot outer-
16690        // composite `&[WitContract]`-return slice-shape pin:
16691        // [`Caixa::contratos`] must return the `:contratos` typed
16692        // `Vec<WitContract>` verbatim as a `&[WitContract]` slice-view
16693        // over the same backing buffer the raw
16694        // `self.contratos.as_slice()` field access borrows from,
16695        // element-equal across every representative fixture in the
16696        // accept-set — `[]` (the "no contracts declared" arm every
16697        // non-`Aplicacao`-kind `defcaixa` carries by
16698        // `#[serde(default)]` and every leaf-Aplicacao with a single
16699        // member carries), a canonical single-edge fixture (the
16700        // minimal directed-graph shape: one HTTP-shape `(cart → catalog)`
16701        // edge), and a canonical multi-edge fixture with three distinct
16702        // edges (the checkout-shape Aplicacao's HTTP-fan pattern:
16703        // `(cart → catalog)`, `(cart → pricing)`, `(cart → auth)`).
16704        //
16705        // Pins against a future silent detour that returned an owned
16706        // `Vec<WitContract>` (which would type-check but silently clone
16707        // on every accessor call, breaking the zero-cost projection
16708        // every peer sibling slice accessor carries), an axis-shuffled
16709        // projection (a future detour that reordered edges through the
16710        // accessor would silently split the paired
16711        // [`crate::StandardLayout::verify`] per-Aplicacao gate's
16712        // traversal input from the peer [`Self::aplicacao_view`] fold-
16713        // in path's clone-order input, since every canonical
16714        // `caixa-mesh` renderer's per-`(:de, :para)` adjacency-list
16715        // seed dispatch reads the edge set through the same slice),
16716        // or a reference to an operator-resolved overlay (the future
16717        // per-cluster `:contratos-overrides` slot — its resolution
16718        // must land at exactly this accessor body, not silently divert
16719        // the raw slot away from a second consumer).
16720        //
16721        // Fourth outer top-level [`Caixa`] `&[Composite]`-return slice
16722        // accessor pin on the substrate primitive for M2 / M3 typed-
16723        // slot vec-carry axes — closes the outer-`Caixa`
16724        // `&[Composite]` composite-slice sub-family the sibling M2
16725        // `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
16726        // (2a1f907) and
16727        // `children_returns_children_slice_verbatim_across_permutations`
16728        // (c17b51e) pins opened and the M3
16729        // `membros_returns_membros_slice_verbatim_across_permutations`
16730        // (0f26987) pin folded on, closing the outer-`Caixa` M3 mesh-
16731        // slot arm of the composite-slice sub-family. Peer at the outer
16732        // altitude of the closed inner-
16733        // [`crate::AplicacaoSpec::contratos`] (0dcc926) accessor on the
16734        // same MESH-COMPOSITION per-Aplicacao contract-list axis.
16735        let fixtures: Vec<Vec<crate::aplicacao::WitContract>> = vec![
16736            vec![],
16737            vec![contrato_http_for_test("cart", "catalog", "/items")],
16738            vec![
16739                contrato_http_for_test("cart", "catalog", "/items"),
16740                contrato_http_for_test("cart", "pricing", "/price"),
16741                contrato_http_for_test("cart", "auth", "/whoami"),
16742            ],
16743        ];
16744        for contratos in fixtures {
16745            let c = caixa_aplicacao_with_contratos(contratos.clone());
16746            assert_eq!(
16747                c.contratos(),
16748                contratos.as_slice(),
16749                "Caixa::contratos must return :contratos verbatim \
16750                 (got {:?}, expected {contratos:?})",
16751                c.contratos(),
16752            );
16753            assert_eq!(
16754                c.contratos(),
16755                c.contratos.as_slice(),
16756                "Caixa::contratos must element-equal the raw \
16757                 `self.contratos.as_slice()` field access across every \
16758                 value in the Vec<WitContract> accept-set",
16759            );
16760            assert_eq!(
16761                c.contratos().is_empty(),
16762                c.contratos.is_empty(),
16763                "Caixa::contratos().is_empty() must byte-equal \
16764                 self.contratos.is_empty() — a presence-bit drift would \
16765                 silently split the paired Caixa::declared_mesh_slots \
16766                 mesh declared-slot enumerator's presence probe from \
16767                 the peer Caixa::aplicacao_view typed-view composer's \
16768                 fold-in path",
16769            );
16770        }
16771    }
16772
16773    #[test]
16774    fn declared_mesh_slots_contratos_arm_routes_through_accessor() {
16775        // Composition pin: [`Caixa::declared_mesh_slots`]'s `:contratos`
16776        // presence-probe arm must key off [`Caixa::contratos`], not the
16777        // raw `!self.contratos.is_empty()` field-probe. Structurally: a
16778        // `Caixa { contratos: vec![WitContract { .. }], .. }` must push
16779        // `M3_AUTHOR_KEY_CONTRATOS` onto the declared-slot list (the
16780        // presence bit is non-empty, so the mesh kind-coherence gate
16781        // must surface the slot as "declared"), and a `Caixa {
16782        // contratos: vec![], .. }` must NOT push the label (the "author
16783        // omitted the slot entirely" arm — the empty-slice partition
16784        // the serde-default folds onto). The pair jointly pins the
16785        // accessor + declared-slot enumerator composition: any future
16786        // silent detour that had the accessor collapse
16787        // `[WitContract { .. }]` to `[]` (a `.filter(|c| c.de() !=
16788        // "__reserved__")` projection) would silently absorb the
16789        // "declared but degenerate" arm at the accessor boundary and
16790        // the [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
16791        // coherence gate would silently accept a struct-literal
16792        // `Caixa` carrying the drift.
16793        //
16794        // Peer of the sibling
16795        // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
16796        // (2a1f907),
16797        // `declared_supervisor_slots_children_arm_routes_through_accessor`
16798        // (c17b51e), and
16799        // `declared_mesh_slots_membros_arm_routes_through_accessor`
16800        // (0f26987) composition pins on the M2 `:upgrade-from` /
16801        // `:children` / M3 `:membros` composite-slice arms — same "the
16802        // enumerator gate must route through the substrate-primitive
16803        // typed dispatch" discipline extended onto the M3 `:contratos`
16804        // composite-slice arm, closing the M3 mesh-slot arm of the
16805        // declared-slot enumerator's routing invariant on the
16806        // composite-slice inputs.
16807        let c = caixa_aplicacao_with_contratos(vec![contrato_http_for_test(
16808            "cart", "catalog", "/items",
16809        )]);
16810        let slots = c.declared_mesh_slots();
16811        assert!(
16812            slots.contains(&crate::render::M3_AUTHOR_KEY_CONTRATOS),
16813            "declared_mesh_slots must push M3_AUTHOR_KEY_CONTRATOS when \
16814             `:contratos` is non-empty — the accessor and the enumerator \
16815             gate must route through the same substrate-primitive \
16816             typed dispatch on the outer :contratos presence bit (got \
16817             slots={slots:?})",
16818        );
16819        let c = caixa_aplicacao_with_contratos(vec![]);
16820        let slots = c.declared_mesh_slots();
16821        assert!(
16822            !slots.contains(&crate::render::M3_AUTHOR_KEY_CONTRATOS),
16823            "declared_mesh_slots must NOT push M3_AUTHOR_KEY_CONTRATOS \
16824             when `:contratos` is empty — the author-omitted arm must \
16825             route through the accessor's empty-slice return unchanged \
16826             (got slots={slots:?})",
16827        );
16828    }
16829
16830    #[test]
16831    fn aplicacao_view_contratos_arm_routes_through_accessor() {
16832        // Composition pin: [`Caixa::aplicacao_view`]'s per-`:contratos`
16833        // fold-in arm must key off [`Caixa::contratos`], not the raw
16834        // `self.contratos.clone()` field-clone. Structurally: a `Caixa
16835        // { kind: Aplicacao, contratos: vec![WitContract { de: "cart",
16836        // .. }, WitContract { de: "pricing", .. }], .. }` must fold the
16837        // per-edge list through the accessor into the typed
16838        // [`crate::AplicacaoSpec`] view's `contratos` slot verbatim —
16839        // every entry the accessor surfaces must land in the view's
16840        // `contratos` slot in the same order. The pair jointly pins
16841        // the accessor + view-composer composition: a future silent
16842        // detour that had the accessor shuffle or drop an edge would
16843        // silently split the paired declared-slot enumerator's
16844        // presence bit from the typed-view composer's edge-list, a
16845        // two-consumer split at the enumerator and the view composer
16846        // far from the source `caixa.lisp`.
16847        //
16848        // Peer of the sibling
16849        // `aplicacao_view_membros_arm_routes_through_accessor`
16850        // (0f26987) composition pin on the M3 `:membros` outer-
16851        // `&[Composite]` composite-slice arm, closing the aplicacao-
16852        // view composer's routing invariant on the composite-slice
16853        // inputs at the outer altitude.
16854        let c = caixa_aplicacao_with_contratos(vec![
16855            contrato_http_for_test("cart", "catalog", "/items"),
16856            contrato_http_for_test("cart", "pricing", "/price"),
16857        ]);
16858        let view = c
16859            .aplicacao_view()
16860            .expect("Aplicacao kind must produce an aplicacao_view");
16861        assert_eq!(
16862            view.contratos(),
16863            c.contratos(),
16864            "aplicacao_view must fold Caixa::contratos verbatim into \
16865             AplicacaoSpec::contratos — the accessor and the view \
16866             composer must route through the same substrate-primitive \
16867             typed dispatch on the outer :contratos slice (got view \
16868             contratos={:?}, expected {:?})",
16869            view.contratos(),
16870            c.contratos(),
16871        );
16872    }
16873
16874    #[test]
16875    fn contratos_projects_slice_by_borrow() {
16876        // The by-borrow pin: [`Caixa::contratos`] returns `&[WitContract]`
16877        // by borrow — the returned slice borrows the underlying
16878        // `Vec<WitContract>` storage of the `:contratos` slot and the
16879        // accessor must not clone the backing `Vec` on every call.
16880        // Peer of the sibling outer top-level [`Caixa`] `&[T]`-return
16881        // by-borrow pins (`autores_projects_slice_by_borrow` b5d813f,
16882        // `etiquetas_projects_slice_by_borrow` 78c7d3c,
16883        // `bibliotecas_projects_slice_by_borrow` 8a36c23,
16884        // `exe_projects_slice_by_borrow` 65d9527,
16885        // `servicos_projects_slice_by_borrow` 611f78b,
16886        // `deps_projects_slice_by_borrow` ad34b4e,
16887        // `deps_dev_projects_slice_by_borrow` f7fd81e,
16888        // `upgrade_from_projects_slice_by_borrow` 2a1f907,
16889        // `children_projects_slice_by_borrow` c17b51e,
16890        // `membros_projects_slice_by_borrow` 0f26987) on the sibling
16891        // outer top-level [`Caixa`] scalar-element and composite-
16892        // element `&[T]` axes — closes the outer-`Caixa` M3 mesh-slot
16893        // composite-element `&[Composite]` axis on the by-borrow pin:
16894        // the accessor's returned slice must borrow from `&self` (the
16895        // returned reference's lifetime is tied to `&self`), and
16896        // calling the accessor twice on the same [`Caixa`] must yield
16897        // slices that are pointer-equal (the underlying byte-buffer is
16898        // the storage `Vec`'s allocation, not a fresh copy) as well as
16899        // value-equal (idempotent, no side effects on `&self`).
16900        //
16901        // Pins against a future silent detour that returned an owned
16902        // `Vec<WitContract>` (which would type-check but silently clone
16903        // on every call), a `&Vec<WitContract>` return (which would
16904        // leak the backing `Vec`'s grow/push/reserve surface no
16905        // downstream consumer reaches for), or a one-arm-only accessor
16906        // that returned a saturating value on some sentinel input.
16907        for contratos in [
16908            vec![],
16909            vec![contrato_http_for_test("cart", "catalog", "/items")],
16910            vec![
16911                contrato_http_for_test("cart", "catalog", "/items"),
16912                contrato_http_for_test("cart", "pricing", "/price"),
16913            ],
16914        ] {
16915            let c = caixa_aplicacao_with_contratos(contratos.clone());
16916            let first = c.contratos();
16917            let second = c.contratos();
16918            assert_eq!(
16919                first, second,
16920                "Caixa::contratos must be idempotent — two successive \
16921                 calls on the same &self must return the same \
16922                 &[WitContract]",
16923            );
16924            assert_eq!(
16925                first.as_ptr(),
16926                second.as_ptr(),
16927                "Caixa::contratos must borrow the underlying \
16928                 Vec<WitContract> storage — two successive calls must \
16929                 return slices with the same backing pointer (a fresh \
16930                 Vec<WitContract> clone would change the pointer on \
16931                 every call)",
16932            );
16933            assert_eq!(
16934                first,
16935                contratos.as_slice(),
16936                "Caixa::contratos must return :contratos verbatim by \
16937                 borrow — got {first:?}, expected {contratos:?}",
16938            );
16939        }
16940    }
16941
16942    // ── drift-detection: Caixa top-level multi-word serde-derive-to-const identity ──
16943
16944    #[test]
16945    fn caixa_multi_word_serde_keys_match_lifted_top_level_key_consts() {
16946        // Load-bearing invariant: every multi-word top-level [`Caixa`]
16947        // serde-derived JSON key routes through a lifted `&'static str`
16948        // const. The Rust field names are `snake_case`
16949        // (`deps_dev` / `upgrade_from` / `max_restarts` /
16950        // `restart_window`); [`Caixa`]'s `#[serde(rename_all =
16951        // "camelCase")]` derive attribute maps each to the camelCase
16952        // byte-string the [`Caixa::to_lisp`] round-trip's
16953        // `serde_json::to_value(self)` step lands under before
16954        // `tatara_lisp::domain::json_to_sexp` re-projects the JSON keys
16955        // to the kebab-case `:deps-dev` / `:upgrade-from` /
16956        // `:max-restarts` / `:restart-window` author surface. Serialize
16957        // a fully-populated [`Caixa`] and pin that each canonical
16958        // byte-sequence appears verbatim in the JSON — a future
16959        // accidental `rename_all = "snake_case"` / `"kebab-case"` /
16960        // verbatim-field-name flip at the derive attribute (any of
16961        // which would silently break every [`Caixa::to_lisp`]
16962        // round-trip and the future M4 operator-side manifest ingest's
16963        // `Value::get(<key>)` navigation) surfaces here as a build-time
16964        // test failure at `manifest.rs`, not as an apply-time
16965        // `.get(<stale-canonical-const>)` returning `None` far from the
16966        // derive-attr drift's commit. Same discipline the sibling
16967        // `supervisor_spec_serde_keys_match_lifted_supervisor_key_consts`
16968        // (40cc4e5), `membro_serde_keys_match_lifted_membro_key_consts`
16969        // (ce80ca0), and `upgrade_from_entry_serde_keys_match_lifted_
16970        // m2_upgrade_from_key_consts` (36ffe65) pins established on the
16971        // sibling M2 supervision-tree, M3 [`Membro`] per-entry, and M2
16972        // [`UpgradeFromEntry`] per-entry axes — extended here to the
16973        // enclosing M0 [`Caixa`] top-level axis so the last of the four
16974        // multi-word top-level [`Caixa`] serde-derived JSON keys
16975        // (`depsDev`) joins the substrate's "one canonical byte-string
16976        // per typed serialized-key axis" discipline.
16977        use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
16978        use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
16979        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16980        c.deps_dev = vec![Dep::simple("tatara-check", "^0.1")];
16981        c.upgrade_from = vec![UpgradeFromEntry {
16982            from: "0.0.1".into(),
16983            instructions: vec![UpgradeInstruction::Restart],
16984        }];
16985        c.estrategia = Some(RestartStrategy::OneForOne);
16986        c.max_restarts = Some(3);
16987        c.restart_window = Some("60s".into());
16988        c.children = vec![ChildSpec {
16989            caixa: "child".into(),
16990            versao: "^0.1".into(),
16991            restart: RestartPolicy::Permanent,
16992        }];
16993        let json = serde_json::to_string(&c).unwrap();
16994        for key in [
16995            crate::render::CAIXA_KEY_DEPS_DEV,
16996            crate::render::M2_KEY_UPGRADE_FROM,
16997            crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
16998            crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
16999        ] {
17000            let quoted = format!("\"{key}\"");
17001            assert!(
17002                json.contains(&quoted),
17003                "serialized Caixa must carry the lifted top-level \
17004                 multi-word byte-sequence {quoted} verbatim in the JSON \
17005                 emission (got: {json})",
17006            );
17007        }
17008    }
17009
17010    #[test]
17011    fn caixa_top_level_multi_word_key_consts_are_pairwise_distinct() {
17012        // Cross-axis drift-detection pin: a future collapse of the four
17013        // canonical [`Caixa`] top-level multi-word byte-strings onto the
17014        // same value (e.g. an accidental copy-paste flip of
17015        // [`crate::render::CAIXA_KEY_DEPS_DEV`] to also read
17016        // `"upgradeFrom"`) would silently reroute every downstream
17017        // `Value::get(<key>)` probe on one axis onto the sibling axis's
17018        // top-level entry and pass every propagation-probe test that
17019        // expected only the stale axis's value. Peer of the sibling
17020        // four-way distinct pin on the `SUPERVISOR_KEY_*` tetrad
17021        // (40cc4e5) and the two-way pin on `MEMBRO_KEY_*` (ce80ca0).
17022        let all = [
17023            crate::render::CAIXA_KEY_DEPS_DEV,
17024            crate::render::M2_KEY_UPGRADE_FROM,
17025            crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
17026            crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
17027        ];
17028        for (i, a) in all.iter().enumerate() {
17029            for b in all.iter().skip(i + 1) {
17030                assert_ne!(
17031                    a, b,
17032                    "Caixa top-level multi-word key consts must be \
17033                     pairwise-distinct canonical byte-sequences — got \
17034                     `{a}` == `{b}`",
17035                );
17036            }
17037        }
17038    }
17039
17040    #[test]
17041    fn caixa_top_level_multi_word_key_consts_are_lower_camel_case_shape() {
17042        // Shape-pin: every [`Caixa`] top-level multi-word key const must
17043        // be a lowerCamelCase byte-sequence (no `snake_case`
17044        // underscores, no `kebab-case` hyphens, no leading colon, no
17045        // `PascalCase` leading capital, no whitespace / dots) — the
17046        // canonical shape the `#[serde(rename_all = "camelCase")]`
17047        // derive produces on [`Caixa`]. A future flip to a
17048        // non-camelCase attribute at the derive surfaces both here
17049        // (this test fails on the stale-constant shape) and at
17050        // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
17051        // (that test fails on the mismatch between const and derive).
17052        // Peer with `membro_key_consts_are_lower_camel_case_shape`
17053        // (ce80ca0) and `supervisor_key_consts_are_lower_camel_case_shape`
17054        // (40cc4e5) on the sibling per-entry / supervisor-tree axes.
17055        for key in [
17056            crate::render::CAIXA_KEY_DEPS_DEV,
17057            crate::render::M2_KEY_UPGRADE_FROM,
17058            crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
17059            crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
17060        ] {
17061            assert!(
17062                !key.is_empty(),
17063                "Caixa top-level multi-word key const must be non-empty \
17064                 (got {key:?})"
17065            );
17066            let first = key.chars().next().unwrap();
17067            assert!(
17068                first.is_ascii_lowercase(),
17069                "Caixa top-level multi-word key const must lead with an \
17070                 ASCII-lowercase byte (got {key:?}, leads with {first:?})",
17071            );
17072            assert!(
17073                key.chars().all(|c| c.is_ascii_alphanumeric()),
17074                "Caixa top-level multi-word key const must be \
17075                 ASCII-alphanumeric only — no `_` / `-` / `:` / `.` / \
17076                 whitespace (got {key:?})",
17077            );
17078        }
17079    }
17080
17081    #[test]
17082    fn caixa_key_deps_dev_pins_canonical_camel_case_byte_string() {
17083        // Scalar-value pin: the byte-string the
17084        // [`crate::render::CAIXA_KEY_DEPS_DEV`] const resolves to,
17085        // asserted verbatim. A future rebrand (`depsDev` → `devDeps`
17086        // matching Cargo's verbatim `dev-dependencies` axis, `depsDev`
17087        // → `depsTest` matching a hypothetical per-test-target
17088        // vocabulary flip) lands as an edit to exactly one const AND
17089        // one derive attribute — the sibling
17090        // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
17091        // pin already ties the const to the derive attribute, so a
17092        // rebrand that touches only one side of the pair fails at
17093        // caixa-core build time. Same "scalar-value pin per const"
17094        // discipline the sibling
17095        // `m2_top_level_author_key_consts_pin_canonical_kebab_case_labels`
17096        // (f49c8b0) and `contrato_key_consts_pin_canonical_camel_case_labels`
17097        // (ca463a4) pins carry on the peer M2 / M3 top-level slot axes.
17098        assert_eq!(crate::render::CAIXA_KEY_DEPS_DEV, "depsDev");
17099    }
17100
17101    #[test]
17102    fn caixa_key_deps_pins_canonical_byte_string() {
17103        // Scalar-value pin: the byte-string the
17104        // [`crate::render::CAIXA_KEY_DEPS`] const resolves to, asserted
17105        // verbatim. Peer of `caixa_key_deps_dev_pins_canonical_camel_case_byte_string`
17106        // on the two-list dep-graph serialized-key axis — the sibling
17107        // pin covers the multi-word `deps_dev → depsDev` camelCase
17108        // arm, this pin covers the single-word `deps → deps` no-op arm
17109        // (the [`crate::Caixa::deps`] field name carries no `_`, so the
17110        // `#[serde(rename_all = "camelCase")]` derive is a no-op on this
17111        // axis and the emitted JSON key equals the source-side field
17112        // name byte-for-byte). A future [`crate::Caixa::deps`] field
17113        // rename (`deps` → `dependencies` matching Cargo's verbatim
17114        // `[dependencies]` axis, `deps` → `runtime_deps` matching a
17115        // hypothetical per-runtime-target vocabulary flip) OR an added
17116        // `#[serde(rename = "…")]` explicit override lands as an edit
17117        // to exactly one const AND one derive-attr / field name — the
17118        // sibling `caixa_deps_serde_key_matches_lifted_caixa_key_deps`
17119        // pin ties the const to the emitted JSON key, so a rebrand
17120        // that touches only one side of the pair fails at caixa-core
17121        // build time.
17122        assert_eq!(crate::render::CAIXA_KEY_DEPS, "deps");
17123    }
17124
17125    #[test]
17126    fn caixa_deps_serde_key_matches_lifted_caixa_key_deps() {
17127        // Load-bearing invariant on the single-word `deps` top-level
17128        // axis: the byte-string [`crate::render::CAIXA_KEY_DEPS`] pins
17129        // must appear verbatim in the JSON [`Caixa::to_lisp`]'s
17130        // `serde_json::to_value(self)` step emits. Serialize a
17131        // populated [`Caixa`] whose `:deps` slot carries at least one
17132        // entry (the `#[serde(default)]` attribute on the field emits
17133        // an empty `[]` even without members, but a non-empty vec
17134        // additionally covers the codec's per-`Dep`-entry emission
17135        // path) and pin that `"deps"` appears verbatim in the JSON
17136        // emission — a future accidental `rename_all = "snake_case"` /
17137        // `"kebab-case"` flip at the derive attribute (or an added
17138        // `#[serde(rename = "…")]` explicit override on the field, or
17139        // a Rust field rename) would break every [`Caixa::to_lisp`]
17140        // round-trip and the future M4 operator-side manifest ingest's
17141        // `Value::get(CAIXA_KEY_DEPS)` navigation — surfaces here as a
17142        // build-time test failure at `manifest.rs`, not as an
17143        // apply-time `.get(<stale-canonical-const>)` returning `None`
17144        // far from the drift's commit. Peer of the sibling
17145        // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
17146        // multi-word pin on the same M0 [`Caixa`] top-level
17147        // serialized-key axis, extended here to the single-word arm
17148        // the multi-word test's `rename_all = "camelCase"` sweep can't
17149        // reach (single-word `deps → deps` is a no-op the multi-word
17150        // pin's `\"depsDev\"` / `\"upgradeFrom\"` / `\"maxRestarts\"` /
17151        // `\"restartWindow\"` byte-scan can never observe).
17152        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
17153        c.deps = vec![Dep::simple("caixa-core", "^0.1")];
17154        let json = serde_json::to_string(&c).unwrap();
17155        let quoted = format!("\"{}\"", crate::render::CAIXA_KEY_DEPS);
17156        assert!(
17157            json.contains(&quoted),
17158            "serialized Caixa must carry the lifted top-level `deps` \
17159             byte-sequence {quoted} verbatim in the JSON emission (got: \
17160             {json})",
17161        );
17162    }
17163
17164    #[test]
17165    fn caixa_dep_graph_two_list_key_consts_are_pairwise_distinct() {
17166        // Cross-axis drift-detection pin on the two-list dep-graph
17167        // renderer-side wire-key axis: a future collapse of the
17168        // canonical [`crate::render::CAIXA_KEY_DEPS`] /
17169        // [`crate::render::CAIXA_KEY_DEPS_DEV`] byte-strings onto the
17170        // same value (e.g. an accidental copy-paste flip of
17171        // `CAIXA_KEY_DEPS_DEV` to also read `"deps"`) would silently
17172        // reroute every downstream `Value::get(<key>)` probe on one
17173        // axis onto the sibling axis's dep-list and pass every
17174        // propagation-probe test that expected only the stale axis's
17175        // value — a dev-only dep would land in the runtime closure at
17176        // publish time, or a runtime dep would be excluded from the
17177        // published lacre. Peer of the sibling four-way distinct pin
17178        // on the top-level multi-word tetrad
17179        // (`caixa_top_level_multi_word_key_consts_are_pairwise_distinct`)
17180        // and the two-way pin on the sibling
17181        // [`DEP_AUTHOR_KEY_DEPS`] / [`DEP_AUTHOR_KEY_DEPS_DEV`]
17182        // author-facing arm (4da6fba's test), extended here to the
17183        // renderer-side wire-key arm of the same two-list dep-graph
17184        // axis so both halves of the "one canonical byte-string per
17185        // typed axis per (author, wire)" grid carry the same
17186        // distinct-ness discipline.
17187        assert_ne!(
17188            crate::render::CAIXA_KEY_DEPS,
17189            crate::render::CAIXA_KEY_DEPS_DEV,
17190            "CAIXA_KEY_DEPS and CAIXA_KEY_DEPS_DEV must be distinct \
17191             canonical byte-sequences on the two-list dep-graph \
17192             renderer-side wire-key axis"
17193        );
17194    }
17195
17196    // ── DepList / Caixa::push_dep pin ────────────────────────────────
17197    //
17198    // The compounding pin: the two-arm closed-set typed enum
17199    // [`crate::dep::DepList`] carries the runtime-closure `:deps`
17200    // (`Prod`) vs dev-only-closure `:deps-dev` (`Dev`) dispatch every
17201    // consumer of the top-level manifest's dep-mutation surface reads
17202    // through, and the typed dispatch [`Caixa::push_dep`] on the
17203    // substrate primitive folds the "select list → check within-list
17204    // dup → push" cascade onto one method call. Prior to this landing
17205    // the two axes lived across two `&'static str` constants
17206    // (`DEP_AUTHOR_KEY_DEPS`, `DEP_AUTHOR_KEY_DEPS_DEV`) with no closed-
17207    // set type carrying the pair; the `feira add` mutation site's
17208    // inline `if self.dev { &mut caixa.deps_dev } else { &mut
17209    // caixa.deps }` dispatch expressed no compile-time link back to
17210    // the substrate primitive, and a future third dep-list axis would
17211    // have silently split at every open-coded mutation site.
17212
17213    #[test]
17214    fn dep_list_as_str_routes_through_lifted_author_key_constants() {
17215        // Every arm returns the same `&'static str` the substrate's
17216        // canonical `DEP_AUTHOR_KEY_DEPS` / `DEP_AUTHOR_KEY_DEPS_DEV`
17217        // constants carry. A future rebrand on either constant reaches
17218        // the enum through one edit; a regression to inline literals
17219        // (e.g. `Prod => ":deps"`) would silently split the diagnostic
17220        // quotes from the wire-format constants every consumer routes
17221        // through and this pin flags it at build time.
17222        assert_eq!(
17223            crate::dep::DepList::Prod.as_str(),
17224            crate::render::DEP_AUTHOR_KEY_DEPS
17225        );
17226        assert_eq!(
17227            crate::dep::DepList::Dev.as_str(),
17228            crate::render::DEP_AUTHOR_KEY_DEPS_DEV
17229        );
17230    }
17231
17232    #[test]
17233    fn dep_list_display_routes_through_as_str() {
17234        // Same as-str-through-Display convergence discipline the
17235        // sibling closed-set typed enums carry — a `format!("{list}")`
17236        // call must land byte-for-byte on the accessor's return so a
17237        // future consumer that formats the enum for a diagnostic line
17238        // reaches the same wire-format constant the wire-format
17239        // producers do.
17240        assert_eq!(
17241            format!("{}", crate::dep::DepList::Prod),
17242            crate::dep::DepList::Prod.as_str()
17243        );
17244        assert_eq!(
17245            format!("{}", crate::dep::DepList::Dev),
17246            crate::dep::DepList::Dev.as_str()
17247        );
17248    }
17249
17250    #[test]
17251    fn dep_list_all_enumerates_every_variant_once() {
17252        // Exhaustive-iteration pin — every arm appears exactly once in
17253        // `ALL`, matching the closed set the compiler enforces on the
17254        // sibling `match self` arms. A future variant addition that
17255        // extends only one method's match without extending `ALL`
17256        // would silently drop the new arm from every consumer that
17257        // iterates the slice.
17258        let variants: &[crate::dep::DepList] = crate::dep::DepList::ALL;
17259        assert!(variants.contains(&crate::dep::DepList::Prod));
17260        assert!(variants.contains(&crate::dep::DepList::Dev));
17261        assert_eq!(variants.len(), 2);
17262    }
17263
17264    #[test]
17265    fn dep_list_from_wire_returns_prod_on_deps_wire_scalar() {
17266        // Reverse projection on the two-list dep-graph axis: the
17267        // author-surface wire tag the sibling `as_str` emitter walks
17268        // for `Prod` (`:deps` via `DEP_AUTHOR_KEY_DEPS`) parses back to
17269        // `Some(DepList::Prod)`. A regression that hand-rolled the
17270        // per-arm match without routing through the lifted
17271        // `DEP_AUTHOR_KEY_DEPS` const would silently disagree on any
17272        // future wire-tag rebrand and this pin flags it at build time.
17273        assert_eq!(
17274            crate::dep::DepList::from_wire(crate::render::DEP_AUTHOR_KEY_DEPS),
17275            Some(crate::dep::DepList::Prod)
17276        );
17277    }
17278
17279    #[test]
17280    fn dep_list_from_wire_returns_dev_on_deps_dev_wire_scalar() {
17281        // Peer of the `Prod`-arm pin on the dev-only axis: the
17282        // author-surface wire tag the sibling `as_str` emitter walks
17283        // for `Dev` (`:deps-dev` via `DEP_AUTHOR_KEY_DEPS_DEV`) parses
17284        // back to `Some(DepList::Dev)`. Same drift-detection posture
17285        // as the peer arm — the sibling method `match` arms are
17286        // compiler-checked exhaustive so a future variant addition
17287        // trips at build time.
17288        assert_eq!(
17289            crate::dep::DepList::from_wire(crate::render::DEP_AUTHOR_KEY_DEPS_DEV),
17290            Some(crate::dep::DepList::Dev)
17291        );
17292    }
17293
17294    #[test]
17295    fn dep_list_from_wire_returns_none_on_unknown_wire_scalar() {
17296        // Every input outside the closed-set arm-string set the
17297        // sibling `as_str` emitter walks lands on the terminal `None`
17298        // fallback — no silent-accept surface. Sweeps a set of
17299        // plausibly-adjacent scalars (unprefixed wire form, PascalCase
17300        // rebrand candidates, foreign wire tags, empty string) so a
17301        // future variant addition that widened one wire form without
17302        // extending the emitter's arm-set would trip the sibling
17303        // round-trip pin below rather than silently accepting the new
17304        // form here.
17305        for candidate in [
17306            "",
17307            "deps",
17308            "deps-dev",
17309            ":deps ",
17310            ":Deps",
17311            ":DEPS",
17312            ":build-dep",
17313            ":tool-dep",
17314            "prod",
17315            "dev",
17316        ] {
17317            assert_eq!(
17318                crate::dep::DepList::from_wire(candidate),
17319                None,
17320                "from_wire({candidate:?}) must return None; every input outside \
17321                 the {{DEP_AUTHOR_KEY_DEPS, DEP_AUTHOR_KEY_DEPS_DEV}} accept-set \
17322                 the sibling as_str emitter walks lands on the terminal fallback",
17323            );
17324        }
17325    }
17326
17327    #[test]
17328    fn dep_list_round_trips_through_as_str_and_from_wire() {
17329        // Load-bearing round-trip pin: every arm the `ALL` iteration
17330        // exposes survives the `as_str` → `from_wire` composition
17331        // byte-for-byte. Same discipline the sibling closed-set enums
17332        // carry — `CaixaKind` /
17333        // `RestartStrategy` / `RestartPolicy` /
17334        // `PlacementStrategy` — extended onto the two-list dep-graph
17335        // axis. A future variant addition that extends `ALL` +
17336        // `as_str` without extending `from_wire` (or vice versa)
17337        // trips at build time on this iteration because the compiler
17338        // enforces exhaustiveness on the sibling `match self` arms.
17339        for &list in crate::dep::DepList::ALL {
17340            assert_eq!(
17341                crate::dep::DepList::from_wire(list.as_str()),
17342                Some(list),
17343                "DepList::from_wire(as_str({list:?})) must round-trip to Some({list:?}) — \
17344                 a silent split between the forward emitter and the reverse parser \
17345                 would drift the two halves of the two-list dep-graph axis's typed dispatch",
17346            );
17347        }
17348    }
17349
17350    #[test]
17351    fn push_dep_routes_to_deps_slot_on_prod_arm() {
17352        // The `Prod` arm dispatches to the runtime-closure `:deps`
17353        // slot every downstream lacre-pipeline consumer resolves at
17354        // build time. A future arm that regressed to inline `&mut
17355        // self.deps_dev` on the `Prod` path would silently reroute
17356        // every runtime dep into the dev-only closure at publish time
17357        // — this pin refuses that regression.
17358        let src = Caixa::template("host");
17359        let mut caixa = Caixa::from_lisp(&src).expect("template parses");
17360        let before_deps = caixa.deps().len();
17361        let before_deps_dev = caixa.deps_dev().len();
17362        let dep = Dep {
17363            nome: "caixa-teia".to_string(),
17364            versao: "^0.1".to_string(),
17365            fonte: None,
17366            opcional: false,
17367            caracteristicas: Vec::new(),
17368        };
17369        caixa
17370            .push_dep(crate::dep::DepList::Prod, dep)
17371            .expect("first push into :deps succeeds");
17372        assert_eq!(caixa.deps().len(), before_deps + 1);
17373        assert_eq!(caixa.deps_dev().len(), before_deps_dev);
17374        assert_eq!(caixa.deps().last().unwrap().nome(), "caixa-teia");
17375    }
17376
17377    #[test]
17378    fn push_dep_routes_to_deps_dev_slot_on_dev_arm() {
17379        // Peer of the sibling `Prod`-arm dispatch pin — the `Dev` arm
17380        // must dispatch to the dev-only-closure `:deps-dev` slot every
17381        // downstream test-facing artifact resolver reads. A future
17382        // regression that inverted the two arms would silently route
17383        // every dev-only dep into the runtime closure at publish time
17384        // and this pin catches it before the drift ships.
17385        let src = Caixa::template("host");
17386        let mut caixa = Caixa::from_lisp(&src).expect("template parses");
17387        let dep = Dep {
17388            nome: "tatara-check".to_string(),
17389            versao: "*".to_string(),
17390            fonte: None,
17391            opcional: false,
17392            caracteristicas: Vec::new(),
17393        };
17394        caixa
17395            .push_dep(crate::dep::DepList::Dev, dep)
17396            .expect("first push into :deps-dev succeeds");
17397        assert!(caixa.deps().is_empty());
17398        assert_eq!(caixa.deps_dev().len(), 1);
17399        assert_eq!(caixa.deps_dev().last().unwrap().nome(), "tatara-check");
17400    }
17401
17402    #[test]
17403    fn push_dep_refuses_within_list_duplicate_nome_with_typed_error() {
17404        // Within-list dup check routes through the canonical
17405        // [`DepError::DuplicateNome`] carrier — the substrate's typed
17406        // diagnostic for the same axis [`Caixa::validate_deps`]'s
17407        // parse-time [`crate::render::insert_first_seen`] walk raises
17408        // on. Prior to the lift the mutation site's inline
17409        // `bail!("dep '{}' already declared", …)` string-diagnostic
17410        // path expressed no through-line back to the typed error;
17411        // routing every dep-list refusal through one carrier means an
17412        // author reading a `feira add` refusal and a `feira build`
17413        // refusal reaches for the same corrective surface without
17414        // switching diagnostic idioms.
17415        let src = Caixa::template("host");
17416        let mut caixa = Caixa::from_lisp(&src).expect("template parses");
17417        let dep = Dep {
17418            nome: "caixa-teia".to_string(),
17419            versao: "^0.1".to_string(),
17420            fonte: None,
17421            opcional: false,
17422            caracteristicas: Vec::new(),
17423        };
17424        caixa
17425            .push_dep(crate::dep::DepList::Prod, dep.clone())
17426            .expect("first push succeeds");
17427        let dup = Dep {
17428            nome: "caixa-teia".to_string(),
17429            versao: "^0.2".to_string(),
17430            fonte: None,
17431            opcional: false,
17432            caracteristicas: Vec::new(),
17433        };
17434        let err = caixa
17435            .push_dep(crate::dep::DepList::Prod, dup)
17436            .expect_err("second push with same :nome refuses");
17437        assert_eq!(
17438            err,
17439            DepError::DuplicateNome {
17440                nome: "caixa-teia".to_string(),
17441                list: crate::render::DEP_AUTHOR_KEY_DEPS,
17442            }
17443        );
17444        // The refused mutation must not corrupt the target list —
17445        // exactly one entry lives past the refusal, matching the
17446        // canonical single-source-of-truth invariant `Caixa::deps()`
17447        // carries.
17448        assert_eq!(caixa.deps().len(), 1);
17449    }
17450
17451    #[test]
17452    fn push_dep_refuses_dup_on_dev_list_arm_names_deps_dev_key() {
17453        // Peer of the sibling `Prod`-arm dup-refusal pin — the `Dev`
17454        // arm's refusal must carry `DEP_AUTHOR_KEY_DEPS_DEV` in the
17455        // `list` payload so a future author reading the refusal grep's
17456        // for the correct `:deps-dev` block in their `caixa.lisp`,
17457        // not the sibling `:deps` block the runtime closure resolves.
17458        let src = Caixa::template("host");
17459        let mut caixa = Caixa::from_lisp(&src).expect("template parses");
17460        let dep = Dep {
17461            nome: "tatara-check".to_string(),
17462            versao: "*".to_string(),
17463            fonte: None,
17464            opcional: false,
17465            caracteristicas: Vec::new(),
17466        };
17467        caixa
17468            .push_dep(crate::dep::DepList::Dev, dep.clone())
17469            .expect("first push succeeds");
17470        let err = caixa
17471            .push_dep(crate::dep::DepList::Dev, dep)
17472            .expect_err("second push with same :nome refuses");
17473        assert!(matches!(
17474            err,
17475            DepError::DuplicateNome {
17476                ref nome,
17477                list,
17478            } if nome == "tatara-check"
17479                && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
17480        ));
17481    }
17482
17483    #[test]
17484    fn push_dep_allows_same_nome_across_prod_and_dev_lists() {
17485        // The within-list dup check is scoped to the target arm — a
17486        // caixa may legitimately carry the same `:nome` under both
17487        // `:deps` and `:deps-dev` (though the substrate's peer
17488        // [`crate::Caixa::validate_deps`] walk still refuses the
17489        // shape at parse time; the mutation-site refusal is scoped to
17490        // the mutation-site's list to match the peer parse-time
17491        // per-list [`crate::render::insert_first_seen`] discipline).
17492        // The two arms hold independent seen-sets.
17493        let src = Caixa::template("host");
17494        let mut caixa = Caixa::from_lisp(&src).expect("template parses");
17495        let dep_prod = Dep {
17496            nome: "shared".to_string(),
17497            versao: "^0.1".to_string(),
17498            fonte: None,
17499            opcional: false,
17500            caracteristicas: Vec::new(),
17501        };
17502        let dep_dev = Dep {
17503            nome: "shared".to_string(),
17504            versao: "*".to_string(),
17505            fonte: None,
17506            opcional: false,
17507            caracteristicas: Vec::new(),
17508        };
17509        caixa
17510            .push_dep(crate::dep::DepList::Prod, dep_prod)
17511            .expect("push into :deps succeeds");
17512        caixa
17513            .push_dep(crate::dep::DepList::Dev, dep_dev)
17514            .expect("push same :nome into :deps-dev succeeds");
17515        assert_eq!(caixa.deps().len(), 1);
17516        assert_eq!(caixa.deps_dev().len(), 1);
17517    }
17518
17519    #[test]
17520    fn deps_of_prod_returns_the_deps_slot_verbatim() {
17521        // The `Prod` arm of the typed-dispatch [`Caixa::deps_of`] read
17522        // accessor must project onto the runtime-closure `:deps` slot —
17523        // element-equal and length-equal to the sibling per-slot
17524        // [`Caixa::deps`] accessor's return over every per-caixa fixture.
17525        // A future arm that regressed to `self.deps_dev()` on the `Prod`
17526        // path would silently reroute every downstream typed-dispatch
17527        // walker (the [`Caixa::validate_deps`] per-list
17528        // [`crate::render::insert_first_seen`] dedup walk, any future
17529        // per-axis-parametrised consumer) into the sibling dev-only
17530        // closure and this pin refuses that regression.
17531        let src = Caixa::template("host");
17532        let mut caixa = Caixa::from_lisp(&src).expect("template parses");
17533        assert_eq!(caixa.deps_of(crate::dep::DepList::Prod), caixa.deps());
17534        assert_eq!(caixa.deps_of(crate::dep::DepList::Prod).len(), 0);
17535        let dep = Dep {
17536            nome: "caixa-teia".to_string(),
17537            versao: "^0.1".to_string(),
17538            fonte: None,
17539            opcional: false,
17540            caracteristicas: Vec::new(),
17541        };
17542        caixa
17543            .push_dep(crate::dep::DepList::Prod, dep.clone())
17544            .expect("push into :deps succeeds");
17545        assert_eq!(caixa.deps_of(crate::dep::DepList::Prod), caixa.deps());
17546        assert_eq!(caixa.deps_of(crate::dep::DepList::Prod).len(), 1);
17547        assert_eq!(
17548            caixa.deps_of(crate::dep::DepList::Prod)[0].nome(),
17549            "caixa-teia"
17550        );
17551    }
17552
17553    #[test]
17554    fn deps_of_dev_returns_the_deps_dev_slot_verbatim() {
17555        // Peer of the sibling `Prod`-arm pin — the `Dev` arm of
17556        // [`Caixa::deps_of`] must project onto the dev-only-closure
17557        // `:deps-dev` slot, element-equal and length-equal to the
17558        // sibling per-slot [`Caixa::deps_dev`] accessor's return. A
17559        // future regression that inverted the two arms would silently
17560        // route every dev-list walker onto the runtime closure and this
17561        // pin catches it before the drift ships.
17562        let src = Caixa::template("host");
17563        let mut caixa = Caixa::from_lisp(&src).expect("template parses");
17564        assert_eq!(caixa.deps_of(crate::dep::DepList::Dev), caixa.deps_dev());
17565        assert_eq!(caixa.deps_of(crate::dep::DepList::Dev).len(), 0);
17566        let dep = Dep {
17567            nome: "tatara-check".to_string(),
17568            versao: "*".to_string(),
17569            fonte: None,
17570            opcional: false,
17571            caracteristicas: Vec::new(),
17572        };
17573        caixa
17574            .push_dep(crate::dep::DepList::Dev, dep)
17575            .expect("push into :deps-dev succeeds");
17576        assert_eq!(caixa.deps_of(crate::dep::DepList::Dev), caixa.deps_dev());
17577        assert_eq!(caixa.deps_of(crate::dep::DepList::Dev).len(), 1);
17578        assert_eq!(
17579            caixa.deps_of(crate::dep::DepList::Dev)[0].nome(),
17580            "tatara-check"
17581        );
17582    }
17583
17584    #[test]
17585    fn deps_of_exhaustive_over_dep_list_all_covers_the_two_slots() {
17586        // Composition pin: iterating [`crate::dep::DepList::ALL`] through
17587        // [`Caixa::deps_of`] must land on the same two-slot partition the
17588        // per-slot [`Caixa::deps`] / [`Caixa::deps_dev`] accessors
17589        // expose — the canonical dispatch a future per-axis-parametrised
17590        // walker (a future `feira app graph` per-list dep summary, a
17591        // future M4 per-cluster dev-closure-audit overlay the CR
17592        // materializer resolves per-CR) reads through. Prior to the
17593        // lift the two-block iteration lived open-coded at every walker,
17594        // so a future third dep-list axis (`:deps-build`, per CAIXA-SDLC
17595        // §I) would have had to grow a third block at every consumer.
17596        // A regression that dropped the `Dev` arm from `ALL` would flip
17597        // the collected pairs to `[(":deps", &[])]` alone and this pin
17598        // refuses that shape.
17599        let src = Caixa::template("host");
17600        let mut caixa = Caixa::from_lisp(&src).expect("template parses");
17601        let prod_dep = Dep {
17602            nome: "caixa-teia".to_string(),
17603            versao: "^0.1".to_string(),
17604            fonte: None,
17605            opcional: false,
17606            caracteristicas: Vec::new(),
17607        };
17608        let dev_dep = Dep {
17609            nome: "tatara-check".to_string(),
17610            versao: "*".to_string(),
17611            fonte: None,
17612            opcional: false,
17613            caracteristicas: Vec::new(),
17614        };
17615        caixa
17616            .push_dep(crate::dep::DepList::Prod, prod_dep)
17617            .expect("push into :deps succeeds");
17618        caixa
17619            .push_dep(crate::dep::DepList::Dev, dev_dep)
17620            .expect("push into :deps-dev succeeds");
17621        let collected: Vec<(&'static str, usize, &str)> = crate::dep::DepList::ALL
17622            .iter()
17623            .map(|&list| {
17624                let slice = caixa.deps_of(list);
17625                (list.as_str(), slice.len(), slice[0].nome())
17626            })
17627            .collect();
17628        assert_eq!(
17629            collected,
17630            vec![
17631                (crate::render::DEP_AUTHOR_KEY_DEPS, 1, "caixa-teia"),
17632                (crate::render::DEP_AUTHOR_KEY_DEPS_DEV, 1, "tatara-check"),
17633            ]
17634        );
17635    }
17636
17637    #[test]
17638    fn validate_deps_iterates_through_dep_list_all_via_deps_of() {
17639        // Composition pin: the [`Caixa::validate_deps`] parse-time gate
17640        // must route its per-list [`crate::render::insert_first_seen`]
17641        // dedup walk through [`Caixa::deps_of`] + [`crate::dep::DepList::ALL`]
17642        // rather than the pre-lift open-coded two-block iteration over
17643        // `self.deps()` + `self.deps_dev()`. A regression that dropped
17644        // one arm (e.g. hand-inlining `self.deps()` alone) would silently
17645        // stop refusing within-list dups on the sibling arm; a
17646        // regression that flipped the arm-to-list-key mapping
17647        // (`Dev => DEP_AUTHOR_KEY_DEPS`) would silently mislabel the
17648        // diagnostic surface. Both drifts surface here through a paired
17649        // duplicate-name refusal per arm plus an offending-list-key
17650        // check on the emitted [`DepError::DuplicateNome`] carrier.
17651        for &list in crate::dep::DepList::ALL {
17652            let src = Caixa::template("host");
17653            let mut caixa = Caixa::from_lisp(&src).expect("template parses");
17654            let dup = Dep {
17655                nome: "twin".to_string(),
17656                versao: "^0.1".to_string(),
17657                fonte: None,
17658                opcional: false,
17659                caracteristicas: Vec::new(),
17660            };
17661            match list {
17662                crate::dep::DepList::Prod => {
17663                    caixa.deps.push(dup.clone());
17664                    caixa.deps.push(dup);
17665                }
17666                crate::dep::DepList::Dev => {
17667                    caixa.deps_dev.push(dup.clone());
17668                    caixa.deps_dev.push(dup);
17669                }
17670            }
17671            let err = caixa
17672                .validate_deps()
17673                .expect_err("within-list duplicate :nome must refuse");
17674            assert_eq!(
17675                err,
17676                DepError::DuplicateNome {
17677                    nome: "twin".to_string(),
17678                    list: list.as_str(),
17679                },
17680                "validate_deps on {list} arm must emit \
17681                 DepError::DuplicateNome carrying the arm's own \
17682                 as_str() diagnostic — the arm-to-list-key mapping \
17683                 flowed through DepList::ALL + Caixa::deps_of"
17684            );
17685        }
17686    }
17687}