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        match crate::dialeto::classify_form(first)? {
295            crate::dialeto::CaixaDialeto::Pacote => {}
296            // `Desconhecido` deliberately falls through to the derive rather
297            // than short-circuiting: a `(defcaixa …)` matching neither schema
298            // is most likely a genuine package manifest with a typo in
299            // `:nome`, and the derive's diagnostic — which names the offending
300            // keyword and suggests the nearest slot — is far better than
301            // anything this classifier could say.
302            crate::dialeto::CaixaDialeto::Desconhecido => {}
303            foreign => {
304                // Only the typed dialect flows into the error — the three
305                // user-facing projections (canonical keyword, description,
306                // consumer) are read at Display time through
307                // [`crate::dialeto::CaixaDialeto`]'s own accessors, so the
308                // variant cannot carry a snapshot that drifts from
309                // [`crate::dialeto::CaixaDialeto::palavra_canonica`] /
310                // `descricao` / `consumidor`.
311                return Err(LeituraError::DialetoEstrangeiro { dialeto: foreign });
312            }
313        }
314
315        Self::compile_from_sexp(first).map_err(LeituraError::Leitura)
316    }
317
318    /// Register `Caixa` with the global tatara-lisp domain registry so
319    /// `defcaixa` is dispatchable from any tatara-lisp binary that seeds
320    /// the registry (e.g. `tatara-check`).
321    ///
322    /// `pending-fallible-register`: upstream `tatara_lisp::domain::register`
323    /// became `-> Result<(), KeywordCollision>` on 2026-07-31, so a second type
324    /// claiming `defcaixa` in one process is refused and named instead of
325    /// silently displacing this one. This workspace pins
326    /// `tatara-lisp = "0.3.3"`, which predates that, so the result cannot be
327    /// checked here yet. Propagate it — `pub fn register() -> Result<(),
328    /// tatara_lisp::KeywordCollision>` — in the same commit that bumps the pin.
329    pub fn register() {
330        tatara_lisp::domain::register::<Self>();
331    }
332
333    /// Substrate-canonical per-`Caixa` `:licenca` SPDX-expression scalar
334    /// accessor every consumer of the top-level manifest's license axis
335    /// keys off — returns the author-declared `:licenca` byte-string
336    /// verbatim as an `Option<&str>`, borrowed from the typed slot's own
337    /// `Option<String>` storage. `None` when the slot is absent (the
338    /// canonical "omit to defer to the caixa-helm renderer's `MIT`
339    /// fallback" shape [`Self::validate_licenca`] documents at
340    /// caixa-core/src/manifest.rs:1560; the peer [`caixa-helm`]
341    /// `build_readme` fold at caixa-helm/src/lib.rs:962 reads this
342    /// predicate too, so an authored-but-unset `:licenca` round-trips to
343    /// a rendered `lareira-<nome>` chart's `README.md` `## License`
344    /// section structurally identical to one that omits the slot).
345    ///
346    /// The `:licenca` slot carries the universal-axis SPDX-expression
347    /// license identifier every kind of caixa emits under (CAIXA-SDLC
348    /// §I — the author-facing surface every `defcaixa` form supplies) —
349    /// the typed slot's `Option<String>` accept-set (empty-string
350    /// rejected through [`ManifestError::LicencaEmpty`], SPDX-alphabet-
351    /// invalid rejected through [`ManifestError::LicencaInvalid`]) maps
352    /// onto the `lareira-<nome>` Helm chart's `README.md` `## License`
353    /// section (caixa-helm/src/lib.rs:962) and (through future
354    /// tightening documented at [`Self::validate_licenca`]) the
355    /// Chart.yaml `annotations["artifacthub.io/license"]` axis every
356    /// registry-facing chart carries. Every downstream consumer that
357    /// reads the license byte-string keys off this scalar (the
358    /// [`Self::validate_licenca`] empty-arm + SPDX-shape gate that
359    /// routes through `self.licenca.as_deref()`, the caixa-helm
360    /// `build_readme` `unwrap_or_else(|| "MIT".into())` fold that keys
361    /// the fallback off the `Option::is_none()` arm, every future
362    /// per-`Caixa` registry-facing renderer the CAIXA-SDLC §I roadmap
363    /// acknowledges).
364    ///
365    /// Prior to this lift the `.licenca` field was accessed inline at
366    /// two production sites — [`Self::validate_licenca`]'s
367    /// `self.licenca.as_deref()` empty-and-shape gate binding and the
368    /// caixa-helm `build_readme` `caixa.licenca.clone().unwrap_or_else(||
369    /// "MIT".into())` `README.md` `## License` fold — two open-coded
370    /// field-accesses that expressed no compile-time link back to the
371    /// typed slot. A future extension of the `:licenca` axis to a
372    /// richer author surface — a per-`:licenca` structured SPDX
373    /// expression parser + license-id allowlist (the future tightening
374    /// [`Self::validate_licenca`]'s docstring acknowledges), a
375    /// per-cluster license-default overlay the M4 CR materializer
376    /// resolves per-CR (the "cluster policy pins `Apache-2.0` for every
377    /// unlisted caixa" arm), a promotion of the plain
378    /// `Option<String>` byte-string to a richer `SpdxExpression` enum
379    /// once the SPDX-expression parser lands — would have had to be
380    /// threaded through both open-coded copies in lockstep or the
381    /// validate gate and the caixa-helm emit path would silently
382    /// disagree on which license a given [`Caixa`] resolves to (an
383    /// author's `:licenca "MIT OR Apache-2.0"` would satisfy validate
384    /// while the emit path silently rendered a stale `MIT` fallback,
385    /// or vice versa). Lifting the resolution to a typed method on the
386    /// substrate primitive means every downstream consumer of the
387    /// caixa's per-`Caixa` license surface reaches for exactly one
388    /// typed dispatch — the resolver's accept-set migrates as a unit
389    /// on any future axis addition.
390    ///
391    /// First `Option<&str>`-return top-level [`Caixa`] scalar accessor —
392    /// opens the "outer [`Caixa`] `Option<&str>` scalar" projection
393    /// pattern the sibling per-`Caixa` `:descricao` / `:repositorio` /
394    /// `:edicao` future lifts fold on. Same "one typed dispatch on the
395    /// substrate primitive, thin projections at each consumer"
396    /// discipline the peer per-`:placement` [`crate::aplicacao::Placement::shard_key`]
397    /// (7cd2a28) / [`crate::aplicacao::Placement::affinity`] (74ec2d3)
398    /// / per-`:contratos` [`crate::aplicacao::WitContract::endpoint`]
399    /// (7020470) / [`crate::aplicacao::WitContract::subject`] (90de675)
400    /// / [`crate::aplicacao::WitContract::slot`] (ed22b66)
401    /// `Option<&str>`-return accessors carry on the sibling M2 / M3
402    /// typed-slot atom axes, extended here to the outer top-level
403    /// `Caixa` universal-axis surface. Named `licenca()` to match the
404    /// storage field's name; the accessor's identity maps onto the
405    /// canonical CAIXA-SDLC §I vocabulary the slot's docstring already
406    /// carries.
407    #[must_use]
408    pub fn licenca(&self) -> Option<&str> {
409        self.licenca.as_deref()
410    }
411
412    /// Substrate-canonical per-`Caixa` `:repositorio` git-repo-URL scalar
413    /// accessor every consumer of the top-level manifest's homepage /
414    /// source-of-truth axis keys off — returns the author-declared
415    /// `:repositorio` byte-string verbatim as an `Option<&str>`, borrowed
416    /// from the typed slot's own `Option<String>` storage. `None` when
417    /// the slot is absent (the canonical "omit to defer to the renderer's
418    /// per-target placeholder" shape — [`caixa-helm`]'s `ChartYaml.home`
419    /// carries the `Option<String>` through verbatim so an author-omitted
420    /// `:repositorio` renders a `Chart.yaml` without a `home:` field
421    /// (`skip_serializing_if = "Option::is_none"`), while [`caixa-flux`]'s
422    /// `ClusterBundleOpts::for_caixa` folds the omitted slot through a
423    /// `format!("https://github.com/{DEFAULT_PLEME_GIT_ORG}/{nome}")`
424    /// fallback derived from `caixa.nome`).
425    ///
426    /// The `:repositorio` slot carries the universal-axis git-repo-URL
427    /// homepage identifier every kind of caixa emits under (CAIXA-SDLC
428    /// §I — the author-facing surface every `defcaixa` form supplies) —
429    /// the typed slot's `Option<String>` accept-set (empty-string
430    /// rejected through [`ManifestError::RepositorioEmpty`], git-repo-URL-
431    /// shape-invalid rejected through [`ManifestError::RepositorioInvalid`]
432    /// past the shared [`crate::render::is_git_repo_url`] predicate the
433    /// peer per-`:deps :fonte :repo` axis also routes through) maps onto
434    /// four load-bearing downstream consumers:
435    ///
436    ///   - [`Self::validate_repositorio`]'s empty-arm + shape-predicate
437    ///     gate binding at caixa-core/src/manifest.rs:1456 — the
438    ///     universal-axis identity gate wired at caixa-build time.
439    ///   - [`caixa-helm`]'s `build_chart_yaml` `ChartYaml.home` fold at
440    ///     caixa-helm/src/lib.rs:840 — the rendered `lareira-<nome>`
441    ///     Helm chart's `Chart.yaml` `home:` field, which every registry
442    ///     that ingests the chart (ArtifactHub, chartmuseum,
443    ///     `helm search repo`) surfaces as the chart's canonical source-
444    ///     of-truth link.
445    ///   - [`caixa-helm`]'s `build_readme` `## Source` fold at
446    ///     caixa-helm/src/lib.rs:957 — the rendered `lareira-<nome>`
447    ///     chart's `README.md` header link back to the source repo,
448    ///     which every author who inspects the rendered chart bundle
449    ///     lands at.
450    ///   - [`caixa-flux`]'s `ClusterBundleOpts::for_caixa`
451    ///     `GitRepository.spec.url` fold at caixa-flux/src/lib.rs:2006 —
452    ///     the rendered `GitRepository` CR's `spec.url` field, which
453    ///     FluxCD's `source-controller` polls to reconcile the caixa's
454    ///     manifest bundle from git.
455    ///
456    /// Prior to this lift the `.repositorio` field was accessed inline
457    /// at four production sites — [`Self::validate_repositorio`]'s
458    /// `self.repositorio.as_deref()` empty-and-shape gate binding, the
459    /// caixa-helm `build_chart_yaml` `caixa.repositorio.clone()`
460    /// `Chart.yaml` `home:` field fold, the caixa-helm `build_readme`
461    /// `caixa.repositorio.clone().unwrap_or_else(|| caixa.nome.clone())`
462    /// `README.md` `## Source` fold, and the caixa-flux
463    /// `ClusterBundleOpts::for_caixa`
464    /// `caixa.repositorio.clone().unwrap_or_else(|| format!(...))`
465    /// `GitRepository.spec.url` fold — four open-coded field-accesses
466    /// that expressed no compile-time link back to the typed slot. A
467    /// future extension of the `:repositorio` axis to a richer author
468    /// surface — a per-`:repositorio` structured
469    /// [`crate::render::GitRepoUrl`]-shaped scheme+host+path parse
470    /// (the future tightening [`Self::validate_repositorio`]'s
471    /// docstring anticipates alongside the peer per-`:deps :fonte
472    /// :repo` axis), a per-cluster repo-mirror overlay the M4 CR
473    /// materializer resolves per-CR (the "cluster policy rewrites
474    /// `github:pleme-io/...` to `git.internal/mirror/pleme-io/...`"
475    /// arm the private-registry story acknowledges), a promotion of
476    /// the plain `Option<String>` byte-string to a richer
477    /// `RepoUrl` enum discriminated on scheme — would have had to be
478    /// threaded through all four open-coded copies in lockstep or the
479    /// validate gate and the three emit paths would silently disagree
480    /// on which URL a given [`Caixa`] resolves to (an author's
481    /// `:repositorio "github:pleme-io/checkout"` would satisfy validate
482    /// while one of the emit paths silently rendered a stale URL, or
483    /// vice versa). Lifting the resolution to a typed method on the
484    /// substrate primitive means every downstream consumer of the
485    /// caixa's per-`Caixa` repo-URL surface reaches for exactly one
486    /// typed dispatch — the resolver's accept-set migrates as a unit on
487    /// any future axis addition.
488    ///
489    /// Second outer top-level [`Caixa`] `Option<&str>`-return scalar
490    /// accessor — sibling of [`Self::licenca`] (6d5bc28), the accessor
491    /// that opened the "outer [`Caixa`] `Option<&str>` scalar"
492    /// projection pattern this lift folds on. Same "one typed dispatch
493    /// on the substrate primitive, thin projections at each consumer"
494    /// discipline the peer per-`:placement`
495    /// [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
496    /// [`crate::aplicacao::Placement::affinity`] (74ec2d3) /
497    /// per-`:contratos` [`crate::aplicacao::WitContract::endpoint`]
498    /// (7020470) / [`crate::aplicacao::WitContract::subject`] (90de675)
499    /// / [`crate::aplicacao::WitContract::slot`] (ed22b66)
500    /// `Option<&str>`-return accessors carry on the sibling M2 / M3
501    /// typed-slot atom axes, extended here to the second outer top-level
502    /// `Caixa` universal-axis surface. Named `repositorio()` to match
503    /// the storage field's name; the accessor's identity maps onto the
504    /// canonical CAIXA-SDLC §I vocabulary the slot's docstring already
505    /// carries.
506    #[must_use]
507    pub fn repositorio(&self) -> Option<&str> {
508        self.repositorio.as_deref()
509    }
510
511    /// Substrate-canonical per-`Caixa` `:descricao` free-form-prose
512    /// chart-description scalar accessor every consumer of the top-level
513    /// manifest's Chart.yaml `description:` axis keys off — returns the
514    /// author-declared `:descricao` byte-string verbatim as an
515    /// `Option<&str>`, borrowed from the typed slot's own
516    /// `Option<String>` storage. `None` when the slot is absent (the
517    /// canonical "omit to defer to the per-renderer `caixa.nome`-derived
518    /// fallback" shape — [`caixa-helm`]'s `build_chart_yaml` folds the
519    /// omitted slot through a `format!("Generated chart for caixa Servico
520    /// {}", caixa.nome)` fallback, [`caixa-helm`]'s `build_readme` folds
521    /// it through a `format!("caixa Servico {}", caixa.nome)` fallback,
522    /// and [`caixa-feira`]'s `render_flake` folds it through a
523    /// `format!("caixa {}", c.nome)` `flake.nix` `description = ""`
524    /// fallback — each derived from `caixa.nome` on the null-carrier arm).
525    ///
526    /// The `:descricao` slot carries the universal-axis free-form-prose
527    /// chart-description identifier every kind of caixa emits under
528    /// (CAIXA-SDLC §I — the author-facing surface every `defcaixa` form
529    /// supplies) — the typed slot's `Option<String>` accept-set
530    /// (empty-string rejected through [`ManifestError::DescricaoEmpty`],
531    /// chart-description-shape-invalid rejected through
532    /// [`ManifestError::DescricaoInvalid`] past the shared
533    /// [`crate::render::is_chart_description_shape`] predicate the peer
534    /// per-`Caixa` `:descricao` axis also routes through) maps onto four
535    /// load-bearing downstream consumers:
536    ///
537    ///   - [`Self::validate_descricao`]'s empty-arm + shape-predicate
538    ///     gate binding — the universal-axis identity gate wired at
539    ///     caixa-build time.
540    ///   - [`caixa-helm`]'s `build_chart_yaml` `ChartYaml.description`
541    ///     `Chart.yaml` field fold — the rendered `lareira-<nome>` Helm
542    ///     chart's `Chart.yaml` `description:` field, which
543    ///     `apiVersion: v2` charts require non-empty (`helm lint` fires
544    ///     `WARNING [chart.metadata.description]: description is required`
545    ///     when absent) and which every registry that ingests the chart
546    ///     (ArtifactHub, chartmuseum, `helm search repo`) surfaces as the
547    ///     chart's canonical one-line prose descriptor.
548    ///   - [`caixa-helm`]'s `build_readme` chart-`README.md` header fold
549    ///     — the rendered `lareira-<nome>` chart's `README.md` prose
550    ///     header directly beneath the `# <chart-name>` title, which
551    ///     every author who inspects the rendered chart bundle lands at.
552    ///   - [`caixa-feira`]'s `render_flake` `flake.nix` `description = ""`
553    ///     top-level fold — the emitted `flake.nix`'s `description`
554    ///     field, which every Nix consumer (`nix flake show`,
555    ///     `nix flake metadata`, downstream flake-registry ingestors)
556    ///     surfaces as the flake's canonical descriptor.
557    ///
558    /// Prior to this lift the `.descricao` field was accessed inline at
559    /// four production sites — [`Self::validate_descricao`]'s
560    /// `self.descricao.as_deref()` empty-and-shape gate binding, the
561    /// caixa-helm `build_chart_yaml`
562    /// `caixa.descricao.clone().unwrap_or_else(|| format!(...))`
563    /// `Chart.yaml` `description:` fold, the caixa-helm `build_readme`
564    /// `caixa.descricao.clone().unwrap_or_else(|| format!(...))`
565    /// `README.md` header fold, and the caixa-feira `render_flake`
566    /// `c.descricao.clone().unwrap_or_else(|| format!(...))` `flake.nix`
567    /// `description = ""` fold — four open-coded field-accesses that
568    /// expressed no compile-time link back to the typed slot. A future
569    /// extension of the `:descricao` axis to a richer author surface —
570    /// a per-`:descricao` locale-tagged multi-language descriptor map
571    /// (the "one caixa, N language-tagged prose descriptions" arm
572    /// author-tooling internationalization anticipates), a
573    /// per-registry-target length-and-shape overlay the M4 CR
574    /// materializer resolves per-CR (the "ArtifactHub caps description
575    /// at 512 bytes but the internal registry caps at 256" arm), a
576    /// promotion of the plain `Option<String>` byte-string to a richer
577    /// `ChartDescription` newtype guaranteeing the
578    /// `is_chart_description_shape` predicate at the type level — would
579    /// have had to be threaded through all four open-coded copies in
580    /// lockstep or the validate gate and the three emit paths would
581    /// silently disagree on which prose string a given [`Caixa`]
582    /// resolves to (an author's
583    /// `:descricao "Checkout flow orchestration."` would satisfy
584    /// validate while one of the emit paths silently rendered a stale
585    /// `caixa.nome`-derived fallback, or vice versa). Lifting the
586    /// resolution to a typed method on the substrate primitive means
587    /// every downstream consumer of the caixa's per-`Caixa`
588    /// chart-description surface reaches for exactly one typed dispatch
589    /// — the resolver's accept-set migrates as a unit on any future
590    /// axis addition.
591    ///
592    /// Third outer top-level [`Caixa`] `Option<&str>`-return scalar
593    /// accessor — sibling of [`Self::licenca`] (6d5bc28) and
594    /// [`Self::repositorio`] (cc7332d), the accessors that opened the
595    /// "outer [`Caixa`] `Option<&str>` scalar" projection pattern this
596    /// lift folds on. Same "one typed dispatch on the substrate
597    /// primitive, thin projections at each consumer" discipline the
598    /// peer per-`:placement`
599    /// [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
600    /// [`crate::aplicacao::Placement::affinity`] (74ec2d3) /
601    /// per-`:contratos` [`crate::aplicacao::WitContract::endpoint`]
602    /// (7020470) / [`crate::aplicacao::WitContract::subject`] (90de675)
603    /// / [`crate::aplicacao::WitContract::slot`] (ed22b66)
604    /// `Option<&str>`-return accessors carry on the sibling M2 / M3
605    /// typed-slot atom axes, extended here to the third outer top-level
606    /// `Caixa` universal-axis surface. Named `descricao()` to match the
607    /// storage field's name; the accessor's identity maps onto the
608    /// canonical CAIXA-SDLC §I vocabulary the slot's docstring already
609    /// carries. The one remaining universal `Option<String>` slot
610    /// (`:edicao`) folds on this pattern next.
611    #[must_use]
612    pub fn descricao(&self) -> Option<&str> {
613        self.descricao.as_deref()
614    }
615
616    /// Substrate-canonical per-`Caixa` `:edicao` language-edition scalar
617    /// accessor every consumer of the top-level manifest's tatara-lisp
618    /// edition-selector axis keys off — returns the author-declared
619    /// `:edicao` byte-string verbatim as an `Option<&str>`, borrowed from
620    /// the typed slot's own `Option<String>` storage. `None` when the
621    /// slot is absent (the canonical "omit the slot to defer to the
622    /// substrate's default edition" shape every existing
623    /// [`caixa-resolver`] integration test fixture carries via
624    /// `edicao: None` — see `caixa-resolver/tests/git_integration.rs`;
625    /// the peer [`Self::validate_edicao`] gate is a no-op on the omitted
626    /// arm by construction, so an author-omitted `:edicao` round-trips
627    /// to a build without triggering the year-shape predicate).
628    ///
629    /// The `:edicao` slot carries the universal-axis 4-digit-ASCII-
630    /// decimal-year language-edition identifier every kind of caixa
631    /// emits under (CAIXA-SDLC §I — the author-facing surface every
632    /// `defcaixa` form supplies) — the typed slot's `Option<String>`
633    /// accept-set (empty-string rejected through
634    /// [`ManifestError::EdicaoEmpty`], year-shape-invalid rejected
635    /// through [`ManifestError::EdicaoInvalid`] past the 4-digit-ASCII-
636    /// decimal-year predicate [`Self::validate_edicao`] enforces) maps
637    /// onto one load-bearing downstream consumer today
638    /// ([`Self::validate_edicao`]'s empty-arm + year-shape-predicate
639    /// gate binding at caixa-core/src/manifest.rs:1959) plus every
640    /// future edition-aware substrate consumer the CAIXA-SDLC §I
641    /// roadmap anticipates (the tatara-lisp compiler's macro-surface
642    /// selector every edition-aware build step keys off, the future
643    /// per-edition compatibility-flag overlay the M4 CR materializer
644    /// resolves per-CR, the peer [`Caixa::template`] canonical
645    /// `:edicao "2026"` scaffold every `feira init` emits verbatim,
646    /// and the renderer-side fixtures at `caixa-helm/src/lib.rs:978` /
647    /// `caixa-flux/src/lib.rs:2319` / `caixa-mesh/src/lib.rs:3208` that
648    /// carry `edicao: Some("2026".into())` by construction).
649    ///
650    /// Prior to this lift the `.edicao` field was accessed inline at
651    /// one production site — [`Self::validate_edicao`]'s
652    /// `self.edicao.as_deref()` empty-and-shape gate binding — one
653    /// open-coded field-access that expressed no compile-time link
654    /// back to the typed slot. A future extension of the `:edicao`
655    /// axis to a richer author surface — a per-`:edicao` known-
656    /// edition allowlist (the future tightening
657    /// [`Self::validate_edicao`]'s docstring acknowledges past the
658    /// structural year-shape floor, rejecting year-shaped values that
659    /// don't name a tatara-lisp edition the substrate actually
660    /// understands — `"1999"` is year-shaped but no `1999` edition
661    /// exists), a per-edition compatibility-flag overlay the M4 CR
662    /// materializer resolves per-CR (the "edition `"2026"` enables
663    /// macro-surface features the sibling `"2018"` gates behind a
664    /// feature flag" arm the edition-selector story anticipates), a
665    /// promotion of the plain `Option<String>` byte-string to a
666    /// richer `CaixaEdition` enum discriminated on year once a sibling
667    /// edition to `"2026"` lands — would have had to be threaded
668    /// through the open-coded copy in lockstep with every future
669    /// edition-aware consumer, or the validate gate and the future
670    /// edition-aware consumer path would silently disagree on which
671    /// edition a given [`Caixa`] resolves to (an author's
672    /// `:edicao "2026"` would satisfy validate while a future
673    /// edition-aware consumer silently defaulted to a stale edition,
674    /// or vice versa). Lifting the resolution to a typed method on
675    /// the substrate primitive means every downstream consumer of the
676    /// caixa's per-`Caixa` edition surface reaches for exactly one
677    /// typed dispatch — the resolver's accept-set migrates as a unit
678    /// on any future axis addition.
679    ///
680    /// Fourth and final outer top-level [`Caixa`] `Option<&str>`-return
681    /// scalar accessor — sibling of [`Self::licenca`] (6d5bc28),
682    /// [`Self::repositorio`] (cc7332d), and [`Self::descricao`]
683    /// (3f16e2f), the accessors that opened the "outer [`Caixa`]
684    /// `Option<&str>` scalar" projection pattern this lift folds on.
685    /// Same "one typed dispatch on the substrate primitive, thin
686    /// projections at each consumer" discipline the peer per-`:placement`
687    /// [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
688    /// [`crate::aplicacao::Placement::affinity`] (74ec2d3) /
689    /// per-`:contratos` [`crate::aplicacao::WitContract::endpoint`]
690    /// (7020470) / [`crate::aplicacao::WitContract::subject`] (90de675)
691    /// / [`crate::aplicacao::WitContract::slot`] (ed22b66)
692    /// `Option<&str>`-return accessors carry on the sibling M2 / M3
693    /// typed-slot atom axes, extended here to close the outer top-level
694    /// `Caixa` universal-axis surface's last unlifted `Option<String>`
695    /// slot. Named `edicao()` to match the storage field's name; the
696    /// accessor's identity maps onto the canonical CAIXA-SDLC §I
697    /// vocabulary the slot's docstring already carries.
698    #[must_use]
699    pub fn edicao(&self) -> Option<&str> {
700        self.edicao.as_deref()
701    }
702
703    /// Substrate-canonical per-`Caixa` `:nome` universal-axis DNS-1123-
704    /// label caixa-identity scalar accessor every consumer of the top-
705    /// level manifest's identity axis keys off — returns the author-
706    /// declared `:nome` byte-string verbatim as an `&str`, borrowed from
707    /// the typed slot's own `String` storage. Non-optional (`:nome` is
708    /// a required-axis scalar every `defcaixa` form must supply; the
709    /// [`Self::from_lisp`] derive rejects an omitted / non-string
710    /// `:nome` at parse time, so a `Caixa` past parse definitionally
711    /// carries a non-`None` `:nome`).
712    ///
713    /// The `:nome` slot carries the universal-axis DNS-1123-label
714    /// caixa-identity every kind of caixa emits under (CAIXA-SDLC §I —
715    /// the primary identity axis every `defcaixa` form supplies
716    /// alongside `:versao` / `:kind`; the substrate-wide identity every
717    /// other typed surface that names a caixa reaches through — `:deps`
718    /// entries, `:membros` entries, `:children` entries, the
719    /// `lareira-<nome>` Helm chart name every per-Servico renderer
720    /// derives, the `pleme-program-<nome>` label every per-Aplicacao
721    /// renderer emits) — the typed slot's `String` accept-set (empty
722    /// rejected through [`ManifestError::NomeEmpty`], DNS-1123-shape-
723    /// invalid rejected through [`ManifestError::NomeInvalid`] past
724    /// the shared [`crate::render::require_valid_dns_1123_label`] gate
725    /// the peer name axes each land on, joint-length-with-`lareira-`-
726    /// prefix rejected through
727    /// [`ManifestError::NomeChartNameBudgetExceeded`] past
728    /// [`crate::render::is_lareira_chart_name_shape`]) maps onto every
729    /// load-bearing downstream consumer the substrate carries — the
730    /// two universal-axis validate gates at caixa-build time
731    /// ([`Self::validate_nome`] + [`Self::validate_nome_chart_name_budget`]),
732    /// [`crate::lareira_chart_name`]'s `lareira-<nome>` Helm chart-name
733    /// derivation every per-Servico renderer keys off, the caixa-helm
734    /// `Chart.yaml`'s `name:` axis, caixa-flux's `programs.yaml` entry
735    /// `name:` axis, caixa-mesh's Cilium `CiliumNetworkPolicy` /
736    /// `HTTPRoute` per-Aplicacao name axes at
737    /// caixa-mesh/src/lib.rs:{2650, 2797, 2919, 2925},
738    /// [`crate::pleme_program_selector`] /
739    /// [`crate::pleme_program_in_aplicacao_selector`] label-selector
740    /// derivations, and every future substrate renderer that emits an
741    /// artifact keyed by the caixa's identity.
742    ///
743    /// Prior to this lift the `.nome` field was accessed inline at a
744    /// dozen production sites across `caixa-core` (the two universal-
745    /// axis validate gates + [`Dep::validate`]-adjacent duplicate
746    /// tracking), `caixa-helm` (the `lareira_chart_name` fold, the
747    /// `ChartYaml.name` / `ChartYaml.description` / `Chart.yaml`
748    /// `keywords` fallback), `caixa-flux` (the `programs.yaml`
749    /// entry `name:` fold, the `flux_kustomization_source_subtree`
750    /// per-cluster subpath derivation), and `caixa-mesh` (the
751    /// `pleme_program_in_aplicacao_selector` label-selector fold, the
752    /// `cilium_network_policy_name` / `gateway_api_http_route_name`
753    /// per-CR name derivations, the `LABEL_APLICACAO` labels-map
754    /// insert) — a dozen open-coded field-accesses that expressed no
755    /// compile-time link back to the typed slot. A future extension of
756    /// the `:nome` axis to a richer author surface — a per-`:nome`
757    /// structured `CaixaIdentity` newtype that carries the joint-
758    /// length-with-prefix invariant [`Self::validate_nome_chart_name_budget`]
759    /// enforces at the type level (rather than as a validate-time
760    /// gate), a per-registry `:nome` namespacing overlay the M4 CR
761    /// materializer resolves per-CR (the "`pleme-io/checkout` vs
762    /// `partner-org/checkout` collision" arm the multi-tenant-registry
763    /// story acknowledges), a promotion of the plain `String` byte-
764    /// string to a richer `CaixaNome` newtype discriminated on
765    /// namespace prefix — would have had to be threaded through every
766    /// open-coded copy in lockstep or the two validate gates and the
767    /// dozen emit paths would silently disagree on which identity a
768    /// given [`Caixa`] resolves to (an author's `:nome "checkout"`
769    /// would satisfy validate while one of the emit paths silently
770    /// rendered a drifted other identity, or vice versa). Lifting the
771    /// resolution to a typed method on the substrate primitive means
772    /// every downstream consumer of the caixa's per-`Caixa` identity
773    /// surface reaches for exactly one typed dispatch — the resolver's
774    /// accept-set migrates as a unit on any future axis addition.
775    ///
776    /// First outer top-level [`Caixa`] `&str`-return required-scalar
777    /// accessor — opens the "outer [`Caixa`] `&str` required-scalar"
778    /// projection pattern the sibling per-`Caixa` `:versao` future lift
779    /// folds on. Sibling in shape to the peer per-`:membros`
780    /// [`crate::aplicacao::Membro::nome`] (4a32abf) / per-`:contratos`
781    /// [`crate::aplicacao::WitContract::source`] /
782    /// [`crate::aplicacao::WitContract::destination`] (7f0fd43),
783    /// [`crate::aplicacao::WitContract::world_ref`] (0804823),
784    /// [`crate::aplicacao::Membro::versao_requirement`] (a40b0e3),
785    /// [`crate::aplicacao::Entrada::destination`] (6db982c),
786    /// [`crate::aplicacao::CircuitBreaker::max_failures`] (3a74062),
787    /// per-sub-struct required-axis accessors carry on the sibling M3
788    /// mesh-slot-atom scalar-value axes, extended here to open the
789    /// outer top-level [`Caixa`] `&str`-return required-scalar surface.
790    /// Named `nome()` to match the storage field's name; the accessor's
791    /// identity maps onto the canonical CAIXA-SDLC §I vocabulary the
792    /// slot's docstring already carries.
793    #[must_use]
794    pub fn nome(&self) -> &str {
795        &self.nome
796    }
797
798    /// Substrate-canonical per-`Caixa` `:versao` universal-axis SemVer-2
799    /// pinned-version scalar accessor every consumer of the top-level
800    /// manifest's version axis keys off — returns the author-declared
801    /// `:versao` byte-string verbatim as an `&str`, borrowed from the
802    /// typed slot's own `String` storage. Non-optional (`:versao` is a
803    /// required-axis scalar every `defcaixa` form must supply alongside
804    /// `:nome` / `:kind`; the [`Self::from_lisp`] derive rejects an
805    /// omitted / non-string `:versao` at parse time, so a `Caixa` past
806    /// parse definitionally carries a non-`None` `:versao`).
807    ///
808    /// The `:versao` slot carries the universal-axis SemVer-2
809    /// concrete-version body every kind of caixa emits under
810    /// (CAIXA-SDLC §I — the required-scalar every `defcaixa` form
811    /// supplies alongside `:nome` / `:kind`; the substrate-wide
812    /// pinned-version every downstream artifact-emitting consumer
813    /// composes under — the `lareira-<nome>` Helm chart's `Chart.yaml`
814    /// `version:` + `appVersion:` axes, the `feira publish` Zig-style
815    /// `v<versao>` git tag the [`crate::DEFAULT_PUBLISH_TAG_PREFIX`]
816    /// prefix composes on top of, the programs.yaml entry's `versao:`
817    /// value the `lareira-fleet-programs` aggregator carries onto each
818    /// rendered `ComputeUnit`, the OCI image's `:v<versao>` / `:latest`
819    /// tags every substrate-side `skopeo push` writes, the lacre
820    /// closure's pinned `concrete_versao`, and the `:upgrade-from :from`
821    /// prior-version references peers in the exact same SemVer-2 shape).
822    /// The typed slot's `String` accept-set (empty rejected through
823    /// [`ManifestError::VersaoEmpty`], SemVer-2-shape-invalid rejected
824    /// through [`ManifestError::VersaoInvalid`] past
825    /// [`semver::Version::parse`]) maps onto every load-bearing
826    /// downstream consumer the substrate carries — the [`Self::validate_versao`]
827    /// universal-axis validate gate at caixa-build time, the
828    /// [`crate::CaixaVersion::parse`] typed-wrapper resolver,
829    /// [`caixa-helm`]'s `Chart.yaml` `version:` / `appVersion:` fold,
830    /// [`caixa-flux`]'s `programs.yaml` entry `versao:` fold + the
831    /// `cluster_bundle` `GitRepository` `ref: { tag: v<versao> }`
832    /// derivation, [`caixa-mesh`]'s per-Aplicacao `programs.yaml` fan-
833    /// out entry `versao:` fold, [`caixa-feira`]'s `feira publish` git-
834    /// tag derivation (`format!("{prefix}{versao}")`), and every future
835    /// substrate renderer that emits an artifact keyed by the caixa's
836    /// pinned version.
837    ///
838    /// Prior to this lift the `.versao` field was accessed inline at a
839    /// dozen production sites across `caixa-core` (the universal-axis
840    /// [`Self::validate_versao`] gate + [`Dep::validate`]-adjacent
841    /// version-shape gates), `caixa-helm` (the `ChartYaml.version` /
842    /// `ChartYaml.app_version` folds), `caixa-flux` (the `programs.yaml`
843    /// entry `versao:` fold, the `cluster_bundle` `GitRepository` `ref:
844    /// { tag: v<versao> }` derivation), `caixa-mesh` (the per-Aplicacao
845    /// `programs.yaml` fan-out entry `versao:` fold), and `caixa-feira`
846    /// (the `feira publish` git-tag derivation + the `feira app graph` /
847    /// `feira app deploy` diagnostic renderers) — a dozen open-coded
848    /// field-accesses that expressed no compile-time link back to the
849    /// typed slot. A future extension of the `:versao` axis to a richer
850    /// author surface — a per-`:versao` structured `CaixaVersion` at the
851    /// storage layer (the substrate already carries a `CaixaVersion`
852    /// newtype at [`crate::version::CaixaVersion`], deferred until the
853    /// serde-transparent-newtype-through-DeriveTataraDomain path lands),
854    /// a per-registry `:versao` immutability overlay the M4 CR
855    /// materializer enforces per-CR, a promotion of the plain `String`
856    /// byte-string to a richer `PinnedVersao` newtype discriminated on
857    /// SemVer-2 pre-release / build-metadata presence — would have had
858    /// to be threaded through every open-coded copy in lockstep or the
859    /// validate gate and the dozen emit paths would silently disagree
860    /// on which version a given [`Caixa`] resolves to (an author's
861    /// `:versao "0.1.0"` would satisfy validate while one of the emit
862    /// paths silently rendered a drifted other version, or vice versa).
863    /// Lifting the resolution to a typed method on the substrate
864    /// primitive means every downstream consumer of the caixa's
865    /// per-`Caixa` pinned-version surface reaches for exactly one typed
866    /// dispatch — the resolver's accept-set migrates as a unit on any
867    /// future axis addition.
868    ///
869    /// Second outer top-level [`Caixa`] `&str`-return required-scalar
870    /// accessor — folds on the "outer [`Caixa`] `&str` required-scalar"
871    /// projection pattern the sibling per-`Caixa` [`Self::nome`]
872    /// (e6b7d97) opened. Sibling in shape to the peer per-`:membros`
873    /// [`crate::aplicacao::Membro::versao_requirement`] (4127bb6) /
874    /// per-`:children` [`crate::supervisor::ChildSpec::versao_requirement`]
875    /// (2c053c8) / per-`:upgrade-from` [`crate::UpgradeFromEntry::prior_versao`]
876    /// (75d27a8) per-sub-struct `:versao`-shaped `&str`-return accessors
877    /// on the sibling per-typed-slot version-carrier axes, extended here
878    /// to close the second outer top-level [`Caixa`] required-`&str`-
879    /// carrying axis so the two universal-axis identity-carrying
880    /// scalars every `defcaixa` form supplies (`:nome` + `:versao`)
881    /// share the same "one typed dispatch per axis" discipline. Named
882    /// `versao()` to match the storage field's name; the accessor's
883    /// identity maps onto the canonical CAIXA-SDLC §I vocabulary the
884    /// slot's docstring already carries.
885    #[must_use]
886    pub fn versao(&self) -> &str {
887        &self.versao
888    }
889
890    /// Substrate-canonical per-`Caixa` `:kind` universal-axis
891    /// closed-set-enum discriminant accessor every consumer of the top-
892    /// level manifest's kind axis keys off — returns the author-declared
893    /// `:kind` variant verbatim as a [`CaixaKind`], `Copy`-projected
894    /// from the typed slot's own [`CaixaKind`] storage. Non-optional
895    /// (`:kind` is a required-axis discriminant every `defcaixa` form
896    /// must supply alongside `:nome` / `:versao`; the [`Self::from_lisp`]
897    /// derive rejects an omitted / non-symbol `:kind` at parse time, so
898    /// a `Caixa` past parse definitionally carries a valid [`CaixaKind`]
899    /// variant).
900    ///
901    /// The `:kind` slot carries the universal-axis closed-set typed-
902    /// discriminant every substrate-side dispatch keys off (CAIXA-SDLC
903    /// §I — the primary shape gate every renderer / verifier /
904    /// operator branches on; the five variants `Biblioteca` /
905    /// `Binario` / `Servico` / `Supervisor` / `Aplicacao` partition
906    /// the caixa surface into disjoint runtime contracts) — the typed
907    /// slot's [`CaixaKind`] accept-set (parse-time-rejected non-symbol
908    /// values through the derive-macro's symbol-arm gate, exhaustively
909    /// matched at every downstream dispatch site) maps onto every
910    /// load-bearing downstream consumer the substrate carries:
911    ///
912    ///   - [`crate::render::require_kind`]'s per-renderer entry-gate
913    ///     predicate — the canonical two-line
914    ///     `require_kind(caixa, Servico)?` prelude every per-Servico
915    ///     renderer (`caixa-helm`, `caixa-flux`, the future `caixa-otel`
916    ///     / per-Servico OCI packager / M4 `wasm.pleme.io/v1alpha1/
917    ///     ComputeUnit` CR materializer) runs at its entry-point,
918    ///     alongside the [`crate::render::KindMismatch`] error carrier's
919    ///     `actual:` field the diagnostic surfaces to name the offending
920    ///     caixa's variant.
921    ///   - [`Self::aplicacao_view`]'s + [`Self::supervisor_view`]'s
922    ///     per-view kind-gate binding — the two `Option<TypedSpec>`
923    ///     `_view` composers that fold the flat mesh-slot / supervisor-
924    ///     slot columns into their typed sub-spec only when the kind
925    ///     matches (returns `None` otherwise); the future per-Servico
926    ///     M2-view composer (`servico_view`) will follow the same shape.
927    ///   - [`Self::declared_foreign_code_slots`]'s per-slot kind-
928    ///     coherence gate — the `!self.kind.requires_exe()` /
929    ///     `!self.kind.requires_servicos()` predicates that fence
930    ///     each code-surface slot from the wrong owning kind.
931    ///   - [`crate::LayoutInvariants::verify`]'s kind ↔ code-surface
932    ///     coherence gates — the six `caixa.kind == CaixaKind::X` /
933    ///     `caixa.kind != CaixaKind::X` predicates and the four kind-
934    ///     coherence error carriers (`SupervisorOwnsCode` /
935    ///     `AplicacaoOwnsCode` / `MeshSlotsOnNonAplicacao` /
936    ///     `SupervisorSlotsOnNonSupervisor` / `ServicoSlotsOnNonServico`
937    ///     / `ForeignCodeSlot`) which each name the offending caixa's
938    ///     variant in their `kind:` field.
939    ///
940    /// Prior to this lift the `.kind` field was accessed inline at
941    /// twenty-plus production sites across `caixa-core` (the
942    /// [`crate::render::require_kind`] entry-gate predicate + the
943    /// [`crate::render::KindMismatch`] `actual:` field, the two `_view`
944    /// composers, the `declared_foreign_code_slots` per-slot kind-
945    /// coherence gate, and the six [`crate::LayoutInvariants::verify`]
946    /// kind ↔ code-surface predicates + four error carriers) — a score
947    /// of open-coded field-accesses that expressed no compile-time link
948    /// back to the typed slot. A future extension of the `:kind` axis
949    /// to a richer author surface — a per-`:kind` sub-variant discriminant
950    /// (e.g. `Servico(ServicoRuntime)` splitting the current single
951    /// variant across the wasm-component / legacy-container / native-
952    /// binary runtime axes the M5 roadmap acknowledges), a per-cluster
953    /// kind-overlay the M4 CR materializer resolves per-CR (the
954    /// "cluster policy demotes `Aplicacao` to `Servico` on a single-
955    /// tenant cluster" arm), a promotion of the plain [`CaixaKind`]
956    /// enum to a richer `KindWithRuntime` discriminated on the
957    /// component-model world axis — would have had to be threaded
958    /// through every open-coded copy in lockstep or the entry gate,
959    /// the view composers, and the layout invariants would silently
960    /// disagree on which kind a given [`Caixa`] resolves to. Lifting
961    /// the resolution to a typed method on the substrate primitive
962    /// means every downstream consumer of the caixa's per-`Caixa`
963    /// kind surface reaches for exactly one typed dispatch — the
964    /// resolver's accept-set migrates as a unit on any future axis
965    /// addition.
966    ///
967    /// First outer top-level [`Caixa`] `Copy`-return required-enum-
968    /// discriminant accessor — opens the "outer [`Caixa`] `Copy`-return
969    /// required-discriminant" projection pattern. Sibling in shape to
970    /// the peer per-`:supervisor` [`crate::supervisor::SupervisorSpec::estrategia`]
971    /// (eafb619), per-`:placement` [`crate::aplicacao::Placement::estrategia`]
972    /// (921fe1b), and per-`:children` [`crate::supervisor::ChildSpec::restart`]
973    /// (dfb4a81) `Copy`-return closed-set-enum discriminant accessors
974    /// on the sibling nested-spec typed-slot discriminator axes,
975    /// extended here to the outer top-level [`Caixa`] universal-axis
976    /// surface. Named `kind()` to match the storage field's name;
977    /// the accessor's identity maps onto the canonical CAIXA-SDLC §I
978    /// vocabulary the slot's docstring already carries.
979    #[must_use]
980    pub fn kind(&self) -> CaixaKind {
981        self.kind
982    }
983
984    /// Substrate-canonical per-`Caixa` `:autores` universal-axis
985    /// maintainer-name-list slice-accessor every consumer of the top-
986    /// level manifest's maintainer axis keys off — returns the author-
987    /// declared `:autores` list verbatim as a `&[String]` slice-view over
988    /// the same backing buffer the raw `self.autores.as_slice()` field
989    /// access borrows from. Empty-list-carrying (`:autores` is a default-
990    /// empty axis every `defcaixa` form supplies with an empty `()` when
991    /// unset; the [`Self::from_lisp`] derive folds an omitted `:autores`
992    /// through `#[serde(default)]` to `Vec::new()`, so a `Caixa` past
993    /// parse definitionally carries a `Vec<String>` slot — possibly
994    /// empty — and the returned `&[String]` degenerates to an empty
995    /// slice on that arm without any silent `None` collapse).
996    ///
997    /// The `:autores` slot carries the universal-axis maintainer-name
998    /// list every kind of caixa emits under (CAIXA-SDLC §I — the author-
999    /// facing surface every `defcaixa` form supplies alongside `:nome` /
1000    /// `:versao` / `:kind`; the substrate-wide contact-carrying axis
1001    /// every downstream registry-facing artifact emits under) — the
1002    /// typed slot's `Vec<String>` accept-set (empty-per-entry rejected
1003    /// through [`ManifestError::AutorEmpty`], non-chart-maintainer-shape
1004    /// rejected through [`ManifestError::AutorInvalid`], cross-entry
1005    /// duplicate rejected through [`ManifestError::AutorDuplicate`]) maps
1006    /// onto every load-bearing downstream consumer the substrate carries
1007    /// — the [`Self::validate_autores`] universal-axis empty-per-entry +
1008    /// shape + duplicate gate at caixa-core/src/manifest.rs, the
1009    /// caixa-helm `build_chart_yaml` `maintainers:` fold at
1010    /// caixa-helm/src/lib.rs that walks each entry into a `Maintainer {
1011    /// name, email: None }` record, every future per-`Caixa` registry-
1012    /// facing renderer the CAIXA-SDLC §I roadmap acknowledges (the
1013    /// future `artifacthub.io/maintainers` `Chart.yaml` annotation the
1014    /// caixa-helm docstring alludes to at [`Self::validate_licenca`],
1015    /// the future per-cluster author-notification overlay the M4 CR
1016    /// materializer resolves per-CR).
1017    ///
1018    /// Prior to this lift the `.autores` field was accessed inline at
1019    /// two production sites — [`Self::validate_autores`]'s `for autor
1020    /// in &self.autores` walk that gates every entry through
1021    /// [`ManifestError::AutorEmpty`] / `AutorInvalid` / `AutorDuplicate`,
1022    /// and the caixa-helm `build_chart_yaml` `caixa.autores.iter().map(|a|
1023    /// Maintainer { name: a.clone(), email: None }).collect()` fold that
1024    /// materializes every entry into a `Chart.yaml` `maintainers:` row —
1025    /// two open-coded field-accesses that expressed no compile-time link
1026    /// back to the typed slot. A future extension of the `:autores` axis
1027    /// to a richer author surface — a per-`:autores` structured
1028    /// `Maintainer { name, email, url }` at the storage layer once the
1029    /// substrate absorbs `artifacthub.io/maintainers`' name+email+url
1030    /// tuple, a per-registry `:autores` allowlist the M4 CR materializer
1031    /// enforces per-CR (the "cluster policy demands every author declare
1032    /// an on-file `mailto:` contact" arm), a promotion of the plain
1033    /// `Vec<String>` byte-string list to a richer
1034    /// `Vec<ChartMaintainer>` newtype discriminated on the RFC-5322
1035    /// `<name> [<email>]` grammar the `is_chart_maintainer_name_shape`
1036    /// predicate already resolves through — would have had to be
1037    /// threaded through both open-coded copies in lockstep or the
1038    /// validate gate and the caixa-helm emit path would silently
1039    /// disagree on which authors a given [`Caixa`] resolves to (an
1040    /// author's `:autores ("alice" "bob")` would satisfy validate while
1041    /// the caixa-helm emit path silently rendered a drifted other
1042    /// maintainer list, or vice versa). Lifting the resolution to a
1043    /// typed method on the substrate primitive means every downstream
1044    /// consumer of the caixa's per-`Caixa` maintainer surface reaches
1045    /// for exactly one typed dispatch — the resolver's accept-set
1046    /// migrates as a unit on any future axis addition.
1047    ///
1048    /// First outer top-level [`Caixa`] `&[T]`-return slice-accessor —
1049    /// opens the "outer [`Caixa`] `&[T]` slice" projection pattern the
1050    /// sibling per-`Caixa` `:etiquetas` / `:deps` / `:deps-dev` / `:exe`
1051    /// / `:bibliotecas` / `:servicos` / `:upgrade-from` / `:children`
1052    /// future lifts fold on. Sibling in shape to the peer per-`:supervisor`
1053    /// [`crate::supervisor::SupervisorSpec::children`] (bc92bce), per-`:placement`
1054    /// [`crate::aplicacao::Placement::clusters`] (a6e18d7), per-`:membros`
1055    /// [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36), per-`:contratos`
1056    /// [`crate::aplicacao::AplicacaoSpec::contratos`] (0dcc926), and
1057    /// per-`:upgrade-from :instructions` [`crate::upgrade::UpgradeFromEntry::instructions`]
1058    /// (0137e5a) `&[T]`-return slice accessors on the sibling per-M2 /
1059    /// per-M3 typed-slot list axes, extended here to the outer top-level
1060    /// [`Caixa`] universal-axis surface. Returns `&[String]` (not
1061    /// `&Vec<String>`) because every downstream consumer of the author
1062    /// list treats it as a read-only sequence — the slice-view is the
1063    /// narrowest borrow that supports every present + roadmapped consumer
1064    /// (`.iter()`, `.len()`, `.is_empty()`) without leaking the backing
1065    /// `Vec`'s grow/push/reserve surface no consumer of the typed view
1066    /// reaches for (the storage-side `Vec` remains reachable through the
1067    /// `pub autores` field for the mutation-carrying serde round-trip and
1068    /// per-test fixture-mutation paths). Named `autores()` to match the
1069    /// storage field's name; the accessor's identity maps onto the
1070    /// canonical CAIXA-SDLC §I vocabulary the slot's docstring already
1071    /// carries.
1072    #[must_use]
1073    pub fn autores(&self) -> &[String] {
1074        self.autores.as_slice()
1075    }
1076
1077    /// Substrate-canonical per-`Caixa` `:etiquetas` universal-axis
1078    /// registry-search-tag-list slice-accessor every consumer of the
1079    /// top-level manifest's topical-tag axis keys off — returns the
1080    /// author-declared `:etiquetas` list verbatim as a `&[String]`
1081    /// slice-view over the same backing buffer the raw
1082    /// `self.etiquetas.as_slice()` field access borrows from. Empty-
1083    /// list-carrying (`:etiquetas` is a default-empty axis every
1084    /// `defcaixa` form supplies with an empty `()` when unset; the
1085    /// [`Self::from_lisp`] derive folds an omitted `:etiquetas` through
1086    /// `#[serde(default)]` to `Vec::new()`, so a `Caixa` past parse
1087    /// definitionally carries a `Vec<String>` slot — possibly empty —
1088    /// and the returned `&[String]` degenerates to an empty slice on
1089    /// that arm without any silent `None` collapse).
1090    ///
1091    /// The `:etiquetas` slot carries the universal-axis topical-tag
1092    /// list every kind of caixa emits under (CAIXA-SDLC §I — the
1093    /// author-facing surface every `defcaixa` form supplies alongside
1094    /// `:nome` / `:versao` / `:kind`; the substrate-wide registry-
1095    /// search-facing axis every downstream registry-facing artifact
1096    /// emits under) — the typed slot's `Vec<String>` accept-set
1097    /// (empty-per-entry rejected through [`ManifestError::EtiquetaEmpty`],
1098    /// non-chart-keyword-shape rejected through
1099    /// [`ManifestError::EtiquetaInvalid`], cross-entry duplicate
1100    /// rejected through [`ManifestError::EtiquetaDuplicate`]) maps onto
1101    /// every load-bearing downstream consumer the substrate carries —
1102    /// the [`Self::validate_etiquetas`] universal-axis empty-per-entry
1103    /// + shape + duplicate gate at caixa-core/src/manifest.rs, the
1104    /// caixa-helm `build_chart_yaml` `keywords:` fold at
1105    /// caixa-helm/src/lib.rs that walks each entry into the rendered
1106    /// `Chart.yaml` `keywords:` array (chained with the
1107    /// [`crate::LAREIRA_CHART_KEYWORDS`] substrate-wide floor set and
1108    /// dedup'd through a `BTreeSet` at emit time), every future per-
1109    /// `Caixa` registry-facing renderer the CAIXA-SDLC §I roadmap
1110    /// acknowledges (the future `artifacthub.io/keywords` `Chart.yaml`
1111    /// annotation, the future per-cluster tag-notification overlay the
1112    /// M4 CR materializer resolves per-CR).
1113    ///
1114    /// Prior to this lift the `.etiquetas` field was accessed inline at
1115    /// two production sites — [`Self::validate_etiquetas`]'s `for
1116    /// etiqueta in &self.etiquetas` walk that gates every entry through
1117    /// [`ManifestError::EtiquetaEmpty`] / `EtiquetaInvalid` /
1118    /// `EtiquetaDuplicate`, and the caixa-helm `build_chart_yaml`
1119    /// `caixa.etiquetas.iter().cloned().chain(...)` fold that
1120    /// materializes every entry into a `Chart.yaml` `keywords:` row —
1121    /// two open-coded field-accesses that expressed no compile-time
1122    /// link back to the typed slot. A future extension of the
1123    /// `:etiquetas` axis to a richer tag surface — a per-`:etiquetas`
1124    /// structured `ChartKeyword { name, uri, category }` at the storage
1125    /// layer once the substrate absorbs `artifacthub.io/keywords`
1126    /// richer tag tuple, a per-registry `:etiquetas` allowlist the M4
1127    /// CR materializer enforces per-CR (the "cluster policy demands
1128    /// every tag come from a substrate-approved taxonomy" arm), a
1129    /// promotion of the plain `Vec<String>` byte-string list to a
1130    /// richer `Vec<ChartKeyword>` newtype discriminated on the DNS-
1131    /// 1123-label-shaped grammar the `is_chart_keyword_shape` predicate
1132    /// already resolves through — would have had to be threaded through
1133    /// both open-coded copies in lockstep or the validate gate and the
1134    /// caixa-helm emit path would silently disagree on which tags a
1135    /// given [`Caixa`] resolves to (an author's `:etiquetas ("demo"
1136    /// "aplicacao")` would satisfy validate while the caixa-helm emit
1137    /// path silently rendered a drifted other keyword list, or vice
1138    /// versa). Lifting the resolution to a typed method on the
1139    /// substrate primitive means every downstream consumer of the
1140    /// caixa's per-`Caixa` topical-tag surface reaches for exactly one
1141    /// typed dispatch — the resolver's accept-set migrates as a unit
1142    /// on any future axis addition.
1143    ///
1144    /// Second outer top-level [`Caixa`] `&[T]`-return slice-accessor —
1145    /// folds on the "outer [`Caixa`] `&[T]` slice" projection pattern
1146    /// [`Self::autores`] (b5d813f) opened, sibling in shape and
1147    /// idiom. The remaining unlifted outer-`Caixa` slice-carrying axes
1148    /// (`:deps` / `:deps-dev` / `:exe` / `:bibliotecas` / `:servicos`
1149    /// / `:upgrade-from` / `:children` / `:membros` / `:contratos`)
1150    /// fold onto the same pattern in future lifts. Sibling in shape to
1151    /// the peer per-`:supervisor`
1152    /// [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
1153    /// per-`:placement` [`crate::aplicacao::Placement::clusters`]
1154    /// (a6e18d7), per-`:membros`
1155    /// [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
1156    /// per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
1157    /// (0dcc926), and per-`:upgrade-from :instructions`
1158    /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
1159    /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
1160    /// typed-slot list axes, extended here to the outer top-level
1161    /// [`Caixa`] universal-axis surface. Returns `&[String]` (not
1162    /// `&Vec<String>`) because every downstream consumer of the tag
1163    /// list treats it as a read-only sequence — the slice-view is the
1164    /// narrowest borrow that supports every present + roadmapped
1165    /// consumer (`.iter()`, `.len()`, `.is_empty()`) without leaking
1166    /// the backing `Vec`'s grow/push/reserve surface no consumer of
1167    /// the typed view reaches for (the storage-side `Vec` remains
1168    /// reachable through the `pub etiquetas` field for the mutation-
1169    /// carrying serde round-trip and per-test fixture-mutation paths).
1170    /// Named `etiquetas()` to match the storage field's name; the
1171    /// accessor's identity maps onto the canonical CAIXA-SDLC §I
1172    /// vocabulary the slot's docstring already carries.
1173    #[must_use]
1174    pub fn etiquetas(&self) -> &[String] {
1175        self.etiquetas.as_slice()
1176    }
1177
1178    /// Substrate-canonical per-`Caixa` `:bibliotecas` universal-axis
1179    /// library-source-path-list slice-accessor every consumer of the
1180    /// top-level manifest's Biblioteca-source axis keys off — returns
1181    /// the author-declared `:bibliotecas` list verbatim as a
1182    /// `&[String]` slice-view over the same backing buffer the raw
1183    /// `self.bibliotecas.as_slice()` field access borrows from. Empty-
1184    /// list-carrying (`:bibliotecas` is a default-empty axis every
1185    /// `defcaixa` form supplies with an empty `()` when unset; the
1186    /// [`Self::from_lisp`] derive folds an omitted `:bibliotecas`
1187    /// through `#[serde(default)]` to `Vec::new()`, so a `Caixa` past
1188    /// parse definitionally carries a `Vec<String>` slot — possibly
1189    /// empty — and the returned `&[String]` degenerates to an empty
1190    /// slice on that arm without any silent `None` collapse).
1191    ///
1192    /// The `:bibliotecas` slot carries the universal-axis lisp-library
1193    /// entry-path list every `:kind Biblioteca` caixa emits under
1194    /// (CAIXA-SDLC §I — the author-facing surface every `defcaixa`
1195    /// form supplies alongside `:nome` / `:versao` / `:kind`; the
1196    /// substrate-wide library-carrier axis every downstream
1197    /// authoring-facing consumer keys off) — the typed slot's
1198    /// `Vec<String>` accept-set (empty-per-entry rejected through
1199    /// [`ManifestError::CodePathEmpty { slot: ":bibliotecas" }`],
1200    /// non-sandboxed-relative-shape rejected through
1201    /// [`ManifestError::CodePathShape`], non-`.lisp`-extension rejected
1202    /// through [`ManifestError::CodePathNonLispExtension`], cross-entry
1203    /// duplicate rejected through [`ManifestError::CodePathDuplicate`])
1204    /// maps onto every load-bearing downstream consumer the substrate
1205    /// carries — the [`crate::LayoutInvariants`] Biblioteca-arm
1206    /// empty-check + per-entry file-exists loop at
1207    /// caixa-core/src/layout.rs that gates each entry through
1208    /// [`crate::LayoutError::MissingLib`] / `MissingEntry`, the
1209    /// [`Self::validate_code_paths`] per-slot shape gate at
1210    /// caixa-core/src/manifest.rs that walks each entry through the
1211    /// sandbox-relative / `.lisp`-extension / cross-entry duplicate
1212    /// gates, the `feira build` per-entry `tatara_lisp::read` parse
1213    /// walk at caixa-feira/src/cmd/build.rs that phase-1-checks each
1214    /// declared library file for lexical / structural errors before
1215    /// downstream `importar` resolution, every future per-`Caixa`
1216    /// library-facing renderer the CAIXA-SDLC §I roadmap acknowledges
1217    /// (the future `tatara-lispc` compilation entry the docstring at
1218    /// caixa-feira/src/cmd/build.rs alludes to, the future per-cluster
1219    /// bytecode-caching overlay the M4 CR materializer resolves per-CR,
1220    /// the future `caixa-lsp` per-library semantic-token stream the
1221    /// caixa-lsp docstring roadmaps).
1222    ///
1223    /// Prior to this lift the `.bibliotecas` field was accessed inline
1224    /// at three production sites — [`crate::LayoutInvariants`]'s
1225    /// `caixa.bibliotecas.is_empty()` `MissingLib`-arm gate + `for p
1226    /// in &caixa.bibliotecas` `MissingEntry` walk that gates each
1227    /// declared library path through the on-disk-existence check,
1228    /// the compound-code-path `has_code = !caixa.bibliotecas.is_empty()
1229    /// || !caixa.exe.is_empty() || !caixa.servicos.is_empty()` OR-fold
1230    /// on the [`crate::LayoutError::SupervisorOwnsCode`] /
1231    /// `AplicacaoOwnsCode` kind-coherence gate, and the `feira build`
1232    /// per-entry `for entry in &caixa.bibliotecas` + `caixa.bibliotecas.
1233    /// len()` phase-1 tatara-lispc-precursor parse walk — three open-
1234    /// coded field-accesses that expressed no compile-time link back
1235    /// to the typed slot. A future extension of the `:bibliotecas`
1236    /// axis to a richer library surface — a per-`:bibliotecas`
1237    /// structured `BibliotecaEntry { path, edition, exports }` at the
1238    /// storage layer once the substrate absorbs the per-library
1239    /// language-edition + explicit-exports tuple the tatara-lisp
1240    /// module-system roadmap acknowledges, a per-registry
1241    /// `:bibliotecas` allowlist the M4 CR materializer enforces
1242    /// per-CR (the "cluster policy demands every biblioteca declare
1243    /// its own :edicao" arm), a promotion of the plain `Vec<String>`
1244    /// byte-string list to a richer `Vec<LibraryPath>` newtype
1245    /// discriminated on the `lib/<nome>.lisp`-shape grammar the
1246    /// [`crate::render::is_sandboxed_relative_path`] +
1247    /// [`crate::render::is_lisp_extension`] predicates already resolve
1248    /// through — would have had to be threaded through all three
1249    /// open-coded copies in lockstep or the layout gate, the shape
1250    /// validator, and the `feira build` phase-1 parse walk would
1251    /// silently disagree on which library paths a given [`Caixa`]
1252    /// resolves to (an author's `:bibliotecas ("lib/foo.lisp"
1253    /// "lib/bar.lisp")` would satisfy layout while `feira build`
1254    /// silently parsed a drifted other list, or vice versa). Lifting
1255    /// the resolution to a typed method on the substrate primitive
1256    /// means every downstream consumer of the caixa's per-`Caixa`
1257    /// library-source surface reaches for exactly one typed dispatch
1258    /// — the resolver's accept-set migrates as a unit on any future
1259    /// axis addition.
1260    ///
1261    /// Third outer top-level [`Caixa`] `&[T]`-return slice-accessor —
1262    /// folds on the "outer [`Caixa`] `&[T]` slice" projection pattern
1263    /// [`Self::autores`] (b5d813f) opened and [`Self::etiquetas`]
1264    /// (78c7d3c) folded on, sibling in shape and idiom. The remaining
1265    /// unlifted outer-`Caixa` slice-carrying axes (`:deps` /
1266    /// `:deps-dev` / `:exe` / `:servicos` / `:upgrade-from` /
1267    /// `:children` / `:membros` / `:contratos`) fold onto the same
1268    /// pattern in future lifts. Sibling in shape to the peer
1269    /// per-`:supervisor`
1270    /// [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
1271    /// per-`:placement` [`crate::aplicacao::Placement::clusters`]
1272    /// (a6e18d7), per-`:membros`
1273    /// [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
1274    /// per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
1275    /// (0dcc926), and per-`:upgrade-from :instructions`
1276    /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
1277    /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
1278    /// typed-slot list axes, extended here to the outer top-level
1279    /// [`Caixa`] universal-axis surface. Returns `&[String]` (not
1280    /// `&Vec<String>`) because every downstream consumer of the
1281    /// library-source list treats it as a read-only sequence — the
1282    /// slice-view is the narrowest borrow that supports every
1283    /// present + roadmapped consumer (`.iter()`, `.len()`,
1284    /// `.is_empty()`) without leaking the backing `Vec`'s
1285    /// grow/push/reserve surface no consumer of the typed view
1286    /// reaches for (the storage-side `Vec` remains reachable through
1287    /// the `pub bibliotecas` field for the mutation-carrying serde
1288    /// round-trip and per-test fixture-mutation paths). Named
1289    /// `bibliotecas()` to match the storage field's name; the
1290    /// accessor's identity maps onto the canonical CAIXA-SDLC §I
1291    /// vocabulary the slot's docstring already carries.
1292    #[must_use]
1293    pub fn bibliotecas(&self) -> &[String] {
1294        self.bibliotecas.as_slice()
1295    }
1296
1297    /// Substrate-canonical per-`Caixa` `:exe` universal-axis
1298    /// nix-built-executable-entry-path-list slice-accessor every consumer
1299    /// of the top-level manifest's Binario-executable axis keys off —
1300    /// returns the author-declared `:exe` list verbatim as a `&[String]`
1301    /// slice-view over the same backing buffer the raw
1302    /// `self.exe.as_slice()` field access borrows from. Empty-list-
1303    /// carrying (`:exe` is a default-empty axis every `defcaixa` form
1304    /// supplies with an empty `()` when unset; the [`Self::from_lisp`]
1305    /// derive folds an omitted `:exe` through `#[serde(default)]` to
1306    /// `Vec::new()`, so a `Caixa` past parse definitionally carries a
1307    /// `Vec<String>` slot — possibly empty — and the returned `&[String]`
1308    /// degenerates to an empty slice on that arm without any silent
1309    /// `None` collapse).
1310    ///
1311    /// The `:exe` slot carries the universal-axis nix-built executable
1312    /// entry-path list every `:kind Binario` caixa emits under
1313    /// (CAIXA-SDLC §I — the author-facing surface every `defcaixa`
1314    /// form supplies alongside `:nome` / `:versao` / `:kind`; the
1315    /// substrate-wide `exe/`-directory-fenced entry-carrier axis every
1316    /// downstream flake-build-facing consumer keys off) — the typed
1317    /// slot's `Vec<String>` accept-set (empty-per-entry rejected
1318    /// through [`ManifestError::CodePathEmpty { slot: ":exe" }`],
1319    /// non-sandboxed-relative-shape rejected through
1320    /// [`ManifestError::CodePathShape`], cross-entry duplicate rejected
1321    /// through [`ManifestError::CodePathDuplicate`], out-of-`exe/`-
1322    /// directory paths rejected past the layout's
1323    /// [`crate::LayoutError::ExeOutsideDir`] `starts_with` fence) maps
1324    /// onto every load-bearing downstream consumer the substrate carries
1325    /// — the [`crate::LayoutInvariants`] Binario-arm empty-check +
1326    /// per-entry file-exists + `exe/`-directory-fence loop at
1327    /// caixa-core/src/layout.rs that gates each entry through
1328    /// [`crate::LayoutError::BinarioWithoutExe`] / `MissingEntry` /
1329    /// `ExeOutsideDir`, the compound `has_code` OR-fold on the
1330    /// [`crate::LayoutError::SupervisorOwnsCode`] /
1331    /// [`crate::LayoutError::AplicacaoOwnsCode`] kind-coherence gate
1332    /// that fences code-surface slots off from the two no-code kinds,
1333    /// [`Self::declared_foreign_code_slots`]'s `!self.exe.is_empty()`
1334    /// arm on the [`crate::LayoutError::ForeignCodeSlot`] gate that
1335    /// fences the `:exe` code surface off from every non-Binario code-
1336    /// running kind, [`Self::validate_code_paths`]'s per-slot shape gate
1337    /// that walks each entry through the sandbox-relative / cross-entry
1338    /// duplicate gates, every future per-`Caixa` executable-facing
1339    /// renderer the CAIXA-SDLC §I roadmap acknowledges (the future
1340    /// `caixa-flake` per-Binario `packages.<system>.<nome>` derivation
1341    /// entry the caixa-flake docstring roadmaps, the future per-cluster
1342    /// `nix-store` overlay the M4 CR materializer resolves per-CR, the
1343    /// future `feira nix` per-executable Binario-target emit path).
1344    ///
1345    /// Prior to this lift the `.exe` field was accessed inline at three
1346    /// production sites — the compound-code-path `has_code =
1347    /// !caixa.bibliotecas().is_empty() || !caixa.exe.is_empty() ||
1348    /// !caixa.servicos.is_empty()` OR-fold on the
1349    /// [`crate::LayoutError::SupervisorOwnsCode`] /
1350    /// `AplicacaoOwnsCode` kind-coherence gate, the Binario-arm
1351    /// `caixa.exe.is_empty()` [`crate::LayoutError::BinarioWithoutExe`]
1352    /// gate, the per-entry `for p in &caixa.exe`
1353    /// `MissingEntry`/`ExeOutsideDir` walk, and the
1354    /// [`Self::declared_foreign_code_slots`]'s
1355    /// `!self.exe.is_empty()` arm on the `ForeignCodeSlot` gate — four
1356    /// open-coded field-accesses that expressed no compile-time link
1357    /// back to the typed slot. A future extension of the `:exe` axis
1358    /// to a richer executable surface — a per-`:exe` structured
1359    /// `BinarioEntry { path, wrapper, capabilities }` at the storage
1360    /// layer once the substrate absorbs the per-executable
1361    /// nix-wrapper + linux-capabilities tuple the CAIXA-SDLC §I
1362    /// executable roadmap acknowledges, a per-registry `:exe` allowlist
1363    /// the M4 CR materializer enforces per-CR (the "cluster policy
1364    /// demands every Binario declare an explicit `:wrapper`" arm), a
1365    /// promotion of the plain `Vec<String>` byte-string list to a
1366    /// richer `Vec<ExecutablePath>` newtype discriminated on the
1367    /// `exe/<nome>`-shape grammar the layout's `starts_with(exe_dir)`
1368    /// fence already resolves through — would have had to be threaded
1369    /// through all four open-coded copies in lockstep or the layout
1370    /// gate, the shape validator, and the `feira nix` emit path would
1371    /// silently disagree on which executable paths a given [`Caixa`]
1372    /// resolves to (an author's `:exe ("exe/cli" "exe/serve")` would
1373    /// satisfy layout while `feira nix` silently packaged a drifted
1374    /// other list, or vice versa). Lifting the resolution to a typed
1375    /// method on the substrate primitive means every downstream
1376    /// consumer of the caixa's per-`Caixa` executable-source surface
1377    /// reaches for exactly one typed dispatch — the resolver's accept-
1378    /// set migrates as a unit on any future axis addition.
1379    ///
1380    /// Fourth outer top-level [`Caixa`] `&[T]`-return slice-accessor —
1381    /// folds on the "outer [`Caixa`] `&[T]` slice" projection pattern
1382    /// [`Self::autores`] (b5d813f) opened, [`Self::etiquetas`]
1383    /// (78c7d3c) folded on, and [`Self::bibliotecas`] (8a36c23) closed
1384    /// the universal-axis text-tag family of. Opens the outer-`Caixa`
1385    /// foreign-code-slot `&[T]` sub-family the sibling `:servicos`
1386    /// future lift closes onto (per the trio of code-surface list slots
1387    /// the [`Self::validate_code_paths`] per-slot dispatch tuple
1388    /// already carries — `:bibliotecas` + `:exe` + `:servicos`, of which
1389    /// `:bibliotecas` landed at 8a36c23 and `:servicos` remains as the
1390    /// last unlifted code-surface slot). Sibling in shape to the peer
1391    /// per-`:supervisor` [`crate::supervisor::SupervisorSpec::children`]
1392    /// (bc92bce), per-`:placement`
1393    /// [`crate::aplicacao::Placement::clusters`] (a6e18d7),
1394    /// per-`:membros` [`crate::aplicacao::AplicacaoSpec::membros`]
1395    /// (6c77e36), per-`:contratos`
1396    /// [`crate::aplicacao::AplicacaoSpec::contratos`] (0dcc926), and
1397    /// per-`:upgrade-from :instructions`
1398    /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
1399    /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
1400    /// typed-slot list axes, extended here to the outer top-level
1401    /// [`Caixa`] universal-axis surface. Returns `&[String]` (not
1402    /// `&Vec<String>`) because every downstream consumer of the
1403    /// executable-source list treats it as a read-only sequence — the
1404    /// slice-view is the narrowest borrow that supports every
1405    /// present + roadmapped consumer (`.iter()`, `.len()`,
1406    /// `.is_empty()`) without leaking the backing `Vec`'s
1407    /// grow/push/reserve surface no consumer of the typed view
1408    /// reaches for (the storage-side `Vec` remains reachable through
1409    /// the `pub exe` field for the mutation-carrying serde
1410    /// round-trip and per-test fixture-mutation paths). Named `exe()`
1411    /// to match the storage field's name; the accessor's identity
1412    /// maps onto the canonical CAIXA-SDLC §I vocabulary the slot's
1413    /// docstring already carries.
1414    #[must_use]
1415    pub fn exe(&self) -> &[String] {
1416        self.exe.as_slice()
1417    }
1418
1419    /// Substrate-canonical per-`Caixa` `:servicos` universal-axis
1420    /// ComputeUnit-CR-YAML-entry-path-list slice-accessor every consumer
1421    /// of the top-level manifest's Servico-component axis keys off —
1422    /// returns the author-declared `:servicos` list verbatim as a
1423    /// `&[String]` slice-view over the same backing buffer the raw
1424    /// `self.servicos.as_slice()` field access borrows from. Empty-list-
1425    /// carrying (`:servicos` is a default-empty axis every `defcaixa`
1426    /// form supplies with an empty `()` when unset; the
1427    /// [`Self::from_lisp`] derive folds an omitted `:servicos` through
1428    /// `#[serde(default)]` to `Vec::new()`, so a `Caixa` past parse
1429    /// definitionally carries a `Vec<String>` slot — possibly empty —
1430    /// and the returned `&[String]` degenerates to an empty slice on
1431    /// that arm without any silent `None` collapse).
1432    ///
1433    /// The `:servicos` slot carries the universal-axis
1434    /// `.computeunit.yaml` ComputeUnit-CR entry-path list every
1435    /// `:kind Servico` caixa emits under (CAIXA-SDLC §I — the
1436    /// author-facing surface every `defcaixa` form supplies alongside
1437    /// `:nome` / `:versao` / `:kind`; the substrate-wide
1438    /// `servicos/`-directory-fenced entry-carrier axis every downstream
1439    /// Servico-facing renderer keys off) — the typed slot's
1440    /// `Vec<String>` accept-set (empty-per-entry rejected through
1441    /// [`ManifestError::CodePathEmpty { slot: ":servicos" }`],
1442    /// non-sandboxed-relative-shape rejected through
1443    /// [`ManifestError::CodePathShape`], non-`.computeunit.yaml`
1444    /// extension rejected through
1445    /// [`ManifestError::CodePathNonComputeUnitYamlExtension`], cross-
1446    /// entry duplicate rejected through
1447    /// [`ManifestError::CodePathDuplicate`], `len != 1` rejected by the
1448    /// V0 [`crate::ServicoCountMismatch`] gate on the per-Servico
1449    /// renderer entry-points, out-of-`servicos/`-directory paths
1450    /// rejected past the layout's [`crate::LayoutError::ServicoOutsideDir`]
1451    /// `starts_with` fence) maps onto every load-bearing downstream
1452    /// consumer the substrate carries — the [`crate::LayoutInvariants`]
1453    /// Servico-arm empty-check + per-entry file-exists + `servicos/`-
1454    /// directory-fence loop at caixa-core/src/layout.rs that gates each
1455    /// entry through [`crate::LayoutError::ServicoWithoutServicos`] /
1456    /// `MissingEntry` / `ServicoOutsideDir`, the compound `has_code`
1457    /// OR-fold on the [`crate::LayoutError::SupervisorOwnsCode`] /
1458    /// [`crate::LayoutError::AplicacaoOwnsCode`] kind-coherence gate
1459    /// that fences code-surface slots off from the two no-code kinds,
1460    /// [`Self::declared_foreign_code_slots`]'s
1461    /// `!self.servicos.is_empty()` arm on the
1462    /// [`crate::LayoutError::ForeignCodeSlot`] gate that fences the
1463    /// `:servicos` code surface off from every non-Servico code-running
1464    /// kind, [`Self::validate_code_paths`]'s per-slot shape gate that
1465    /// walks each entry through the sandbox-relative / `.computeunit.
1466    /// yaml`-extension / cross-entry duplicate gates, the
1467    /// [`crate::require_single_servico`] V0 singularity gate every
1468    /// per-Servico renderer entry-point runs through
1469    /// [`crate::require_v0_servico_shape`], the `feira chart` /
1470    /// `feira deploy` per-verb `first_servico_path` walk at
1471    /// caixa-feira/src/cmd/chart.rs that resolves the singleton
1472    /// ComputeUnit-CR file, every future per-`Caixa` Servico-facing
1473    /// renderer the CAIXA-SDLC §I roadmap acknowledges (the future
1474    /// per-Servico OCI packager, the future M4
1475    /// `wasm.pleme.io/v1alpha1/ComputeUnit` CR materializer, the future
1476    /// per-Servico OTel collector-config emit).
1477    ///
1478    /// Prior to this lift the `.servicos` field was accessed inline at
1479    /// five production sites — the compound-code-path `has_code =
1480    /// !caixa.bibliotecas().is_empty() || !caixa.exe().is_empty() ||
1481    /// !caixa.servicos.is_empty()` OR-fold on the
1482    /// [`crate::LayoutError::SupervisorOwnsCode`] /
1483    /// `AplicacaoOwnsCode` kind-coherence gate, the Servico-arm
1484    /// `caixa.servicos.is_empty()`
1485    /// [`crate::LayoutError::ServicoWithoutServicos`] gate, the
1486    /// per-entry `for p in &caixa.servicos`
1487    /// `MissingEntry`/`ServicoOutsideDir` walk, the
1488    /// [`Self::declared_foreign_code_slots`]'s
1489    /// `!self.servicos.is_empty()` arm on the `ForeignCodeSlot` gate,
1490    /// and the [`crate::require_single_servico`] V0 count gate's
1491    /// `caixa.servicos.len() == 1` / `caixa.servicos.len()` count
1492    /// projection (both the accept-arm predicate and the
1493    /// diagnostic-carrying `ServicoCountMismatch { count }`
1494    /// projection) — five open-coded field-accesses across three
1495    /// crates that expressed no compile-time link back to the typed
1496    /// slot. A future extension of the `:servicos` axis to a richer
1497    /// component surface — a per-`:servicos` structured
1498    /// `ServicoEntry { path, world, capabilities }` at the storage
1499    /// layer once the substrate absorbs the per-component WIT-world +
1500    /// capability-set tuple the CAIXA-SDLC §I Servico roadmap
1501    /// acknowledges, a per-registry `:servicos` allowlist the M4 CR
1502    /// materializer enforces per-CR (the "cluster policy demands every
1503    /// Servico declare an explicit `:world`" arm), a promotion of the
1504    /// plain `Vec<String>` byte-string list to a richer
1505    /// `Vec<ComputeUnitPath>` newtype discriminated on the
1506    /// `servicos/<nome>.computeunit.yaml`-shape grammar the layout's
1507    /// `starts_with(servicos_dir)` fence and the
1508    /// [`crate::render::is_computeunit_yaml_extension`] predicate
1509    /// already resolve through, a promotion of the V0 singleton
1510    /// contract to a multi-component `Vec<ComputeUnitPath>` past the M5
1511    /// component-model multi-world boundary — would have had to be
1512    /// threaded through all five open-coded copies in lockstep or the
1513    /// layout gate, the shape validator, the V0 count gate, and the
1514    /// `feira chart` / `feira deploy` entry-point walks would silently
1515    /// disagree on which ComputeUnit-CR paths a given [`Caixa`]
1516    /// resolves to (an author's `:servicos ("servicos/foo.computeunit.
1517    /// yaml")` would satisfy layout while `feira chart` silently
1518    /// packaged a drifted other list, or vice versa). Lifting the
1519    /// resolution to a typed method on the substrate primitive means
1520    /// every downstream consumer of the caixa's per-`Caixa`
1521    /// ComputeUnit-CR-source surface reaches for exactly one typed
1522    /// dispatch — the resolver's accept-set migrates as a unit on any
1523    /// future axis addition.
1524    ///
1525    /// Fifth and final outer top-level [`Caixa`] `&[T]`-return slice-
1526    /// accessor — folds on the "outer [`Caixa`] `&[T]` slice"
1527    /// projection pattern [`Self::autores`] (b5d813f) opened,
1528    /// [`Self::etiquetas`] (78c7d3c) folded on, [`Self::bibliotecas`]
1529    /// (8a36c23) closed the universal-axis text-tag family of, and
1530    /// [`Self::exe`] (65d9527) opened the foreign-code-slot sub-family
1531    /// of. Closes the outer-`Caixa` foreign-code-slot `&[T]` sub-family
1532    /// — with `:bibliotecas`, `:exe`, and `:servicos` now each carrying
1533    /// a substrate-canonical slice accessor, the trio of code-surface
1534    /// list slots the [`Self::validate_code_paths`] per-slot dispatch
1535    /// tuple carries is complete on the typed dispatch surface (the
1536    /// internal `[(":bibliotecas", &self.bibliotecas, ..), (":exe",
1537    /// &self.exe, ..), (":servicos", &self.servicos, ..)]` per-slot
1538    /// dispatch tuple's homogeneous `&Vec<String>`-typed shape blocks a
1539    /// per-element accessor swap in isolation — a future companion lift
1540    /// promotes the tuple's element type to `&[String]` and threads the
1541    /// triple of typed dispatches through as a unit). Sibling in shape
1542    /// to the peer per-`:supervisor`
1543    /// [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
1544    /// per-`:placement` [`crate::aplicacao::Placement::clusters`]
1545    /// (a6e18d7), per-`:membros`
1546    /// [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
1547    /// per-`:contratos`
1548    /// [`crate::aplicacao::AplicacaoSpec::contratos`] (0dcc926), and
1549    /// per-`:upgrade-from :instructions`
1550    /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
1551    /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
1552    /// typed-slot list axes, extended here to the outer top-level
1553    /// [`Caixa`] universal-axis surface. Returns `&[String]` (not
1554    /// `&Vec<String>`) because every downstream consumer of the
1555    /// ComputeUnit-CR-source list treats it as a read-only sequence —
1556    /// the slice-view is the narrowest borrow that supports every
1557    /// present + roadmapped consumer (`.iter()`, `.len()`,
1558    /// `.is_empty()`, `.first()`) without leaking the backing `Vec`'s
1559    /// grow/push/reserve surface no consumer of the typed view reaches
1560    /// for (the storage-side `Vec` remains reachable through the
1561    /// `pub servicos` field for the mutation-carrying serde round-trip
1562    /// and per-test fixture-mutation paths, and for the
1563    /// [`Self::validate_code_paths`] per-slot dispatch tuple whose
1564    /// homogeneous-element-type shape carries the raw field access
1565    /// until the trio-closure lift promotes the tuple as a unit).
1566    /// Named `servicos()` to match the storage field's name; the
1567    /// accessor's identity maps onto the canonical CAIXA-SDLC §I
1568    /// vocabulary the slot's docstring already carries.
1569    #[must_use]
1570    pub fn servicos(&self) -> &[String] {
1571        self.servicos.as_slice()
1572    }
1573
1574    /// Substrate-canonical per-`Caixa` `:deps` universal-axis
1575    /// runtime-dependency-declaration-list slice-accessor every consumer
1576    /// of the top-level manifest's runtime-dep-graph axis keys off —
1577    /// returns the author-declared `:deps` list verbatim as a `&[Dep]`
1578    /// slice-view over the same backing buffer the raw
1579    /// `self.deps.as_slice()` field access borrows from. Empty-list-
1580    /// carrying (`:deps` is a default-empty axis every `defcaixa` form
1581    /// supplies with an empty `()` when unset; the [`Self::from_lisp`]
1582    /// derive folds an omitted `:deps` through `#[serde(default)]` to
1583    /// `Vec::new()`, so a `Caixa` past parse definitionally carries a
1584    /// `Vec<Dep>` slot — possibly empty — and the returned `&[Dep]`
1585    /// degenerates to an empty slice on that arm without any silent
1586    /// `None` collapse).
1587    ///
1588    /// The `:deps` slot carries the universal-axis runtime dependency
1589    /// list every kind of caixa emits under (CAIXA-SDLC §I — the author-
1590    /// facing surface every `defcaixa` form supplies alongside `:nome` /
1591    /// `:versao` / `:kind`; the substrate-wide runtime-closure-input axis
1592    /// every downstream resolver-facing artifact emits under) — the
1593    /// typed slot's `Vec<Dep>` accept-set (empty-`:nome` rejected through
1594    /// [`DepError::NomeEmpty`], non-DNS-1123-label `:nome` rejected
1595    /// through [`DepError::NomeInvalid`], malformed `:versao` rejected
1596    /// through [`DepError::VersaoInvalid`], empty `:fonte.repo` rejected
1597    /// through [`DepError::FonteRepoEmpty`], within-list duplicate `:nome`
1598    /// rejected through [`DepError::DuplicateNome { list: ":deps" }`])
1599    /// maps onto every load-bearing downstream consumer the substrate
1600    /// carries — the [`Self::validate_deps`] per-entry
1601    /// [`Dep::validate`] + within-list dedup walk at
1602    /// caixa-core/src/manifest.rs, the [`crate::dep::validate_no_self_dep`]
1603    /// cross-list self-reference gate at caixa-core/src/layout.rs that
1604    /// checks each entry against the caixa's own `:nome`, the
1605    /// caixa-resolver `for dep in &root.deps` closure walk at
1606    /// caixa-resolver/src/resolve.rs that seeds every git-clone target
1607    /// through the resolver's [`crate::Dep`]-keyed pipeline, the
1608    /// caixa-crd `caixa.deps.iter().map(dep_into_ref).collect()` fold at
1609    /// caixa-crd/src/conversion.rs that materializes each entry into the
1610    /// K8s `Caixa` CR's `spec.deps` field, every future per-`Caixa`
1611    /// resolver-facing renderer the CAIXA-SDLC §I roadmap acknowledges
1612    /// (the future per-cluster runtime-closure-audit overlay the M4 CR
1613    /// materializer resolves per-CR, the future `lacre.lisp` BLAKE3-
1614    /// closure emit walk the caixa-resolver docstring roadmaps).
1615    ///
1616    /// First outer top-level [`Caixa`] `&[Dep]`-return slice-accessor —
1617    /// opens the outer-`Caixa` dependency-slot `&[Dep]` sub-family the
1618    /// sibling `:deps-dev` future lift closes on. Peer of the closed
1619    /// outer-`Caixa` foreign-code-slot `&[String]` sub-family
1620    /// ([`Self::bibliotecas`] 8a36c23, [`Self::exe`] 65d9527,
1621    /// [`Self::servicos`] 611f78b) and the outer-`Caixa` universal-axis
1622    /// text-tag family ([`Self::autores`] b5d813f, [`Self::etiquetas`]
1623    /// 78c7d3c) — extends the "outer [`Caixa`] `&[T]` slice" projection
1624    /// pattern onto a novel element-type axis (`Dep` composite vs the
1625    /// prior sibling family's `String` scalar). Sibling in shape to the
1626    /// peer per-`:supervisor`
1627    /// [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
1628    /// per-`:placement` [`crate::aplicacao::Placement::clusters`]
1629    /// (a6e18d7), per-`:membros`
1630    /// [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
1631    /// per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
1632    /// (0dcc926), and per-`:upgrade-from :instructions`
1633    /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
1634    /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
1635    /// typed-slot list axes, extended here to the outer top-level
1636    /// [`Caixa`] universal-axis dep-graph surface. Returns `&[Dep]`
1637    /// (not `&Vec<Dep>`) because every downstream consumer of the
1638    /// runtime-dep list treats it as a read-only sequence — the slice-
1639    /// view is the narrowest borrow that supports every present +
1640    /// roadmapped consumer (`.iter()`, `.len()`, `.is_empty()`) without
1641    /// leaking the backing `Vec`'s grow/push/reserve surface no consumer
1642    /// of the typed view reaches for (the storage-side `Vec` remains
1643    /// reachable through the `pub deps` field for the mutation-carrying
1644    /// serde round-trip and per-test fixture-mutation paths). Named
1645    /// `deps()` to match the storage field's name; the accessor's
1646    /// identity maps onto the canonical CAIXA-SDLC §I vocabulary the
1647    /// slot's docstring already carries.
1648    #[must_use]
1649    pub fn deps(&self) -> &[Dep] {
1650        self.deps.as_slice()
1651    }
1652
1653    /// Substrate-canonical per-`Caixa` `:deps-dev` universal-axis
1654    /// development-only-dependency-declaration-list slice-accessor every
1655    /// consumer of the top-level manifest's dev-dep-graph axis keys off —
1656    /// returns the author-declared `:deps-dev` list verbatim as a `&[Dep]`
1657    /// slice-view over the same backing buffer the raw
1658    /// `self.deps_dev.as_slice()` field access borrows from. Empty-list-
1659    /// carrying (`:deps-dev` is a default-empty axis every `defcaixa`
1660    /// form supplies with an empty `()` when unset; the
1661    /// [`Self::from_lisp`] derive folds an omitted `:deps-dev` through
1662    /// `#[serde(default)]` to `Vec::new()`, so a `Caixa` past parse
1663    /// definitionally carries a `Vec<Dep>` slot — possibly empty — and
1664    /// the returned `&[Dep]` degenerates to an empty slice on that arm
1665    /// without any silent `None` collapse).
1666    ///
1667    /// The `:deps-dev` slot carries the universal-axis dev-only
1668    /// dependency list every kind of caixa emits under (CAIXA-SDLC §I —
1669    /// the author-facing sibling of `:deps` that every `defcaixa` form
1670    /// supplies to declare tests / lint / bench closures the runtime
1671    /// `:deps` axis does not carry; the substrate-wide dev-closure-input
1672    /// axis every downstream test-facing artifact emits under, matching
1673    /// Cargo's `[dev-dependencies]` table's dev-time-only visibility
1674    /// contract) — the typed slot's `Vec<Dep>` accept-set (empty-`:nome`
1675    /// rejected through [`DepError::NomeEmpty`], non-DNS-1123-label
1676    /// `:nome` rejected through [`DepError::NomeInvalid`], malformed
1677    /// `:versao` rejected through [`DepError::VersaoInvalid`], empty
1678    /// `:fonte.repo` rejected through [`DepError::FonteRepoEmpty`],
1679    /// within-list duplicate `:nome` rejected through
1680    /// [`DepError::DuplicateNome { list: ":deps-dev" }`]) maps onto every
1681    /// load-bearing downstream consumer the substrate carries — the
1682    /// [`Self::validate_deps`] per-entry [`Dep::validate`] + within-list
1683    /// dedup walk at caixa-core/src/manifest.rs, the
1684    /// [`crate::dep::validate_no_self_dep`] cross-list self-reference
1685    /// gate at caixa-core/src/layout.rs that checks each entry against
1686    /// the caixa's own `:nome`, the caixa-resolver
1687    /// `for dep in &root.deps_dev` closure walk at
1688    /// caixa-resolver/src/resolve.rs that seeds every dev-only git-clone
1689    /// target through the resolver's [`crate::Dep`]-keyed pipeline, and
1690    /// every future per-`Caixa` resolver-facing renderer the CAIXA-SDLC
1691    /// §I roadmap acknowledges (the future per-cluster dev-closure-audit
1692    /// overlay the M4 CR materializer resolves per-CR, the future
1693    /// `lacre.lisp` BLAKE3-closure emit walk the caixa-resolver docstring
1694    /// roadmaps).
1695    ///
1696    /// Second outer top-level [`Caixa`] `&[Dep]`-return slice-accessor —
1697    /// closes the outer-`Caixa` dependency-slot `&[Dep]` sub-family the
1698    /// sibling [`Self::deps`] (ad34b4e) opened on. The two accessors
1699    /// jointly close the two-list dep-graph surface every downstream
1700    /// resolver-facing consumer keys off (runtime `:deps` +
1701    /// dev-only `:deps-dev`, the canonical Cargo-shaped dependency-table
1702    /// pair the [`Self::validate_deps`] gate already walks in canonical
1703    /// order). Peer of the closed outer-`Caixa` foreign-code-slot
1704    /// `&[String]` sub-family ([`Self::bibliotecas`] 8a36c23,
1705    /// [`Self::exe`] 65d9527, [`Self::servicos`] 611f78b) and the outer-
1706    /// `Caixa` universal-axis text-tag family ([`Self::autores`]
1707    /// b5d813f, [`Self::etiquetas`] 78c7d3c) — folds the "outer
1708    /// [`Caixa`] `&[T]` slice" projection pattern onto the sibling
1709    /// dev-dep composite-element axis (`Dep` composite, matching the
1710    /// [`Self::deps`] element type). Sibling in shape to the peer
1711    /// per-`:supervisor` [`crate::supervisor::SupervisorSpec::children`]
1712    /// (bc92bce), per-`:placement`
1713    /// [`crate::aplicacao::Placement::clusters`] (a6e18d7),
1714    /// per-`:membros` [`crate::aplicacao::AplicacaoSpec::membros`]
1715    /// (6c77e36), per-`:contratos`
1716    /// [`crate::aplicacao::AplicacaoSpec::contratos`] (0dcc926), and
1717    /// per-`:upgrade-from :instructions`
1718    /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
1719    /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
1720    /// typed-slot list axes, folded here to the outer top-level
1721    /// [`Caixa`] universal-axis dev-dep-graph surface. Returns `&[Dep]`
1722    /// (not `&Vec<Dep>`) because every downstream consumer of the
1723    /// dev-dep list treats it as a read-only sequence — the slice-view
1724    /// is the narrowest borrow that supports every present +
1725    /// roadmapped consumer (`.iter()`, `.len()`, `.is_empty()`) without
1726    /// leaking the backing `Vec`'s grow/push/reserve surface no consumer
1727    /// of the typed view reaches for (the storage-side `Vec` remains
1728    /// reachable through the `pub deps_dev` field for the mutation-
1729    /// carrying serde round-trip and per-test fixture-mutation paths).
1730    /// Named `deps_dev()` to match the storage field's `snake_case` name;
1731    /// the kebab-case author-surface tag `:deps-dev` is the same axis
1732    /// after tatara-lisp's kebab↔snake fold and the accessor's identity
1733    /// maps onto the canonical CAIXA-SDLC §I vocabulary the slot's
1734    /// docstring already carries.
1735    #[must_use]
1736    pub fn deps_dev(&self) -> &[Dep] {
1737        self.deps_dev.as_slice()
1738    }
1739
1740    /// Substrate-canonical per-[`Caixa`] typed-mutation dispatch every
1741    /// consumer that appends to one of the two dep-list axes keys off
1742    /// — routes the `(list: DepList, dep: Dep)` tuple through one typed
1743    /// method on the substrate primitive rather than the prior
1744    /// `feira add`-side open-coded `if self.dev { &mut caixa.deps_dev }
1745    /// else { &mut caixa.deps }` inline dispatch + open-coded
1746    /// `.iter().any(|d| d.nome == …)` dup-check cascade. Refuses the
1747    /// mutation with the canonical typed [`DepError::DuplicateNome`] on
1748    /// a within-list name collision — the same `list: &'static str`
1749    /// diagnostic shape [`Self::validate_deps`]'s per-list
1750    /// [`crate::render::insert_first_seen`] walk raises on the peer
1751    /// parse-time within-list dedup axis, so a future author reading a
1752    /// `feira add` refusal and a `feira build` refusal reaches for the
1753    /// same corrective surface without switching diagnostic idioms.
1754    ///
1755    /// The two-arm [`crate::dep::DepList`] enum is the substrate's
1756    /// closed-set typed carrier for the "runtime-closure `:deps` vs
1757    /// dev-only-closure `:deps-dev`" axis every dep-list consumer
1758    /// dispatches on — the compiler-checked exhaustiveness on the
1759    /// enum's `match` arms is the build-time guarantee that no future
1760    /// per-list mutation-site regresses to a bare-`bool`-flag
1761    /// (`is_dev: bool`) inline dispatch that a future third
1762    /// dep-list axis (a `:deps-build` build-only closure once the
1763    /// substrate grows cross-artifact heterogeneous dep-graphs, per
1764    /// CAIXA-SDLC §I) would silently split at every consumer.
1765    ///
1766    /// Same "one typed dispatch on the substrate primitive, thin
1767    /// projections at each consumer" discipline the sibling per-slot
1768    /// read accessors ([`Self::deps`] ad34b4e, [`Self::deps_dev`],
1769    /// [`Self::nome`] e6b7d97, [`Self::versao`], [`Self::kind`])
1770    /// carry — extended onto the outer-[`Caixa`] typed-mutation surface,
1771    /// the substrate's first typed-mutation dispatch on the top-level
1772    /// manifest. The prior `feira add` open-coded `&mut caixa.deps` /
1773    /// `&mut caixa.deps_dev` inline field-access + `bail!` string-
1774    /// diagnostic path routed no through-line back to the typed slot,
1775    /// so a future extension of either dep-list axis to a richer author
1776    /// surface (a per-cluster override the operator pins through a
1777    /// future `:placement`-scoped dep-list slot the CAIXA-SDLC §I
1778    /// roadmap acknowledges, an M4
1779    /// `mesh.pleme.io/v1alpha1/Caixa` CR materializer's per-CR
1780    /// admission-webhook that normalized the list at admission time)
1781    /// would have had to be threaded through the `feira add` mutation
1782    /// site in lockstep with every read consumer or one path would
1783    /// silently disagree with the other on which list a given dep lands
1784    /// in. Lifting the resolution rule to a typed method on the
1785    /// substrate primitive means every downstream dep-list-mutating
1786    /// consumer of the top-level manifest reaches for exactly one typed
1787    /// dispatch — the resolver's accept-set migrates as a unit on any
1788    /// future axis addition.
1789    ///
1790    /// # Errors
1791    ///
1792    /// Returns [`DepError::DuplicateNome`] with `list = list.as_str()`
1793    /// when another entry in the same list already carries the same
1794    /// `:nome` — the mutation is refused and the caller can surface the
1795    /// typed diagnostic to the author (the `feira add` verb routes the
1796    /// error through `anyhow::Error::from`, which preserves the
1797    /// canonical `#[error(...)]`-templated diagnostic body).
1798    pub fn push_dep(&mut self, list: crate::dep::DepList, dep: Dep) -> Result<(), DepError> {
1799        let target = match list {
1800            crate::dep::DepList::Prod => &mut self.deps,
1801            crate::dep::DepList::Dev => &mut self.deps_dev,
1802        };
1803        if target.iter().any(|d| d.nome() == dep.nome()) {
1804            return Err(DepError::DuplicateNome {
1805                nome: dep.nome().to_string(),
1806                list: list.as_str(),
1807            });
1808        }
1809        target.push(dep);
1810        Ok(())
1811    }
1812
1813    /// Substrate-canonical per-`Caixa` `:limits` M2 typed-slot outer-
1814    /// composite Lunatic-per-process wasm32-sandboxing-composite optional-
1815    /// composite-reference accessor every consumer of the top-level
1816    /// manifest's per-Servico [`LimitsSpec`] outer-composite reader keys
1817    /// off — returns the author-declared `:limits` typed composite
1818    /// verbatim as an `Option<&LimitsSpec>` reference over the same
1819    /// backing storage the raw `self.limits.as_ref()` field access
1820    /// borrows from, with `None` naming the "no `:limits` block
1821    /// authored — every per-axis Lunatic-sandbox cap defers to the
1822    /// wasm-engine-default arm named on the per-axis
1823    /// [`LimitsSpec::memory`] / [`LimitsSpec::fuel`] /
1824    /// [`LimitsSpec::wall_clock`] / [`LimitsSpec::cpu`] scalar-accessor
1825    /// docstrings" partition every downstream Servico-M2-overlay
1826    /// emitter treats as "emit nothing" and the sibling
1827    /// [`crate::StandardLayout::verify`] per-`:limits` shape gate
1828    /// treats as "skip the per-axis
1829    /// [`crate::LimitsError::MemoryZero`] / `MemoryBelowWasm32Page` /
1830    /// `FuelZero` / `WallClockZero` / `CpuZero` refusal cascade".
1831    ///
1832    /// The outer `:limits` slot carries the M2 Servico-runtime typed
1833    /// composite — the load-bearing container of every Lunatic-shaped
1834    /// per-process wasm32-sandbox cap axis every long-running wasm
1835    /// component's runtime dispatches on (INSPIRATIONS §III.1 —
1836    /// Lunatic per-process linear-memory / fuel / wall-clock /
1837    /// millicore cap primitives translated onto pleme-io's typed
1838    /// `:limits :memory` / `:limits :fuel` / `:limits :wall-clock` /
1839    /// `:limits :cpu` sub-slot axes; CAIXA-SDLC §II — the typed-M2
1840    /// slot algebra the wasm-engine + `pleme-computeunit` Helm-library
1841    /// chart both fan on). Every per-`:limits` axis threads through a
1842    /// lifted per-slot accessor on the [`LimitsSpec`] type: the
1843    /// [`LimitsSpec::memory`] wasm32 linear-memory byte-cap scalar
1844    /// accessor, the [`LimitsSpec::fuel`] wasmtime fuel-cap scalar
1845    /// accessor, the [`LimitsSpec::wall_clock`] per-call wall-clock
1846    /// deadline scalar accessor, and the [`LimitsSpec::cpu`]
1847    /// K8s-millicore soft-CPU-share scalar accessor. Every downstream
1848    /// consumer that reaches for a limits axis first passes through
1849    /// this outer accessor onto the composite and then dispatches
1850    /// onto the per-axis accessor — the two-level dispatch means
1851    /// every per-`:limits` reader now routes through a typed dispatch
1852    /// on the substrate primitive at both altitudes.
1853    ///
1854    /// Prior to this lift the `.limits` `Option<LimitsSpec>` composite
1855    /// was accessed inline at three production sites — the
1856    /// [`crate::StandardLayout::verify`] per-`:limits` shape gate's
1857    /// `if let Some(l) = &caixa.limits { … }` traversal head
1858    /// (caixa-core/src/layout.rs:882, which drives the per-axis
1859    /// refusal cascade on the composite: the `LimitsError::MemoryZero`
1860    /// / `MemoryBelowWasm32Page` / `MemoryExceedsWasm32Max` /
1861    /// `FuelZero` / `FuelExceedsMax` / `WallClockZero` /
1862    /// `WallClockExceedsMax` / `CpuZero` / `CpuExceedsMax` refusals
1863    /// [`LimitsSpec::validate`] fans onto), the
1864    /// [`crate::render::servico_m2_overlay`] per-Servico M2 overlay
1865    /// emitter's `if let Some(limits) = &caixa.limits { … }` traversal
1866    /// head (caixa-core/src/render.rs:18504, which drives the
1867    /// `M2_KEY_LIMITS`-keyed `limits.is_empty()`-gated `serde_yaml`
1868    /// projection every `caixa-helm` / `caixa-flux` Servico values-
1869    /// block emitter fans on), and the
1870    /// [`Self::declared_servico_slots`] per-Servico M2 declared-slot-
1871    /// set enumerator's `self.limits.is_some()` presence probe
1872    /// (caixa-core/src/manifest.rs:1788, which drives the
1873    /// `M2_AUTHOR_KEY_LIMITS` kebab-case author-label push every
1874    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
1875    /// gate reads) — three open-coded outer-field accesses that
1876    /// expressed no compile-time link back to the typed slot at the
1877    /// [`Caixa`] altitude. A future extension of the `:limits` outer
1878    /// axis to a richer author surface (a multi-`:limits` list the M4
1879    /// CR materializer resolves per-CR at admission time so a Servico
1880    /// can expose a compute-heavy + IO-heavy limits pair, a per-
1881    /// cluster `:limits-overrides` slot the operator pins so a
1882    /// cluster-specific policy can tighten a caixa-declared cap
1883    /// without re-authoring the `caixa.lisp`, a promotion of the
1884    /// plain `Option<LimitsSpec>` to a richer
1885    /// `{static, dynamic}` partition once the wasm-engine's runtime-
1886    /// resolved dynamic-cap surface lands) would have had to be
1887    /// threaded through all three open-coded copies in lockstep or
1888    /// one consumer would silently disagree with the peers on which
1889    /// limits composite a given Caixa resolves to — the layout gate's
1890    /// per-axis bracket-dispatch seed reading the raw slot while the
1891    /// peer `servico_m2_overlay` emitter read an operator-resolved
1892    /// slot would silently split the build-time sandbox-shape gate
1893    /// from the runtime `ComputeUnit` CR emission gate, a three-
1894    /// consumer split at the layout gate, the M2 overlay emitter, and
1895    /// the declared-slot enumerator far from the source `caixa.lisp`
1896    /// with no field naming the limits-drift root cause. Lifting the
1897    /// resolution rule to a typed method on the substrate primitive
1898    /// means every downstream consumer of the caixa's per-`Caixa`
1899    /// Lunatic-sandboxing outer-composite surface reaches for exactly
1900    /// one typed dispatch — the resolver's accept-set migrates as a
1901    /// unit on any future axis addition.
1902    ///
1903    /// First outer top-level [`Caixa`] `Option<&Composite>`-return
1904    /// composite-reference accessor — opens the outer-`Caixa`
1905    /// `Option<&Composite>` composite-reference projection pattern the
1906    /// sibling per-`Caixa` `:behavior` [`crate::BehaviorSpec`] /
1907    /// `:politicas` [`crate::aplicacao::MeshPolicy`] / `:placement`
1908    /// [`crate::aplicacao::Placement`] / `:entrada`
1909    /// [`crate::aplicacao::Entrada`] future outer-composite lifts
1910    /// fold on. Peer of the M3 mesh-slot outer-composite family the
1911    /// sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
1912    /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
1913    /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
1914    /// accessors already close on the outer [`crate::AplicacaoSpec`]
1915    /// altitude — extends that "one typed dispatch on the substrate
1916    /// primitive, thin projections at each consumer" discipline onto
1917    /// the outer top-level [`Caixa`] altitude, opening the M2 Servico-
1918    /// runtime slot family's outer-composite axis. Returns
1919    /// `Option<&LimitsSpec>` (not the owning composite by copy or
1920    /// clone) because every downstream consumer of the limits
1921    /// composite treats it as a read-only per-axis dispatch source —
1922    /// the reference-view is the narrowest borrow that supports every
1923    /// present + roadmapped consumer (per-axis accessor dispatch,
1924    /// `.is_empty()`-gated overlay projection, presence-probe early
1925    /// return on the "author-omitted `:limits` ⇒ engine-default
1926    /// applies" partition) without cloning the composite through
1927    /// every consumer's fast path. The `Option` half of the return-
1928    /// type preserves the load-bearing "author-omitted `:limits` ⇒
1929    /// engine-default applies" partition (not a default composite the
1930    /// downstream must reject on emptiness) — the accessor projects
1931    /// the raw `Option<LimitsSpec>` slot's presence bit through the
1932    /// reference-return unchanged. Named `limits()` to match the
1933    /// storage field's name verbatim and the tatara-lisp author-
1934    /// surface term (`:limits`) the field's own docstring already
1935    /// carries.
1936    #[must_use]
1937    pub fn limits(&self) -> Option<&LimitsSpec> {
1938        self.limits.as_ref()
1939    }
1940
1941    /// Substrate-canonical per-`Caixa` `:behavior` M2 typed-slot outer-
1942    /// composite OTP-`gen_server`-shaped callback-table optional-
1943    /// composite-reference accessor every consumer of the top-level
1944    /// manifest's per-Servico [`BehaviorSpec`] outer-composite reader
1945    /// keys off — returns the author-declared `:behavior` typed
1946    /// composite verbatim as an `Option<&BehaviorSpec>` reference over
1947    /// the same backing storage the raw `self.behavior.as_ref()` field
1948    /// access borrows from, with `None` naming the "no `:behavior`
1949    /// block authored — every per-callback OTP-shaped hook defers to
1950    /// the wasm-engine's runtime default arm named on the per-axis
1951    /// [`BehaviorSpec::on_init`] / [`BehaviorSpec::on_call`] /
1952    /// [`BehaviorSpec::on_cast`] / [`BehaviorSpec::on_info`] /
1953    /// [`BehaviorSpec::on_state_change`] /
1954    /// [`BehaviorSpec::on_terminate`] scalar-accessor docstrings"
1955    /// partition every downstream Servico-M2-overlay emitter treats as
1956    /// "emit nothing" and the sibling [`crate::StandardLayout::verify`]
1957    /// per-`:behavior` shape gate treats as "skip the per-arm
1958    /// [`crate::behavior::BehaviorError`] refusal cascade + the
1959    /// per-callback on-disk `MissingEntry` existence check".
1960    ///
1961    /// The outer `:behavior` slot carries the M2 Servico-runtime typed
1962    /// composite — the load-bearing container of every OTP-shaped
1963    /// per-Servico lifecycle-callback path axis every long-running wasm
1964    /// component's runtime dispatches on (INSPIRATIONS §II.3 — Erlang/
1965    /// OTP `gen_server:init/1` / `handle_call/3` / `handle_cast/2` /
1966    /// `handle_info/2` / `code_change/3` / `terminate/2` primitives
1967    /// translated onto pleme-io's typed `:behavior :on-init` /
1968    /// `:on-call` / `:on-cast` / `:on-info` / `:on-state-change` /
1969    /// `:on-terminate` sub-slot axes; CAIXA-SDLC §II — the typed-M2
1970    /// slot algebra the wasm-engine + `pleme-computeunit` Helm-library
1971    /// chart both fan on). Every per-`:behavior` axis threads through a
1972    /// lifted per-callback accessor on the [`BehaviorSpec`] type
1973    /// (9b4ecde / d66c702 / 156ddbe / 99616ac / 4846cef / 701add7).
1974    /// Every downstream consumer that reaches for a behavior axis
1975    /// first passes through this outer accessor onto the composite
1976    /// and then dispatches onto the per-callback accessor — the
1977    /// two-level dispatch means every per-`:behavior` reader now
1978    /// routes through a typed dispatch on the substrate primitive at
1979    /// both altitudes.
1980    ///
1981    /// Composes cross-slot with the M2 `:upgrade-from` gate: the
1982    /// [`crate::upgrade::validate_upgrade_from_against_behavior`]
1983    /// cross-slot composition gate at [`crate::StandardLayout::verify`]
1984    /// keys the "per-version `:state-change` instruction must have a
1985    /// `:on-state-change` callback" precondition off this accessor's
1986    /// composite (the callback-side counterpart to the
1987    /// `:upgrade-from :instructions :state-change :script` refusal at
1988    /// the appup-side). Threading that gate's traversal input through
1989    /// this accessor closes the cross-slot invariant on the substrate
1990    /// primitive, not on the raw field.
1991    ///
1992    /// Prior to this lift the `.behavior` `Option<BehaviorSpec>`
1993    /// composite was accessed inline at four production sites — the
1994    /// [`crate::StandardLayout::verify`] per-`:behavior` shape gate's
1995    /// `if let Some(b) = &caixa.behavior { … }` traversal head
1996    /// (caixa-core/src/layout.rs:896, which drives the per-arm
1997    /// `BehaviorError` refusal cascade + the per-callback on-disk
1998    /// [`crate::LayoutError::MissingEntry`] existence check under
1999    /// [`crate::render::LAYOUT_MISSING_ENTRY_KIND_BEHAVIOR_CALLBACK`]),
2000    /// the [`crate::upgrade::validate_upgrade_from_against_behavior`]
2001    /// cross-slot composition gate's `caixa.behavior.as_ref()`
2002    /// traversal-input feed (caixa-core/src/layout.rs:1008, which
2003    /// drives the `:state-change` ↔ `:on-state-change` precondition
2004    /// refusal), the [`crate::render::servico_m2_overlay`] per-Servico
2005    /// M2 overlay emitter's `if let Some(behavior) = &caixa.behavior
2006    /// { … }` traversal head (caixa-core/src/render.rs:18513, which
2007    /// drives the `M2_KEY_BEHAVIOR`-keyed `behavior.is_empty()`-gated
2008    /// `serde_yaml` projection every `caixa-helm` / `caixa-flux`
2009    /// Servico values-block emitter fans on), and the
2010    /// [`Self::declared_servico_slots`] per-Servico M2 declared-slot-
2011    /// set enumerator's `self.behavior.is_some()` presence probe
2012    /// (caixa-core/src/manifest.rs:1919, which drives the
2013    /// `M2_AUTHOR_KEY_BEHAVIOR` kebab-case author-label push every
2014    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
2015    /// gate reads) — four open-coded outer-field accesses that
2016    /// expressed no compile-time link back to the typed slot at the
2017    /// [`Caixa`] altitude. A future extension of the `:behavior`
2018    /// outer axis to a richer author surface (a per-callback overlay
2019    /// resolver the operator materializes at admission time so a
2020    /// cluster-specific policy can inject a per-callback tracing
2021    /// interceptor without re-authoring the `caixa.lisp`, a promotion
2022    /// of the plain `Option<BehaviorSpec>` to a richer `{static,
2023    /// dynamic}` partition once a runtime-resolved behavior-swap
2024    /// surface lands, the M4 per-callback middleware chain the
2025    /// caixa-operator's per-Servico admission webhook keys off) would
2026    /// have had to be threaded through all four open-coded copies in
2027    /// lockstep or one consumer would silently disagree with the
2028    /// peers on which behavior composite a given Caixa resolves to —
2029    /// the layout gate's per-callback existence-check seed reading
2030    /// the raw slot while the peer `servico_m2_overlay` emitter read
2031    /// an operator-resolved slot would silently split the build-time
2032    /// callback-shape gate from the runtime `ComputeUnit` CR emission
2033    /// gate from the cross-slot `:state-change` composition gate from
2034    /// the M2 declared-slot enumerator, a four-consumer split far
2035    /// from the source `caixa.lisp` with no field naming the
2036    /// behavior-drift root cause. Lifting the resolution rule to a
2037    /// typed method on the substrate primitive means every downstream
2038    /// consumer of the caixa's per-`Caixa` OTP-callback-table outer-
2039    /// composite surface reaches for exactly one typed dispatch — the
2040    /// resolver's accept-set migrates as a unit on any future axis
2041    /// addition.
2042    ///
2043    /// Second outer top-level [`Caixa`] `Option<&Composite>`-return
2044    /// composite-reference accessor — sibling to the opening
2045    /// [`Self::limits`] (b2bd9d7) accessor on the outer-`Caixa`
2046    /// `Option<&Composite>` composite-reference sub-family, extends
2047    /// the "one typed dispatch on the substrate primitive, thin
2048    /// projections at each consumer" discipline onto the second of
2049    /// the three M2 Servico-runtime slots. The remaining
2050    /// `Option<&Composite>` axes at the outer top-level [`Caixa`]
2051    /// altitude — the M3 mesh-slot family (`:politicas`,
2052    /// `:placement`, `:entrada` — already closed on the inner
2053    /// [`crate::AplicacaoSpec`] altitude via 534dc21 / 9abb8f0 /
2054    /// d32111c) — remain the future sibling lifts on the outer
2055    /// top-level projection. Returns `Option<&BehaviorSpec>` (not
2056    /// the owning composite by copy or clone) because every
2057    /// downstream consumer of the behavior composite treats it as a
2058    /// read-only per-callback dispatch source — the reference-view is
2059    /// the narrowest borrow that supports every present + roadmapped
2060    /// consumer (per-callback accessor dispatch, `.is_empty()`-gated
2061    /// overlay projection, presence-probe early return on the
2062    /// "author-omitted `:behavior` ⇒ runtime-default applies"
2063    /// partition, cross-slot `:state-change` composition input)
2064    /// without cloning the composite through every consumer's fast
2065    /// path. The `Option` half of the return-type preserves the
2066    /// load-bearing "author-omitted `:behavior` ⇒ runtime-default
2067    /// applies" partition (not a default composite the downstream
2068    /// must reject on emptiness) — the accessor projects the raw
2069    /// `Option<BehaviorSpec>` slot's presence bit through the
2070    /// reference-return unchanged. Named `behavior()` to match the
2071    /// storage field's name verbatim and the tatara-lisp author-
2072    /// surface term (`:behavior`) the field's own docstring already
2073    /// carries.
2074    #[must_use]
2075    pub fn behavior(&self) -> Option<&crate::BehaviorSpec> {
2076        self.behavior.as_ref()
2077    }
2078
2079    /// Substrate-canonical per-`Caixa` `:politicas` M3 mesh-slot outer-
2080    /// composite MESH-COMPOSITION-shaped mesh-policy optional-composite-
2081    /// reference accessor every consumer of the top-level manifest's
2082    /// per-Aplicacao [`crate::aplicacao::MeshPolicy`] outer-composite
2083    /// reader keys off — returns the author-declared `:politicas` typed
2084    /// composite verbatim as an `Option<&MeshPolicy>` reference over the
2085    /// same backing storage the raw `self.politicas.as_ref()` field
2086    /// access borrows from, with `None` naming the "no `:politicas`
2087    /// block authored — every per-axis mesh-policy scalar defers to the
2088    /// cluster-default arm named on the per-axis
2089    /// [`crate::aplicacao::MeshPolicy::timeout`] /
2090    /// [`crate::aplicacao::MeshPolicy::retries`] /
2091    /// [`crate::aplicacao::MeshPolicy::circuit_breaker`] /
2092    /// [`crate::aplicacao::MeshPolicy::mtls_required`] /
2093    /// [`crate::aplicacao::MeshPolicy::rate_limit`] scalar-accessor
2094    /// docstrings" partition every downstream caixa-mesh /
2095    /// caixa-flux / caixa-helm Aplicacao-artifact emitter treats as
2096    /// "emit no per-`:politicas` overlay" and the sibling
2097    /// [`Self::aplicacao_view`] Aplicacao-composition seed folds through
2098    /// the [`crate::aplicacao::MeshPolicy::default`] cluster-default
2099    /// arm.
2100    ///
2101    /// The outer `:politicas` slot carries the M3 mesh-slot per-
2102    /// Aplicacao typed composite — the load-bearing container of every
2103    /// mesh-level policy axis every Cilium NetworkPolicy / Gateway API
2104    /// v1.x HTTPRoute / future M4 per-edge policy overlay emitter fans
2105    /// on (MESH-COMPOSITION §III.2 — the Aplicacao's typed mesh-policy
2106    /// composite; §V — the "no infinite blocking" per-call deadline +
2107    /// "sandboxing-by-default" mTLS-enforcement CSE invariants; §III.3
2108    /// — the typed inter-Servico contrato-edge overlay the per-`(:de,
2109    /// :para)` mesh renderer keys off). Every per-`:politicas` axis
2110    /// threads through a lifted per-slot accessor on the
2111    /// [`crate::aplicacao::MeshPolicy`] type: the
2112    /// [`crate::aplicacao::MeshPolicy::mtls_required`] (c0110f1) Cilium
2113    /// mTLS-enforcement toggle, the
2114    /// [`crate::aplicacao::MeshPolicy::retries`] (bdfb399) transient-
2115    /// failure retry budget, the [`crate::aplicacao::MeshPolicy::timeout`]
2116    /// (7073d0f) Gateway-API per-call deadline, the
2117    /// [`crate::aplicacao::MeshPolicy::circuit_breaker`] (b0e741a)
2118    /// Envoy-outlier-detection composite. Every downstream consumer
2119    /// that reaches for a mesh-policy axis first passes through this
2120    /// outer accessor onto the composite and then dispatches onto the
2121    /// per-axis accessor — the two-level dispatch means every per-
2122    /// `:politicas` reader now routes through a typed dispatch on the
2123    /// substrate primitive at both altitudes.
2124    ///
2125    /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2126    /// seed: the Aplicacao-view builder folds the outer `Option`'s
2127    /// author-omitted arm onto the [`crate::aplicacao::MeshPolicy::default`]
2128    /// cluster-default, so the peer inner [`crate::AplicacaoSpec::politicas`]
2129    /// (534dc21) `&MeshPolicy`-return accessor observes a typed
2130    /// composite whether or not the author declared the outer slot.
2131    /// The outer accessor preserves the "author-omitted vs authored-
2132    /// empty" partition the inner accessor's `is_empty()`-gated
2133    /// renderer overlay collapses — routing the presence bit through
2134    /// this accessor keeps the [`Self::declared_mesh_slots`] M3 kind-
2135    /// coherence enumerator's `M3_AUTHOR_KEY_POLITICAS` push separate
2136    /// from the inner `MeshPolicy::is_empty()`-gated overlay elision.
2137    ///
2138    /// Prior to this lift the `.politicas` `Option<MeshPolicy>`
2139    /// composite was accessed inline at two production sites — the
2140    /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2141    /// `self.politicas.clone().unwrap_or_default()` traversal head
2142    /// (caixa-core/src/manifest.rs:1899, which drives the fold onto
2143    /// the [`crate::aplicacao::MeshPolicy::default`] cluster-default
2144    /// arm the inner [`crate::AplicacaoSpec::politicas`] accessor
2145    /// then observes), and the [`Self::declared_mesh_slots`] M3
2146    /// declared-slot-set enumerator's `self.politicas.is_some()`
2147    /// presence probe (caixa-core/src/manifest.rs:1961, which drives
2148    /// the `M3_AUTHOR_KEY_POLITICAS` kebab-case author-label push
2149    /// every [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
2150    /// coherence gate reads) — two open-coded outer-field accesses
2151    /// that expressed no compile-time link back to the typed slot at
2152    /// the [`Caixa`] altitude. A future extension of the `:politicas`
2153    /// outer axis to a richer author surface (a per-cluster
2154    /// `:politicas-overrides` slot the operator materializes at
2155    /// admission time so a cluster-specific policy can tighten the
2156    /// caixa-declared bound without re-authoring the `caixa.lisp`, a
2157    /// promotion of the plain `Option<MeshPolicy>` to a richer
2158    /// `{static, dynamic}` partition once the M4 per-edge
2159    /// contrato-scoped policy-override surface lands, the M5 traffic-
2160    /// shaping composition the caixa-operator's per-Aplicacao mesh
2161    /// admission webhook keys off) would have had to be threaded
2162    /// through both open-coded copies in lockstep or the Aplicacao-
2163    /// composition seed's default-fold arm would silently disagree
2164    /// with the M3 declared-slot enumerator on which policy composite
2165    /// a given Caixa resolves to — the seed reading an operator-
2166    /// resolved slot while the enumerator's presence probe read the
2167    /// raw slot would silently split the build-time mesh-artifact
2168    /// emission gate from the M3 declared-slot enumerator's kind-
2169    /// coherence gate, a two-consumer split far from the source
2170    /// `caixa.lisp` with no field naming the policy-drift root cause.
2171    /// Lifting the resolution rule to a typed method on the substrate
2172    /// primitive means every downstream consumer of the caixa's per-
2173    /// `Caixa` MESH-COMPOSITION mesh-policy outer-composite surface
2174    /// reaches for exactly one typed dispatch — the resolver's
2175    /// accept-set migrates as a unit on any future axis addition.
2176    ///
2177    /// Third outer top-level [`Caixa`] `Option<&Composite>`-return
2178    /// composite-reference accessor — sibling to the opening
2179    /// [`Self::limits`] (b2bd9d7) and [`Self::behavior`] (35d8b52)
2180    /// accessors on the outer-`Caixa` `Option<&Composite>` composite-
2181    /// reference sub-family, extends the "one typed dispatch on the
2182    /// substrate primitive, thin projections at each consumer"
2183    /// discipline onto the first of the three M3 mesh-slot axes.
2184    /// Peer of the closed inner mesh-slot outer-composite family the
2185    /// sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
2186    /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2187    /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2188    /// accessor pins already close on the inner [`crate::AplicacaoSpec`]
2189    /// altitude — opens the outer top-level [`Caixa`] altitude's M3
2190    /// mesh-slot arm of the composite-reference family the remaining
2191    /// two axes (`:placement`, `:entrada`) fold onto in future
2192    /// sibling lifts. Returns `Option<&MeshPolicy>` (not the owning
2193    /// composite by copy or clone) because every downstream consumer
2194    /// of the mesh-policy composite treats it as a read-only per-axis
2195    /// dispatch source — the reference-view is the narrowest borrow
2196    /// that supports every present + roadmapped consumer (per-axis
2197    /// accessor dispatch, `.is_empty()`-gated overlay projection,
2198    /// presence-probe early return on the "author-omitted `:politicas`
2199    /// ⇒ cluster-default applies" partition, `Aplicacao`-composition
2200    /// seed's default-fold arm) without cloning the composite through
2201    /// every consumer's fast path. The `Option` half of the return-
2202    /// type preserves the load-bearing "author-omitted `:politicas` ⇒
2203    /// cluster-default applies" partition (not a default composite
2204    /// the downstream must reject on emptiness) — the accessor
2205    /// projects the raw `Option<MeshPolicy>` slot's presence bit
2206    /// through the reference-return unchanged. Named `politicas()` to
2207    /// match the storage field's name verbatim and the tatara-lisp
2208    /// author-surface term (`:politicas`) the field's own docstring
2209    /// already carries.
2210    #[must_use]
2211    pub fn politicas(&self) -> Option<&crate::aplicacao::MeshPolicy> {
2212        self.politicas.as_ref()
2213    }
2214
2215    /// Substrate-canonical per-`Caixa` `:placement` M3 mesh-slot outer-
2216    /// composite MESH-COMPOSITION-shaped distribution optional-composite-
2217    /// reference accessor every consumer of the top-level manifest's
2218    /// per-Aplicacao [`crate::aplicacao::Placement`] outer-composite
2219    /// reader keys off — returns the author-declared `:placement` typed
2220    /// composite verbatim as an `Option<&Placement>` reference over the
2221    /// same backing storage the raw `self.placement.as_ref()` field
2222    /// access borrows from, with `None` naming the "no `:placement`
2223    /// block authored — every per-axis placement scalar defers to the
2224    /// cluster-default arm named on the per-axis
2225    /// [`crate::aplicacao::Placement::estrategia`] /
2226    /// [`crate::aplicacao::Placement::clusters`] /
2227    /// [`crate::aplicacao::Placement::affinity`] /
2228    /// [`crate::aplicacao::Placement::shard_key`] scalar-accessor
2229    /// docstrings" partition every downstream caixa-mesh /
2230    /// caixa-flux / caixa-helm Aplicacao-artifact emitter treats as
2231    /// "emit no per-`:placement` overlay" and the sibling
2232    /// [`Self::aplicacao_view`] Aplicacao-composition seed folds through
2233    /// the [`crate::aplicacao::Placement::default`] cluster-default arm.
2234    ///
2235    /// The outer `:placement` slot carries the M3 mesh-slot per-
2236    /// Aplicacao typed distribution composite — the load-bearing
2237    /// container of every where-does-this-Aplicacao-run axis every
2238    /// caixa-mesh programs.yaml per-cluster distribution overlay /
2239    /// caixa-flux per-Aplicacao GitRepository/HelmRelease fan-out /
2240    /// future M4 per-Aplicacao Akka-style cluster-sharding entity-id
2241    /// resolver emitter fans on (MESH-COMPOSITION §II.4 — the
2242    /// Aplicacao's typed distribution composite; §V CSE invariants —
2243    /// "distribution is a first-class typed composite, not a runtime
2244    /// scheduler hint" the per-axis scalars enforce; §III.3 — the
2245    /// typed inter-Servico contrato-edge overlay the per-cluster
2246    /// mesh renderer keys off). Every per-`:placement` axis threads
2247    /// through a lifted per-slot accessor on the
2248    /// [`crate::aplicacao::Placement`] type: the
2249    /// [`crate::aplicacao::Placement::estrategia`] (921fe1b)
2250    /// MESH-COMPOSITION distribution-strategy scalar, the
2251    /// [`crate::aplicacao::Placement::clusters`] (a6e18d7) per-cluster
2252    /// distribution-target slice, the [`crate::aplicacao::Placement::affinity`]
2253    /// M3-Adaptive-compression-hint optional-scalar, and the
2254    /// [`crate::aplicacao::Placement::shard_key`] (7cd2a28) Akka-cluster-
2255    /// sharding extractor-expression optional-scalar. Every downstream
2256    /// consumer that reaches for a placement axis first passes through
2257    /// this outer accessor onto the composite and then dispatches onto
2258    /// the per-axis accessor — the two-level dispatch means every per-
2259    /// `:placement` reader now routes through a typed dispatch on the
2260    /// substrate primitive at both altitudes.
2261    ///
2262    /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2263    /// seed: the Aplicacao-view builder folds the outer `Option`'s
2264    /// author-omitted arm onto the [`crate::aplicacao::Placement::default`]
2265    /// cluster-default, so the peer inner [`crate::AplicacaoSpec::placement`]
2266    /// (9abb8f0) `&Placement`-return accessor observes a typed composite
2267    /// whether or not the author declared the outer slot. The outer
2268    /// accessor preserves the "author-omitted vs authored-empty" partition
2269    /// the inner accessor collapses at the cluster-default fold —
2270    /// routing the presence bit through this accessor keeps the
2271    /// [`Self::declared_mesh_slots`] M3 kind-coherence enumerator's
2272    /// `M3_AUTHOR_KEY_PLACEMENT` push separate from the inner
2273    /// [`crate::AplicacaoSpec::validate_placement`]-gated overlay
2274    /// dispatch.
2275    ///
2276    /// Prior to this lift the `.placement` `Option<Placement>`
2277    /// composite was accessed inline at two production sites — the
2278    /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2279    /// `self.placement.clone().unwrap_or_default()` traversal head
2280    /// (caixa-core/src/manifest.rs:2036, which drives the fold onto
2281    /// the [`crate::aplicacao::Placement::default`] cluster-default
2282    /// arm the inner [`crate::AplicacaoSpec::placement`] accessor
2283    /// then observes), and the [`Self::declared_mesh_slots`] M3
2284    /// declared-slot-set enumerator's `self.placement.is_some()`
2285    /// presence probe (caixa-core/src/manifest.rs:2100, which drives
2286    /// the `M3_AUTHOR_KEY_PLACEMENT` kebab-case author-label push
2287    /// every [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
2288    /// coherence gate reads) — two open-coded outer-field accesses
2289    /// that expressed no compile-time link back to the typed slot at
2290    /// the [`Caixa`] altitude. A future extension of the `:placement`
2291    /// outer axis to a richer author surface (a per-cluster
2292    /// `:placement-overrides` slot the operator materializes at
2293    /// admission time so a cluster-specific placement can tighten the
2294    /// caixa-declared bound without re-authoring the `caixa.lisp`, a
2295    /// per-tenant placement-alias table the M4
2296    /// `mesh.pleme.io/v1alpha1/Aplicacao` CR materializer resolves
2297    /// per-CR at admission time, a promotion of the plain
2298    /// `Option<Placement>` to a richer `{static, dynamic}` partition
2299    /// once Orleans-style virtual-actor dynamic placement comes into
2300    /// typed scope) would have had to be threaded through both open-
2301    /// coded copies in lockstep or the Aplicacao-composition seed's
2302    /// default-fold arm would silently disagree with the M3 declared-
2303    /// slot enumerator on which distribution composite a given Caixa
2304    /// resolves to — the seed reading an operator-resolved slot while
2305    /// the enumerator's presence probe read the raw slot would
2306    /// silently split the build-time distribution-artifact emission
2307    /// gate from the M3 declared-slot enumerator's kind-coherence
2308    /// gate, a two-consumer split far from the source `caixa.lisp`
2309    /// with no field naming the distribution-drift root cause.
2310    /// Lifting the resolution rule to a typed method on the substrate
2311    /// primitive means every downstream consumer of the caixa's per-
2312    /// `Caixa` MESH-COMPOSITION distribution outer-composite surface
2313    /// reaches for exactly one typed dispatch — the resolver's
2314    /// accept-set migrates as a unit on any future axis addition.
2315    ///
2316    /// Fourth outer top-level [`Caixa`] `Option<&Composite>`-return
2317    /// composite-reference accessor — sibling to the opening
2318    /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) M2-
2319    /// Servico-runtime pair and the peer [`Self::politicas`] (5d23d29)
2320    /// M3-mesh-slot arm on the outer-`Caixa` `Option<&Composite>`
2321    /// composite-reference sub-family, folds on the "one typed
2322    /// dispatch on the substrate primitive, thin projections at each
2323    /// consumer" discipline extended onto the second of the three M3
2324    /// mesh-slot axes. Peer of the closed inner mesh-slot outer-
2325    /// composite family the sibling
2326    /// [`crate::AplicacaoSpec::politicas`] (534dc21) /
2327    /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2328    /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2329    /// accessor pins already close on the inner
2330    /// [`crate::AplicacaoSpec`] altitude — folds on the outer top-
2331    /// level [`Caixa`] altitude's M3 mesh-slot arm the sibling
2332    /// [`Self::politicas`] opened, extending the discipline onto the
2333    /// second of the three M3 mesh-slot axes. The remaining M3
2334    /// mesh-slot axis (`:entrada`) folds onto this accessor's
2335    /// discipline in the final sibling lift, closing the outer top-
2336    /// level [`Caixa`] `Option<&Composite>` M3 mesh-slot sub-family.
2337    /// Returns `Option<&Placement>` (not the owning composite by copy
2338    /// or clone) because every downstream consumer of the placement
2339    /// composite treats it as a read-only per-axis dispatch source —
2340    /// the reference-view is the narrowest borrow that supports every
2341    /// present + roadmapped consumer (per-axis accessor dispatch,
2342    /// serde composite-serialization on the programs.yaml overlay,
2343    /// presence-probe early return on the "author-omitted `:placement`
2344    /// ⇒ cluster-default applies" partition, `Aplicacao`-composition
2345    /// seed's default-fold arm) without cloning the composite through
2346    /// every consumer's fast path. The `Option` half of the return-
2347    /// type preserves the load-bearing "author-omitted `:placement` ⇒
2348    /// cluster-default applies" partition (not a default composite
2349    /// the downstream must reject on emptiness) — the accessor
2350    /// projects the raw `Option<Placement>` slot's presence bit
2351    /// through the reference-return unchanged. Named `placement()` to
2352    /// match the storage field's name verbatim and the tatara-lisp
2353    /// author-surface term (`:placement`) the field's own docstring
2354    /// already carries.
2355    #[must_use]
2356    pub fn placement(&self) -> Option<&crate::aplicacao::Placement> {
2357        self.placement.as_ref()
2358    }
2359
2360    /// Substrate-canonical per-`Caixa` `:entrada` M3 mesh-slot outer-
2361    /// composite MESH-COMPOSITION-shaped external-gateway optional-
2362    /// composite-reference accessor every consumer of the top-level
2363    /// manifest's per-Aplicacao [`crate::aplicacao::Entrada`] outer-
2364    /// composite reader keys off — returns the author-declared
2365    /// `:entrada` typed composite verbatim as an `Option<&Entrada>`
2366    /// reference over the same backing storage the raw
2367    /// `self.entrada.as_ref()` field access borrows from, with `None`
2368    /// naming the "no `:entrada` block authored — this Aplicacao is
2369    /// cluster-internal, no `Gateway`/`HTTPRoute` fan-out emitted"
2370    /// partition every downstream caixa-mesh Gateway-API artifact
2371    /// emitter treats as "emit no gateway-listener + no `HTTPRoute`
2372    /// backend for this Aplicacao" and the sibling
2373    /// [`Self::aplicacao_view`] Aplicacao-composition seed forwards
2374    /// verbatim (unlike the peer `:politicas` / `:placement` arms,
2375    /// `:entrada` has no cluster-default fold — an omitted `:entrada`
2376    /// stays `None` on the projected [`crate::AplicacaoSpec`] and the
2377    /// peer inner [`crate::AplicacaoSpec::entrada`] accessor observes
2378    /// the same `Option<&Entrada>` presence bit unchanged).
2379    ///
2380    /// The outer `:entrada` slot carries the M3 mesh-slot per-
2381    /// Aplicacao typed external-gateway composite — the load-bearing
2382    /// container of every how-does-the-outside-world-reach-this-
2383    /// Aplicacao axis every caixa-mesh `Gateway`/`HTTPRoute` fan-out
2384    /// emitter fans on (MESH-COMPOSITION §II.5 — the Aplicacao's typed
2385    /// external-entry composite; §V CSE invariants — "the external
2386    /// gateway is a first-class typed composite, not a per-Servico
2387    /// ingress annotation" the per-axis scalars enforce; §III.4 — the
2388    /// typed hostname + backend-Servico pair the per-cluster Gateway-
2389    /// API renderer keys off). Every per-`:entrada` axis threads
2390    /// through a lifted per-slot accessor on the
2391    /// [`crate::aplicacao::Entrada`] type: the
2392    /// [`crate::aplicacao::Entrada::host`] Gateway-API `Listener.hostname`
2393    /// scalar, the [`crate::aplicacao::Entrada::para`] backend-Servico
2394    /// caixa-name scalar, the [`crate::aplicacao::Entrada::paths`]
2395    /// per-rule `HTTPPathMatch` list, the [`crate::aplicacao::Entrada::port`]
2396    /// backend `trigger.service.port` scalar, and the
2397    /// [`crate::aplicacao::Entrada::resolved_paths`] URL-path fallback
2398    /// resolver every HTTPRoute-aware renderer consumes. Every
2399    /// downstream consumer that reaches for an entry axis first passes
2400    /// through this outer accessor onto the composite and then
2401    /// dispatches onto the per-axis accessor — the two-level dispatch
2402    /// means every per-`:entrada` reader now routes through a typed
2403    /// dispatch on the substrate primitive at both altitudes.
2404    ///
2405    /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2406    /// seed: the Aplicacao-view builder forwards the outer `Option`
2407    /// arm verbatim (no default fold — `:entrada` is inherently
2408    /// optional; a cluster-internal Aplicacao has no external gateway
2409    /// at all, not "an external gateway that defaults to nothing"), so
2410    /// the peer inner [`crate::AplicacaoSpec::entrada`] (d32111c)
2411    /// `Option<&Entrada>`-return accessor observes the same presence
2412    /// bit whether or not the author declared the outer slot. Routing
2413    /// the presence bit through this accessor keeps the
2414    /// [`Self::declared_mesh_slots`] M3 kind-coherence enumerator's
2415    /// `M3_AUTHOR_KEY_ENTRADA` push separate from the inner
2416    /// [`crate::AplicacaoSpec::validate_entrada`]-gated
2417    /// hostname/backend/path emission dispatch.
2418    ///
2419    /// Prior to this lift the `.entrada` `Option<Entrada>` composite
2420    /// was accessed inline at two production sites — the
2421    /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2422    /// `self.entrada.clone()` traversal head (caixa-core/src/manifest.rs:2182,
2423    /// which drives the forward onto the peer inner
2424    /// [`crate::AplicacaoSpec::entrada`] accessor the caixa-mesh
2425    /// Gateway-API fan-out then observes), and the
2426    /// [`Self::declared_mesh_slots`] M3 declared-slot-set enumerator's
2427    /// `self.entrada.is_some()` presence probe (caixa-core/src/manifest.rs:2248,
2428    /// which drives the `M3_AUTHOR_KEY_ENTRADA` kebab-case author-
2429    /// label push every [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
2430    /// kind-coherence gate reads) — two open-coded outer-field
2431    /// accesses that expressed no compile-time link back to the typed
2432    /// slot at the [`Caixa`] altitude. A future extension of the
2433    /// `:entrada` outer axis to a richer author surface (a per-cluster
2434    /// `:entrada-overrides` slot the operator materializes at admission
2435    /// time so a cluster-specific hostname can pin the caixa-declared
2436    /// bound without re-authoring the `caixa.lisp`, a per-tenant
2437    /// gateway-alias table the M4 `mesh.pleme.io/v1alpha1/Aplicacao`
2438    /// CR materializer resolves per-CR at admission time, a promotion
2439    /// of the plain `Option<Entrada>` to a richer
2440    /// `{public, private, internal}` partition once Cilium-identity-
2441    /// scoped internal gateways come into typed scope) would have had
2442    /// to be threaded through both open-coded copies in lockstep or the
2443    /// Aplicacao-composition seed's forward arm would silently
2444    /// disagree with the M3 declared-slot enumerator on which external-
2445    /// gateway composite a given Caixa resolves to — the seed reading
2446    /// an operator-resolved slot while the enumerator's presence probe
2447    /// read the raw slot would silently split the build-time gateway-
2448    /// artifact emission gate from the M3 declared-slot enumerator's
2449    /// kind-coherence gate, a two-consumer split far from the source
2450    /// `caixa.lisp` with no field naming the entry-drift root cause.
2451    /// Lifting the resolution rule to a typed method on the substrate
2452    /// primitive means every downstream consumer of the caixa's per-
2453    /// `Caixa` MESH-COMPOSITION external-gateway outer-composite
2454    /// surface reaches for exactly one typed dispatch — the resolver's
2455    /// accept-set migrates as a unit on any future axis addition.
2456    ///
2457    /// Fifth and final outer top-level [`Caixa`] `Option<&Composite>`-
2458    /// return composite-reference accessor — closes the outer-`Caixa`
2459    /// `Option<&Composite>` composite-reference sub-family opened by
2460    /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) on the
2461    /// M2 Servico-runtime arm and extended onto the M3 mesh-slot arm
2462    /// by [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074),
2463    /// folds on the "one typed dispatch on the substrate primitive,
2464    /// thin projections at each consumer" discipline extended onto the
2465    /// third and final M3 mesh-slot axis. Peer of the closed inner
2466    /// mesh-slot outer-composite family the sibling
2467    /// [`crate::AplicacaoSpec::politicas`] (534dc21) /
2468    /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2469    /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2470    /// accessor pins already close on the inner
2471    /// [`crate::AplicacaoSpec`] altitude — this lift closes the mirror
2472    /// sub-family on the outer top-level [`Caixa`] altitude, so both
2473    /// altitudes of the outer-composite reference-return discipline
2474    /// (per-`Caixa` outer-slot presence + per-`AplicacaoSpec` inner-
2475    /// slot presence) now carry the full five-arm accept-set behind a
2476    /// typed dispatch on the substrate primitive. Returns
2477    /// `Option<&Entrada>` (not the owning composite by copy or clone)
2478    /// because every downstream consumer of the entrada composite
2479    /// treats it as a read-only per-axis dispatch source — the
2480    /// reference-view is the narrowest borrow that supports every
2481    /// present + roadmapped consumer (per-axis accessor dispatch,
2482    /// serde composite-serialization on the programs.yaml overlay,
2483    /// presence-probe early return on the "author-omitted `:entrada`
2484    /// ⇒ cluster-internal Aplicacao" partition, `Aplicacao`-composition
2485    /// seed's forward arm) without cloning the composite through every
2486    /// consumer's fast path. The `Option` half of the return-type
2487    /// preserves the load-bearing "author-omitted `:entrada` ⇒
2488    /// cluster-internal Aplicacao" partition (not a default composite
2489    /// the downstream must reject on emptiness — a cluster-internal
2490    /// Aplicacao has no external gateway at all, not "a default gateway
2491    /// that emits nothing"); the accessor projects the raw
2492    /// `Option<Entrada>` slot's presence bit through the reference-
2493    /// return unchanged. Named `entrada()` to match the storage field's
2494    /// name verbatim and the tatara-lisp author-surface term
2495    /// (`:entrada`) the field's own docstring already carries.
2496    #[must_use]
2497    pub fn entrada(&self) -> Option<&crate::aplicacao::Entrada> {
2498        self.entrada.as_ref()
2499    }
2500
2501    /// Substrate-canonical per-`Caixa` `:ci` slot accessor — returns the
2502    /// author-declared typed CI run (`canteiro_types::CiRun`) verbatim as
2503    /// an `Option<&CiRun>`, borrowed from the typed slot's own
2504    /// `Option<CiRun>` storage. `None` when the slot is absent (every
2505    /// non-`Acao` kind, and an `Acao` caixa that hasn't declared `:ci`
2506    /// yet — the latter is caught by [`crate::LayoutError::MissingCi`],
2507    /// not silently accepted).
2508    ///
2509    /// Named `ci()` to match the storage field's name and the
2510    /// tatara-lisp author surface (`:ci`); mirrors the sibling
2511    /// `Option<&Composite>` accessors on this same `Caixa` altitude
2512    /// ([`Self::limits`], [`Self::behavior`], [`Self::politicas`],
2513    /// [`Self::placement`], [`Self::entrada`]) — one typed dispatch on
2514    /// the substrate primitive rather than an open-coded `self.ci.as_ref()`
2515    /// at every consumer.
2516    #[must_use]
2517    pub fn ci(&self) -> Option<&canteiro_types::CiRun> {
2518        self.ci.as_ref()
2519    }
2520
2521    /// Substrate-canonical per-`Caixa` `:estrategia` M2 supervisor-tree-
2522    /// slot flat-spread OTP-shaped sibling-restart-strategy discriminant
2523    /// accessor every consumer of the top-level manifest's per-Supervisor
2524    /// restart-strategy axis keys off — returns the author-declared
2525    /// `:estrategia` variant verbatim as an `Option<RestartStrategy>`,
2526    /// `Copy`-projected from the typed slot's own
2527    /// `Option<crate::supervisor::RestartStrategy>` storage. Optional
2528    /// (`:estrategia` is a flat-spread supervisor-only slot every
2529    /// non-`Supervisor`-kind `defcaixa` carries as `None` by
2530    /// `#[serde(default)]`, and every `Supervisor`-kind `defcaixa` may
2531    /// still omit to defer to [`RestartStrategy::default`] —
2532    /// [`RestartStrategy::OneForOne`] — through the [`Self::supervisor_view`]
2533    /// `unwrap_or_default()` fold; a returned `None` degenerates to the
2534    /// [`SupervisorSpec::default`]-inherited strategy without any silent
2535    /// promotion to a fresh explicit variant at the accessor boundary).
2536    ///
2537    /// The `:estrategia` slot carries the M2 typed OTP-shaped sibling-
2538    /// restart-strategy discriminant every substrate-side per-Supervisor
2539    /// dispatch fans on (INSPIRATIONS §II.2 — OTP `supervisor:strategy`
2540    /// closed-set `one_for_one | one_for_all | rest_for_one |
2541    /// simple_one_for_one` algebra translated onto pleme-io's typed
2542    /// [`RestartStrategy`] enum; CAIXA-SDLC §II — the M2 supervisor-tree
2543    /// slot algebra the operator's hierarchical reconciliation scheduler
2544    /// fans on). The slot is *flat-spread* on the outer top-level `Caixa`
2545    /// (per the field-shape docstring at caixa-core/src/manifest.rs — "The
2546    /// supervisor slots are flat on Caixa (vs nested under a
2547    /// `SupervisorSpec` sub-form) to keep tatara-lisp authoring at one
2548    /// level of nesting"), so the accessor's altitude is the outer
2549    /// [`Caixa`] surface rather than the composed [`SupervisorSpec`]
2550    /// altitude the sibling [`crate::supervisor::SupervisorSpec::estrategia`]
2551    /// (eafb619) accessor keys off. The two typed axes — the outer
2552    /// author-surface `Option<RestartStrategy>` on the [`Caixa`] altitude
2553    /// (author-omitted arm carried as `None`) and the inner post-
2554    /// composition `RestartStrategy` on the [`SupervisorSpec`] altitude
2555    /// (`Option` collapsed through the [`Self::supervisor_view`]
2556    /// `unwrap_or_default()` fold) — now share one accessor discipline for
2557    /// the shared substrate concept "the author-declared OTP-shaped
2558    /// sibling-restart-strategy variant that partitions the downstream
2559    /// per-Supervisor renderer's per-arm fan-out"; the outer-altitude
2560    /// `None` arm is the pre-composition presence bit every declared-slot
2561    /// enumerator ([`Self::declared_supervisor_slots`]) reads, and the
2562    /// inner-altitude non-`Option` `RestartStrategy` is the post-
2563    /// composition partition-dispatch input every strategy-arm consumer
2564    /// ([`SupervisorSpec::validate`], the future wasm-operator's per-
2565    /// Supervisor sibling-restart branch, the future M4
2566    /// `mesh.pleme.io/v1alpha1/Supervisor` CR materializer's admission
2567    /// webhook) fans on.
2568    ///
2569    /// Prior to this lift the `.estrategia` field was accessed inline at
2570    /// two production sites in `caixa-core/src/manifest.rs` — the
2571    /// [`Self::declared_supervisor_slots`] `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA`
2572    /// presence-probe arm at `if self.estrategia.is_some()` (which drives
2573    /// the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
2574    /// coherence gate's per-slot label push) and the [`Self::supervisor_view`]
2575    /// `SupervisorSpec` construction site at `estrategia:
2576    /// self.estrategia.unwrap_or_default()` (which composes the flat-
2577    /// spread outer author-surface `Option<RestartStrategy>` onto the
2578    /// inner post-composition [`SupervisorSpec`] `RestartStrategy` field
2579    /// the [`SupervisorSpec::estrategia`] accessor keys off) — two open-
2580    /// coded field-accesses that expressed no compile-time link back to
2581    /// the typed slot. A future extension of the outer `:estrategia` axis
2582    /// to a richer author surface (a per-cluster strategy override the
2583    /// operator pins through a future `:estrategia-overrides` overlay the
2584    /// MESH-COMPOSITION §III.2 supervision-canary roadmap acknowledges,
2585    /// a per-tenant strategy-alias table the M4 CR materializer resolves
2586    /// per-CR, a per-Supervisor dynamic strategy derivation the future
2587    /// adaptive-supervision engine computes from child-failure-history
2588    /// topology, a per-child-cohort strategy split the future
2589    /// `RestForCohort` extension the INSPIRATIONS.md §II.2 Erlang/OTP
2590    /// absorption roadmap acknowledges, a promotion of the plain
2591    /// `Option<RestartStrategy>` to a richer
2592    /// `AuthorDeclaredStrategy { declared, overlay }` newtype once the
2593    /// operator-resolved overlay lands) would have had to be threaded
2594    /// through both open-coded copies in lockstep or the enumerator's
2595    /// presence probe and the composition site's `unwrap_or_default()`
2596    /// fold would silently disagree on which strategy a given [`Caixa`]
2597    /// resolves to (an author's `:estrategia OneForAll` would satisfy
2598    /// the enumerator's presence probe while the composition site
2599    /// silently rendered a stale `OneForOne`, or vice versa). Lifting
2600    /// the resolution rule to a typed method on the substrate primitive
2601    /// means every downstream consumer of the caixa's per-`Caixa` outer-
2602    /// altitude sibling-restart-strategy surface reaches for exactly one
2603    /// typed dispatch — the resolver's accept-set migrates as a unit on
2604    /// any future axis addition.
2605    ///
2606    /// First outer top-level [`Caixa`] `Option<Copy>`-return supervisor-
2607    /// tree-slot flat-spread accessor for M2 supervisor-slot Copy-carry
2608    /// axes — opens the outer-`Caixa` `Option<Copy>` flat-spread
2609    /// projection pattern the sibling per-`Caixa` `:max-restarts`
2610    /// `Option<u32>` and (through the future duration-newtype landing)
2611    /// `:restart-window` `Option<Duration>` future outer-scalar lifts
2612    /// fold on. Peer of the inner-altitude [`crate::supervisor::SupervisorSpec::estrategia`]
2613    /// (eafb619) `Copy`-return sibling-restart-strategy scalar accessor on
2614    /// the post-composition [`SupervisorSpec`] altitude — same "one
2615    /// typed dispatch on the substrate primitive, thin projections at
2616    /// each consumer" discipline extended onto the pre-composition outer
2617    /// author-surface [`Caixa`] altitude for the same OTP-shaped
2618    /// sibling-restart-strategy axis. Peer of the closed outer-`Caixa`
2619    /// `Option<&Composite>` composite-reference family the sibling
2620    /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) /
2621    /// [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074) /
2622    /// [`Self::entrada`] (e4128e4) accessor pins already carry on the
2623    /// outer `Option<&Composite>` altitude — extends the outer-`Caixa`
2624    /// typed-slot accessor discipline onto the flat-spread M2 supervisor-
2625    /// tree `Option<Copy>`-discriminant sub-family the sibling M3
2626    /// [`crate::aplicacao::Placement::estrategia`] (921fe1b)
2627    /// `PlacementStrategy` `Copy`-composite-enum scalar accessor already
2628    /// pins on the inner-altitude per-`:placement` composite. Named
2629    /// `estrategia()` to match the storage field's name and the
2630    /// per-[`SupervisorSpec`] peer [`crate::supervisor::SupervisorSpec::estrategia`]
2631    /// / per-[`crate::aplicacao::Placement`] peer
2632    /// [`crate::aplicacao::Placement::estrategia`] method-name discipline
2633    /// verbatim; the accessor's identity name maps onto the canonical
2634    /// OTP-shape supervision vocabulary the [`RestartStrategy`] enum's
2635    /// docstring already carries.
2636    #[must_use]
2637    pub fn estrategia(&self) -> Option<crate::supervisor::RestartStrategy> {
2638        self.estrategia
2639    }
2640
2641    /// Substrate-canonical per-`Caixa` `:max-restarts` M2 supervisor-tree-
2642    /// slot flat-spread OTP-`MaxIntensity`-shaped restart-budget-count
2643    /// scalar accessor every consumer of the top-level manifest's per-
2644    /// Supervisor `:max-restarts` restart-budget-count axis keys off —
2645    /// returns the author-declared `:max-restarts` typed `Option<u32>`
2646    /// verbatim, `Copy`-projected from the typed slot's own `Option<u32>`
2647    /// storage (`u32` is `Copy`, so `Option<u32>` is `Copy` and the
2648    /// accessor returns by value; no borrow of `&self` past the call).
2649    /// Optional (`:max-restarts` is a flat-spread supervisor-only slot
2650    /// every non-`Supervisor`-kind `defcaixa` carries as `None` by
2651    /// `#[serde(default)]`, and every `Supervisor`-kind `defcaixa` may
2652    /// still omit to defer to the [`Self::supervisor_view`]
2653    /// `unwrap_or(5)` fold's OTP-canonical `{intensity, 5, 60}` default).
2654    ///
2655    /// The `:max-restarts` slot carries the M2 typed Erlang/OTP-shaped
2656    /// `MaxIntensity` restart-budget count that pairs with the sibling
2657    /// `:restart-window` `Period` to form the `MaxIntensity / Period`
2658    /// restart-intensity ratio the supervisor trips its own escalation on
2659    /// (INSPIRATIONS §II.2 — Erlang/OTP `supervisor` `{intensity, 5, 60}`
2660    /// worker-supervisor default; RUNTIME-PATTERNS §II.2; CAIXA-SDLC §II
2661    /// — the M2 supervisor-tree slot algebra the operator's hierarchical
2662    /// reconciliation scheduler fans on). The slot is *flat-spread* on
2663    /// the outer top-level `Caixa` (per the field-shape docstring at
2664    /// caixa-core/src/manifest.rs — "The supervisor slots are flat on
2665    /// Caixa (vs nested under a `SupervisorSpec` sub-form)"), so the
2666    /// accessor's altitude is the outer [`Caixa`] surface rather than the
2667    /// composed [`SupervisorSpec`] altitude the sibling
2668    /// [`crate::supervisor::SupervisorSpec::max_restarts`] accessor keys
2669    /// off. The two typed axes — the outer author-surface `Option<u32>`
2670    /// on the [`Caixa`] altitude (author-omitted arm carried as `None`)
2671    /// and the inner post-composition `u32` on the [`SupervisorSpec`]
2672    /// altitude (`Option` collapsed through the [`Self::supervisor_view`]
2673    /// `unwrap_or(5)` fold) — now share one accessor discipline for the
2674    /// shared substrate concept "the author-declared OTP-shaped
2675    /// restart-budget count every downstream per-Supervisor consumer's
2676    /// restart-intensity budget-vs-count comparator fans on".
2677    ///
2678    /// Prior to this lift the `.max_restarts` field was accessed inline
2679    /// at two production sites in `caixa-core/src/manifest.rs` — the
2680    /// [`Self::declared_supervisor_slots`] `SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS`
2681    /// presence-probe arm at `if self.max_restarts.is_some()` (which
2682    /// drives the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
2683    /// kind-coherence gate's per-slot label push) and the
2684    /// [`Self::supervisor_view`] `SupervisorSpec` construction site at
2685    /// `max_restarts: self.max_restarts.unwrap_or(5)` (which composes the
2686    /// flat-spread outer author-surface `Option<u32>` onto the inner
2687    /// post-composition [`SupervisorSpec`] `u32` field the
2688    /// [`SupervisorSpec::max_restarts`] accessor keys off) — two open-
2689    /// coded field-accesses that expressed no compile-time link back to
2690    /// the typed slot. A future extension of the outer `:max-restarts`
2691    /// axis to a richer author surface (a per-cluster restart-budget
2692    /// override the operator pins through a future `:max-restarts-overrides`
2693    /// overlay the MESH-COMPOSITION §III.2 supervision-canary roadmap
2694    /// acknowledges, a per-tenant restart-budget-alias table the M4 CR
2695    /// materializer resolves per-CR, a per-Supervisor dynamic restart-
2696    /// budget derivation the future adaptive-supervision engine computes
2697    /// from child-failure-history topology, a promotion of the plain
2698    /// `Option<u32>` count to a richer `{MaxR, MaxT}` per-child-cohort
2699    /// restart-budget-partition once the INSPIRATIONS §II.2 Erlang/OTP
2700    /// per-child-cohort roadmap lands) would have had to be threaded
2701    /// through both open-coded copies in lockstep or the enumerator's
2702    /// presence probe and the composition site's `unwrap_or(5)` fold
2703    /// would silently disagree on which restart-budget a given [`Caixa`]
2704    /// resolves to (an author's `:max-restarts 10` would satisfy the
2705    /// enumerator's presence probe while the composition site silently
2706    /// composed the OTP-canonical `5`, or vice versa). Lifting the
2707    /// resolution rule to a typed method on the substrate primitive means
2708    /// every downstream consumer of the caixa's per-`Caixa` outer-altitude
2709    /// restart-budget-count surface reaches for exactly one typed dispatch
2710    /// — the resolver's accept-set migrates as a unit on any future axis
2711    /// addition.
2712    ///
2713    /// Second outer top-level [`Caixa`] `Option<Copy>`-return supervisor-
2714    /// tree-slot flat-spread accessor for M2 supervisor-slot Copy-carry
2715    /// axes — folds on the outer-`Caixa` `Option<Copy>` flat-spread
2716    /// projection pattern the sibling per-`Caixa`
2717    /// [`Self::estrategia`] (ed04d3c) accessor opened, extends the
2718    /// sub-family onto the sibling `Option<u32>` restart-budget-count arm.
2719    /// Peer of the inner-altitude
2720    /// [`crate::supervisor::SupervisorSpec::max_restarts`] `u32` accessor
2721    /// on the post-composition [`SupervisorSpec`] altitude — same "one
2722    /// typed dispatch on the substrate primitive, thin projections at
2723    /// each consumer" discipline extended onto the pre-composition outer
2724    /// author-surface [`Caixa`] altitude for the same OTP-`MaxIntensity`-
2725    /// shaped restart-budget-count axis. Named `max_restarts()` to match
2726    /// the storage field's name and the per-[`SupervisorSpec`] peer
2727    /// [`crate::supervisor::SupervisorSpec::max_restarts`] method-name
2728    /// discipline verbatim; the accessor's identity maps onto the
2729    /// canonical OTP-shape supervision vocabulary the `:max-restarts`
2730    /// field's docstring already carries.
2731    #[must_use]
2732    pub const fn max_restarts(&self) -> Option<u32> {
2733        self.max_restarts
2734    }
2735
2736    /// Substrate-canonical per-`Caixa` `:restart-window` M2 supervisor-
2737    /// tree-slot flat-spread OTP-`Period`-shaped restart-intensity-
2738    /// denominator raw-duration-string scalar accessor every consumer of
2739    /// the top-level manifest's per-Supervisor `:restart-window` sliding-
2740    /// window axis keys off — returns the author-declared `:restart-window`
2741    /// typed `Option<String>` verbatim as an `Option<&str>`, borrowed
2742    /// from the typed slot's own `Option<String>` storage. `None` when
2743    /// the slot is absent (the canonical "never reset — every restart
2744    /// across the supervisor's lifetime counts against the sibling
2745    /// `:max-restarts` budget" sentinel every non-`Supervisor`-kind
2746    /// `defcaixa` carries by `#[serde(default)]` and every
2747    /// `Supervisor`-kind `defcaixa` may still omit to defer to the
2748    /// [`Self::supervisor_view`] `restart_window: None` composition
2749    /// through the [`crate::supervisor::duration_codec::parse`] soft-
2750    /// swallow `.and_then(|s| … .ok())` fold).
2751    ///
2752    /// The `:restart-window` slot carries the raw M2 typed Erlang/OTP-
2753    /// shaped `Period` sliding-observation-interval duration string that
2754    /// pairs with the sibling `:max-restarts` `MaxIntensity` restart-
2755    /// budget count to form the `MaxIntensity / Period` restart-intensity
2756    /// ratio the supervisor trips its own escalation on (INSPIRATIONS
2757    /// §II.2 — Erlang/OTP `supervisor` `{intensity, 5, 60}` worker-
2758    /// supervisor default; RUNTIME-PATTERNS §II.2). The outer-`Caixa`
2759    /// slot stores the raw duration string (`"60s"`, `"5m"`, `"500ms"`)
2760    /// authored under `:restart-window` — the typed [`SupervisorSpec`]
2761    /// holds an `Option<Duration>` routed through the shared
2762    /// [`crate::supervisor::duration_codec`] via `with = "duration_codec"`
2763    /// — so the outer altitude's accessor returns `Option<&str>` (raw
2764    /// authoring surface) while the inner altitude's
2765    /// [`crate::supervisor::SupervisorSpec::restart_window`] returns
2766    /// `Option<Duration>` (parsed typed surface). The parse-refusal arm
2767    /// is closed by the sibling [`Self::validate_restart_window`] gate
2768    /// that surfaces [`ManifestError::RestartWindowMalformed`] naming
2769    /// the offending value; the view-construction path
2770    /// [`Self::supervisor_view`] soft-swallows the same parse error to
2771    /// `None` to keep the view best-effort.
2772    ///
2773    /// Prior to this lift the `.restart_window` field was accessed inline
2774    /// at three production sites in `caixa-core/src/manifest.rs` — the
2775    /// [`Self::declared_supervisor_slots`]
2776    /// `SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW` presence-probe arm at
2777    /// `if self.restart_window.is_some()` (which drives the
2778    /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
2779    /// coherence gate's per-slot label push), the
2780    /// [`Self::validate_restart_window`] `let Some(s) =
2781    /// self.restart_window.as_deref()` empty-and-shape gate binding
2782    /// (which folds the raw string through the shared
2783    /// [`crate::supervisor::duration_codec::parse`] to surface
2784    /// [`ManifestError::RestartWindowMalformed`] naming the offending
2785    /// value), and the [`Self::supervisor_view`] `self.restart_window
2786    /// .as_deref().and_then(…)` view-construction fold (which composes
2787    /// the flat-spread outer author-surface `Option<String>` onto the
2788    /// inner post-composition [`SupervisorSpec`] `Option<Duration>`
2789    /// field the [`SupervisorSpec::restart_window`] accessor keys off) —
2790    /// three open-coded field-accesses that expressed no compile-time
2791    /// link back to the typed slot. A future extension of the outer
2792    /// `:restart-window` axis to a richer author surface (a per-cluster
2793    /// window override, a per-tenant window-alias table, a per-Supervisor
2794    /// dynamic window derivation the future adaptive-supervision engine
2795    /// computes from child-failure-history topology, a promotion of the
2796    /// plain `Option<String>` raw duration to a typed `Option<Duration>`
2797    /// once the future author-surface parser lands at the [`Caixa`]
2798    /// altitude and the raw-string form is retired) would have had to be
2799    /// threaded through every open-coded copy in lockstep or the three
2800    /// consumers would silently disagree on which raw string a given
2801    /// [`Caixa`] resolves to. Lifting the resolution rule to a typed
2802    /// method on the substrate primitive means every downstream consumer
2803    /// of the caixa's per-`Caixa` outer-altitude restart-window raw-
2804    /// string surface reaches for exactly one typed dispatch — the
2805    /// resolver's accept-set migrates as a unit on any future axis
2806    /// addition.
2807    ///
2808    /// Third outer top-level [`Caixa`] supervisor-tree-slot flat-spread
2809    /// accessor — folds on the outer-`Caixa` M2 supervisor-tree flat-
2810    /// spread projection pattern the sibling per-`Caixa`
2811    /// [`Self::estrategia`] (ed04d3c) `Option<Copy>` and
2812    /// [`Self::max_restarts`] `Option<Copy>` accessors opened, extends
2813    /// the sub-family onto the sibling `Option<&str>` raw-duration-
2814    /// string arm (the outer altitude's raw-string form; the inner
2815    /// altitude's parsed [`Duration`] form is the peer
2816    /// [`crate::supervisor::SupervisorSpec::restart_window`] accessor).
2817    /// Peer of the sibling per-`Caixa` `Option<&str>`-return scalar
2818    /// accessors ([`Self::licenca`] / [`Self::repositorio`] /
2819    /// [`Self::descricao`] / [`Self::edicao`]) on the universal-axis
2820    /// outer scalar-projection family the outer-`Caixa` `Option<&str>`
2821    /// sub-family already carries — same "one typed dispatch on the
2822    /// substrate primitive, thin projections at each consumer"
2823    /// discipline extended onto the M2 supervisor-tree flat-spread
2824    /// `Option<&str>` raw-duration-string arm. Named `restart_window()`
2825    /// to match the storage field's name and the per-[`SupervisorSpec`]
2826    /// peer [`crate::supervisor::SupervisorSpec::restart_window`]
2827    /// method-name discipline verbatim; the accessor's identity maps
2828    /// onto the canonical OTP-shape supervision vocabulary the
2829    /// `:restart-window` field's docstring already carries.
2830    #[must_use]
2831    pub fn restart_window(&self) -> Option<&str> {
2832        self.restart_window.as_deref()
2833    }
2834
2835    /// Substrate-canonical per-`Caixa` `:upgrade-from` M2 typed-slot
2836    /// outer-composite OTP-appup-shaped per-prior-version migration-
2837    /// entry-list slice accessor every consumer of the top-level
2838    /// manifest's per-Servico hot-upgrade-block `&[UpgradeFromEntry]`
2839    /// slice-view keys off — returns the author-declared `:upgrade-from`
2840    /// typed `Vec<UpgradeFromEntry>` verbatim as a
2841    /// `&[UpgradeFromEntry]` slice-view over the same backing buffer
2842    /// the raw `self.upgrade_from.as_slice()` field access borrows
2843    /// from. Empty-slice-carrying (the "no hot-upgrade path declared"
2844    /// arm every `defcaixa` without an `:upgrade-from` block carries;
2845    /// the [`Self::from_lisp`] derive folds an omitted `:upgrade-from`
2846    /// through `#[serde(default)]` to `Vec::new()`, so a `Caixa` past
2847    /// parse definitionally carries a `Vec<UpgradeFromEntry>` slot —
2848    /// possibly empty — and the returned `&[UpgradeFromEntry]`
2849    /// degenerates to an empty slice on that arm without any silent
2850    /// `None` collapse).
2851    ///
2852    /// The outer `:upgrade-from` slot carries the M2 typed OTP-appup
2853    /// migration block — the load-bearing container of every per-
2854    /// prior-`:versao` migration-instruction list the wasm-operator
2855    /// dispatches on at hot-upgrade time (INSPIRATIONS §II.4 — OTP
2856    /// `.appup` per-prior-version `LoadModule | StateChange |
2857    /// SoftPurge | Purge | Restart` instruction algebra translated
2858    /// onto pleme-io's typed `:upgrade-from :from` + `:instructions`
2859    /// entry list; CAIXA-SDLC §II — the typed-M2 slot algebra the
2860    /// operator's hot-upgrade dispatch fans on). Every per-entry axis
2861    /// threads through a lifted per-entry accessor on the
2862    /// [`UpgradeFromEntry`] type: the
2863    /// [`UpgradeFromEntry::prior_versao`] SemVer-shaped previous-
2864    /// version scalar accessor and the
2865    /// [`UpgradeFromEntry::instructions`] `&[UpgradeInstruction]`-
2866    /// return per-entry instruction-list accessor (0137e5a). Every
2867    /// downstream consumer of the hot-upgrade path first passes
2868    /// through this outer accessor onto the slice and then dispatches
2869    /// per-entry through the inner accessors — the two-level dispatch
2870    /// means every per-`:upgrade-from` reader now routes through a
2871    /// typed dispatch on the substrate primitive at both altitudes.
2872    ///
2873    /// Prior to this lift the `.upgrade_from` `Vec<UpgradeFromEntry>`
2874    /// slot was accessed inline at production sites across three
2875    /// files — the [`Self::declared_servico_slots`] M2 declared-slot
2876    /// enumerator's `self.upgrade_from.is_empty()` presence probe
2877    /// (caixa-core/src/manifest.rs, which drives the
2878    /// `M2_AUTHOR_KEY_UPGRADE_FROM` kebab-case author-label push every
2879    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
2880    /// gate reads), the [`crate::StandardLayout::verify`] per-
2881    /// `:upgrade-from` three-stage validation pass (caixa-core/src/
2882    /// layout.rs, which fans onto the
2883    /// [`crate::upgrade::validate_upgrade_from`] per-entry shape +
2884    /// cross-entry duplicate gate, the
2885    /// [`crate::upgrade::validate_upgrade_from_against_versao`]
2886    /// SemVer-precedence cross-slot gate, the
2887    /// [`crate::upgrade::validate_upgrade_from_against_behavior`]
2888    /// `:state-change` ↔ `:on-state-change` cross-slot composition
2889    /// gate, and the per-instruction script-path existence-probe walk
2890    /// that reads each entry's [`UpgradeFromEntry::instructions`] to
2891    /// resolve every declared migration script against the layout
2892    /// root), and the [`crate::render::servico_m2_overlay`] per-
2893    /// Servico M2 overlay emitter's `!caixa.upgrade_from.is_empty()`
2894    /// presence gate + `serde_yaml::to_value(&caixa.upgrade_from)`
2895    /// projection (caixa-core/src/render.rs, which drives the
2896    /// `M2_KEY_UPGRADE_FROM`-keyed `serde_yaml` projection every
2897    /// `caixa-helm` / `caixa-flux` Servico values-block emitter fans
2898    /// on and lands as the ComputeUnit CR's `spec.upgradeFrom` field).
2899    /// A future extension of the outer `:upgrade-from` axis (a per-
2900    /// cluster `:upgrade-overrides` overlay the wasm-engine operator
2901    /// resolves at admission time so a cluster-specific migration
2902    /// policy can tighten a caixa-declared step without re-authoring
2903    /// the `caixa.lisp`, promotion of the plain
2904    /// `Vec<UpgradeFromEntry>` to a richer `{static, dynamic}`
2905    /// partition once runtime-resolved hot-upgrade instructions land,
2906    /// per-entry priority annotation once multi-strategy fan-out
2907    /// lands) would have had to be threaded through all six open-
2908    /// coded copies in lockstep or one consumer would silently
2909    /// disagree with the peers on which upgrade slice a given Caixa
2910    /// resolves to — a six-consumer split at the enumerator, the
2911    /// three-stage validate pass, the script-path probe walk, and the
2912    /// M2 overlay emitter, far from the source `caixa.lisp` with no
2913    /// field naming the upgrade-drift root cause. Lifting the
2914    /// resolution rule to a typed method on the substrate primitive
2915    /// means every downstream consumer of the caixa's per-`Caixa`
2916    /// OTP-appup outer-slice surface reaches for exactly one typed
2917    /// dispatch — the resolver's accept-set migrates as a unit on any
2918    /// future axis addition.
2919    ///
2920    /// First outer top-level [`Caixa`] `&[Composite]`-return slice
2921    /// accessor for M2 / M3 typed-slot vec-carry axes — opens the
2922    /// outer-`Caixa` `&[Composite]` composite-slice projection
2923    /// pattern the sibling `:children`
2924    /// [`crate::supervisor::ChildSpec`] / `:membros`
2925    /// [`crate::aplicacao::Membro`] / `:contratos`
2926    /// [`crate::aplicacao::WitContract`] future outer-composite-slice
2927    /// lifts fold on. Peer of the closed outer-`Caixa` scalar
2928    /// `Option<&Composite>` composite-reference family the sibling
2929    /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) /
2930    /// [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074) /
2931    /// [`Self::entrada`] (e4128e4) accessors closed on the outer
2932    /// `Option<&Composite>` altitude, extended here to the outer-
2933    /// `Caixa` `&[Composite]` vec-carry altitude. Peer at the inner
2934    /// altitude of [`crate::upgrade::UpgradeFromEntry::instructions`]
2935    /// (0137e5a) — same "one typed dispatch on the substrate
2936    /// primitive, thin projections at each consumer" discipline
2937    /// folded onto the outer top-level [`Caixa`] altitude, opening the
2938    /// M2 vec-carry slot family's outer-composite-slice axis. Sibling
2939    /// in shape to the peer outer-`Caixa` `&[Dep]`-return
2940    /// [`Self::deps`] (ad34b4e) / [`Self::deps_dev`] (f7fd81e) and
2941    /// `&[String]`-return [`Self::autores`] (b5d813f) /
2942    /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`]
2943    /// (8a36c23) / [`Self::exe`] (65d9527) / [`Self::servicos`]
2944    /// (611f78b) slice-accessors on the sibling outer-`Caixa` scalar-
2945    /// element vec-carry axes — folds the "outer [`Caixa`] `&[T]`
2946    /// slice" projection pattern onto the sibling M2 typed-composite-
2947    /// element axis (`UpgradeFromEntry` composite, matching the
2948    /// per-inner [`UpgradeFromEntry::instructions`] element type at a
2949    /// different altitude).
2950    ///
2951    /// Returns `&[UpgradeFromEntry]` (not `&Vec<UpgradeFromEntry>`)
2952    /// because every downstream consumer of the hot-upgrade list
2953    /// treats it as a read-only sequence — the slice-view is the
2954    /// narrowest borrow that supports every present + roadmapped
2955    /// consumer (`.iter()`, `.len()`, `.is_empty()`, `serde` slice-
2956    /// serialization through
2957    /// `serde_yaml::to_value(&[UpgradeFromEntry])`) without leaking
2958    /// the backing `Vec`'s grow/push/reserve surface no consumer of
2959    /// the typed view reaches for (the storage-side `Vec` remains
2960    /// reachable through the `pub upgrade_from` field for the
2961    /// mutation-carrying serde round-trip and per-test fixture-
2962    /// mutation paths). Named `upgrade_from()` to match the storage
2963    /// field's `snake_case` name; the kebab-case author-surface tag
2964    /// `:upgrade-from` is the same axis after tatara-lisp's
2965    /// kebab↔snake fold and the accessor's identity maps onto the
2966    /// canonical CAIXA-SDLC §II vocabulary the slot's docstring
2967    /// already carries.
2968    #[must_use]
2969    pub fn upgrade_from(&self) -> &[UpgradeFromEntry] {
2970        self.upgrade_from.as_slice()
2971    }
2972
2973    /// Substrate-canonical per-`Caixa` `:children` M2 supervisor-tree-
2974    /// slot outer-composite OTP-shaped per-supervisor static-child-list
2975    /// slice accessor every consumer of the top-level manifest's per-
2976    /// Supervisor `&[ChildSpec]` slice-view keys off — returns the
2977    /// author-declared `:children` typed `Vec<crate::supervisor::ChildSpec>`
2978    /// verbatim as a `&[crate::supervisor::ChildSpec]` slice-view over
2979    /// the same backing buffer the raw `self.children.as_slice()` field
2980    /// access borrows from. Empty-slice-carrying (the "no static children
2981    /// declared" arm every non-`Supervisor`-kind `defcaixa` carries by
2982    /// #[serde(default)] and every `SimpleOneForOne` supervisor carries
2983    /// by [`crate::supervisor::SupervisorError::SimpleOneForOneWithStaticChildren`]
2984    /// gate; the returned `&[ChildSpec]` degenerates to an empty slice
2985    /// on those arms without any silent `None` collapse).
2986    ///
2987    /// The outer `:children` slot carries the M2 typed OTP-supervisor
2988    /// static-child list — the load-bearing container of every per-
2989    /// child `{caixa, versao, restart}` triple the wasm-operator's
2990    /// hierarchical reconciler dispatches on at supervisor-tree
2991    /// materialization time (INSPIRATIONS §II.2 — OTP `supervisor:init/1`
2992    /// static-child list translated onto pleme-io's typed
2993    /// [`crate::supervisor::ChildSpec`] entry list; CAIXA-SDLC §II —
2994    /// the typed-M2 slot algebra the operator's per-supervisor fan-out
2995    /// dispatch fans on). Every per-child axis threads through a lifted
2996    /// per-entry accessor on the [`crate::supervisor::ChildSpec`] type:
2997    /// the [`crate::supervisor::ChildSpec::nome`] DNS-1123-label
2998    /// child-caixa-identity scalar accessor, the peer versao SemVer-2
2999    /// version-requirement scalar accessor, and the
3000    /// [`crate::supervisor::ChildSpec::restart`] `Copy`-composite-enum
3001    /// per-child post-exit restart-decision-policy discriminant
3002    /// accessor (dfb4a81). Every downstream consumer of the supervisor-
3003    /// tree path first passes through this outer accessor onto the
3004    /// slice and then dispatches per-child through the inner accessors
3005    /// — the two-level dispatch means every per-`:children` reader now
3006    /// routes through a typed dispatch on the substrate primitive at
3007    /// both altitudes.
3008    ///
3009    /// Prior to this lift the `.children` `Vec<ChildSpec>` slot was
3010    /// accessed inline at three production sites across two files —
3011    /// the [`Self::declared_supervisor_slots`] supervisor-tree
3012    /// declared-slot enumerator's `!self.children.is_empty()` presence
3013    /// probe (caixa-core/src/manifest.rs, which drives the
3014    /// `SUPERVISOR_AUTHOR_KEY_CHILDREN` kebab-case author-label push
3015    /// every [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
3016    /// kind-coherence gate reads), the [`Self::supervisor_view`]
3017    /// per-supervisor typed-view composer's `self.children.clone()`
3018    /// per-child fold-in path (caixa-core/src/manifest.rs, which
3019    /// materializes the typed [`crate::supervisor::SupervisorSpec`]
3020    /// view every [`crate::StandardLayout::verify`] Supervisor-arm gate
3021    /// dispatches on), and the [`crate::StandardLayout::verify`] per-
3022    /// `:children :caixa` self-parent refusal probe's
3023    /// `&caixa.children`-borrowed
3024    /// [`crate::supervisor::validate_no_self_supervision`] input
3025    /// (caixa-core/src/layout.rs, which pins the "no child names the
3026    /// supervisor's own `:nome`" cross-slot coherence gate). A future
3027    /// extension of the outer `:children` axis (a per-cluster
3028    /// `:children-overrides` overlay the wasm-engine operator resolves
3029    /// at admission time so a cluster-specific child-set can tighten
3030    /// a caixa-declared list without re-authoring the `caixa.lisp`,
3031    /// promotion of the plain `Vec<ChildSpec>` to a richer
3032    /// `{static, dynamic}` partition once Erlang/OTP's
3033    /// `simple_one_for_one`-shaped dynamic-child slot lands as a typed
3034    /// axis, per-child priority annotation once multi-strategy fan-out
3035    /// lands) would have had to be threaded through all three open-
3036    /// coded copies in lockstep or one consumer would silently
3037    /// disagree with the peers on which child slice a given Caixa
3038    /// resolves to — the enumerator's presence probe reading the raw
3039    /// slot while the peer view-composer's fold-in path read an
3040    /// operator-resolved slot would silently split the paired
3041    /// declared-slot enumerator and typed-view composition, and the
3042    /// [`crate::supervisor::validate_no_self_supervision`] self-parent
3043    /// refusal probe reading a third borrow would silently drift the
3044    /// cross-slot coherence gate's traversal input from the two peers,
3045    /// a three-consumer split at the enumerator, the view composer,
3046    /// and the self-parent gate far from the source `caixa.lisp` with
3047    /// no field naming the child-set-drift root cause. Lifting the
3048    /// resolution rule to a typed method on the substrate primitive
3049    /// means every downstream consumer of the caixa's per-`Caixa`
3050    /// OTP-supervisor outer-slice surface reaches for exactly one
3051    /// typed dispatch — the resolver's accept-set migrates as a unit
3052    /// on any future axis addition.
3053    ///
3054    /// Second outer top-level [`Caixa`] `&[Composite]`-return slice
3055    /// accessor for M2 / M3 typed-slot vec-carry axes — folds on the
3056    /// outer-`Caixa` `&[Composite]` composite-slice sub-family the
3057    /// sibling [`Self::upgrade_from`] (2a1f907) accessor opened, peer
3058    /// at the outer altitude of the closed inner-`SupervisorSpec`
3059    /// [`crate::SupervisorSpec::children`] (bc92bce) accessor on the
3060    /// same OTP-supervisor static-child-list axis — same "byte-equal,
3061    /// borrow-shared" outer-accessor discipline extended onto the
3062    /// second outer-`Caixa` `&[Composite]` vec-carry axis. Sibling in
3063    /// shape to the peer outer-`Caixa` `&[Dep]`-return [`Self::deps`]
3064    /// (ad34b4e) / [`Self::deps_dev`] (f7fd81e) and `&[String]`-return
3065    /// [`Self::autores`] (b5d813f) / [`Self::etiquetas`] (78c7d3c) /
3066    /// [`Self::bibliotecas`] (8a36c23) / [`Self::exe`] (65d9527) /
3067    /// [`Self::servicos`] (611f78b) slice-accessors on the sibling
3068    /// outer-`Caixa` scalar-element vec-carry axes — folds the "outer
3069    /// [`Caixa`] `&[T]` slice" projection pattern onto the sibling
3070    /// M2 typed-composite-element axis
3071    /// ([`crate::supervisor::ChildSpec`] composite, matching the
3072    /// per-inner [`crate::SupervisorSpec::children`] element type at a
3073    /// different altitude).
3074    ///
3075    /// Returns `&[crate::supervisor::ChildSpec]` (not
3076    /// `&Vec<ChildSpec>`) because every downstream consumer of the
3077    /// child list treats it as a read-only sequence — the slice-view
3078    /// is the narrowest borrow that supports every present +
3079    /// roadmapped consumer (`.iter()`, `.len()`, `.is_empty()`, the
3080    /// [`crate::supervisor::validate_no_self_supervision`] `&[ChildSpec]`
3081    /// input, `serde` slice-serialization) without leaking the backing
3082    /// `Vec`'s grow/push/reserve surface no consumer of the typed view
3083    /// reaches for (the storage-side `Vec` remains reachable through
3084    /// the `pub children` field for the mutation-carrying serde round-
3085    /// trip and per-test fixture-mutation paths, including the
3086    /// [`Self::supervisor_view`] fold-in path that clones the slot
3087    /// into the typed view). Named `children()` to match the storage
3088    /// field's name verbatim and the tatara-lisp author-surface term
3089    /// (`:children`) the field's own docstring already carries; the
3090    /// accessor's identity maps onto the canonical OTP supervision
3091    /// vocabulary the [`Caixa::children`] field's docstring already
3092    /// reaches for ("Static children of a supervisor").
3093    #[must_use]
3094    pub fn children(&self) -> &[crate::supervisor::ChildSpec] {
3095        self.children.as_slice()
3096    }
3097
3098    /// Substrate-canonical per-`Caixa` `:membros` M3 mesh-slot outer-
3099    /// composite MESH-COMPOSITION-shaped per-Aplicacao member-list slice
3100    /// accessor every consumer of the top-level manifest's per-Aplicacao
3101    /// `&[crate::aplicacao::Membro]` slice-view keys off — returns the
3102    /// author-declared `:membros` typed `Vec<crate::aplicacao::Membro>`
3103    /// verbatim as a `&[crate::aplicacao::Membro]` slice-view over the
3104    /// same backing buffer the raw `self.membros.as_slice()` field access
3105    /// borrows from. Empty-slice-carrying (the "no members declared" arm
3106    /// every non-`Aplicacao`-kind `defcaixa` carries by `#[serde(default)]`
3107    /// and every partially-authored Aplicacao carries before the
3108    /// [`crate::AplicacaoError::MembrosEmpty`] gate fires; the returned
3109    /// `&[Membro]` degenerates to an empty slice on those arms without any
3110    /// silent `None` collapse).
3111    ///
3112    /// The outer `:membros` slot carries the M3 typed MESH-COMPOSITION
3113    /// per-Aplicacao member list — the load-bearing container of every
3114    /// per-member `{caixa, versao}` pair the caixa-mesh renderer's
3115    /// per-Aplicacao program-emission dispatch fans on at mesh-artifact
3116    /// materialization time (MESH-COMPOSITION §III.1 — the typed graph's
3117    /// vertex set the `:contratos` `:de`/`:para` edges resolve against and
3118    /// the `:entrada :para` external-gateway destination validates
3119    /// against; CAIXA-SDLC §II — the typed-M3 slot algebra the operator's
3120    /// per-Aplicacao fan-out dispatch fans on). Every per-member axis
3121    /// threads through a lifted per-entry accessor on the
3122    /// [`crate::aplicacao::Membro`] type: the
3123    /// [`crate::aplicacao::Membro::nome`] DNS-1123-label member-caixa-
3124    /// identity scalar accessor (4a32abf) and the peer
3125    /// [`crate::aplicacao::Membro::versao_requirement`] SemVer-2
3126    /// version-requirement scalar accessor (a40b0e3). Every downstream
3127    /// consumer of the mesh-graph path first passes through this outer
3128    /// accessor onto the slice and then dispatches per-member through
3129    /// the inner accessors — the two-level dispatch means every per-
3130    /// `:membros` reader now routes through a typed dispatch on the
3131    /// substrate primitive at both altitudes.
3132    ///
3133    /// Prior to this lift the `.membros` `Vec<Membro>` slot was accessed
3134    /// inline at three production sites across two files — the
3135    /// [`Self::declared_mesh_slots`] mesh-slot declared-slot
3136    /// enumerator's `!self.membros.is_empty()` presence probe
3137    /// (caixa-core/src/manifest.rs, which drives the
3138    /// `M3_AUTHOR_KEY_MEMBROS` kebab-case author-label push every
3139    /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-coherence
3140    /// gate reads), the [`Self::aplicacao_view`] per-Aplicacao typed-view
3141    /// composer's `self.membros.clone()` per-member fold-in path
3142    /// (caixa-core/src/manifest.rs, which materializes the typed
3143    /// [`crate::aplicacao::AplicacaoSpec`] view every
3144    /// [`crate::StandardLayout::verify`] Aplicacao-arm gate dispatches
3145    /// on), and the [`crate::StandardLayout::verify`] per-`:membros
3146    /// :caixa` self-membership refusal probe's `&caixa.membros`-borrowed
3147    /// [`crate::aplicacao::validate_no_self_membership`] input
3148    /// (caixa-core/src/layout.rs, which pins the "no member names the
3149    /// Aplicacao's own `:nome`" cross-slot coherence gate). A future
3150    /// extension of the outer `:membros` axis (a per-cluster
3151    /// `:membros-overrides` overlay the wasm-engine operator resolves at
3152    /// admission time so a cluster-specific member-set can tighten a
3153    /// caixa-declared list without re-authoring the `caixa.lisp`,
3154    /// promotion of the plain `Vec<Membro>` to a richer
3155    /// `{static, dynamic}` partition once runtime-resolved Aplicacao
3156    /// members land as a typed axis, per-member priority annotation once
3157    /// multi-strategy fan-out lands) would have had to be threaded
3158    /// through all three open-coded copies in lockstep or one consumer
3159    /// would silently disagree with the peers on which member slice a
3160    /// given Caixa resolves to — the enumerator's presence probe reading
3161    /// the raw slot while the peer view-composer's fold-in path read an
3162    /// operator-resolved slot would silently split the paired
3163    /// declared-slot enumerator and typed-view composition, and the
3164    /// [`crate::aplicacao::validate_no_self_membership`] self-membership
3165    /// refusal probe reading a third borrow would silently drift the
3166    /// cross-slot coherence gate's traversal input from the two peers, a
3167    /// three-consumer split at the enumerator, the view composer, and
3168    /// the self-membership gate far from the source `caixa.lisp` with no
3169    /// field naming the member-set-drift root cause. Lifting the
3170    /// resolution rule to a typed method on the substrate primitive
3171    /// means every downstream consumer of the caixa's per-`Caixa`
3172    /// MESH-COMPOSITION outer-slice surface reaches for exactly one
3173    /// typed dispatch — the resolver's accept-set migrates as a unit on
3174    /// any future axis addition.
3175    ///
3176    /// Third outer top-level [`Caixa`] `&[Composite]`-return slice
3177    /// accessor for M2 / M3 typed-slot vec-carry axes — opens the outer-
3178    /// `Caixa` M3 mesh-slot arm of the `&[Composite]` composite-slice
3179    /// sub-family the sibling M2 [`Self::upgrade_from`] (2a1f907) /
3180    /// [`Self::children`] (c17b51e) accessors opened for the M2 vec-carry
3181    /// altitude. Peer at the outer altitude of the closed inner-
3182    /// [`crate::AplicacaoSpec::membros`] (6c77e36) accessor on the same
3183    /// MESH-COMPOSITION per-Aplicacao member-list axis — the two
3184    /// altitudes now share the same "byte-equal, borrow-shared" outer-
3185    /// accessor discipline. Sibling in shape to the peer outer-`Caixa`
3186    /// `&[Dep]`-return [`Self::deps`] (ad34b4e) / [`Self::deps_dev`]
3187    /// (f7fd81e) and `&[String]`-return [`Self::autores`] (b5d813f) /
3188    /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`] (8a36c23) /
3189    /// [`Self::exe`] (65d9527) / [`Self::servicos`] (611f78b) slice-
3190    /// accessors on the sibling outer-`Caixa` scalar-element vec-carry
3191    /// axes — folds the "outer [`Caixa`] `&[T]` slice" projection
3192    /// pattern onto the sibling M3 typed-composite-element axis
3193    /// ([`crate::aplicacao::Membro`] composite, matching the per-inner
3194    /// [`crate::AplicacaoSpec::membros`] element type at a different
3195    /// altitude).
3196    ///
3197    /// Returns `&[crate::aplicacao::Membro]` (not `&Vec<Membro>`)
3198    /// because every downstream consumer of the member list treats it
3199    /// as a read-only sequence — the slice-view is the narrowest borrow
3200    /// that supports every present + roadmapped consumer (`.iter()`,
3201    /// `.len()`, `.is_empty()`, the
3202    /// [`crate::aplicacao::validate_no_self_membership`] `&[Membro]`
3203    /// input, `serde` slice-serialization) without leaking the backing
3204    /// `Vec`'s grow/push/reserve surface no consumer of the typed view
3205    /// reaches for (the storage-side `Vec` remains reachable through the
3206    /// `pub membros` field for the mutation-carrying serde round-trip
3207    /// and per-test fixture-mutation paths, including the
3208    /// [`Self::aplicacao_view`] fold-in path that clones the slot into
3209    /// the typed view). Named `membros()` to match the storage field's
3210    /// name verbatim and the tatara-lisp author-surface term
3211    /// (`:membros`) the field's own docstring already carries; the
3212    /// accessor's identity maps onto the canonical MESH-COMPOSITION
3213    /// vocabulary the [`Caixa::membros`] field's docstring already
3214    /// reaches for ("Member Servicos that make up this Aplicacao").
3215    #[must_use]
3216    pub fn membros(&self) -> &[crate::aplicacao::Membro] {
3217        self.membros.as_slice()
3218    }
3219
3220    /// Substrate-canonical per-`Caixa` `:contratos` M3 mesh-slot outer-
3221    /// composite MESH-COMPOSITION-shaped per-Aplicacao WIT-typed
3222    /// inter-Servico contract-list slice accessor every consumer of the
3223    /// top-level manifest's per-Aplicacao `&[crate::aplicacao::WitContract]`
3224    /// slice-view keys off — returns the author-declared `:contratos`
3225    /// typed `Vec<crate::aplicacao::WitContract>` verbatim as a
3226    /// `&[crate::aplicacao::WitContract]` slice-view over the same
3227    /// backing buffer the raw `self.contratos.as_slice()` field access
3228    /// borrows from. Empty-slice-carrying (the "no contracts declared"
3229    /// arm every non-`Aplicacao`-kind `defcaixa` carries by
3230    /// `#[serde(default)]` and every leaf Aplicacao carrying only a
3231    /// single member with no inter-Servico edge carries; the returned
3232    /// `&[WitContract]` degenerates to an empty slice on those arms
3233    /// without any silent `None` collapse).
3234    ///
3235    /// The outer `:contratos` slot carries the M3 typed MESH-COMPOSITION
3236    /// per-Aplicacao WIT-typed inter-Servico edge list — the load-bearing
3237    /// container of every per-edge `{de, para, wit, endpoint | subject |
3238    /// slot}` quadruple the caixa-mesh renderer's per-Aplicacao
3239    /// `CiliumNetworkPolicy` fan-out (one L7 policy per edge —
3240    /// MESH-COMPOSITION §III.2 point 2) and per-`(:de, :para)`
3241    /// adjacency-list seed dispatch on at mesh-artifact materialization
3242    /// time (MESH-COMPOSITION §III.1 — the typed graph's edge set the
3243    /// `:membros` vertex set resolves against, closed by the
3244    /// [`crate::AplicacaoError::ContractoUnknownMember`] / cycle-refusal
3245    /// gates in §III.3; CAIXA-SDLC §II — the typed-M3 slot algebra the
3246    /// operator's per-Aplicacao fan-out dispatch fans on). Every
3247    /// per-edge axis threads through a lifted per-entry accessor on the
3248    /// [`crate::aplicacao::WitContract`] type: the peer `de` / `para`
3249    /// DNS-1123-label member-caixa-name endpoint scalar accessors, the
3250    /// [`crate::aplicacao::WitContract::endpoint`] (7020470) HTTP-shape
3251    /// / [`crate::aplicacao::WitContract::subject`] (90de675)
3252    /// NATS-pub-sub-shape / [`crate::aplicacao::WitContract::slot`]
3253    /// (ed22b66) `wasi:keyvalue/store`-shape payload-carrier accessors,
3254    /// and the WIT-world discriminant. Every downstream consumer of the
3255    /// mesh-graph edge path first passes through this outer accessor
3256    /// onto the slice and then dispatches per-contract through the
3257    /// inner accessors — the two-level dispatch means every
3258    /// per-`:contratos` reader now routes through a typed dispatch on
3259    /// the substrate primitive at both altitudes.
3260    ///
3261    /// Prior to this lift the `.contratos` `Vec<WitContract>` slot was
3262    /// accessed inline at two production sites in
3263    /// caixa-core/src/manifest.rs — the [`Self::declared_mesh_slots`]
3264    /// mesh-slot declared-slot enumerator's
3265    /// `!self.contratos.is_empty()` presence probe (which drives the
3266    /// `M3_AUTHOR_KEY_CONTRATOS` kebab-case author-label push every
3267    /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-coherence
3268    /// gate reads) and the [`Self::aplicacao_view`] per-Aplicacao
3269    /// typed-view composer's `self.contratos.clone()` per-contract
3270    /// fold-in path (which materializes the typed
3271    /// [`crate::aplicacao::AplicacaoSpec`] view every
3272    /// [`crate::StandardLayout::verify`] Aplicacao-arm gate and every
3273    /// downstream `caixa-mesh` renderer dispatches on). A future
3274    /// extension of the outer `:contratos` axis (a per-cluster
3275    /// `:contratos-overrides` overlay the wasm-engine operator resolves
3276    /// at admission time so a cluster-specific edge-set can tighten a
3277    /// caixa-declared list without re-authoring the `caixa.lisp`,
3278    /// promotion of the plain `Vec<WitContract>` to a richer
3279    /// `{static, dynamic}` partition once runtime-resolved contract
3280    /// edges land, per-edge policy annotation once the M4 per-edge
3281    /// policy overlay axis lands) would have had to be threaded through
3282    /// both open-coded copies in lockstep or one consumer would
3283    /// silently disagree with the peer on which edge slice a given
3284    /// Caixa resolves to — the enumerator's presence probe reading the
3285    /// raw slot while the peer view-composer's fold-in path read an
3286    /// operator-resolved slot would silently split the paired
3287    /// declared-slot enumerator and typed-view composition, a
3288    /// two-consumer split at the enumerator and the view composer far
3289    /// from the source `caixa.lisp` with no field naming the edge-set-
3290    /// drift root cause. Lifting the resolution rule to a typed method
3291    /// on the substrate primitive means every downstream consumer of
3292    /// the caixa's per-`Caixa` MESH-COMPOSITION outer-slice surface
3293    /// reaches for exactly one typed dispatch — the resolver's
3294    /// accept-set migrates as a unit on any future axis addition.
3295    ///
3296    /// Fourth and final outer top-level [`Caixa`] `&[Composite]`-return
3297    /// slice accessor for M2 / M3 typed-slot vec-carry axes — closes
3298    /// the outer-`Caixa` `&[Composite]` composite-slice sub-family the
3299    /// sibling M2 [`Self::upgrade_from`] (2a1f907) / [`Self::children`]
3300    /// (c17b51e) accessors opened and the M3 [`Self::membros`]
3301    /// (0f26987) accessor folded on, and closes the outer-`Caixa` M3
3302    /// mesh-slot arm of the composite-slice sub-family the sibling
3303    /// [`Self::membros`] accessor opened for the M3 vec-carry altitude.
3304    /// Peer at the outer altitude of the closed inner-
3305    /// [`crate::AplicacaoSpec::contratos`] (0dcc926) accessor on the
3306    /// same MESH-COMPOSITION per-Aplicacao contract-list axis — the two
3307    /// altitudes now share the same "byte-equal, borrow-shared" outer-
3308    /// accessor discipline. Sibling in shape to the peer outer-`Caixa`
3309    /// `&[Dep]`-return [`Self::deps`] (ad34b4e) / [`Self::deps_dev`]
3310    /// (f7fd81e) and `&[String]`-return [`Self::autores`] (b5d813f) /
3311    /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`] (8a36c23) /
3312    /// [`Self::exe`] (65d9527) / [`Self::servicos`] (611f78b) slice-
3313    /// accessors on the sibling outer-`Caixa` scalar-element vec-carry
3314    /// axes — folds the "outer [`Caixa`] `&[T]` slice" projection
3315    /// pattern onto the sibling M3 typed-composite-element axis
3316    /// ([`crate::aplicacao::WitContract`] composite, matching the
3317    /// per-inner [`crate::AplicacaoSpec::contratos`] element type at a
3318    /// different altitude).
3319    ///
3320    /// Returns `&[crate::aplicacao::WitContract]` (not
3321    /// `&Vec<WitContract>`) because every downstream consumer of the
3322    /// contract list treats it as a read-only sequence — the slice-view
3323    /// is the narrowest borrow that supports every present + roadmapped
3324    /// consumer (`.iter()`, `.len()`, `.is_empty()`, per-edge WIT-world
3325    /// discriminant dispatch, `serde` slice-serialization) without
3326    /// leaking the backing `Vec`'s grow/push/reserve surface no
3327    /// consumer of the typed view reaches for (the storage-side `Vec`
3328    /// remains reachable through the `pub contratos` field for the
3329    /// mutation-carrying serde round-trip and per-test fixture-mutation
3330    /// paths, including the [`Self::aplicacao_view`] fold-in path that
3331    /// clones the slot into the typed view). Named `contratos()` to
3332    /// match the storage field's name verbatim and the tatara-lisp
3333    /// author-surface term (`:contratos`) the field's own docstring
3334    /// already carries; the accessor's identity maps onto the canonical
3335    /// MESH-COMPOSITION vocabulary the [`Caixa::contratos`] field's
3336    /// docstring already reaches for ("WIT-typed inter-Servico
3337    /// contracts").
3338    #[must_use]
3339    pub fn contratos(&self) -> &[crate::aplicacao::WitContract] {
3340        self.contratos.as_slice()
3341    }
3342
3343    /// Compose the Aplicacao-related flat slots into a single typed
3344    /// [`crate::aplicacao::AplicacaoSpec`] for validation +
3345    /// downstream renderer consumption. Returns `None` when the
3346    /// caixa isn't a `:kind Aplicacao`.
3347    #[must_use]
3348    pub fn aplicacao_view(&self) -> Option<crate::aplicacao::AplicacaoSpec> {
3349        if !self.kind().is_aplicacao() {
3350            return None;
3351        }
3352        Some(crate::aplicacao::AplicacaoSpec {
3353            membros: self.membros().to_vec(),
3354            contratos: self.contratos().to_vec(),
3355            politicas: self.politicas().cloned().unwrap_or_default(),
3356            placement: self.placement().cloned().unwrap_or_default(),
3357            entrada: self.entrada().cloned(),
3358        })
3359    }
3360
3361    /// The kebab-case `:slot` tags of every M3 mesh slot this caixa
3362    /// *declares* a value on, in canonical declaration order
3363    /// (`:membros` → `:contratos` → `:politicas` → `:placement` →
3364    /// `:entrada`). A slot counts as declared when its backing field
3365    /// carries a value — a non-empty `Vec`, or a `Some(...)`.
3366    ///
3367    /// The M3 mesh slots compose the typed graph of a `:kind Aplicacao`
3368    /// (MESH-COMPOSITION §III.1). [`Self::aplicacao_view`] only folds
3369    /// them into a validatable [`crate::aplicacao::AplicacaoSpec`] when
3370    /// the kind matches (returns `None` otherwise), and the caixa-mesh /
3371    /// caixa-flux / caixa-helm renderers only emit them for an
3372    /// Aplicacao. On any *other* kind a declared mesh slot is the
3373    /// manifest field's documented "ignored otherwise" (see the
3374    /// `:membros` … `:entrada` field docs): it silently passes
3375    /// [`Caixa::from_lisp`] and then vanishes — never validated, never
3376    /// rendered — far from the source caixa.lisp.
3377    /// [`crate::StandardLayout::verify`] consults this to reject that
3378    /// silent-drop at caixa-build time
3379    /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`]), mirroring the
3380    /// `SupervisorOwnsCode` / `AplicacaoOwnsCode` kind-coherence gates:
3381    /// a slot foreign to the kind is a build error, not a silent drop.
3382    ///
3383    /// Lifted as a typed method (rather than an inline disjunction at
3384    /// the verify call site) so the mesh-slot set lives in one place —
3385    /// a future M4 axis added to the Aplicacao surface (per-edge policy
3386    /// overlay, distributed-app takeover config) is one push here, and
3387    /// every consumer reaching for "which mesh slots are set" (the
3388    /// verify gate, a future `feira lint` kind-coherence advisory)
3389    /// inherits the canonical order without rolling its own.
3390    ///
3391    /// Each per-arm kebab-case label is routed through the peer
3392    /// [`crate::M3_AUTHOR_KEY_MEMBROS`] /
3393    /// [`crate::M3_AUTHOR_KEY_CONTRATOS`] /
3394    /// [`crate::M3_AUTHOR_KEY_POLITICAS`] /
3395    /// [`crate::M3_AUTHOR_KEY_PLACEMENT`] /
3396    /// [`crate::M3_AUTHOR_KEY_ENTRADA`] consts declared next to the
3397    /// [`crate::M3_KEY_PLACEMENT`] renderer-side wire-key peer, so both
3398    /// halves of every M3 top-level mesh slot's dual axis (author-facing
3399    /// kebab-case label + renderer-side artifact key) route through one
3400    /// canonical declaration per arm — same discipline the peer
3401    /// [`crate::M2_AUTHOR_KEY_LIMITS`] / [`crate::M2_AUTHOR_KEY_BEHAVIOR`]
3402    /// / [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] top-level M2 slot consts
3403    /// (f49c8b0) establish on the sibling per-Servico M2 top-level slot
3404    /// axis, extended here to close the M3 mesh-slot author-facing-label
3405    /// axis so both altitudes of the typed-slot algebra
3406    /// (per-Servico M2 + per-Aplicacao M3) share the same
3407    /// "one canonical byte-string per arm, next to the axis" discipline.
3408    #[must_use]
3409    pub fn declared_mesh_slots(&self) -> Vec<&'static str> {
3410        let mut slots = Vec::new();
3411        if !self.membros().is_empty() {
3412            slots.push(crate::render::M3_AUTHOR_KEY_MEMBROS);
3413        }
3414        if !self.contratos().is_empty() {
3415            slots.push(crate::render::M3_AUTHOR_KEY_CONTRATOS);
3416        }
3417        if self.politicas().is_some() {
3418            slots.push(crate::render::M3_AUTHOR_KEY_POLITICAS);
3419        }
3420        if self.placement().is_some() {
3421            slots.push(crate::render::M3_AUTHOR_KEY_PLACEMENT);
3422        }
3423        if self.entrada().is_some() {
3424            slots.push(crate::render::M3_AUTHOR_KEY_ENTRADA);
3425        }
3426        slots
3427    }
3428
3429    /// The kebab-case `:slot` tags of every supervisor-tree slot this
3430    /// caixa *declares* a value on, in canonical declaration order
3431    /// (`:estrategia` → `:max-restarts` → `:restart-window` →
3432    /// `:children`). A slot counts as declared when its backing field
3433    /// carries a value — a `Some(...)`, or a non-empty `Vec`.
3434    ///
3435    /// The supervisor-tree slots compose the typed OTP supervisor of a
3436    /// `:kind Supervisor` (INSPIRATIONS §II.2; the `:estrategia` +
3437    /// `:children` field docs above). [`Self::supervisor_view`] only
3438    /// folds them into a validatable [`SupervisorSpec`] when the kind
3439    /// matches (returns `None` otherwise), and the wasm-operator's
3440    /// hierarchical reconciler only consumes them for a Supervisor. On
3441    /// any *other* kind a declared supervisor slot is the manifest
3442    /// field's documented "ignored otherwise" (see the `:estrategia` …
3443    /// `:children` field docs): it silently passes [`Caixa::from_lisp`]
3444    /// and then vanishes — never validated, never reconciled — far from
3445    /// the source caixa.lisp. [`crate::StandardLayout::verify`] consults
3446    /// this to reject that silent-drop at caixa-build time
3447    /// ([`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]), the
3448    /// exact mirror of the [`Self::declared_mesh_slots`] /
3449    /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] gate on the
3450    /// Aplicacao-only slot set: a slot foreign to the kind is a build
3451    /// error, not a silent drop.
3452    #[must_use]
3453    pub fn declared_supervisor_slots(&self) -> Vec<&'static str> {
3454        let mut slots = Vec::new();
3455        if self.estrategia().is_some() {
3456            slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA);
3457        }
3458        if self.max_restarts().is_some() {
3459            slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS);
3460        }
3461        if self.restart_window().is_some() {
3462            slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW);
3463        }
3464        if !self.children().is_empty() {
3465            slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN);
3466        }
3467        slots
3468    }
3469
3470    /// The kebab-case `:slot` tags of every M2 Servico-runtime slot this
3471    /// caixa *declares* a value on, in canonical declaration order
3472    /// (`:limits` → `:behavior` → `:upgrade-from`). A slot counts as
3473    /// declared when its backing field carries a value — a `Some(...)`,
3474    /// or a non-empty `Vec`.
3475    ///
3476    /// The M2 slots configure the runtime of a long-running wasm
3477    /// component, i.e. a `:kind Servico`: `:limits` is Lunatic
3478    /// per-process sandboxing (INSPIRATIONS §III.1), `:behavior` is the
3479    /// OTP `gen_server` callback set (§II.3), `:upgrade-from` is the OTP
3480    /// appup hot-code-reload table (§II.4). The caixa-helm / caixa-flux
3481    /// renderers gate on [`crate::require_kind`]`(_, Servico)` and only
3482    /// emit these slots for a Servico; on any *other* kind a declared M2
3483    /// slot is the manifest field's documented "ignored otherwise": its
3484    /// well-formedness is checked by [`crate::StandardLayout::verify`]
3485    /// but the value is never rendered into a chart / programs.yaml entry
3486    /// — it silently passes [`Caixa::from_lisp`] + `feira build` and then
3487    /// vanishes, far from the source caixa.lisp.
3488    /// [`crate::StandardLayout::verify`] consults this to reject that
3489    /// silent-drop at caixa-build time
3490    /// ([`crate::LayoutError::ServicoSlotsOnNonServico`]), the exact
3491    /// mirror of the [`Self::declared_mesh_slots`] /
3492    /// [`Self::declared_supervisor_slots`] gates on the peer
3493    /// kind-exclusive slot sets: a slot foreign to the kind is a build
3494    /// error, not a silent drop.
3495    ///
3496    /// Each per-arm kebab-case label is routed through the peer
3497    /// [`crate::M2_AUTHOR_KEY_LIMITS`] / [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
3498    /// [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] consts declared next to the
3499    /// [`crate::M2_KEY_LIMITS`] / [`crate::M2_KEY_BEHAVIOR`] /
3500    /// [`crate::M2_KEY_UPGRADE_FROM`] renderer-side wire-key peers, so
3501    /// both halves of the M2 top-level slot's dual axis (author-facing
3502    /// kebab-case label + renderer-side camelCase overlay-container wire
3503    /// key) route through one canonical declaration per arm — same
3504    /// discipline the peer [`crate::M2_BEHAVIOR_AUTHOR_KEY_ON_*`] sub-slot
3505    /// author-label consts (889dc18) establish on the sibling
3506    /// per-callback axis inside the `:behavior` overlay block.
3507    #[must_use]
3508    pub fn declared_servico_slots(&self) -> Vec<&'static str> {
3509        let mut slots = Vec::new();
3510        if self.limits().is_some() {
3511            slots.push(crate::render::M2_AUTHOR_KEY_LIMITS);
3512        }
3513        if self.behavior().is_some() {
3514            slots.push(crate::render::M2_AUTHOR_KEY_BEHAVIOR);
3515        }
3516        if !self.upgrade_from().is_empty() {
3517            slots.push(crate::render::M2_AUTHOR_KEY_UPGRADE_FROM);
3518        }
3519        slots
3520    }
3521
3522    /// The kebab-case `:slot` tags of every code-surface slot this caixa
3523    /// declares a value on that its [`CaixaKind`] doesn't natively own,
3524    /// in canonical declaration order (`:exe` → `:servicos`). A
3525    /// code-surface slot is owned by exactly one kind: `:exe` by
3526    /// [`CaixaKind::Binario`] (the nix-built executable surface), and
3527    /// `:servicos` by [`CaixaKind::Servico`] (the wasm component +
3528    /// `ComputeUnit` daemon surface).
3529    ///
3530    /// Each is silently ignored when declared on the wrong kind: the
3531    /// caixa-helm / caixa-flux / caixa-flake renderers gate on
3532    /// [`crate::require_kind`]`(_, <owning-kind>)`, so on any *other*
3533    /// code-running kind a declared `:exe` / `:servicos` is the manifest
3534    /// field's documented "ignored otherwise" — its path is checked for
3535    /// existence by the layout's `bibliotecas`/`exe`/`servicos` loops
3536    /// (which run after [`Caixa::from_lisp`]), but the value is never
3537    /// rendered into a build target or programs.yaml entry. It silently
3538    /// passes [`Caixa::from_lisp`] + `feira build`, far from the source
3539    /// caixa.lisp, with no field naming which slot is foreign.
3540    ///
3541    /// [`crate::StandardLayout::verify`] consults this to reject that
3542    /// silent-drop at caixa-build time
3543    /// ([`crate::LayoutError::ForeignCodeSlot`]), beside the M2
3544    /// servico-runtime, supervisor-tree, and M3 mesh kind-coherence
3545    /// gates ([`Self::declared_servico_slots`] /
3546    /// [`Self::declared_supervisor_slots`] /
3547    /// [`Self::declared_mesh_slots`]): the fourth kind ↔ slot algebra
3548    /// axis to be closed on the typed surface. The Supervisor /
3549    /// Aplicacao "no code at all" cases ([`crate::LayoutError::SupervisorOwnsCode`]
3550    /// / [`crate::LayoutError::AplicacaoOwnsCode`]) keep their dedicated
3551    /// diagnostics — they fire ahead of this gate on the same `verify`
3552    /// pass, so for Supervisor / Aplicacao the `OwnCode` arm always wins
3553    /// and this method is moot. For Biblioteca / Binario / Servico, this
3554    /// gate fires when a code-running kind declares another code-running
3555    /// kind's exclusive code surface.
3556    ///
3557    /// `:bibliotecas` is deliberately excluded — a Binario or Servico
3558    /// may legitimately ship a `lib/` helper that the underlying
3559    /// substrate (the nix flake for Binario, the wasm component build
3560    /// for Servico) bundles into its build, so the slot's
3561    /// declared-on-wrong-kind cardinality isn't a structural error on
3562    /// either code-running kind. A Biblioteca declaring `:bibliotecas`
3563    /// is the native case (the slot's owning kind). Supervisor /
3564    /// Aplicacao declaring `:bibliotecas` is gated upstream by
3565    /// [`crate::LayoutError::SupervisorOwnsCode`] /
3566    /// [`crate::LayoutError::AplicacaoOwnsCode`].
3567    ///
3568    /// Lifted as a typed method (rather than an inline disjunction at
3569    /// the verify call site) so the foreign-code-slot set lives in one
3570    /// place — a future kind that gains its own code-surface slot is
3571    /// one push here, and every consumer reaching for "which code
3572    /// surfaces are foreign to this kind" (the verify gate, a future
3573    /// `feira lint` kind-coherence advisory, the future `app-operator`'s
3574    /// per-caixa build-target classifier) inherits the canonical order
3575    /// without rolling its own.
3576    #[must_use]
3577    pub fn declared_foreign_code_slots(&self) -> Vec<&'static str> {
3578        let mut slots = Vec::new();
3579        if !self.exe().is_empty() && !self.kind().requires_exe() {
3580            slots.push(":exe");
3581        }
3582        if !self.servicos().is_empty() && !self.kind().requires_servicos() {
3583            slots.push(":servicos");
3584        }
3585        slots
3586    }
3587
3588    /// Validate every entry of `:deps` and `:deps-dev` through
3589    /// [`Dep::validate`] — closing the parity loop with the per-axis
3590    /// `:versao` gates already wired into the typed-graph
3591    /// ([`crate::AplicacaoSpec::validate_membros`] for `:membros`,
3592    /// 9888b13) and typed supervisor tree
3593    /// ([`crate::SupervisorSpec::validate`] for `:children`, b38ff3a).
3594    ///
3595    /// Until this gate landed `:deps :versao` and `:deps-dev :versao`
3596    /// were the only `:versao` axes still untyped past
3597    /// [`Caixa::from_lisp`]: the derive macro stored the requirement
3598    /// as a String without parsing it, so a malformed-but-non-empty
3599    /// requirement (`"^bad-version"`, `"^^0.1"`, `"v0.1"`, `"not-a-req"`)
3600    /// silently passed parse and the `semver::Error` surfaced at
3601    /// lacre-resolve time, far from the source caixa.lisp, with no
3602    /// field naming which `:deps` entry carried the typo. Lifting the
3603    /// gate here makes the four `:versao` typed surfaces (`:deps`,
3604    /// `:deps-dev`, `:membros`, `:children`) structurally equivalent —
3605    /// every requirement string past `validate_deps` is round-trippable
3606    /// through [`crate::parse_requirement`] without re-checking at the
3607    /// resolver layer.
3608    ///
3609    /// Both lists run through the same per-entry validator so a typo
3610    /// in `:deps-dev` surfaces with the same diagnostic as one in
3611    /// `:deps` — neither axis is a second-class citizen of the typed
3612    /// surface.
3613    ///
3614    /// Within each list, [`DepError::DuplicateNome`] closes the
3615    /// set-not-multiset discipline on the `:nome` axis: two entries
3616    /// naming the same caixa carry two `:versao` / `:fonte` / feature
3617    /// triples that the caixa-resolver's lacre pipeline collapses to one
3618    /// via its `HashMap`-keyed-by-`:nome` consumption — the second entry
3619    /// silently overwrites the first at `concrete_versao`-resolve time
3620    /// (the same "second wins / one silently overwrites the other"
3621    /// shape the peer typed-graph duplicate gates already close on every
3622    /// other Vec-shaped authoring surface that keys by name). The
3623    /// duplicate check fires per-list and runs *after* each per-entry
3624    /// [`Dep::validate`] call so a malformed-and-duplicated entry
3625    /// surfaces its narrower per-entry diagnostic
3626    /// ([`DepError::NomeInvalid`], [`DepError::VersaoInvalid`],
3627    /// [`DepError::FonteRepoEmpty`], …) before the cross-entry duplicate
3628    /// diagnostic — the canonical "per-entry shape before cross-entry
3629    /// uniqueness" precedence the peer `:children :caixa`
3630    /// ([`crate::SupervisorSpec::validate`]), `:membros :caixa`
3631    /// ([`crate::AplicacaoSpec::validate_membros`]), `:contratos`
3632    /// ([`crate::AplicacaoSpec::validate`]), `:placement :clusters`
3633    /// ([`crate::AplicacaoSpec::validate_placement`]),
3634    /// `:entrada :paths` ([`crate::AplicacaoSpec::validate`]),
3635    /// `:upgrade-from :from` ([`crate::upgrade::validate_upgrade_from`]),
3636    /// and the within-`:upgrade-from`-entry per-instruction-class
3637    /// singularity gates ([`crate::UpgradeError::DuplicateLoadModule`],
3638    /// [`crate::UpgradeError::DuplicateStateChange`],
3639    /// [`crate::UpgradeError::DuplicateCleanup`]) all establish.
3640    ///
3641    /// Cross-list (`:deps` ↔ `:deps-dev`) coincidence is *not* gated
3642    /// here: Cargo's `[dependencies]` + `[dev-dependencies]` accept the
3643    /// same name in both tables (the dev table's pin overrides the
3644    /// runtime table's pin in test/dev contexts), and caixa's surface
3645    /// mirrors that convention until a deliberate choice retires the
3646    /// override pattern. Only within-list duplicates are structurally
3647    /// incoherent — those are what this gate closes.
3648    pub fn validate_deps(&self) -> Result<(), DepError> {
3649        let mut seen = std::collections::HashSet::new();
3650        for dep in self.deps() {
3651            dep.validate()?;
3652            crate::render::insert_first_seen(&mut seen, dep.nome(), || DepError::DuplicateNome {
3653                nome: dep.nome().to_string(),
3654                list: crate::render::DEP_AUTHOR_KEY_DEPS,
3655            })?;
3656        }
3657        let mut seen_dev = std::collections::HashSet::new();
3658        for dep in self.deps_dev() {
3659            dep.validate()?;
3660            crate::render::insert_first_seen(&mut seen_dev, dep.nome(), || {
3661                DepError::DuplicateNome {
3662                    nome: dep.nome().to_string(),
3663                    list: crate::render::DEP_AUTHOR_KEY_DEPS_DEV,
3664                }
3665            })?;
3666        }
3667        Ok(())
3668    }
3669
3670    /// Reject `:nome` values the K8s apiserver would refuse at admission
3671    /// time. The top-level Caixa identity flows directly into every
3672    /// substrate-side artifact's `metadata.name` axis: the
3673    /// `lareira-<nome>` Helm chart name ([`caixa-helm::lib::chart_name`]),
3674    /// the programs.yaml `name:` entry the `lareira-fleet-programs`
3675    /// aggregator keys ComputeUnit derivation off
3676    /// ([`caixa-flux::lib::programs_yaml_entry`]), the
3677    /// `LABEL_APLICACAO` label value carried on every Aplicacao-owned
3678    /// pod and the per-`:contratos` CiliumNetworkPolicy `metadata.name`
3679    /// (`<aplicacao>-<de>-to-<para>`) and the per-`:entrada`
3680    /// `<aplicacao>-<para>` HTTPRoute `metadata.name`
3681    /// ([`caixa-mesh::lib::cilium_network_policies`],
3682    /// [`caixa-mesh::lib::gateway_routes`]), and the default
3683    /// `lib/<nome>.lisp` / `exe/<nome>` layout paths
3684    /// ([`crate::StandardLayout::verify`]). Each K8s apiserver-side
3685    /// schema enforces the DNS-1123 label rule on admission; a
3686    /// structurally invalid `:nome` (`"MyApp"` — the canonical
3687    /// "I copied the display name verbatim" footgun, `"my_app"` — the
3688    /// Python-/Postgres-leak, `"team.app"` — `:nome` is a single label
3689    /// not a subdomain, `"-app"` / `"app-"` — DNS-1123 boundary
3690    /// violations, `"my app"` — the paste-from-doc footgun, `"café"` —
3691    /// IDN must be pre-encoded as Punycode, the 64-byte UUID-shaped
3692    /// over-cap slug) silently passed [`Caixa::from_lisp`] and the
3693    /// failure surfaced at `kubectl apply` time as a `metadata.name:
3694    /// Invalid value` rejection on whichever derived artifact admitted
3695    /// first, far from the source `caixa.lisp` and without any field
3696    /// naming the offending `:nome`.
3697    ///
3698    /// Thin wrapper around [`crate::render::is_dns_1123_label`] (the
3699    /// substrate-side predicate the per-axis name gates already share:
3700    /// `:membros :caixa` 3f9d7a0, `:placement :clusters` 6cbb900,
3701    /// `:children :caixa` 31bfa43) that maps the shared parser-shaped
3702    /// reason into the [`ManifestError::NomeInvalid`] variant, so the
3703    /// diagnostic is self-locating (the offending `:nome` is named
3704    /// verbatim) and the author can grep their `caixa.lisp` for
3705    /// `:nome "<value>"` and fix it in one edit. Same diagnostic shape
3706    /// every per-axis sibling gate already exposes
3707    /// ([`crate::AplicacaoError::MembroCaixaInvalid`],
3708    /// [`crate::AplicacaoError::PlacementClusterInvalid`],
3709    /// [`crate::SupervisorError::ChildCaixaInvalid`]).
3710    ///
3711    /// Empty `:nome` (which [`Caixa::from_lisp`] does not reject — the
3712    /// derive macro stores the raw String) is gated by the narrower
3713    /// [`ManifestError::NomeEmpty`] arm before the predicate is
3714    /// consulted, mirroring the empty-first cascade every per-axis
3715    /// name gate already uses (e.g. `MembroCaixaEmpty` before
3716    /// `MembroCaixaInvalid`, `EmptyChildName` before `ChildCaixaInvalid`).
3717    pub fn validate_nome(&self) -> Result<(), ManifestError> {
3718        // Routes through the shared
3719        // [`crate::render::require_valid_dns_1123_label`] gate the peer
3720        // name axes each land on so drift between the eight axes'
3721        // accepted DNS-1123-label sets is structurally impossible.
3722        let nome = self.nome();
3723        crate::render::require_valid_dns_1123_label(
3724            nome,
3725            || ManifestError::NomeEmpty,
3726            |reason| ManifestError::NomeInvalid {
3727                nome: nome.to_string(),
3728                reason,
3729            },
3730        )
3731    }
3732
3733    /// Reject `:nome` values whose joint length with the canonical
3734    /// [`crate::LAREIRA_CHART_NAME_PREFIX`] (`"lareira-"`) overflows
3735    /// the K8s DNS-1123 label cap [`crate::DNS_1123_LABEL_MAX_LEN`]
3736    /// (63 bytes). Every per-Servico / per-Aplicacao renderer the
3737    /// substrate carries materializes the caixa's `:nome` through the
3738    /// canonical [`crate::lareira_chart_name`] helper (f7320d7) into a
3739    /// `lareira-<nome>` artifact that lands as a K8s `metadata.name` /
3740    /// Helm chart name / `HelmRelease` `release_name`: `caixa-helm`'s
3741    /// `ChartDir.name` + `Chart.yaml::name`
3742    /// (caixa-helm/src/lib.rs:207), `caixa-flux`'s `cluster_bundle`
3743    /// `HelmRelease` `chart:` slot (caixa-flux/src/lib.rs:329),
3744    /// `caixa-tatara`'s `process_for_aplicacao` `release_name` +
3745    /// `oci://<registry>/lareira-<nome>` chart ref
3746    /// (caixa-tatara/src/lib.rs:124,178). Helm's own `Chart.yaml::name`
3747    /// admission rule strict-parses against DNS-1123-label, the Helm
3748    /// operator's tracking-secret name is derived from `release_name`
3749    /// and is itself DNS-1123-label-bounded, and the rendered chart's
3750    /// K8s object `metadata.name` axes embed the chart name as a
3751    /// prefix — every one fails admission on a > 63-byte chart name.
3752    ///
3753    /// The per-axis [`Self::validate_nome`] gate (6c992f8) already
3754    /// caps `:nome` itself at 63 bytes via [`is_dns_1123_label`], so a
3755    /// `:nome` of 56–63 bytes silently passed validate (the inner
3756    /// DNS-1123 check accepts the bare `:nome`) but produced a
3757    /// `lareira-<nome>` of 64–71 bytes that the apiserver / `helm lint`
3758    /// rejected at admission — far from the source `caixa.lisp`, with
3759    /// no field naming the overflow root cause. The
3760    /// [`lareira_chart_name`] helper's own doc comment
3761    /// (caixa-core/src/render.rs:3198) explicitly deferred the fix:
3762    /// "the M4 admission webhook will pin the joint-length invariant
3763    /// when it lands". This gate lands the invariant at the
3764    /// manifest-validate layer rather than waiting for the apiserver
3765    /// — the same fail-at-the-source posture every peer per-axis
3766    /// value-shape gate (DNS-1123 on `:nome`, SemVer-2 on `:versao`,
3767    /// SPDX-expression-shape on `:licenca`, 4-digit decimal year on
3768    /// `:edicao`, etc.) takes.
3769    ///
3770    /// Thin wrapper around
3771    /// [`crate::render::is_lareira_chart_name_shape`] (the
3772    /// substrate-side predicate that composes [`lareira_chart_name`] +
3773    /// [`is_dns_1123_label`] via the lifted
3774    /// [`crate::LAREIRA_CHART_NAME_NOME_MAX_LEN`] budget); maps the
3775    /// shared parser-shaped reason into the
3776    /// [`ManifestError::NomeChartNameBudgetExceeded`] variant so the
3777    /// diagnostic is self-locating (the offending `:nome` is named
3778    /// verbatim alongside the rendered chart name and the budget) and
3779    /// the author can shorten in one edit. The gate runs across every
3780    /// `:kind` — `:nome` is the substrate-wide identity axis any
3781    /// future renderer the substrate adds can derive a
3782    /// `lareira-<nome>` artifact from, and uniform enforcement closes
3783    /// the drift footgun where a future kind grows a chart-emitting
3784    /// render path while the validate cascade doesn't catch it.
3785    ///
3786    /// Runs *after* [`Self::validate_nome`] so the narrower
3787    /// `NomeEmpty` / `NomeInvalid` shape diagnostics fire first — a
3788    /// structurally-malformed `:nome` (empty, uppercase, underscore,
3789    /// dot, leading/trailing hyphen, Unicode, > 63 bytes) surfaces its
3790    /// specific shape error rather than the chart-name-budget error,
3791    /// preserving the legitimate "well-shaped `:nome` that happens to
3792    /// overflow the joint cap" arm for this gate.
3793    pub fn validate_nome_chart_name_budget(&self) -> Result<(), ManifestError> {
3794        let nome = self.nome();
3795        crate::render::is_lareira_chart_name_shape(nome).map_err(|reason| {
3796            ManifestError::NomeChartNameBudgetExceeded {
3797                nome: nome.to_string(),
3798                reason,
3799            }
3800        })
3801    }
3802
3803    /// Reject `:versao` values that don't parse as [`semver::Version`].
3804    /// The top-level Caixa version flows directly into every
3805    /// substrate-side artifact that carries a "this is which version of
3806    /// the caixa" axis: the `lareira-<nome>` Helm chart's `Chart.yaml`
3807    /// `version:` + `appVersion:` axes ([`caixa-helm::lib`] —
3808    /// SemVer-2-strict at `helm template` / `helm install` time per
3809    /// https://helm.sh/docs/topics/charts/#charts-and-versioning), the
3810    /// `feira publish` Zig-style `v<versao>` git tag
3811    /// ([`caixa-flux::lib::programs_yaml_entry`] / the
3812    /// `caixa-publish.yml` reusable workflow), the programs.yaml entry's
3813    /// `versao:` value the `lareira-fleet-programs` aggregator carries
3814    /// onto each rendered ComputeUnit, the OCI image's `:v<versao>` /
3815    /// `:latest` tags the substrate's `wasi-service-flake` builds with
3816    /// `skopeo push`, the lacre closure's pinned versions
3817    /// ([`caixa-resolver`] keys `concrete_versao`), and the
3818    /// `:upgrade-from :from` references peers in this exact `versao`
3819    /// shape (`semver::Version`, not `VersionReq`). Each consumer
3820    /// expects a strict three-part `MAJOR.MINOR.PATCH` (optionally
3821    /// `-prerelease` and/or `+build`); a structurally invalid `:versao`
3822    /// (`"0.1"` — missing patch, the canonical "I shortened it" footgun;
3823    /// `"v0.1.0"` — the git-tag-shape-leaking-into-versao typo;
3824    /// `"latest"` / `"main"` — the "I confused it with a docker tag"
3825    /// footgun; `"^0.1"` / `"~0.1.2"` — the requirement-shape leaking
3826    /// into the version field a peer `:deps :versao` accepts;
3827    /// `"0.1.0.0"` — the four-part Java/Microsoft convention DNS
3828    /// SemVer-2 forbids) silently passed [`Caixa::from_lisp`] (the
3829    /// derive macro stores the raw String) and the failure surfaced at
3830    /// the *first* downstream consumer that strict-parses it: at
3831    /// `helm install` time as a chart-version rejection, at
3832    /// `feira publish` time as a malformed git tag, at lacre-resolve
3833    /// time as a `semver::Error` not naming the offending caixa, at
3834    /// `feira upgrade --to <versao>` time as an unresolvable
3835    /// `:upgrade-from :from` match — far from the source `caixa.lisp`
3836    /// and without any field naming the offending `:versao`.
3837    ///
3838    /// Thin wrapper around [`semver::Version::parse`] — the same parser
3839    /// [`crate::CaixaVersion::parse`] (the typed `:versao` accessor)
3840    /// and [`crate::UpgradeFromEntry::validate`] (the peer
3841    /// `:upgrade-from :from` axis, 26da2c7) consume. Maps the
3842    /// `semver::Error` reason into the [`ManifestError::VersaoInvalid`]
3843    /// variant, carrying the offending `:versao` verbatim + a
3844    /// parser-shaped reason naming the specific violation, so the
3845    /// diagnostic is self-locating (the author can grep their
3846    /// `caixa.lisp` for `:versao "<value>"` and fix it in one edit).
3847    /// Same diagnostic shape as [`ManifestError::NomeInvalid`]
3848    /// (6c992f8) and [`crate::UpgradeError::FromInvalid`]
3849    /// (b0c8389) on the peer axes. With this gate, the typed `:versao`
3850    /// surfaces — top-level `:versao`, `:upgrade-from :from` — are
3851    /// now structurally equivalent (every value past validate is
3852    /// round-trippable through [`semver::Version::parse`] without
3853    /// re-checking at the renderer, resolver, or operator hot-upgrade
3854    /// layer), peer with the four `:versao` requirement axes (`:deps`,
3855    /// `:deps-dev`, `:membros`, `:children`) the prior commits
3856    /// (2420c44, 9888b13, b38ff3a) wired through `parse_requirement`.
3857    ///
3858    /// Empty `:versao` (which [`Caixa::from_lisp`] does not reject —
3859    /// the derive macro stores the raw String) is gated by the
3860    /// narrower [`ManifestError::VersaoEmpty`] arm before the parser is
3861    /// consulted, mirroring the empty-first cascade every per-axis
3862    /// version gate already uses (e.g. `MembroVersaoEmpty` before
3863    /// `MembroVersaoInvalid`, `EmptyChildVersion` before
3864    /// `ChildVersaoInvalid`, `NomeEmpty` before `NomeInvalid`).
3865    pub fn validate_versao(&self) -> Result<(), ManifestError> {
3866        let versao = self.versao();
3867        if versao.is_empty() {
3868            return Err(ManifestError::VersaoEmpty);
3869        }
3870        semver::Version::parse(versao).map_err(|e| ManifestError::VersaoInvalid {
3871            versao: versao.to_string(),
3872            reason: e.to_string(),
3873        })?;
3874        Ok(())
3875    }
3876
3877    /// Reject `:restart-window` values the shared
3878    /// [`crate::supervisor::duration_codec::parse`] refuses. The flat
3879    /// `restart_window: Option<String>` slot on [`Caixa`] is stored
3880    /// raw by the derive macro (the typed [`SupervisorSpec`] holds an
3881    /// `Option<Duration>` routed through the shared codec via `with =
3882    /// "duration_codec"`); the inline `Caixa → SupervisorSpec`
3883    /// view-construction path ([`Self::supervisor_view`]) folds the
3884    /// raw string through the same shared codec and soft-swallows the
3885    /// parse error as `None` to keep the view best-effort. Without
3886    /// this gate a malformed `:restart-window` (`"1.5s"` — the
3887    /// fractional-seconds drift class; `"1.0s"` — the decimal-shaped
3888    /// integer drift; `"0.5m"` — the unit-fraction drift; `"+30s"` /
3889    /// `"-30s"` — the leading-sign drift; `"30x"` — the unknown-unit
3890    /// footgun; `"abc"` — pure garbage; `""` — the empty-after-trim
3891    /// edge case) silently produced a `SupervisorSpec` with
3892    /// `restart_window: None`, indistinguishable from the canonical
3893    /// "omit the slot to express no reset" authoring shape — Erlang/OTP's
3894    /// `MaxIntensity / Period` invariant turns into a never-reset
3895    /// supervisor far from the source `caixa.lisp`, with no field
3896    /// naming the offending `:restart-window`. Lifting the gate to a
3897    /// Caixa-level validator mirrors the trajectory of the peer
3898    /// per-axis identity gates ([`Self::validate_nome`] 6c992f8,
3899    /// [`Self::validate_versao`] 1fdaa02, [`Self::validate_deps`]
3900    /// a7f0d8c) and the ABSORPTION-ROADMAP.md M2.2 test pin
3901    /// (line 196: "reject invalid `:restart-window` (non-duration)").
3902    ///
3903    /// Thin wrapper around [`crate::supervisor::duration_codec::parse`]
3904    /// (the shared codec backing `:supervisor :restart-window` as
3905    /// serde-routed on [`SupervisorSpec`], `:politicas :timeout`, and
3906    /// `:politicas :circuit-breaker :window` — all three covered by
3907    /// the integer-magnitude gate 1c55a2a). Maps the codec's parse
3908    /// error verbatim into the [`ManifestError::RestartWindowMalformed`]
3909    /// variant, carrying the offending raw string + a parser-shaped
3910    /// reason naming the canonical authoring form, so the diagnostic
3911    /// is self-locating (the author can grep their `caixa.lisp` for
3912    /// `:restart-window "<value>"` and fix it in one edit) and
3913    /// uniform with every other manifest-level validate diagnostic.
3914    /// With this gate the four `:restart-window`-shaped surfaces (the
3915    /// flat raw string on [`Caixa`], the typed `Option<Duration>` on
3916    /// [`SupervisorSpec`], the two `MeshPolicy` peer durations) are
3917    /// now structurally equivalent — every value past the codec is in
3918    /// one accepted set, by construction.
3919    ///
3920    /// `None` (the canonical "omit the slot to express no reset"
3921    /// shape) is accepted trivially — the gate is a no-op when the
3922    /// author didn't author a window. The empty string is rejected by
3923    /// the shared codec (its digit-only gate refuses an empty
3924    /// magnitude), surfacing the same `RestartWindowMalformed`
3925    /// diagnostic as every other rejected non-canonical shape.
3926    pub fn validate_restart_window(&self) -> Result<(), ManifestError> {
3927        let Some(s) = self.restart_window() else {
3928            return Ok(());
3929        };
3930        crate::supervisor::duration_codec::parse(s)
3931            .map(|_| ())
3932            .map_err(|reason| ManifestError::RestartWindowMalformed {
3933                restart_window: s.to_string(),
3934                reason,
3935            })
3936    }
3937
3938    /// Reject per-entry values on the three Caixa-level code-surface
3939    /// path lists (`:bibliotecas`, `:exe`, `:servicos`) that the
3940    /// layout checker's `root.join(p)` sandbox would silently subvert.
3941    /// Same three structural footguns the peer
3942    /// [`BehaviorSpec::validate`] (b0c8389) and
3943    /// [`crate::UpgradeInstruction::validate`] `StateChange` arm
3944    /// (26da2c7) already close on the M2 `:behavior :on-*` and
3945    /// `:upgrade-from :state-change :script` axes, here lifted onto
3946    /// the three top-level code-path axes through the shared
3947    /// [`is_sandboxed_relative_path`] predicate:
3948    ///
3949    ///   - empty entry (`(:bibliotecas (""))` / `(:exe (""))` /
3950    ///     `(:servicos (""))`): `PathBuf::new()` round-trips through
3951    ///     [`Path::join`] as the base itself — `root.join("")` ==
3952    ///     `root`, so the existence check (`self.exists(&root)`)
3953    ///     trivially passes (the project root exists), and the layout
3954    ///     silently treats the project root as a biblioteca / exe /
3955    ///     servico entry. The `:bibliotecas` loop then hands the root
3956    ///     to `tatara_lisp::read` at `feira build` time as if the root
3957    ///     directory itself were a Lisp source file — a parse error
3958    ///     far from the source `caixa.lisp` with no field naming the
3959    ///     offending entry.
3960    ///   - absolute path (`(:bibliotecas ("/etc/passwd"))`):
3961    ///     [`Path::join`] *replaces* the base when the right-hand side
3962    ///     is absolute, so `root.join("/etc/passwd")` resolves to
3963    ///     `"/etc/passwd"` and escapes the project sandbox entirely.
3964    ///     The existence check then silently consults whatever the
3965    ///     escaped path resolves to — for `:bibliotecas`, the layout
3966    ///     has no `starts_with`-fence (only `:exe` is fenced under
3967    ///     `exe/` and `:servicos` under `servicos/`), so an absolute
3968    ///     `:bibliotecas` entry that happens to resolve on disk
3969    ///     silently passes. For `:exe` / `:servicos` the fence catches
3970    ///     the absolute case downstream as `ExeOutsideDir` /
3971    ///     `ServicoOutsideDir` (or `MissingEntry` if the absolute path
3972    ///     doesn't exist), but with a downstream-shaped diagnostic
3973    ///     that names the resolved escape path rather than the
3974    ///     authoring footgun at the source.
3975    ///   - parent-escape (`(:bibliotecas ("../sibling/x.lisp"))` /
3976    ///     `(:exe ("exe/../../escape.lisp"))`): a [`PathBuf`] with any
3977    ///     [`std::path::Component::ParentDir`] anywhere round-trips
3978    ///     through [`Path::join`] as a traversal above the caixa root.
3979    ///     The `:exe` / `:servicos` `starts_with(<dir>)` fence is
3980    ///     *component-aware* (not canonical-path-aware), so
3981    ///     `root.join("exe/../../escape.lisp")` `starts_with(exe_dir)`
3982    ///     is **true** even though the canonical resolution
3983    ///     `{parent of root}/escape.lisp` lives outside the caixa root
3984    ///     — the fence silently lets the parent-escape through, and
3985    ///     the existence check passes if that escape-target happens
3986    ///     to exist. Caught regardless of where the `..` sits
3987    ///     (leading, mid-path, trailing) so the gate matches the peer
3988    ///     predicate's full coverage.
3989    ///
3990    /// Same `Empty` → `Absolute` → `ParentEscape` arm-ordering every peer
3991    /// `is_sandboxed_relative_path` consumer follows (b0c8389 / 26da2c7);
3992    /// same per-slot diagnostic shape every peer per-axis path-gate
3993    /// exposes (`*Empty { slot }` / `*Absolute { slot, path }` /
3994    /// `*ParentEscape { slot, path }`). Cross-slot precedence is
3995    /// `:bibliotecas` → `:exe` → `:servicos` — the same declaration
3996    /// order [`Caixa::declared_foreign_code_slots`] uses for its
3997    /// canonical foreign-code-slot diagnostic, so a manifest with
3998    /// multiple malformed slots surfaces the lexicographically-earliest
3999    /// slot's diagnostic deterministically.
4000    ///
4001    /// Lifted to the typed surface as a Caixa-level validator (peer
4002    /// of [`Self::validate_nome`] / [`Self::validate_versao`] /
4003    /// [`Self::validate_deps`] / [`Self::validate_restart_window`])
4004    /// and wired into [`crate::StandardLayout::verify`] before the
4005    /// existence-check loops so the diagnostic names the offending
4006    /// slot at the source caixa.lisp rather than reporting a
4007    /// downstream `MissingEntry` / `ExeOutsideDir` /
4008    /// `ServicoOutsideDir` against the resolved sandbox-escape path.
4009    /// The fourth typed code-path surface — every author-supplied
4010    /// path on the manifest — is now structurally accept-shaped
4011    /// past validate, peer with `:behavior :on-*` and
4012    /// `:upgrade-from :state-change :script`.
4013    pub fn validate_code_paths(&self) -> Result<(), ManifestError> {
4014        /// Per-slot file-type contract for the three Caixa-level
4015        /// code-path surfaces (`:bibliotecas`, `:exe`, `:servicos`).
4016        /// Each variant names the predicate the per-entry file-type
4017        /// gate consults; [`Self::None`] opts the slot out of any
4018        /// file-type contract. Lifted as a typed local enum so the
4019        /// per-slot dispatch is exhaustive at the `match` — adding a
4020        /// future axis to the typed-substrate `:` slot set (the
4021        /// future `:assets` resource axis the M5 roadmap names, the
4022        /// future `:nix-flake` derivation axis the caixa-flake
4023        /// emitter consults) lands as one variant + one `match` arm,
4024        /// not a coordinated rewrite of every per-slot bool flag.
4025        ///
4026        /// Peer of the typed-substrate per-slot variant disciplines
4027        /// already established on this surface
4028        /// ([`crate::supervisor::RestartStrategy`] +
4029        /// [`crate::supervisor::RestartPolicy`] on the OTP-shape
4030        /// supervision-tree axis,
4031        /// [`crate::aplicacao::PlacementStrategy`] on the §III.1
4032        /// placement axis, [`crate::aplicacao::WitTarget`] on the
4033        /// `:contratos` payload-target axis): the typed `enum` is
4034        /// the substrate's single source of truth for the per-axis
4035        /// dispatch, and every consumer (the per-arm body here, the
4036        /// future feira-lint per-slot diagnostic renderer, the M4
4037        /// per-axis admission webhook) reaches for the same typed
4038        /// surface rather than re-deriving the partition from inline
4039        /// flag combinations.
4040        enum CodePathFileType {
4041            /// `:exe` — nix-build derivation output, no terminating-
4042            /// extension contract (the canonical `"exe/<name>"`
4043            /// fixtures the layout's `ExeOutsideDir` error message
4044            /// documents carry no extension by convention).
4045            None,
4046            /// `:bibliotecas` — tatara-lisp source files the
4047            /// `feira build` loop reads through `tatara_lisp::read`
4048            /// at parse time. Routes to [`is_lisp_extension`].
4049            LispSource,
4050            /// `:servicos` — ComputeUnit-CR YAML files the
4051            /// caixa-helm / caixa-flux renderers consume through
4052            /// `serde_yaml::from_str`. Routes to
4053            /// [`is_computeunit_yaml_extension`].
4054            ComputeUnitYaml,
4055        }
4056
4057        // The per-slot [`CodePathFileType`] selects which axes carry the
4058        // lifted file-type predicate. `:bibliotecas` is the tatara-lisp
4059        // source axis (the `feira build` loop at
4060        // `caixa-feira/src/cmd/build.rs:33` reads each entry through
4061        // `tatara_lisp::read` at parse time) — the lifted
4062        // [`is_lisp_extension`] predicate gates the `.lisp` extension.
4063        // `:exe` is the nix-built executable surface (per the canonical
4064        // `"exe/<name>"`-shaped fixtures the layout's `ExeOutsideDir`
4065        // error message documents and every in-tree
4066        // `caixa_with_code_paths` positive control uses) — its file-type
4067        // contract is "nix-build derivation output", not a typed source
4068        // file, so [`CodePathFileType::None`] opts the slot out of any
4069        // file-type gate. `:servicos` is the `.computeunit.yaml`
4070        // ComputeUnit-CR axis (the peer caixa-helm / caixa-flux
4071        // renderers consume each entry through `serde_yaml::from_str` as
4072        // a typed `ComputeUnit` CR) — the lifted
4073        // [`is_computeunit_yaml_extension`] predicate gates the compound
4074        // `.computeunit.yaml` suffix. All three axes are surfaced through
4075        // the same iteration so the sandbox-shape + duplicate gates
4076        // apply uniformly; the typed file-type dispatch fires per-slot
4077        // exactly where the downstream consumer's accepted set demands
4078        // it. The third file-type variant ([`ComputeUnitYaml`]) is the
4079        // compounding lift on the peer 64772a9 `:bibliotecas`
4080        // `.lisp`-gate trajectory — the second of the three code-path
4081        // axes to land on a typed compound-suffix gate, with the same
4082        // self-locating per-slot diagnostic shape every peer per-axis
4083        // file-type lift uses (`*NonLispExtension { slot, path }` /
4084        // `*NonComputeUnitYamlExtension { slot, path }`).
4085        for (slot, list, file_type) in [
4086            (
4087                ":bibliotecas",
4088                &self.bibliotecas,
4089                CodePathFileType::LispSource,
4090            ),
4091            (":exe", &self.exe, CodePathFileType::None),
4092            (
4093                ":servicos",
4094                &self.servicos,
4095                CodePathFileType::ComputeUnitYaml,
4096            ),
4097        ] {
4098            // Per-slot set-not-multiset gate on the typed code-path axis.
4099            // Every peer Vec-shaped author-supplied list past validate is
4100            // a set, not a multiset: `:membros :caixa`
4101            // ([`crate::AplicacaoError::MembroDuplicate`]), `:placement
4102            // :clusters` ([`crate::AplicacaoError::PlacementClusterDuplicate`]),
4103            // `:entrada :paths` ([`crate::AplicacaoError::EntradaPathDuplicate`]),
4104            // `:contratos` ([`crate::AplicacaoError::ContratoDuplicate`]),
4105            // `:children :caixa` ([`crate::SupervisorError::DuplicateChild`]),
4106            // `:deps` / `:deps-dev` `:nome` ([`crate::DepError::DuplicateNome`]
4107            // per 359fba5), `:upgrade-from :from` ([`crate::UpgradeError::DuplicateFrom`]),
4108            // `:etiquetas` ([`ManifestError::EtiquetaDuplicate`] per 360a499),
4109            // `:autores` ([`ManifestError::AutorDuplicate`] per 86c769b) —
4110            // the three code-path lists are the last Vec-shaped author-
4111            // supplied slots on the typed Caixa surface still admitting a
4112            // duplicate entry silently. Scope is per-list (`:bibliotecas`
4113            // duplicates are flagged within `:bibliotecas`, not across
4114            // `:bibliotecas` ↔ `:exe`) — the same per-list scope `:deps`
4115            // ↔ `:deps-dev` use (a `:nome` present in both lists is a
4116            // legitimate dev-vs-runtime shape on the dep axis, fenced
4117            // separately by [`crate::dep::validate_no_self_dep`]). On the
4118            // code-path axis a cross-slot collision is structurally
4119            // impossible by the layout's `starts_with(<exe|servicos>_dir)`
4120            // fence — `:exe` and `:servicos` entries are confined to their
4121            // own directory trees, so the only way a string could appear
4122            // on two code-path lists is the (rare, structurally invalid)
4123            // case where `:bibliotecas` carries an `"exe/<x>"` or
4124            // `"servicos/<x>.yaml"`-shaped path.
4125            //
4126            // Without the gate three authoring footguns silently passed:
4127            //
4128            //   - `:bibliotecas ("lib/foo.lisp" "lib/foo.lisp")` — the
4129            //     canonical copy-paste-the-wrong-file footgun. `feira
4130            //     build` (`caixa-feira/src/cmd/build.rs:33`) walks the
4131            //     list and re-parses the same file twice, wasting work
4132            //     and silently masking the author's intent to declare a
4133            //     *second* biblioteca.
4134            //   - `:exe ("exe/cli" "exe/cli")` — the same footgun on the
4135            //     Binario surface. The future `caixa-flake` `nix flake`
4136            //     emitter that materializes each `:exe` entry as a flake
4137            //     `packages.<exe-name>` derivation would collide on the
4138            //     duplicate package name and surface a flake-eval error
4139            //     far from the source `caixa.lisp`.
4140            //   - `:servicos ("servicos/x.computeunit.yaml"
4141            //     "servicos/x.computeunit.yaml")` — the same footgun on
4142            //     the Servico surface. The peer `caixa-helm` / `caixa-flux`
4143            //     renderers already refuse `:servicos.len() != 1` with
4144            //     the narrower [`UnsupportedServicoCount`] diagnostic, but
4145            //     that diagnostic surfaces "too many servicos" without
4146            //     naming "duplicate entry" — the typed self-locating
4147            //     "which entry is the duplicate" framing only lands at
4148            //     this gate.
4149            //
4150            // Same `seen.insert(entry.as_str())` shape every peer per-list
4151            // duplicate gate uses (`:etiquetas` 360a499, `:autores`
4152            // 86c769b, `:deps` 359fba5) and the same "structural shape
4153            // checks fire before the duplicate check on the same entry"
4154            // ordering (a `(:bibliotecas ("" "lib/x.lisp" "lib/x.lisp"))`
4155            // shape surfaces the narrower [`Self::CodePathEmpty`] for the
4156            // empty entry first, not the duplicate on the later pair).
4157            let mut seen = std::collections::HashSet::new();
4158            for entry in list {
4159                let path = Path::new(entry);
4160                match is_sandboxed_relative_path(path) {
4161                    Ok(()) => {}
4162                    Err(PathShapeViolation::Empty) => {
4163                        return Err(ManifestError::CodePathEmpty { slot });
4164                    }
4165                    Err(PathShapeViolation::Absolute) => {
4166                        return Err(ManifestError::CodePathAbsolute {
4167                            slot,
4168                            path: path.to_path_buf(),
4169                        });
4170                    }
4171                    Err(PathShapeViolation::ParentEscape) => {
4172                        return Err(ManifestError::CodePathParentEscape {
4173                            slot,
4174                            path: path.to_path_buf(),
4175                        });
4176                    }
4177                }
4178                // The per-slot file-type gate dispatched through the
4179                // typed [`CodePathFileType`] selector above. Each variant
4180                // routes to the lifted predicate the downstream consumer
4181                // demands:
4182                //
4183                //   - [`LispSource`] → [`is_lisp_extension`] for
4184                //     `:bibliotecas` (the `feira build` loop's
4185                //     `tatara_lisp::read` consumer);
4186                //   - [`ComputeUnitYaml`] → [`is_computeunit_yaml_extension`]
4187                //     for `:servicos` (the caixa-helm / caixa-flux
4188                //     `serde_yaml::from_str` consumer's `ComputeUnit` CR
4189                //     accepted set);
4190                //   - [`None`] for `:exe` — the nix-build derivation-
4191                //     output axis has no terminating-extension contract.
4192                //
4193                // Fires after the sandbox-shape arms so a path that is
4194                // *both* sandbox-escaping and wrong-extension surfaces
4195                // the more fundamental sandbox-shape diagnostic first
4196                // (mirrors the peer `EmptyPath` → `AbsolutePath` →
4197                // `ParentEscape` → `NonLispExtension` arm-ordering on
4198                // `:behavior :on-*` c97815a, and `EmptyScript` →
4199                // `AbsoluteScript` → `ParentEscapeScript` →
4200                // `NonLispExtensionScript` on
4201                // `:upgrade-from :state-change :script` 33cc830), and
4202                // before the duplicate gate so the narrower per-entry
4203                // file-type shape dominates the cross-entry uniqueness
4204                // diagnostic (a
4205                // `("servicos/x.yaml" "servicos/x.yaml")` shape on
4206                // `:servicos` surfaces
4207                // `CodePathNonComputeUnitYamlExtension` on the first
4208                // entry rather than `CodePathDuplicate` on the pair —
4209                // peer with the 64772a9 `:bibliotecas`
4210                // `("lib/x.txt" "lib/x.txt")` ordering).
4211                match file_type {
4212                    CodePathFileType::None => {}
4213                    CodePathFileType::LispSource => {
4214                        if !is_lisp_extension(path) {
4215                            return Err(ManifestError::CodePathNonLispExtension {
4216                                slot,
4217                                path: path.to_path_buf(),
4218                            });
4219                        }
4220                    }
4221                    CodePathFileType::ComputeUnitYaml => {
4222                        if !is_computeunit_yaml_extension(path) {
4223                            return Err(ManifestError::CodePathNonComputeUnitYamlExtension {
4224                                slot,
4225                                path: path.to_path_buf(),
4226                            });
4227                        }
4228                    }
4229                }
4230                crate::render::insert_first_seen(&mut seen, entry.as_str(), || {
4231                    ManifestError::CodePathDuplicate {
4232                        slot,
4233                        path: path.to_path_buf(),
4234                    }
4235                })?;
4236            }
4237        }
4238        Ok(())
4239    }
4240
4241    /// Reject `:etiquetas` lists with an empty entry or with two entries
4242    /// agreeing on the same string. `:etiquetas` is the universal
4243    /// registry-search-tag axis on [`Caixa`] (every kind carries the
4244    /// `Vec<String>` slot) and lands verbatim as the Helm chart
4245    /// `Chart.yaml` `keywords:` array on every Servico (caixa-helm's
4246    /// `build_chart_yaml` at `caixa-helm/src/lib.rs:236` folds it through
4247    /// a [`std::collections::BTreeSet`] alongside the four substrate-
4248    /// fixed tags `lareira` / `wasm` / `tatara-lisp` / `caixa-servico`).
4249    /// Two authoring footguns silently passed validate without this gate:
4250    ///
4251    ///   - Empty entry (`(:etiquetas (""))` — the canonical paste-from-
4252    ///     blank-doc footgun) rendered as `keywords: ["", "caixa-servico",
4253    ///     "lareira", "tatara-lisp", "wasm"]` in `Chart.yaml`. Helm's
4254    ///     `chart.metadata.keywords` admits the value without a strict
4255    ///     parser-side gate, but the empty keyword has no operational
4256    ///     meaning — it indexes nothing in the future caixa-registry
4257    ///     search axis and clutters the rendered chart with a no-op tag.
4258    ///   - Duplicate entries (`(:etiquetas ("demo" "demo"))` — the
4259    ///     copy-paste-the-wrong-tag footgun) silently passed validate
4260    ///     and were silently dedup'd by caixa-helm's `BTreeSet` collect
4261    ///     at chart render — a "second wins / one silently disappears"
4262    ///     shape divergent from every peer typed-graph set gate
4263    ///     ([`crate::AplicacaoError::MembroDuplicate`] on `:membros`,
4264    ///     [`crate::AplicacaoError::PlacementClusterDuplicate`] on
4265    ///     `:placement :clusters`, [`crate::AplicacaoError::EntradaPathDuplicate`]
4266    ///     on `:entrada :paths`, [`crate::AplicacaoError::ContratoDuplicate`]
4267    ///     on `:contratos`, [`crate::DepError::DuplicateNome`] on
4268    ///     `:deps` / `:deps-dev` per 359fba5, [`crate::UpgradeError::DuplicateFrom`]
4269    ///     on `:upgrade-from`, the per-instruction-class singularity
4270    ///     gates [`crate::UpgradeError::DuplicateLoadModule`] /
4271    ///     [`crate::UpgradeError::DuplicateStateChange`] /
4272    ///     [`crate::UpgradeError::DuplicateCleanup`]). The typed-graph
4273    ///     discipline is uniform: every Vec-shaped author-supplied list
4274    ///     past validate is set-not-multiset, by construction.
4275    ///
4276    /// Past the empty arm the gate enforces the chart-keyword shape
4277    /// predicate via [`crate::render::is_chart_keyword_shape`]: Cargo's
4278    /// crates.io `[package] keywords` grammar — 1..=20 bytes, starts
4279    /// with an ASCII letter, ASCII alphanumeric / `_` / `-`
4280    /// continuation. Closes the canonical paste-from-doc footguns the
4281    /// bare empty + duplicate arms left open: paste-from-aligned-doc
4282    /// whitespace (`" mesh"`, `"mesh "`), paste-from-multiline-doc
4283    /// newline (`"mesh\nhttp"` — the author pasted a multi-tag block
4284    /// into one entry instead of splitting), paste-from-Windows-CRLF-doc
4285    /// carriage return, CSV-list-separator confusion (`"mesh,http,grpc"`
4286    /// — the author meant three separate list entries), path-separator
4287    /// confusion (`"caixa/servico"`), namespace-suffix (`"http.1"`),
4288    /// leading-digit (`"1foo"`), kebab-leak (`"-foo"`), snake-leak
4289    /// (`"_foo"`), non-ASCII (`"café"`), and paste-from-binary-blob
4290    /// control bytes that would silently land as malformed search tags
4291    /// in the rendered Chart.yaml `keywords:` array and break the
4292    /// Artifact Hub keyword index lookup far from the source caixa.lisp.
4293    /// Mirrors the [`Self::validate_autores`] shape-predicate cascade
4294    /// established on the sibling universal-axis `Vec<String>` surface
4295    /// — the second universal-axis Vec<String> surface to land the
4296    /// empty-first-then-shape-then-duplicate per-entry cascade.
4297    ///
4298    /// Same empty-first cascade discipline every peer per-axis gate
4299    /// uses: the per-entry empty arm fires before the per-entry shape
4300    /// arm fires before the cross-entry duplicate arm, so an
4301    /// `("" "mesh" "mesh")` authoring shape surfaces the narrower
4302    /// [`ManifestError::EtiquetaEmpty`] (the structural "this entry
4303    /// has no value" defect) before either the shape or the duplicate
4304    /// diagnostic. Walks the list in declaration order so the
4305    /// first-collision diagnostic surfaces the lexicographically-
4306    /// earliest offending position, peer with every other duplicate
4307    /// gate on this surface.
4308    ///
4309    /// Universal-axis (every kind carries `:etiquetas`), so wired at the
4310    /// caixa-build gate alongside the peer universal gates
4311    /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4312    /// [`Self::validate_deps`] / [`Self::validate_code_paths`] — before
4313    /// the kind-coherence gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`]
4314    /// / [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4315    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4316    /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-specific
4317    /// slot sets. The future caixa-registry search axis can reach for
4318    /// `caixa.etiquetas` knowing every entry is a non-empty distinct
4319    /// chart-keyword-shaped string without re-deriving the precondition.
4320    pub fn validate_etiquetas(&self) -> Result<(), ManifestError> {
4321        let mut seen = std::collections::HashSet::new();
4322        for etiqueta in self.etiquetas() {
4323            if etiqueta.is_empty() {
4324                return Err(ManifestError::EtiquetaEmpty);
4325            }
4326            crate::render::is_chart_keyword_shape(etiqueta).map_err(|reason| {
4327                ManifestError::EtiquetaInvalid {
4328                    etiqueta: etiqueta.clone(),
4329                    reason,
4330                }
4331            })?;
4332            crate::render::insert_first_seen(&mut seen, etiqueta.as_str(), || {
4333                ManifestError::EtiquetaDuplicate {
4334                    etiqueta: etiqueta.clone(),
4335                }
4336            })?;
4337        }
4338        Ok(())
4339    }
4340
4341    /// Reject `:autores` lists with an empty entry or with two entries
4342    /// agreeing on the same string. `:autores` is the universal
4343    /// maintainer-axis on [`Caixa`] (every kind carries the
4344    /// `Vec<String>` slot) and lands verbatim as the Helm chart
4345    /// `Chart.yaml` `maintainers:` array on every Servico (caixa-helm's
4346    /// `build_chart_yaml` at `caixa-helm/src/lib.rs:251` maps each entry
4347    /// to a `Maintainer { name, email: None }` without dedup). Two
4348    /// authoring footguns silently passed validate without this gate:
4349    ///
4350    ///   - Empty entry (`(:autores (""))` — the canonical paste-from-
4351    ///     blank-doc footgun) rendered as
4352    ///     `maintainers: [{name: "", email: null}]` in `Chart.yaml`. The
4353    ///     empty maintainer name has no operational meaning — it
4354    ///     identifies no one in the substrate's authorship index and
4355    ///     clutters the rendered chart with a no-op maintainer.
4356    ///   - Duplicate entries (`(:autores ("pleme-io" "pleme-io"))` —
4357    ///     the copy-paste-the-wrong-author footgun) silently passed
4358    ///     validate and rendered as two identical maintainer entries.
4359    ///     Unlike the [`Self::validate_etiquetas`] peer (caixa-helm's
4360    ///     `BTreeSet`-collect on `:etiquetas` silently dedups the
4361    ///     rendered `keywords:` array at chart-render time), the
4362    ///     `maintainers:` rendering has *no* dedup — duplicate `:autores`
4363    ///     entries stack verbatim in the chart, divergent from every
4364    ///     peer typed-graph set gate ([`crate::AplicacaoError::MembroDuplicate`]
4365    ///     on `:membros`, [`crate::AplicacaoError::PlacementClusterDuplicate`]
4366    ///     on `:placement :clusters`, [`crate::AplicacaoError::EntradaPathDuplicate`]
4367    ///     on `:entrada :paths`, [`crate::AplicacaoError::ContratoDuplicate`]
4368    ///     on `:contratos`, [`crate::DepError::DuplicateNome`] on
4369    ///     `:deps` / `:deps-dev`, [`crate::UpgradeError::DuplicateFrom`]
4370    ///     on `:upgrade-from`, [`ManifestError::EtiquetaDuplicate`] on
4371    ///     `:etiquetas`).
4372    ///
4373    /// Past the empty arm the gate enforces the chart-maintainer-name
4374    /// shape predicate via [`crate::render::is_chart_maintainer_name_shape`]:
4375    /// the structural single-line printable-UTF-8 floor every realistic
4376    /// Helm chart maintainer name carries — 1..=128 bytes, no leading
4377    /// or trailing whitespace, no ASCII control characters anywhere,
4378    /// Unicode bytes accepted. Closes the canonical paste-from-doc
4379    /// footguns the bare empty + duplicate arms left open:
4380    /// paste-from-aligned-doc whitespace (`" pleme-io"`, `"pleme-io "`),
4381    /// paste-from-multiline-doc newline (`"alice\nbob"` — the author
4382    /// pasted a multi-line block of author records into one `:autores`
4383    /// entry instead of splitting into one entry per author),
4384    /// paste-from-Windows-CRLF-doc carriage return, tab-from-aligned-doc,
4385    /// and the paste-from-binary-blob control bytes that would silently
4386    /// land as YAML-illegal byte sequences in the rendered Chart.yaml
4387    /// `maintainers:` array. Mirrors the shape-predicate cascade
4388    /// [`Self::validate_descricao`] / [`Self::validate_licenca`] /
4389    /// [`Self::validate_edicao`] / [`Self::validate_repositorio`]
4390    /// establish past their own empty arms on the sibling universal-axis
4391    /// `Option<String>` surfaces — the first universal-axis Vec<String>
4392    /// surface to land the empty-first-then-shape-then-duplicate per-entry
4393    /// cascade.
4394    ///
4395    /// Same empty-first cascade discipline every peer per-axis gate
4396    /// uses: the per-entry empty arm fires before the per-entry shape
4397    /// arm before the cross-entry duplicate arm. Walks the list in
4398    /// declaration order so the first-collision diagnostic surfaces the
4399    /// lexicographically-earliest offending position, peer with every
4400    /// other duplicate gate on this surface.
4401    ///
4402    /// Universal-axis (every kind carries `:autores`), so wired at the
4403    /// caixa-build gate alongside the peer universal gates
4404    /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4405    /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
4406    /// [`Self::validate_code_paths`] — before the kind-coherence gates
4407    /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
4408    /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4409    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4410    /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-specific
4411    /// slot sets.
4412    pub fn validate_autores(&self) -> Result<(), ManifestError> {
4413        let mut seen = std::collections::HashSet::new();
4414        for autor in self.autores() {
4415            if autor.is_empty() {
4416                return Err(ManifestError::AutorEmpty);
4417            }
4418            crate::render::is_chart_maintainer_name_shape(autor).map_err(|reason| {
4419                ManifestError::AutorInvalid {
4420                    autor: autor.clone(),
4421                    reason,
4422                }
4423            })?;
4424            crate::render::insert_first_seen(&mut seen, autor.as_str(), || {
4425                ManifestError::AutorDuplicate {
4426                    autor: autor.clone(),
4427                }
4428            })?;
4429        }
4430        Ok(())
4431    }
4432
4433    /// Reject `:repositorio` values whose shape the shared
4434    /// [`crate::render::is_git_repo_url`] predicate refuses. The flat
4435    /// `repositorio: Option<String>` slot on [`Caixa`] is the
4436    /// universal git-shaped homepage axis every kind carries — the
4437    /// substrate routes the same string through two load-bearing
4438    /// consumers:
4439    ///
4440    ///   - [`caixa-helm`] folds it verbatim into the rendered
4441    ///     `lareira-<nome>` Helm chart's `Chart.yaml` `home:` field
4442    ///     (`build_chart_yaml` at `caixa-helm/src/lib.rs:268`) and into
4443    ///     the chart `README.md` `repo = …` interpolation
4444    ///     (`caixa-helm/src/lib.rs:359`).
4445    ///   - [`caixa-flux`] folds it verbatim into the standalone
4446    ///     `ClusterBundleOpts::for_caixa` `git_url:` field
4447    ///     (`caixa-flux/src/lib.rs:293`), which becomes the `FluxCD`
4448    ///     `GitRepository.spec.url` the cluster's source-controller
4449    ///     polls — the load-bearing deploy-time axis.
4450    ///
4451    /// Both consumers use `Option::unwrap_or_else(|| <fallback>)` to
4452    /// substitute a placeholder when the slot is absent (`None` → the
4453    /// fallback fires); a `Some("")` *skips the fallback* and silently
4454    /// passes the empty string through to `Chart.yaml home: ""` /
4455    /// `GitRepository url: ""` — Helm's chart lint and `FluxCD`'s source
4456    /// controller both reject the empty URL far from the source
4457    /// `caixa.lisp`, with no field naming the offending `:repositorio`.
4458    /// Similarly a malformed `:repositorio` (whitespace, control char,
4459    /// missing `:` separator, leading `-`) silently lands in the
4460    /// rendered artifacts and breaks at `git clone` / `helm template`
4461    /// / `flux reconcile` time.
4462    ///
4463    /// Thin wrapper around [`crate::render::is_git_repo_url`] — the
4464    /// same shared predicate the peer [`crate::DepSource::validate`]
4465    /// routes the `:fonte (:tipo git :repo …)` axis through. With this
4466    /// gate the two `git URL`-shaped surfaces on the typed Caixa
4467    /// (`:repositorio` here, `:deps :fonte :repo` peer) are
4468    /// structurally equivalent: every value past validate is
4469    /// guaranteed-acceptable by the predicate's union of constraints
4470    /// (non-empty, length-bounded, no leading `-`, no whitespace, no
4471    /// control chars, ASCII only, no leading `:`, contains a `:`
4472    /// separator). The predicate accepts every documented authoring
4473    /// shape — `github:org/repo` shorthand, `https://host/path`,
4474    /// `ssh://[user@]host/path`, `git://host/path`, `git@host:path`
4475    /// scp-style SSH, `file:///path` — and refuses the canonical
4476    /// paste-from-blank-doc / paste-from-multiline-doc / CLI-arg-
4477    /// injection footguns at validate time. Maps the predicate's
4478    /// `String` reason verbatim into the
4479    /// [`ManifestError::RepositorioInvalid`] variant, carrying the
4480    /// offending value + parser-shaped reason so the diagnostic is
4481    /// self-locating (the author can grep their `caixa.lisp` for
4482    /// `:repositorio "<value>"` and fix it in one edit).
4483    ///
4484    /// `None` (the canonical "omit the slot to express no published
4485    /// homepage" shape) is accepted trivially — the gate is a no-op
4486    /// when the author didn't declare a value. `Some("")` is gated by
4487    /// the narrower [`ManifestError::RepositorioEmpty`] arm before the
4488    /// shape predicate is consulted, mirroring the empty-first cascade
4489    /// every peer per-axis identity gate uses
4490    /// ([`ManifestError::NomeEmpty`] → [`ManifestError::NomeInvalid`],
4491    /// [`ManifestError::VersaoEmpty`] → [`ManifestError::VersaoInvalid`],
4492    /// [`crate::DepError::FonteRepoEmpty`] →
4493    /// [`crate::DepError::FonteRepoInvalid`]).
4494    ///
4495    /// Universal-axis (every kind carries `:repositorio`), so wired at
4496    /// the caixa-build gate alongside the peer universal gates
4497    /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4498    /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
4499    /// [`Self::validate_autores`] / [`Self::validate_code_paths`] —
4500    /// before the kind-coherence gates
4501    /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
4502    /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4503    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4504    /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
4505    /// specific slot sets.
4506    pub fn validate_repositorio(&self) -> Result<(), ManifestError> {
4507        let Some(s) = self.repositorio() else {
4508            return Ok(());
4509        };
4510        if s.is_empty() {
4511            return Err(ManifestError::RepositorioEmpty);
4512        }
4513        is_git_repo_url(s).map_err(|reason| ManifestError::RepositorioInvalid {
4514            repositorio: s.to_string(),
4515            reason,
4516        })
4517    }
4518
4519    /// Reject `:descricao` values that are the empty string. The flat
4520    /// `descricao: Option<String>` slot on [`Caixa`] is the universal
4521    /// free-form-prose homepage axis every kind carries — the
4522    /// substrate routes the same string through two load-bearing
4523    /// consumers in the [`caixa-helm`] renderer:
4524    ///
4525    ///   - `build_chart_yaml` folds it verbatim into the rendered
4526    ///     `lareira-<nome>` Helm chart's `Chart.yaml` `description:`
4527    ///     field (`caixa-helm/src/lib.rs:232-235`).
4528    ///   - `build_readme` folds it verbatim into the rendered chart
4529    ///     `README.md` header (`caixa-helm/src/lib.rs:333-336`).
4530    ///
4531    /// Both consumers use `Option::unwrap_or_else(|| <fallback>)` to
4532    /// substitute a `caixa.nome`-derived placeholder when the slot is
4533    /// absent (`None` → the fallback fires); a `Some("")` *skips the
4534    /// fallback* and silently passes the empty string through to
4535    /// `Chart.yaml description: ""` / a blank chart `README.md`
4536    /// header. Helm's chart spec requires a non-empty `description:`
4537    /// field on `apiVersion: v2` charts (`helm lint` surfaces it as
4538    /// `WARNING [chart.metadata.description]: description is required`),
4539    /// so the empty `Some("")` silently lands in the rendered
4540    /// artifacts and breaks at `helm lint` / `helm install` time far
4541    /// from the source `caixa.lisp`, with no field naming the
4542    /// offending `:descricao`.
4543    ///
4544    /// `None` (the canonical "omit the slot to defer to the renderer's
4545    /// `caixa.nome`-derived fallback" shape) is accepted trivially —
4546    /// the gate is a no-op when the author didn't declare a value.
4547    /// `Some("")` is gated by the narrower
4548    /// [`ManifestError::DescricaoEmpty`] arm, mirroring the empty-arm
4549    /// shape every peer per-axis empty gate uses
4550    /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
4551    /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
4552    /// [`ManifestError::RepositorioEmpty`]).
4553    ///
4554    /// Universal-axis (every kind carries `:descricao`), so wired at
4555    /// the caixa-build gate alongside the peer universal gates
4556    /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4557    /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
4558    /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
4559    /// [`Self::validate_code_paths`] — before the kind-coherence
4560    /// gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
4561    /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4562    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4563    /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
4564    /// specific slot sets.
4565    ///
4566    /// Past the empty arm the gate enforces the chart-description
4567    /// shape predicate via [`crate::render::is_chart_description_shape`]:
4568    /// the structural single-line UTF-8 floor every realistic chart
4569    /// description in the wild matches — 1..=512 bytes, no leading
4570    /// or trailing whitespace, no ASCII control characters anywhere
4571    /// (`0x00..=0x1F` plus `0x7F` DEL — banning tab, newline,
4572    /// carriage return, and every other control byte), Unicode
4573    /// continuation bytes accepted (the canonical fixtures carry
4574    /// `→` and `—`). Closes the canonical paste-from-doc footguns
4575    /// the bare empty-arm gate left open: paste-from-aligned-doc
4576    /// leading / trailing whitespace (`" Checkout flow."`,
4577    /// `"Checkout flow. "`), paste-from-multiline-doc newline
4578    /// (`"Checkout\nflow."`), paste-from-Windows-CRLF-doc CR
4579    /// (`"Checkout\rflow."`), tab-from-aligned-doc
4580    /// (`"Checkout\tflow."`), and paste-from-binary-blob NUL / BEL /
4581    /// ESC / DEL bytes. Mirrors the shape-predicate cascade
4582    /// [`Self::validate_repositorio`] / [`Self::validate_licenca`] /
4583    /// [`Self::validate_edicao`] establish past their own empty arms
4584    /// on the sibling universal-axis `Option<String>` Caixa-level
4585    /// value-shape surfaces.
4586    ///
4587    /// The empty-first cascade discipline mirrors every peer per-axis
4588    /// identity gate: [`ManifestError::DescricaoEmpty`] runs before
4589    /// [`ManifestError::DescricaoInvalid`], so the narrower empty
4590    /// diagnostic surfaces on `Some("")` rather than the broader
4591    /// shape-predicate diagnostic — peer with how
4592    /// [`ManifestError::LicencaEmpty`] runs before
4593    /// [`ManifestError::LicencaInvalid`],
4594    /// [`ManifestError::EdicaoEmpty`] runs before
4595    /// [`ManifestError::EdicaoInvalid`],
4596    /// [`ManifestError::RepositorioEmpty`] runs before
4597    /// [`ManifestError::RepositorioInvalid`].
4598    pub fn validate_descricao(&self) -> Result<(), ManifestError> {
4599        let Some(s) = self.descricao() else {
4600            return Ok(());
4601        };
4602        if s.is_empty() {
4603            return Err(ManifestError::DescricaoEmpty);
4604        }
4605        crate::render::is_chart_description_shape(s).map_err(|reason| {
4606            ManifestError::DescricaoInvalid {
4607                descricao: s.to_string(),
4608                reason,
4609            }
4610        })?;
4611        Ok(())
4612    }
4613
4614    /// Reject `:licenca` values that are the empty string. The flat
4615    /// `licenca: Option<String>` slot on [`Caixa`] is the universal
4616    /// SPDX-shaped license-expression axis every kind carries — the
4617    /// substrate routes the same string through the [`caixa-helm`]
4618    /// renderer's `build_readme` which folds it verbatim into the
4619    /// rendered `lareira-<nome>` Helm chart's `README.md` `## License`
4620    /// section (`caixa-helm/src/lib.rs:361`) via
4621    /// `caixa.licenca.clone().unwrap_or_else(|| "MIT".into())`. The
4622    /// fallback only fires on `None`; a `Some("")` *skips the
4623    /// fallback* and silently passes the empty string through to a
4624    /// chart `README.md` whose `License` section renders as the bare
4625    /// trailing period (`.\n`) — peer footgun with the
4626    /// `Some("")`-skips-`unwrap_or_else` shape the
4627    /// [`Self::validate_descricao`] and [`Self::validate_repositorio`]
4628    /// gates close on the sibling free-form-prose and git-URL axes.
4629    ///
4630    /// `None` (the canonical "omit the slot to defer to the
4631    /// renderer's `MIT` fallback" shape every existing fixture
4632    /// carries) is accepted trivially — the gate is a no-op when the
4633    /// author didn't declare a value. `Some("")` is gated by the
4634    /// narrower [`ManifestError::LicencaEmpty`] arm, mirroring the
4635    /// empty-arm shape every peer per-axis empty gate uses
4636    /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
4637    /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
4638    /// [`ManifestError::RepositorioEmpty`],
4639    /// [`ManifestError::DescricaoEmpty`]).
4640    ///
4641    /// Universal-axis (every kind carries `:licenca`), so wired at
4642    /// the caixa-build gate alongside the peer universal gates
4643    /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4644    /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
4645    /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
4646    /// [`Self::validate_descricao`] / [`Self::validate_code_paths`]
4647    /// — before the kind-coherence gates
4648    /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
4649    /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4650    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4651    /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
4652    /// specific slot sets.
4653    ///
4654    /// Past the empty arm the gate enforces the SPDX-expression shape
4655    /// predicate via [`crate::render::is_spdx_expression_shape`]: the
4656    /// structural alphabet floor every realistic SPDX expression in
4657    /// the wild uses — ASCII alphanumeric plus `.`, `-`, `+`, `(`,
4658    /// `)`, `:` (the `DocumentRef-…:LicenseRef-…` separator), and a
4659    /// single ASCII space (token separator). Closes the canonical
4660    /// paste-from-doc footguns the bare empty-arm gate left open:
4661    /// paste-from-doc whitespace (`"MIT "`, `" MIT"`), paste-from-
4662    /// multiline-doc CRLF (`"MIT\n"`), tab-from-aligned-doc
4663    /// (`"MIT\tOR Apache-2.0"`), non-ASCII smart-quote paste,
4664    /// underscore-instead-of-hyphen typo (`"Apache_2.0"`),
4665    /// comma-instead-of-`OR`-keyword colloquial idiom (`"MIT,
4666    /// Apache-2.0"`), slash-dual-license colloquial idiom (`"MIT/
4667    /// Apache-2.0"`), and semicolon-list-separator confusion
4668    /// (`"MIT; Apache-2.0"`). Mirrors the shape-predicate cascade
4669    /// [`Self::validate_repositorio`] / [`Self::validate_edicao`]
4670    /// establish past their own empty arms.
4671    ///
4672    /// The empty-first cascade discipline mirrors every peer per-axis
4673    /// identity gate: [`ManifestError::LicencaEmpty`] runs before
4674    /// [`ManifestError::LicencaInvalid`], so the narrower empty
4675    /// diagnostic surfaces on `Some("")` rather than the broader
4676    /// shape-predicate diagnostic — peer with how
4677    /// [`ManifestError::EdicaoEmpty`] runs before
4678    /// [`ManifestError::EdicaoInvalid`],
4679    /// [`ManifestError::RepositorioEmpty`] runs before
4680    /// [`ManifestError::RepositorioInvalid`].
4681    ///
4682    /// A future tightening on this axis can extend the alphabet
4683    /// floor into a full SPDX expression parser + license-id
4684    /// allowlist (rejecting alphabet-valid values that don't name a
4685    /// real SPDX license identifier — e.g., `"NotAReal"` is
4686    /// alphabet-valid but no `NotAReal` license-id exists). That
4687    /// parser only becomes meaningful past a real SPDX-spec
4688    /// dependency; this gate establishes the structural floor by
4689    /// refusing every non-SPDX-alphabet value at validate time.
4690    pub fn validate_licenca(&self) -> Result<(), ManifestError> {
4691        let Some(s) = self.licenca() else {
4692            return Ok(());
4693        };
4694        if s.is_empty() {
4695            return Err(ManifestError::LicencaEmpty);
4696        }
4697        crate::render::is_spdx_expression_shape(s).map_err(|reason| {
4698            ManifestError::LicencaInvalid {
4699                licenca: s.to_string(),
4700                reason,
4701            }
4702        })?;
4703        Ok(())
4704    }
4705
4706    /// Reject `:edicao` values that are the empty string. The flat
4707    /// `edicao: Option<String>` slot on [`Caixa`] is the universal
4708    /// language-edition axis every kind carries — it determines the
4709    /// tatara-lisp macro surface + compatibility flags the substrate
4710    /// applies when building a caixa, and lands verbatim in the
4711    /// `Caixa::template` author-time scaffold (the canonical
4712    /// `:edicao "2026"` line every `feira init` emits via
4713    /// [`Caixa::template`] at `caixa-core/src/manifest.rs:1193`) and
4714    /// in every renderer-side fixture (`caixa-helm/src/lib.rs:375`,
4715    /// `caixa-flux/src/lib.rs:445`, `caixa-mesh/src/lib.rs:629`,
4716    /// `caixa-core/src/render.rs:2510`) via
4717    /// `edicao: Some("2026".into())`.
4718    ///
4719    /// `None` (the canonical "omit the slot to defer to the
4720    /// substrate's default edition" shape every existing
4721    /// [`caixa-resolver`] integration test fixture carries via
4722    /// `edicao: None` — see `caixa-resolver/tests/git_integration.rs`)
4723    /// is accepted trivially — the gate is a no-op when the author
4724    /// didn't declare a value. `Some("")` is gated by the narrower
4725    /// [`ManifestError::EdicaoEmpty`] arm, mirroring the empty-arm
4726    /// shape every peer per-axis empty gate uses
4727    /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
4728    /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
4729    /// [`ManifestError::RepositorioEmpty`],
4730    /// [`ManifestError::DescricaoEmpty`], [`ManifestError::LicencaEmpty`]).
4731    ///
4732    /// Universal-axis (every kind carries `:edicao`), so wired at
4733    /// the caixa-build gate alongside the peer universal gates
4734    /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4735    /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
4736    /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
4737    /// [`Self::validate_descricao`] / [`Self::validate_licenca`] /
4738    /// [`Self::validate_code_paths`] — before the kind-coherence
4739    /// gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
4740    /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4741    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4742    /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
4743    /// specific slot sets.
4744    ///
4745    /// Past the empty arm the gate enforces the canonical year-shape
4746    /// predicate: every documented tatara-lisp edition is a 4-digit
4747    /// ASCII decimal year (`"2026"` is the only edition currently
4748    /// minted; future-introduced siblings will follow the same
4749    /// shape, peer with Cargo's `[package] edition` grammar which
4750    /// every value Cargo has ever accepted matches — `"2015"`,
4751    /// `"2018"`, `"2021"`, `"2024"`). Any value that's not exactly
4752    /// 4 ASCII decimal bytes is rejected with the narrower
4753    /// [`ManifestError::EdicaoInvalid`] arm, mirroring the
4754    /// shape-predicate cascade [`Self::validate_repositorio`]
4755    /// establishes past its own empty arm
4756    /// ([`ManifestError::RepositorioEmpty`] →
4757    /// [`ManifestError::RepositorioInvalid`]). Closes the canonical
4758    /// paste-from-doc footguns the bare empty-arm gate left open:
4759    ///
4760    ///   - leading / trailing whitespace from a paste-from-doc
4761    ///     (`"2026 "`, `" 2026"`)
4762    ///   - control characters / CRLF from a paste-from-multiline-doc
4763    ///     (`"2026\n"`)
4764    ///   - non-ASCII look-alikes from a fullwidth keyboard
4765    ///     (`"2026"`) which would silently land as a non-ASCII
4766    ///     string in the rendered caixa.lisp
4767    ///   - free-form non-year values (`"x"`, `"latest"`,
4768    ///     `"nightly"`) that have no operational meaning on the
4769    ///     substrate's build-time edition selector
4770    ///   - leading non-digit prefixes (`"v2026"`, `"e2026"`,
4771    ///     `"r2026"`) — common version-tag idioms that don't apply
4772    ///     to the year-shaped edition axis
4773    ///   - decimal-shaped values (`"2026.1"`, `"2026.0"`) — every
4774    ///     edition is a year, not a fractional version
4775    ///   - wrong-length numeric values (`"26"`, `"202"`, `"20260"`,
4776    ///     `"00026"`) that don't name a year
4777    ///
4778    /// `None` (the canonical "omit the slot to defer to the
4779    /// substrate's default edition" shape every existing
4780    /// [`caixa-resolver`] integration test fixture carries via
4781    /// `edicao: None` — see `caixa-resolver/tests/git_integration.rs`)
4782    /// is accepted trivially — the gate is a no-op when the author
4783    /// didn't declare a value. The empty-first cascade discipline
4784    /// mirrors every peer per-axis identity gate:
4785    /// [`ManifestError::EdicaoEmpty`] runs before
4786    /// [`ManifestError::EdicaoInvalid`], so the narrower empty
4787    /// diagnostic surfaces on `Some("")` rather than the broader
4788    /// shape-predicate diagnostic — peer with how
4789    /// [`ManifestError::NomeEmpty`] runs before
4790    /// [`ManifestError::NomeInvalid`],
4791    /// [`ManifestError::VersaoEmpty`] runs before
4792    /// [`ManifestError::VersaoInvalid`],
4793    /// [`ManifestError::RepositorioEmpty`] runs before
4794    /// [`ManifestError::RepositorioInvalid`].
4795    ///
4796    /// A future tightening on this axis can extend the shape
4797    /// predicate into a known-edition allowlist (rejecting
4798    /// year-shaped values that don't name a tatara-lisp edition
4799    /// the substrate actually understands — e.g., `"1999"` is
4800    /// year-shaped but no `1999` edition exists). That allowlist
4801    /// only becomes meaningful past the introduction of a sibling
4802    /// edition to `"2026"`; this gate establishes the structural
4803    /// floor by refusing every non-year-shaped value at validate
4804    /// time.
4805    pub fn validate_edicao(&self) -> Result<(), ManifestError> {
4806        let Some(s) = self.edicao() else {
4807            return Ok(());
4808        };
4809        if s.is_empty() {
4810            return Err(ManifestError::EdicaoEmpty);
4811        }
4812        if s.len() != 4 || !s.bytes().all(|b| b.is_ascii_digit()) {
4813            return Err(ManifestError::EdicaoInvalid {
4814                edicao: s.to_string(),
4815                reason: "must be a 4-digit ASCII decimal year (canonical \"2026\")".to_string(),
4816            });
4817        }
4818        Ok(())
4819    }
4820
4821    /// Compose the supervisor-related flat slots into a single
4822    /// [`SupervisorSpec`] for validation. Returns `None` when the
4823    /// caixa isn't a `:kind Supervisor`.
4824    ///
4825    /// The flat representation in [`Caixa`] keeps tatara-lisp authoring
4826    /// simple (one form, no nested `:supervisor (…)` block); this view
4827    /// is the "typed shape" the operator + supervisor reconciler
4828    /// consume.
4829    #[must_use]
4830    pub fn supervisor_view(&self) -> Option<SupervisorSpec> {
4831        if !self.kind().is_supervisor() {
4832            return None;
4833        }
4834        // Fold through the shared `supervisor::duration_codec::parse`
4835        // — the same parser the serde-routed `with = "duration_codec"`
4836        // on `SupervisorSpec::restart_window`, the `:politicas
4837        // :timeout` codec, and the `:politicas :circuit-breaker
4838        // :window` codec all consume. The prior inline f64-shaped
4839        // duplicate (`parse_window_inline`) admitted every magnitude
4840        // the integer-magnitude gate (1c55a2a) rejects on the three
4841        // serde-routed siblings — `"1.5s"`, `"1.0s"`, `"0.5m"`,
4842        // `"+30s"`, `"-30s"` — and silently dropped malformed input as
4843        // `None` (i.e. "no reset"), divergent from the shared codec's
4844        // integer-magnitude discipline by construction. The fold
4845        // closes the divergence: every value the typed
4846        // `SupervisorSpec` carries past `supervisor_view` is in the
4847        // shared codec's accepted set. The `.ok()` here preserves the
4848        // existing soft-swallow shape on this view-construction path;
4849        // the new [`Caixa::validate_restart_window`] (sibling of
4850        // [`Self::validate_nome`] / [`Self::validate_versao`]) names
4851        // the offending raw string at build time so authoring tools
4852        // (`feira lint`, the future layout-side wire-up) surface a
4853        // self-locating diagnostic instead of a silently dropped
4854        // window.
4855        let restart_window = self
4856            .restart_window()
4857            .and_then(|s| crate::supervisor::duration_codec::parse(s).ok());
4858        Some(SupervisorSpec {
4859            estrategia: self.estrategia().unwrap_or_default(),
4860            max_restarts: self.max_restarts().unwrap_or(5),
4861            restart_window,
4862            children: self.children().to_vec(),
4863        })
4864    }
4865
4866    /// A minimal starter manifest emitted by `feira init`.
4867    #[must_use]
4868    pub fn template(nome: &str) -> String {
4869        format!(
4870            "(defcaixa\n  \
4871               :nome        {nome:?}\n  \
4872               :versao      \"0.1.0\"\n  \
4873               :kind        Biblioteca\n  \
4874               :edicao      \"2026\"\n  \
4875               :descricao   \"FIXME — describe this caixa\"\n  \
4876               :autores     ()\n  \
4877               :etiquetas   ()\n  \
4878               :deps        ()\n  \
4879               :deps-dev    ()\n  \
4880               :bibliotecas (\"lib/{nome}.lisp\"))\n"
4881        )
4882    }
4883
4884    /// Serialize to a canonical `caixa.lisp` source — suitable for writing
4885    /// back after mutation (e.g. `feira add`).
4886    ///
4887    /// Goes through serde JSON → canonical Sexp → per-field pretty print.
4888    /// The derive-macro `compile_from_sexp` path is the inverse, so any
4889    /// `Caixa` round-trips through `to_lisp` + `from_lisp`.
4890    #[must_use]
4891    pub fn to_lisp(&self) -> String {
4892        let json = serde_json::to_value(self).expect("Caixa serialize");
4893        let sexp = tatara_lisp::domain::json_to_sexp(&json);
4894        let tatara_lisp::Sexp::List(items) = sexp else {
4895            return format!("(defcaixa {sexp})\n");
4896        };
4897        let mut out = String::from("(defcaixa");
4898        let mut i = 0;
4899        while i + 1 < items.len() {
4900            out.push_str("\n  ");
4901            out.push_str(&items[i].to_string());
4902            out.push(' ');
4903            out.push_str(&items[i + 1].to_string());
4904            i += 2;
4905        }
4906        out.push_str(")\n");
4907        out
4908    }
4909}
4910
4911/// Errors raised by top-level [`Caixa`] validators that don't fit
4912/// the per-axis [`DepError`] / [`crate::AplicacaoError`] /
4913/// [`crate::SupervisorError`] / [`crate::LayoutError`] families —
4914/// the Caixa's own identity axes (`:nome`, `:versao`) that flow
4915/// through every substrate-side artifact's `metadata.name` /
4916/// version derivation.
4917///
4918/// A future top-level sum (the M4 `CaixaError` the [`DepError`]
4919/// doc-comment anticipates) can hold one of each per-axis error
4920/// family without reshaping individual diagnostics; this enum is
4921/// the first such per-Caixa-identity family.
4922#[derive(Debug, Error, PartialEq, Eq)]
4923pub enum ManifestError {
4924    #[error(
4925        ":nome is empty (every caixa must name itself; the value flows \
4926         into every K8s artifact's `metadata.name` derivation and into \
4927         the default `lib/<nome>.lisp` / `exe/<nome>` layout paths)"
4928    )]
4929    NomeEmpty,
4930    #[error(
4931        ":nome {nome:?} is not a valid DNS-1123 label: {reason} (the K8s \
4932         apiserver enforces this rule on every `metadata.name` the \
4933         caixa's substrate-side renderers derive from `:nome` — the \
4934         `lareira-<nome>` Helm chart name, the programs.yaml entry \
4935         name, the `LABEL_APLICACAO` label value, the `<aplicacao>-<de>-to-<para>` \
4936         CiliumNetworkPolicy name, the `<aplicacao>-<para>` HTTPRoute \
4937         name; use a lowercase alphanumeric + hyphen identifier like \
4938         `\"checkout\"` or `\"cart-v2\"`)"
4939    )]
4940    NomeInvalid { nome: String, reason: String },
4941    #[error(
4942        ":nome {nome:?} overflows the joint-length budget on the canonical \
4943         `lareira-<nome>` chart-name shape: {reason} (every per-Servico / \
4944         per-Aplicacao renderer the substrate carries — `caixa-helm`'s \
4945         `Chart.yaml::name`, `caixa-flux`'s `cluster_bundle` HelmRelease \
4946         `chart:` slot, `caixa-tatara`'s `release_name` + \
4947         `oci://<registry>/lareira-<nome>` chart ref — derives the same \
4948         joint name through the canonical `lareira_chart_name` helper, and \
4949         Helm's `Chart.yaml::name` admission rule + the K8s apiserver's \
4950         DNS-1123 label cap on every chart-name-derived `metadata.name` \
4951         reject any joint name exceeding 63 bytes; the narrower \
4952         `:nome` shape (`NomeInvalid`) gates the bare-`:nome` budget, this \
4953         arm gates the chart-name budget downstream renderers inherit)"
4954    )]
4955    NomeChartNameBudgetExceeded { nome: String, reason: String },
4956    #[error(
4957        ":versao is empty (every caixa must pin its own version; the value flows \
4958         into the `lareira-<nome>` Helm chart's `Chart.yaml` version + appVersion, \
4959         the `feira publish` `v<versao>` git tag, the OCI image's `:v<versao>` / \
4960         `:latest` tags, the lacre closure's `concrete_versao`, and the \
4961         `:upgrade-from :from` peers — use a SemVer-2 literal like `\"0.1.0\"`)"
4962    )]
4963    VersaoEmpty,
4964    #[error(
4965        ":versao {versao:?} is not a valid SemVer-2 version: {reason} (the substrate \
4966         consumes this string as `semver::Version` — three-part `MAJOR.MINOR.PATCH` \
4967         with optional `-prerelease` and `+build` — across every artifact derived \
4968         from `:versao`: the `lareira-<nome>` Helm chart's `Chart.yaml` version + \
4969         appVersion (Helm SemVer-2-strict), the `feira publish` `v<versao>` git tag, \
4970         the OCI image's `:v<versao>` tag, the lacre closure's `concrete_versao`, \
4971         and the `:upgrade-from :from` peers that match against this exact shape; \
4972         use a literal like `\"0.1.0\"`, `\"0.2.0-rc.1\"`, or `\"1.0.0+build.42\"` — \
4973         not a git-tag-shape like `\"v0.1.0\"`, a docker-tag-shape like `\"latest\"`, \
4974         a requirement-shape like `\"^0.1\"`, or a four-part `\"0.1.0.0\"`)"
4975    )]
4976    VersaoInvalid { versao: String, reason: String },
4977    #[error(
4978        ":restart-window {restart_window:?} is not a valid duration: {reason} (the \
4979         substrate consumes this string through the shared \
4980         `supervisor::duration_codec` — the same parser routed via `with = \
4981         \"duration_codec\"` onto the typed `SupervisorSpec::restart_window`, \
4982         `:politicas :timeout`, and `:politicas :circuit-breaker :window` slots; \
4983         the canonical authoring form is `<integer><unit>` where the unit is one \
4984         of `ms` / `s` / `m` / `h` and the magnitude has no decimal point and no \
4985         leading `+` / `-` sign — e.g. `\"60s\"`, `\"5m\"`, `\"1h\"`, `\"500ms\"`. \
4986         Without this gate a malformed `:restart-window` silently produced a \
4987         supervisor with `restart_window: None` (\"never reset\"), turning OTP's \
4988         `MaxIntensity / Period` invariant into a never-reset supervisor far from \
4989         the source `caixa.lisp`; the gate moves the diagnostic to the manifest \
4990         layer with the offending value named verbatim. Omit the slot entirely to \
4991         express \"no reset\"; carry a positive integer duration to express the \
4992         sliding window)"
4993    )]
4994    RestartWindowMalformed {
4995        restart_window: String,
4996        reason: String,
4997    },
4998    #[error(
4999        "{slot} entry is an empty path string — every {slot} entry must name \
5000         a file relative to the caixa root; omit the entry to omit the file \
5001         (the layout checker's `root.join(\"\")` resolves to the caixa root \
5002         itself, so an empty entry silently aliases the project root as a \
5003         declared {slot} file, then fails downstream at parse / existence \
5004         time with a diagnostic that names the root rather than the offending \
5005         entry)"
5006    )]
5007    CodePathEmpty { slot: &'static str },
5008    #[error(
5009        "{slot} entry {} is an absolute path — entries must be relative to \
5010         the caixa root, since `Path::join` replaces the base with an absolute \
5011         right-hand side and `root.join(\"/abs/...\")` resolves to \"/abs/...\" \
5012         outside the caixa root sandbox; rewrite the entry as a relative path \
5013         under the caixa root (e.g. `\"lib/<name>.lisp\"`, `\"exe/<name>\"`, \
5014         `\"servicos/<name>.computeunit.yaml\"`)",
5015        path.display()
5016    )]
5017    CodePathAbsolute { slot: &'static str, path: PathBuf },
5018    #[error(
5019        "{slot} entry {} contains a `..` component — entries must not traverse \
5020         above the caixa root (the layout's `starts_with(<dir>)` fence on \
5021         `:exe` / `:servicos` is component-aware, not canonical-path-aware, \
5022         so a mid-path `..` silently traverses the sandbox; `:bibliotecas` \
5023         has no such fence, so a leading `..` escapes unconditionally if the \
5024         resolved target happens to exist)",
5025        path.display()
5026    )]
5027    CodePathParentEscape { slot: &'static str, path: PathBuf },
5028    #[error(
5029        "{slot} entry {} does not terminate in the `.lisp` extension — every \
5030         `:bibliotecas` entry is a tatara-lisp source file the `feira build` \
5031         loop reads through `tatara_lisp::read` at parse time, so any other \
5032         extension (`.rs`, `.txt`, `.lisp.bak`) or no-extension shape is \
5033         structurally a parser error far from the source caixa.lisp, with \
5034         no field naming the offending `:bibliotecas` entry. Pin a relative \
5035         path under the caixa root whose terminating extension is \
5036         lowercase-`.lisp` (e.g. `\"lib/<name>.lisp\"`, \
5037         `\"lib/handlers.lisp\"`) — the same file-type contract the peer \
5038         `:behavior :on-*` (c97815a) and `:upgrade-from :state-change :script` \
5039         (33cc830) axes already carry through the same lifted \
5040         `is_lisp_extension` predicate",
5041        path.display()
5042    )]
5043    CodePathNonLispExtension { slot: &'static str, path: PathBuf },
5044    #[error(
5045        "{slot} entry {} does not terminate in the `.computeunit.yaml` \
5046         compound suffix — every `:servicos` entry is a typed `ComputeUnit` \
5047         CR YAML file the peer caixa-helm / caixa-flux renderers consume \
5048         through `serde_yaml::from_str` at chart / FluxCD bundle render \
5049         time, so any other extension (`.yaml`, `.yml`, `.json`, the \
5050         off-by-one-segment `.computeunit-yaml`, the editor-backup \
5051         `.computeunit.yaml.bak`) or no-extension shape is structurally a \
5052         YAML-parser error / `ComputeUnit` schema-mismatch far from the \
5053         source caixa.lisp, with no field naming the offending `:servicos` \
5054         entry. Pin a relative path under the caixa root whose terminating \
5055         compound suffix is lowercase-`.computeunit.yaml` (e.g. \
5056         `\"servicos/<name>.computeunit.yaml\"`, \
5057         `\"servicos/hello-rio.computeunit.yaml\"`) — the same file-type \
5058         contract the sibling `:bibliotecas` axis (64772a9) already carries \
5059         on the tatara-lisp-source axis through the peer lifted \
5060         `is_lisp_extension` predicate, here on the compound-suffix axis \
5061         `Path::extension` can't express on its own through the lifted \
5062         `is_computeunit_yaml_extension` predicate",
5063        path.display()
5064    )]
5065    CodePathNonComputeUnitYamlExtension { slot: &'static str, path: PathBuf },
5066    #[error(
5067        "{slot} entry {} appears more than once (the code-path list is \
5068         a set, not a multiset; every peer Vec-shaped author-supplied \
5069         list past validate is set-not-multiset — `:membros :caixa`, \
5070         `:placement :clusters`, `:entrada :paths`, `:contratos`, \
5071         `:children :caixa`, `:deps` / `:deps-dev` `:nome`, \
5072         `:upgrade-from :from`, `:etiquetas`, `:autores` — and the three \
5073         code-path lists are the last Vec-shaped author-supplied slots on \
5074         the typed Caixa surface still admitting a duplicate entry. \
5075         `:bibliotecas` duplicates re-parse the same file at \
5076         `feira build` time and silently mask the author's intent to \
5077         declare a *second* biblioteca; `:exe` duplicates collide on the \
5078         flake `packages.<name>` derivation key at the future \
5079         `caixa-flake` materializer; `:servicos` duplicates surface as the \
5080         narrower [`caixa-helm`] / [`caixa-flux`] `UnsupportedServicoCount` \
5081         rejection far from the source `caixa.lisp`. Drop the duplicate \
5082         or rename it to the actual second file intended)",
5083        path.display()
5084    )]
5085    CodePathDuplicate { slot: &'static str, path: PathBuf },
5086    #[error(
5087        ":etiquetas entry is empty (every tag must carry a non-empty \
5088         registry-search identifier; the empty entry has no operational \
5089         meaning — it indexes nothing in the future caixa-registry search \
5090         axis and clutters the rendered Helm `Chart.yaml` `keywords:` array \
5091         with a no-op tag; omit the entry to express \"no tag on this \
5092         position\")"
5093    )]
5094    EtiquetaEmpty,
5095    #[error(
5096        ":etiquetas entry {etiqueta:?} appears more than once (the \
5097         registry-search tag set is a set, not a multiset; duplicate \
5098         entries are silently dedup'd by caixa-helm's `BTreeSet` collect \
5099         at chart render — a \"second wins / one silently disappears\" \
5100         shape divergent from every peer typed-graph set gate \
5101         (`:membros :caixa`, `:placement :clusters`, `:entrada :paths`, \
5102         `:contratos`, `:deps :nome`, `:upgrade-from :from`); drop the \
5103         duplicate or rename it to the actual tag intended)"
5104    )]
5105    EtiquetaDuplicate { etiqueta: String },
5106    #[error(
5107        ":etiquetas entry {etiqueta:?} is not a valid chart-keyword shape: \
5108         {reason} (the substrate consumes this string through the shared \
5109         `crate::render::is_chart_keyword_shape` predicate — the same \
5110         Cargo crates.io `[package] keywords` grammar entry shape: 1..=20 \
5111         bytes, starts with an ASCII letter, ASCII alphanumeric / `_` / `-` \
5112         continuation. The canonical authoring shapes are short kebab-case \
5113         identifiers like `\"mesh\"`, `\"wasm\"`, `\"tatara-lisp\"`, \
5114         `\"hello-world\"`, `\"caixa-servico\"`, `\"infrastructure\"`. \
5115         Without this gate a malformed `:etiquetas` entry (paste-from-doc \
5116         leading / trailing whitespace `\" mesh\"` / `\"mesh \"`; \
5117         paste-from-multiline-doc newline `\"mesh\\nhttp\"`; \
5118         paste-from-Windows-CRLF-doc CR; CSV-list-separator confusion \
5119         `\"mesh,http,grpc\"` — the author meant to author three separate \
5120         list entries; path-separator confusion `\"caixa/servico\"`; \
5121         namespace-suffix `\"http.1\"`; leading-digit `\"1foo\"`; \
5122         kebab-leak `\"-foo\"`; snake-leak `\"_foo\"`; non-ASCII \
5123         `\"café\"` — every legitimate search tag is strict ASCII; \
5124         paste-from-binary-blob NUL / BEL / ESC / DEL byte) silently \
5125         passed `from_lisp` + `validate_etiquetas` + \
5126         `StandardLayout::verify` and landed in the rendered \
5127         `lareira-<nome>` Helm chart's `Chart.yaml keywords:` array as a \
5128         malformed search tag — Artifact Hub's keyword index + the future \
5129         caixa-registry's keyword index would either silently drop the \
5130         tag or fail to index it far from the source caixa.lisp; the gate \
5131         moves the diagnostic to the manifest layer with the offending \
5132         value named verbatim)"
5133    )]
5134    EtiquetaInvalid { etiqueta: String, reason: String },
5135    #[error(
5136        ":autores entry is empty (every maintainer must carry a non-empty \
5137         identifier; the empty entry has no operational meaning — it \
5138         identifies no one in the substrate's authorship index and renders \
5139         as `maintainers: [{{name: \"\", email: null}}]` in the Helm chart's \
5140         `Chart.yaml`, a no-op maintainer the substrate cannot route to; \
5141         omit the entry to express \"no maintainer on this position\")"
5142    )]
5143    AutorEmpty,
5144    #[error(
5145        ":autores entry {autor:?} appears more than once (the maintainer \
5146         set is a set, not a multiset; unlike `:etiquetas`, caixa-helm's \
5147         `maintainers:` rendering does *no* dedup — duplicate entries \
5148         stack verbatim in `Chart.yaml` as two identical \
5149         `Maintainer {{ name, email: None }}` records, divergent from every \
5150         peer typed-graph set gate (`:etiquetas`, `:membros :caixa`, \
5151         `:placement :clusters`, `:entrada :paths`, `:contratos`, \
5152         `:deps :nome`, `:upgrade-from :from`); drop the duplicate or \
5153         rename it to the actual author intended)"
5154    )]
5155    AutorDuplicate { autor: String },
5156    #[error(
5157        ":autores entry {autor:?} is not a valid chart-maintainer-name shape: \
5158         {reason} (the substrate consumes this string through the shared \
5159         `crate::render::is_chart_maintainer_name_shape` predicate — the same \
5160         single-line-UTF-8 floor every realistic chart maintainer name carries: \
5161         1..=128 bytes, no leading or trailing whitespace, no ASCII control \
5162         characters anywhere, Unicode bytes accepted. The canonical authoring \
5163         shapes are short single-line identifiers like `\"pleme-io\"`, \
5164         `\"Pleme Contributors\"`, `\"alice <alice@example.com>\"`, \
5165         `\"François Dupont\"`. Without this gate a malformed `:autores` entry \
5166         (paste-from-aligned-doc leading whitespace `\" pleme-io\"` / trailing \
5167         whitespace `\"pleme-io \"`; paste-from-multiline-doc newline \
5168         `\"alice\\nbob\"` — the author pasted a multi-line block of author \
5169         records into one entry instead of splitting into one entry per author; \
5170         paste-from-Windows-CRLF-doc carriage return `\"alice\\rbob\"`; \
5171         tab-from-aligned-doc `\"Pleme\\tContributors\"`; paste-from-binary-blob \
5172         NUL / BEL / ESC / DEL byte) silently passed `from_lisp` + \
5173         `validate_autores` + `StandardLayout::verify` and landed in the \
5174         rendered `lareira-<nome>` Helm chart's `Chart.yaml maintainers:` array \
5175         as a YAML-illegal multi-line scalar or a silently-trimmed whitespace \
5176         round-trip — every chart-aware UI (`helm list`, `helm search`, \
5177         Artifact Hub maintainer index) would render the maintainer name in a \
5178         single-line column far from the source caixa.lisp; the gate moves the \
5179         diagnostic to the manifest layer with the offending value named \
5180         verbatim)"
5181    )]
5182    AutorInvalid { autor: String, reason: String },
5183    #[error(
5184        ":repositorio is the empty string (every published caixa names its \
5185         git source via a non-empty `:repositorio` locator — the value \
5186         flows verbatim into the rendered `lareira-<nome>` Helm chart's \
5187         `Chart.yaml` `home:` field via `caixa-helm` and into the FluxCD \
5188         `GitRepository.spec.url` via `caixa-flux`'s \
5189         `ClusterBundleOpts::for_caixa`; both consumers' \
5190         `Option::unwrap_or_else` fallbacks only fire when the slot is \
5191         `None`, so an empty `Some(\"\")` silently lands as `home: \"\"` / \
5192         `url: \"\"` in the rendered artifacts and breaks at `helm \
5193         template` / FluxCD source-controller reconcile time far from the \
5194         source caixa.lisp; omit the slot entirely to defer to the \
5195         renderer's `https://github.com/pleme-io/<nome>` / \
5196         `caixa.nome`-derived fallback, or carry a canonical authoring \
5197         shape like `\"github:org/repo\"`, `\"https://host/path\"`, \
5198         `\"ssh://[user@]host/path\"`, `\"git@host:path\"`, or \
5199         `\"file:///path\"`)"
5200    )]
5201    RepositorioEmpty,
5202    #[error(
5203        ":repositorio {repositorio:?} is not a valid git repo URL: {reason} \
5204         (the substrate consumes this string through the shared \
5205         `crate::render::is_git_repo_url` predicate — the same parser the \
5206         peer `:deps :fonte (:tipo git :repo …)` axis routes its `:repo` \
5207         value through via `DepSource::validate`; the canonical authoring \
5208         shapes are `\"github:org/repo\"` shorthand, `\"https://host/path\"` \
5209         / `\"ssh://[user@]host/path\"` / `\"git://host/path\"` / \
5210         `\"file:///path\"` URL schemes, or the `\"git@host:path\"` \
5211         scp-style SSH form. Without this gate a malformed `:repositorio` \
5212         (whitespace from a paste-from-doc; control characters / CRLF \
5213         from a paste-from-multiline-doc; a leading `-` from a \
5214         CLI-argument-injection footgun; a missing `:` separator from a \
5215         bare `org/repo` shape git treats as a relative filesystem path) \
5216         silently landed in the rendered `Chart.yaml home:` and the \
5217         FluxCD `GitRepository.spec.url` and broke at `git clone` / \
5218         FluxCD reconcile time far from the source caixa.lisp; the gate \
5219         moves the diagnostic to the manifest layer with the offending \
5220         value named verbatim)"
5221    )]
5222    RepositorioInvalid { repositorio: String, reason: String },
5223    #[error(
5224        ":descricao is the empty string (every published caixa names \
5225         its purpose via a non-empty `:descricao` summary — the value \
5226         flows verbatim into the rendered `lareira-<nome>` Helm \
5227         chart's `Chart.yaml` `description:` field via `caixa-helm`'s \
5228         `build_chart_yaml` and into the chart `README.md` header via \
5229         `build_readme`; both consumers' `Option::unwrap_or_else` \
5230         `caixa.nome`-derived fallbacks only fire when the slot is \
5231         `None`, so an empty `Some(\"\")` silently lands as \
5232         `description: \"\"` / a blank `README.md` header in the \
5233         rendered artifacts and breaks at `helm lint` time \
5234         (`WARNING [chart.metadata.description]: description is \
5235         required` on `apiVersion: v2` charts) far from the source \
5236         caixa.lisp; omit the slot entirely to defer to the \
5237         renderer's `\"Generated chart for caixa Servico <nome>\"` / \
5238         `\"caixa Servico <nome>\"` fallbacks, or carry a non-empty \
5239         summary like `\"Canonical Rust→wasm32-wasip2 caixa \
5240         Servico.\"`)"
5241    )]
5242    DescricaoEmpty,
5243    #[error(
5244        ":descricao {descricao:?} is not a valid chart-description shape: \
5245         {reason} (the substrate consumes this string through the shared \
5246         `crate::render::is_chart_description_shape` predicate — the same \
5247         single-line-UTF-8 floor every realistic chart description carries: \
5248         1..=512 bytes, no leading or trailing whitespace, no ASCII control \
5249         characters anywhere, Unicode prose bytes accepted. The canonical \
5250         authoring shapes are short single-line summaries like `\"Canonical \
5251         Rust→wasm32-wasip2 caixa Servico.\"`, `\"Checkout flow.\"`, \
5252         `\"AWS provider caixa for tatara-lisp\"`. Without this gate a \
5253         malformed `:descricao` (paste-from-aligned-doc leading whitespace \
5254         `\" Checkout flow.\"` / trailing whitespace `\"Checkout flow. \"`; \
5255         paste-from-multiline-doc newline `\"Checkout\\nflow.\"`; \
5256         paste-from-Windows-CRLF-doc carriage return `\"Checkout\\rflow.\"`; \
5257         tab-from-aligned-doc `\"Checkout\\tflow.\"`; paste-from-binary-blob \
5258         NUL / BEL / ESC / DEL byte) silently passed `from_lisp` + \
5259         `validate_descricao` + `StandardLayout::verify` and landed in the \
5260         rendered `lareira-<nome>` Helm chart's `Chart.yaml description:` \
5261         field + `README.md` header paragraph as a YAML-illegal multi-line \
5262         scalar or a silently-trimmed whitespace round-trip — every \
5263         chart-aware UI (`helm list`, `helm search`, Artifact Hub) would \
5264         render the description in a single-line column far from the source \
5265         caixa.lisp; the gate moves the diagnostic to the manifest layer \
5266         with the offending value named verbatim)"
5267    )]
5268    DescricaoInvalid { descricao: String, reason: String },
5269    #[error(
5270        ":licenca is the empty string (every published caixa names \
5271         its license via a non-empty `:licenca` SPDX expression — the \
5272         value flows verbatim into the rendered `lareira-<nome>` Helm \
5273         chart's `README.md` `## License` section via `caixa-helm`'s \
5274         `build_readme` at `caixa-helm/src/lib.rs:361`; the consumer's \
5275         `Option::unwrap_or_else(|| \"MIT\".into())` `MIT` fallback \
5276         only fires when the slot is `None`, so an empty `Some(\"\")` \
5277         silently lands as a bare trailing period in the rendered \
5278         chart `README.md` `License` section far from the source \
5279         caixa.lisp; omit the slot entirely to defer to the \
5280         renderer's `MIT` fallback, or carry a canonical SPDX \
5281         expression like `\"MIT\"`, `\"Apache-2.0\"`, \
5282         `\"Apache-2.0 OR MIT\"`)"
5283    )]
5284    LicencaEmpty,
5285    #[error(
5286        ":licenca {licenca:?} is not a valid SPDX expression shape: {reason} \
5287         (the substrate consumes this string through the shared \
5288         `crate::render::is_spdx_expression_shape` predicate — the same \
5289         alphabet-floor parser every peer per-axis value-shape gate routes \
5290         its value through; the canonical authoring shapes are single \
5291         license identifiers like `\"MIT\"`, `\"Apache-2.0\"`, `\"BSD-3-Clause\"`, \
5292         compound expressions like `\"Apache-2.0 OR MIT\"`, \
5293         `\"MIT AND BSD-3-Clause\"`, `\"(MIT OR Apache-2.0) AND ISC\"`, \
5294         license-with-exception forms like `\"Apache-2.0 WITH LLVM-exception\"`, \
5295         `+`-suffix variants like `\"GPL-2.0+\"`, and user-defined references \
5296         like `\"LicenseRef-MyLicense\"` / \
5297         `\"DocumentRef-doc:LicenseRef-MyLicense\"`. Without this gate a \
5298         malformed `:licenca` (paste-from-doc whitespace `\"MIT \"` / \
5299         `\" MIT\"`; paste-from-multiline-doc CRLF `\"MIT\\n\"`; \
5300         tab-from-aligned-doc `\"MIT\\tOR Apache-2.0\"`; non-ASCII byte from \
5301         a smart-quote paste; underscore-instead-of-hyphen typo \
5302         `\"Apache_2.0\"`; comma-instead-of-`OR`-keyword colloquial idiom \
5303         `\"MIT, Apache-2.0\"`; slash-dual-license colloquial idiom \
5304         `\"MIT/Apache-2.0\"`; semicolon-list-separator confusion \
5305         `\"MIT; Apache-2.0\"`) silently landed in the rendered chart \
5306         `README.md` `## License` section + a future SPDX-aware \
5307         `Chart.yaml license:` emitter would refuse the value at \
5308         `helm lint` time far from the source caixa.lisp; the gate moves \
5309         the diagnostic to the manifest layer with the offending value \
5310         named verbatim)"
5311    )]
5312    LicencaInvalid { licenca: String, reason: String },
5313    #[error(
5314        ":edicao is the empty string (every published caixa names \
5315         its language edition via a non-empty `:edicao` value — the \
5316         edition determines the tatara-lisp macro surface + \
5317         compatibility flags the substrate applies when building \
5318         the caixa; the canonical `Caixa::template` scaffold every \
5319         `feira init` emits carries `:edicao \"2026\"` verbatim and \
5320         every renderer-side fixture (`caixa-helm`, `caixa-flux`, \
5321         `caixa-mesh`) carries `edicao: Some(\"2026\".into())` by \
5322         construction, so an empty `Some(\"\")` silently lands as a \
5323         bare `(:edicao \"\")` line in the rendered `caixa.lisp` and \
5324         a future renderer-side consumer that folds it through \
5325         `Option::unwrap_or_else` will skip the fallback and pass the \
5326         empty edition through to the substrate's build-time edition \
5327         selector far from the source caixa.lisp; omit the slot \
5328         entirely to defer to the substrate's default edition, or \
5329         carry a canonical edition like `\"2026\"`)"
5330    )]
5331    EdicaoEmpty,
5332    #[error(
5333        ":edicao {edicao:?} is not a valid edition: {reason} (every \
5334         documented tatara-lisp edition is a 4-digit ASCII decimal \
5335         year — `\"2026\"` is the only edition currently minted; \
5336         future-introduced siblings will follow the same shape, peer \
5337         with Cargo's `[package] edition` grammar which every value \
5338         Cargo has ever accepted matches: `\"2015\"`, `\"2018\"`, \
5339         `\"2021\"`, `\"2024\"`. Without this gate the canonical \
5340         paste-from-doc footguns silently passed: a trailing space \
5341         (`\"2026 \"`) from a paste-from-doc, a CRLF (`\"2026\\n\"`) \
5342         from a paste-from-multiline-doc, a fullwidth-keyboard \
5343         look-alike (`\"2026\"`), a free-form non-year value \
5344         (`\"x\"`, `\"latest\"`, `\"nightly\"`), a leading non-digit \
5345         version-tag prefix (`\"v2026\"`, `\"e2026\"`), a \
5346         decimal-shaped pseudo-version (`\"2026.1\"`), or a \
5347         wrong-length numeric value (`\"26\"`, `\"202\"`, \
5348         `\"20260\"`) all landed as `(:edicao \"<garbage>\")` in the \
5349         rendered caixa.lisp and broke at the substrate's \
5350         build-time edition selector far from the source caixa.lisp; \
5351         omit the slot entirely to defer to the substrate's default \
5352         edition, or carry a canonical 4-digit ASCII decimal year \
5353         like `\"2026\"`)"
5354    )]
5355    EdicaoInvalid { edicao: String, reason: String },
5356}
5357
5358#[cfg(test)]
5359mod tests {
5360    use super::*;
5361
5362    #[test]
5363    fn template_round_trips() {
5364        let src = Caixa::template("demo");
5365        let c = Caixa::from_lisp(&src).expect("template must parse");
5366        assert_eq!(c.nome, "demo");
5367        assert_eq!(c.versao, "0.1.0");
5368        assert_eq!(c.kind, CaixaKind::Biblioteca);
5369        assert_eq!(c.bibliotecas, vec!["lib/demo.lisp".to_string()]);
5370        assert!(c.deps.is_empty());
5371        assert!(c.deps_dev.is_empty());
5372    }
5373
5374    #[test]
5375    fn register_populates_registry() {
5376        Caixa::register();
5377        let kws = tatara_lisp::domain::registered_keywords();
5378        assert!(kws.contains(&"defcaixa"));
5379    }
5380
5381    #[test]
5382    fn to_lisp_round_trips() {
5383        let src = Caixa::template("demo");
5384        let c1 = Caixa::from_lisp(&src).unwrap();
5385        let emitted = c1.to_lisp();
5386        let c2 = Caixa::from_lisp(&emitted).expect("emitted lisp parses back");
5387        assert_eq!(c1, c2);
5388    }
5389
5390    // ── DialetoEstrangeiro carries a single typed axis ────────────────────
5391    //
5392    // The compounding pin: the variant stores only the typed
5393    // [`crate::dialeto::CaixaDialeto`], and every user-facing byte-string
5394    // (canonical keyword, description, consumer) routes through the enum's
5395    // own accessors at Display time. Prior to that closure the variant
5396    // carried each accessor's return value as a stored `&'static str`
5397    // snapshot alongside `dialeto`; a caller could construct the variant
5398    // with a snapshot that drifted from what `dialeto`'s accessors would
5399    // return, and every downstream user-facing projection would silently
5400    // disagree with the classification. Storing only the axis makes the
5401    // drift structurally impossible.
5402
5403    #[test]
5404    fn dialeto_estrangeiro_variant_carries_only_the_typed_dialeto_axis() {
5405        // Single-field construction is the whole compounding shape — a
5406        // future re-introduction of a snapshot field (a `palavra_canonica:
5407        // &'static str`, a stored `descricao:`, a stored `consumidor:`)
5408        // would re-open the drift surface and this construction would fail
5409        // to compile with "missing field" until every snapshot was seeded
5410        // at the call site again. The compile-time guarantee is the
5411        // invariant; the assertion below only witnesses that the
5412        // construction is well-formed after the closure.
5413        let err = LeituraError::DialetoEstrangeiro {
5414            dialeto: crate::dialeto::CaixaDialeto::Molde,
5415        };
5416        assert!(matches!(
5417            err,
5418            LeituraError::DialetoEstrangeiro {
5419                dialeto: crate::dialeto::CaixaDialeto::Molde,
5420            }
5421        ));
5422    }
5423
5424    #[test]
5425    fn dialeto_estrangeiro_display_routes_through_typed_dialeto_accessors() {
5426        // For every foreign-dialect classification the variant surfaces —
5427        // [`crate::dialeto::CaixaDialeto::Molde`] and
5428        // [`crate::dialeto::CaixaDialeto::MoldePosicional`], the two
5429        // variants [`Caixa::from_lisp`] raises this error for — the
5430        // rendered [`std::fmt::Display`] byte-string must interpolate each
5431        // typed accessor's return verbatim. A future re-introduction of a
5432        // stored `&'static str` snapshot alongside `dialeto` that Display
5433        // read instead of the accessor would fail this pin as soon as the
5434        // two disagreed; a future accessor rebrand (a per-dialect
5435        // consumer rename, a canonical-keyword shift once the substrate
5436        // migration named in [`crate::dialeto`] completes) reaches every
5437        // consumer through one typed dispatch and this pin verifies the
5438        // display path is one of them.
5439        for d in [
5440            crate::dialeto::CaixaDialeto::Molde,
5441            crate::dialeto::CaixaDialeto::MoldePosicional,
5442        ] {
5443            let rendered = LeituraError::DialetoEstrangeiro { dialeto: d }.to_string();
5444            assert!(
5445                rendered.contains(d.palavra_canonica()),
5446                "Display must interpolate `dialeto.palavra_canonica()` \
5447                 verbatim — a stored snapshot would silently drift from \
5448                 the typed accessor. dialect: {d}, rendered: {rendered:?}"
5449            );
5450            assert!(
5451                rendered.contains(d.descricao()),
5452                "Display must interpolate `dialeto.descricao()` verbatim. \
5453                 dialect: {d}, rendered: {rendered:?}"
5454            );
5455            assert!(
5456                rendered.contains(d.consumidor()),
5457                "Display must interpolate `dialeto.consumidor()` verbatim. \
5458                 dialect: {d}, rendered: {rendered:?}"
5459            );
5460        }
5461    }
5462
5463    #[test]
5464    fn from_lisp_rejects_molde_dialect_via_typed_variant() {
5465        // The end-to-end pin the compounding closure defends: a
5466        // Molde-dialect source lands as [`LeituraError::DialetoEstrangeiro`]
5467        // carrying [`crate::dialeto::CaixaDialeto::Molde`], and the
5468        // rendered Display byte-string names the Molde accessors'
5469        // returns verbatim. Any future path that constructed the variant
5470        // with a mismatched snapshot (a stored `palavra_canonica:
5471        // "defcaixa"` on a `Molde` classification) would land Display
5472        // pointing at `defcaixa` while the typed axis said `Molde` — the
5473        // exact drift the closure removes.
5474        let src = r#"
5475          (defcaixa
5476            :name "x"
5477            :kind :Biblioteca
5478            :ecosystem :rust-single-crate
5479            :package {:name "x" :version "0.1.0"})
5480        "#;
5481        let err = Caixa::from_lisp(src).expect_err("Molde dialect must not parse as Pacote");
5482        match err {
5483            LeituraError::DialetoEstrangeiro { dialeto } => {
5484                assert_eq!(dialeto, crate::dialeto::CaixaDialeto::Molde);
5485                let rendered = LeituraError::DialetoEstrangeiro { dialeto }.to_string();
5486                assert!(rendered.contains(dialeto.palavra_canonica()));
5487                assert!(rendered.contains(dialeto.consumidor()));
5488                assert!(rendered.contains(dialeto.descricao()));
5489            }
5490            other => panic!("expected DialetoEstrangeiro, got {other:?}"),
5491        }
5492    }
5493
5494    // ── M2 typed-substrate slot tests (limits, behavior, upgrade-from, supervisor) ──
5495
5496    #[test]
5497    fn limits_round_trip_via_json() {
5498        use crate::LimitsSpec;
5499        use std::time::Duration;
5500        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5501        c.limits = Some(LimitsSpec {
5502            memory: Some(64 * 1024 * 1024),
5503            fuel: Some(1_000_000),
5504            wall_clock: Some(Duration::from_secs(30)),
5505            cpu: Some(500),
5506        });
5507        let json = serde_json::to_string(&c).unwrap();
5508        assert!(json.contains("\"limits\""));
5509        assert!(json.contains("\"64MiB\""));
5510        assert!(json.contains("\"30s\""));
5511        assert!(json.contains("\"500m\""));
5512        let back: Caixa = serde_json::from_str(&json).unwrap();
5513        assert_eq!(c.limits, back.limits);
5514    }
5515
5516    #[test]
5517    fn behavior_round_trip_via_json() {
5518        use crate::BehaviorSpec;
5519        use std::path::PathBuf;
5520        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5521        c.behavior = Some(BehaviorSpec {
5522            on_init: Some(PathBuf::from("lib/init.lisp")),
5523            on_call: Some(PathBuf::from("lib/handlers.lisp")),
5524            ..Default::default()
5525        });
5526        let json = serde_json::to_string(&c).unwrap();
5527        let back: Caixa = serde_json::from_str(&json).unwrap();
5528        assert_eq!(c.behavior, back.behavior);
5529    }
5530
5531    #[test]
5532    fn upgrade_from_round_trip_via_json() {
5533        use crate::{UpgradeFromEntry, UpgradeInstruction};
5534        use std::path::PathBuf;
5535        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5536        c.upgrade_from = vec![UpgradeFromEntry {
5537            from: "0.1.0".into(),
5538            instructions: vec![
5539                UpgradeInstruction::LoadModule {
5540                    module: "demo".into(),
5541                },
5542                UpgradeInstruction::StateChange {
5543                    script: PathBuf::from("lib/migrations/v01-to-v02.lisp"),
5544                },
5545                UpgradeInstruction::SoftPurge {
5546                    module: "demo-old".into(),
5547                },
5548            ],
5549        }];
5550        let json = serde_json::to_string(&c).unwrap();
5551        let back: Caixa = serde_json::from_str(&json).unwrap();
5552        assert_eq!(c.upgrade_from, back.upgrade_from);
5553    }
5554
5555    #[test]
5556    fn supervisor_view_returns_typed_shape() {
5557        use crate::{ChildSpec, RestartPolicy, RestartStrategy};
5558        let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
5559        c.kind = CaixaKind::Supervisor;
5560        c.bibliotecas.clear();
5561        c.estrategia = Some(RestartStrategy::OneForOne);
5562        c.max_restarts = Some(5);
5563        c.restart_window = Some("60s".into());
5564        c.children = vec![ChildSpec {
5565            caixa: "worker".into(),
5566            versao: "^0.1".into(),
5567            restart: RestartPolicy::Permanent,
5568        }];
5569        let view = c.supervisor_view().expect("Supervisor kind has a view");
5570        assert_eq!(view.estrategia, RestartStrategy::OneForOne);
5571        assert_eq!(view.max_restarts, 5);
5572        assert_eq!(
5573            view.restart_window,
5574            Some(std::time::Duration::from_secs(60))
5575        );
5576        assert_eq!(view.children.len(), 1);
5577        view.validate().unwrap();
5578    }
5579
5580    #[test]
5581    fn supervisor_view_none_for_non_supervisor_kinds() {
5582        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5583        assert!(c.supervisor_view().is_none());
5584    }
5585
5586    #[test]
5587    fn declared_mesh_slots_empty_for_bare_caixa() {
5588        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5589        assert!(c.declared_mesh_slots().is_empty());
5590    }
5591
5592    #[test]
5593    fn declared_mesh_slots_reports_only_set_slots_in_canonical_order() {
5594        use crate::{Entrada, Membro};
5595        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5596        // Set a non-adjacent pair (:membros + :entrada) to pin that the
5597        // canonical declaration order is preserved regardless of which
5598        // subset is populated.
5599        c.membros = vec![Membro {
5600            caixa: "a".into(),
5601            versao: "^0.1".into(),
5602        }];
5603        c.entrada = Some(Entrada {
5604            host: "x.example.com".into(),
5605            para: "a".into(),
5606            paths: vec![],
5607            port: 8080,
5608        });
5609        assert_eq!(
5610            c.declared_mesh_slots(),
5611            vec![
5612                crate::render::M3_AUTHOR_KEY_MEMBROS,
5613                crate::render::M3_AUTHOR_KEY_ENTRADA,
5614            ]
5615        );
5616    }
5617
5618    #[test]
5619    fn m3_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
5620        // Scalar-value pin: the five author-facing kebab-case labels the
5621        // `(defcaixa … :<slot> (…))` surface admits on the M3 top-level
5622        // mesh slot axis, one arm per typed slot. Mirrors the peer
5623        // scalar-value pin the sibling
5624        // [`crate::M2_AUTHOR_KEY_LIMITS`] /
5625        // [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
5626        // [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] M2 top-level slot consts
5627        // carry (f49c8b0), so both altitudes of the typed-slot algebra
5628        // (per-Servico M2 + per-Aplicacao M3) share the same
5629        // "one canonical byte-string per arm" discipline. A future
5630        // rebrand (`:membros` → `:members`, `:contratos` → `:contracts`,
5631        // `:politicas` → `:policies`, `:placement` → `:distribution`,
5632        // `:entrada` → `:ingress`) lands as an edit to exactly one const,
5633        // and every consumer that reaches for the label picks it up at
5634        // build time rather than at runtime as a downstream mismatch.
5635        assert_eq!(crate::render::M3_AUTHOR_KEY_MEMBROS, ":membros");
5636        assert_eq!(crate::render::M3_AUTHOR_KEY_CONTRATOS, ":contratos");
5637        assert_eq!(crate::render::M3_AUTHOR_KEY_POLITICAS, ":politicas");
5638        assert_eq!(crate::render::M3_AUTHOR_KEY_PLACEMENT, ":placement");
5639        assert_eq!(crate::render::M3_AUTHOR_KEY_ENTRADA, ":entrada");
5640    }
5641
5642    #[test]
5643    fn declared_mesh_slots_route_through_lifted_m3_author_key_consts() {
5644        // Production-through-const pin: the five per-arm labels the
5645        // [`Caixa::declared_mesh_slots`] tagger pushes onto its return
5646        // `Vec` route through the lifted
5647        // [`crate::M3_AUTHOR_KEY_MEMBROS`] /
5648        // [`crate::M3_AUTHOR_KEY_CONTRATOS`] /
5649        // [`crate::M3_AUTHOR_KEY_POLITICAS`] /
5650        // [`crate::M3_AUTHOR_KEY_PLACEMENT`] /
5651        // [`crate::M3_AUTHOR_KEY_ENTRADA`] consts, in canonical
5652        // declaration order. A future re-order or drift at the tagger
5653        // (a rename that reaches the tagger but not the const, or vice
5654        // versa) surfaces here at build time rather than at runtime as
5655        // a [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
5656        // `slots: <stale-kebab-case>` diagnostic far from the rename's
5657        // commit. Mirror of the peer
5658        // [`declared_servico_slots_route_through_lifted_m2_author_key_consts`]
5659        // pin (f49c8b0) on the sibling per-Servico M2 top-level slot
5660        // axis.
5661        use crate::{Entrada, Membro, MeshPolicy, Placement, PlacementStrategy, WitContract};
5662        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5663        c.membros = vec![Membro {
5664            caixa: "a".into(),
5665            versao: "^0.1".into(),
5666        }];
5667        c.contratos = vec![WitContract {
5668            de: "a".into(),
5669            para: "a".into(),
5670            wit: "wasi:http/proxy".into(),
5671            endpoint: Some("/x".into()),
5672            subject: None,
5673            slot: None,
5674        }];
5675        c.politicas = Some(MeshPolicy::default());
5676        c.placement = Some(Placement {
5677            estrategia: PlacementStrategy::Replicated,
5678            clusters: vec!["rio".into()],
5679            affinity: None,
5680            shard_key: None,
5681        });
5682        c.entrada = Some(Entrada {
5683            host: "x.example.com".into(),
5684            para: "a".into(),
5685            paths: vec![],
5686            port: 8080,
5687        });
5688        assert_eq!(
5689            c.declared_mesh_slots(),
5690            vec![
5691                crate::render::M3_AUTHOR_KEY_MEMBROS,
5692                crate::render::M3_AUTHOR_KEY_CONTRATOS,
5693                crate::render::M3_AUTHOR_KEY_POLITICAS,
5694                crate::render::M3_AUTHOR_KEY_PLACEMENT,
5695                crate::render::M3_AUTHOR_KEY_ENTRADA,
5696            ]
5697        );
5698    }
5699
5700    #[test]
5701    fn declared_supervisor_slots_empty_for_bare_caixa() {
5702        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5703        assert!(c.declared_supervisor_slots().is_empty());
5704    }
5705
5706    #[test]
5707    fn declared_supervisor_slots_reports_only_set_slots_in_canonical_order() {
5708        use crate::RestartStrategy;
5709        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5710        // Set a non-adjacent pair (:estrategia + :restart-window) to pin
5711        // that the canonical declaration order is preserved regardless
5712        // of which subset is populated.
5713        c.estrategia = Some(RestartStrategy::OneForOne);
5714        c.restart_window = Some("60s".into());
5715        assert_eq!(
5716            c.declared_supervisor_slots(),
5717            vec![
5718                crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
5719                crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
5720            ]
5721        );
5722    }
5723
5724    #[test]
5725    fn supervisor_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
5726        // Scalar-value pin: the four author-facing kebab-case labels the
5727        // `(defcaixa … :<slot> (…))` surface admits on the Supervisor
5728        // supervision-tree slot axis, one arm per typed slot. Mirrors the
5729        // peer scalar-value pins the sibling
5730        // [`crate::render::M2_AUTHOR_KEY_LIMITS`] /
5731        // [`crate::render::M2_AUTHOR_KEY_BEHAVIOR`] /
5732        // [`crate::render::M2_AUTHOR_KEY_UPGRADE_FROM`] top-level M2 slot
5733        // consts and [`crate::render::M3_AUTHOR_KEY_MEMBROS`] etc.
5734        // top-level M3 slot consts carry, so all three kind-scoped
5735        // typed-slot-family author-facing-label axes route through one
5736        // canonical per-arm declaration. A future rebrand
5737        // (`:estrategia` → `:strategy` for English uniformity,
5738        // `:max-restarts` → `:max-intensity` matching Erlang/OTP's
5739        // `MaxIntensity` name, `:restart-window` → `:period` matching
5740        // OTP's `Period` name, `:children` → `:workers` matching Elixir
5741        // idiom) lands as an edit to exactly one const, and every
5742        // consumer that reaches for the label picks it up at build time
5743        // rather than at runtime as a downstream mismatch.
5744        assert_eq!(
5745            crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
5746            ":estrategia"
5747        );
5748        assert_eq!(
5749            crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS,
5750            ":max-restarts"
5751        );
5752        assert_eq!(
5753            crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
5754            ":restart-window"
5755        );
5756        assert_eq!(crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN, ":children");
5757    }
5758
5759    #[test]
5760    fn declared_supervisor_slots_route_through_lifted_supervisor_author_key_consts() {
5761        // Production-through-const pin: the four per-arm labels the
5762        // [`Caixa::declared_supervisor_slots`] tagger pushes onto its
5763        // return `Vec` route through the lifted
5764        // [`crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA`] /
5765        // [`crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS`] /
5766        // [`crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW`] /
5767        // [`crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN`] consts, in
5768        // canonical declaration order. A future re-order or drift at the
5769        // tagger (a rename that reaches the tagger but not the const, or
5770        // vice versa) surfaces here at build time rather than at runtime
5771        // as a [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
5772        // `slots: <stale-kebab-case>` diagnostic far from the rename's
5773        // commit. Mirror of the peer
5774        // [`declared_servico_slots_route_through_lifted_m2_author_key_consts`]
5775        // (f49c8b0) and
5776        // [`declared_mesh_slots_route_through_lifted_m3_author_key_consts`]
5777        // (882f498) pins on the sibling M2 / M3 top-level slot axes.
5778        use crate::{ChildSpec, RestartPolicy, RestartStrategy};
5779        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5780        c.estrategia = Some(RestartStrategy::OneForOne);
5781        c.max_restarts = Some(5);
5782        c.restart_window = Some("60s".into());
5783        c.children = vec![ChildSpec {
5784            caixa: "worker".into(),
5785            versao: "^0.1".into(),
5786            restart: RestartPolicy::Permanent,
5787        }];
5788        assert_eq!(
5789            c.declared_supervisor_slots(),
5790            vec![
5791                crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
5792                crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS,
5793                crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
5794                crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN,
5795            ]
5796        );
5797    }
5798
5799    #[test]
5800    fn declared_servico_slots_empty_for_bare_caixa() {
5801        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5802        assert!(c.declared_servico_slots().is_empty());
5803    }
5804
5805    #[test]
5806    fn declared_servico_slots_reports_only_set_slots_in_canonical_order() {
5807        use crate::{UpgradeFromEntry, UpgradeInstruction};
5808        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5809        // Set a non-adjacent pair (:limits + :upgrade-from) to pin that
5810        // the canonical declaration order is preserved regardless of
5811        // which subset is populated.
5812        c.limits = Some(crate::LimitsSpec {
5813            fuel: Some(1_000_000),
5814            ..Default::default()
5815        });
5816        c.upgrade_from = vec![UpgradeFromEntry {
5817            from: "0.1.0".into(),
5818            instructions: vec![UpgradeInstruction::Restart],
5819        }];
5820        assert_eq!(
5821            c.declared_servico_slots(),
5822            vec![
5823                crate::render::M2_AUTHOR_KEY_LIMITS,
5824                crate::render::M2_AUTHOR_KEY_UPGRADE_FROM,
5825            ]
5826        );
5827    }
5828
5829    #[test]
5830    fn m2_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
5831        // Scalar-value pin: the three author-facing kebab-case labels
5832        // the `(defcaixa … :<slot> (…))` surface admits on the M2
5833        // top-level slot axis, one arm per typed slot. Mirrors the peer
5834        // scalar-value pin the sibling renderer-side
5835        // [`crate::M2_KEY_LIMITS`] / [`crate::M2_KEY_BEHAVIOR`] /
5836        // [`crate::M2_KEY_UPGRADE_FROM`] camelCase overlay-container
5837        // consts carry, so both halves of the M2 top-level slot dual
5838        // axis (author-facing kebab-case label + renderer-side
5839        // camelCase overlay-container wire key) route through one
5840        // canonical per-arm declaration. A future rebrand
5841        // (`:limits` → `:sandbox` matching Lunatic per-process
5842        // terminology INSPIRATIONS §III.1, `:behavior` → `:gen-server`
5843        // matching Erlang's verbatim name, `:upgrade-from` → `:appup`
5844        // matching Erlang's verbatim appup name) lands as an edit to
5845        // exactly one const, and every consumer that reaches for the
5846        // label picks it up at build time rather than at runtime as a
5847        // downstream mismatch.
5848        assert_eq!(crate::render::M2_AUTHOR_KEY_LIMITS, ":limits");
5849        assert_eq!(crate::render::M2_AUTHOR_KEY_BEHAVIOR, ":behavior");
5850        assert_eq!(crate::render::M2_AUTHOR_KEY_UPGRADE_FROM, ":upgrade-from");
5851    }
5852
5853    #[test]
5854    fn declared_servico_slots_route_through_lifted_m2_author_key_consts() {
5855        // Production-through-const pin: the three per-arm labels the
5856        // [`Caixa::declared_servico_slots`] tagger pushes onto its
5857        // return `Vec` route through the lifted
5858        // [`crate::M2_AUTHOR_KEY_LIMITS`] /
5859        // [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
5860        // [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] consts, in canonical
5861        // declaration order. A future re-order or drift at the tagger
5862        // (a rename that reaches the tagger but not the const, or vice
5863        // versa) surfaces here at build time rather than at runtime as
5864        // a [`crate::LayoutError::ServicoSlotsOnNonServico`]
5865        // `slots: <stale-kebab-case>` diagnostic far from the rename's
5866        // commit. Mirror of the peer
5867        // [`crate::behavior::BehaviorSpec::declared_slots`] production
5868        // tagger pin (889dc18) on the sibling per-callback axis.
5869        use crate::{BehaviorSpec, UpgradeFromEntry, UpgradeInstruction};
5870        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5871        c.limits = Some(crate::LimitsSpec {
5872            fuel: Some(1_000_000),
5873            ..Default::default()
5874        });
5875        c.behavior = Some(BehaviorSpec {
5876            on_init: Some(PathBuf::from("lib/init.lisp")),
5877            ..Default::default()
5878        });
5879        c.upgrade_from = vec![UpgradeFromEntry {
5880            from: "0.1.0".into(),
5881            instructions: vec![UpgradeInstruction::Restart],
5882        }];
5883        assert_eq!(
5884            c.declared_servico_slots(),
5885            vec![
5886                crate::render::M2_AUTHOR_KEY_LIMITS,
5887                crate::render::M2_AUTHOR_KEY_BEHAVIOR,
5888                crate::render::M2_AUTHOR_KEY_UPGRADE_FROM,
5889            ]
5890        );
5891    }
5892
5893    #[test]
5894    fn existing_manifests_unaffected_by_new_optional_slots() {
5895        // Regression test: a caixa.lisp authored before M2 typed slots
5896        // should still parse + serialize cleanly. The bare `defcaixa`
5897        // emitted by `Caixa::template` has none of the new fields.
5898        let src = Caixa::template("legacy");
5899        let c = Caixa::from_lisp(&src).unwrap();
5900        assert!(c.limits.is_none());
5901        assert!(c.behavior.is_none());
5902        assert!(c.upgrade_from.is_empty());
5903        assert!(c.estrategia.is_none());
5904        assert!(c.children.is_empty());
5905
5906        // And to_lisp emits a manifest with the new slots in the
5907        // empty/default state — round-trippable.
5908        let emitted = c.to_lisp();
5909        let back = Caixa::from_lisp(&emitted).unwrap();
5910        assert_eq!(c, back);
5911    }
5912
5913    #[test]
5914    fn validate_deps_accepts_canonical_caixa() {
5915        // Positive control: the bare template — zero deps, zero
5916        // deps_dev — passes the gate trivially. A future axis added to
5917        // `Dep::validate` mustn't regress an empty-deps caixa to a
5918        // build error.
5919        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5920        c.validate_deps().unwrap();
5921    }
5922
5923    #[test]
5924    fn validate_deps_rejects_invalid_versao_in_deps() {
5925        // Fail-before-pass-after pin: a malformed `:deps :versao`
5926        // surfaces at validate_deps() time, not at lacre-resolve time.
5927        // Mirrors `rejects_invalid_membro_versao_requirement` and
5928        // `validate_rejects_invalid_child_versao_requirement` on the
5929        // other two `:versao` axes.
5930        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5931        c.deps = vec![Dep::simple("caixa-teia", "^bad-version")];
5932        let err = c.validate_deps().unwrap_err();
5933        assert!(
5934            matches!(
5935                err,
5936                crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
5937                    if nome == "caixa-teia" && versao == "^bad-version"
5938            ),
5939            "got {err:?}"
5940        );
5941    }
5942
5943    #[test]
5944    fn validate_deps_rejects_invalid_versao_in_deps_dev() {
5945        // Parity pin: `:deps-dev` must run through the same per-entry
5946        // validator as `:deps` — a typo in either axis surfaces the
5947        // same diagnostic. Without this leg, `:deps-dev` would be a
5948        // second-class citizen of the typed surface and an author
5949        // could land a build that passes validate_deps but fails at
5950        // `feira lock`-time when the dev-dep is resolved for a test
5951        // build.
5952        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5953        c.deps_dev = vec![Dep::simple("tatara-check", "^^0.1")];
5954        let err = c.validate_deps().unwrap_err();
5955        assert!(
5956            matches!(
5957                err,
5958                crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
5959                    if nome == "tatara-check" && versao == "^^0.1"
5960            ),
5961            "got {err:?}"
5962        );
5963    }
5964
5965    #[test]
5966    fn validate_deps_runs_deps_before_deps_dev() {
5967        // Order pin: when both lists carry typos, the `:deps`
5968        // diagnostic surfaces first. The author's mental model is
5969        // "runtime deps are load-bearing; dev deps are scaffolding";
5970        // surfacing the runtime axis first matches that hierarchy.
5971        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5972        c.deps = vec![Dep::simple("runtime-dep", "^bad-runtime")];
5973        c.deps_dev = vec![Dep::simple("dev-dep", "^bad-dev")];
5974        let err = c.validate_deps().unwrap_err();
5975        assert!(
5976            matches!(
5977                err,
5978                crate::dep::DepError::VersaoInvalid { ref nome, .. }
5979                    if nome == "runtime-dep"
5980            ),
5981            "expected `:deps` typo to surface first, got {err:?}"
5982        );
5983    }
5984
5985    #[test]
5986    fn validate_deps_accepts_canonical_versao_forms_in_both_lists() {
5987        // Positive control sweep across both lists. Pin every
5988        // canonical Cargo-shaped form so a future tightening of the
5989        // accepted set surfaces here as a test failure (parity with
5990        // `accepts_canonical_membro_versao_forms` and
5991        // `validate_accepts_canonical_child_versao_forms`).
5992        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5993        c.deps = vec![
5994            Dep::simple("caret", "^0.1"),
5995            Dep::simple("tilde", "~0.1.2"),
5996            Dep::simple("exact", "0.1.0"),
5997            Dep::simple("wildcard", "*"),
5998            Dep::simple("multi-range", ">=0.1, <2"),
5999        ];
6000        c.deps_dev = vec![
6001            Dep::simple("dev-caret", "^0.1"),
6002            Dep::simple("dev-wildcard", "*"),
6003        ];
6004        c.validate_deps().unwrap();
6005    }
6006
6007    #[test]
6008    fn validate_deps_diagnostic_carries_offending_dep() {
6009        // Diagnostic-shape pin: the error names the offending entry's
6010        // `:nome` + `:versao` verbatim and carries a non-empty
6011        // `reason` from `semver::VersionReq::parse`, so a `feira lint`
6012        // run can render the diagnostic without re-parsing.
6013        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6014        c.deps = vec![Dep::simple("caixa-teia", "not-a-req")];
6015        let err = c.validate_deps().unwrap_err();
6016        let crate::dep::DepError::VersaoInvalid {
6017            nome,
6018            versao,
6019            reason,
6020        } = err
6021        else {
6022            panic!("expected VersaoInvalid, got other variant");
6023        };
6024        assert_eq!(nome, "caixa-teia");
6025        assert_eq!(versao, "not-a-req");
6026        assert!(
6027            !reason.is_empty(),
6028            "VersaoInvalid `reason` must carry the parser's wording verbatim"
6029        );
6030    }
6031
6032    #[test]
6033    fn validate_deps_rejects_ambiguous_fonte_in_deps_dev() {
6034        // Cross-axis pin: `validate_deps` walks both :deps and
6035        // :deps-dev through `Dep::validate`, and the new fonte gate
6036        // (`:tag` + `:branch` both set — the canonical "pin drift"
6037        // footgun) must surface from the :deps-dev arm with the
6038        // offending entry's :nome named. Pin the :deps-dev arm
6039        // explicitly so a future shortcut that only walks :deps
6040        // surfaces here as a regression.
6041        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6042        c.deps_dev = vec![Dep {
6043            nome: "dev-only".into(),
6044            versao: "^0.1".into(),
6045            fonte: Some(crate::DepSource::Git {
6046                repo: "github:p/x".into(),
6047                tag: Some("v1".into()),
6048                rev: None,
6049                branch: Some("main".into()),
6050            }),
6051            opcional: false,
6052            caracteristicas: vec![],
6053        }];
6054        let err = c.validate_deps().unwrap_err();
6055        let crate::dep::DepError::FontePinAmbiguous { nome, pins } = err else {
6056            panic!("expected FontePinAmbiguous from :deps-dev walk");
6057        };
6058        assert_eq!(nome, "dev-only");
6059        assert!(pins.contains(":tag") && pins.contains(":branch"));
6060    }
6061
6062    #[test]
6063    fn validate_deps_rejects_empty_repo_in_deps() {
6064        // Parity pin on the :deps arm: an empty :repo on the runtime
6065        // deps list surfaces the same FonteRepoEmpty diagnostic the
6066        // dep.rs per-entry tests pin, naming the offending entry.
6067        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6068        c.deps = vec![Dep {
6069            nome: "runtime".into(),
6070            versao: "^0.1".into(),
6071            fonte: Some(crate::DepSource::Git {
6072                repo: String::new(),
6073                tag: Some("v1".into()),
6074                rev: None,
6075                branch: None,
6076            }),
6077            opcional: false,
6078            caracteristicas: vec![],
6079        }];
6080        let err = c.validate_deps().unwrap_err();
6081        assert!(
6082            matches!(
6083                err,
6084                crate::dep::DepError::FonteRepoEmpty { ref nome }
6085                    if nome == "runtime"
6086            ),
6087            "got {err:?}"
6088        );
6089    }
6090
6091    // ── validate_deps: within-list :nome set-not-multiset gate ─────────
6092
6093    #[test]
6094    fn validate_deps_rejects_duplicate_nome_in_deps() {
6095        // Fail-before-pass-after pin: two `:deps` entries naming the same
6096        // caixa carry two `:versao` / `:fonte` / feature triples that the
6097        // caixa-resolver's lacre pipeline collapses (the second silently
6098        // overwrites the first at `concrete_versao`-resolve time). The
6099        // gate surfaces the duplicate at validate-time, naming the
6100        // offending caixa + the list, before the resolver-side silent
6101        // drop. Mirrors the peer typed-graph duplicate gates
6102        // (`DuplicateChildCaixa`, `MembroDuplicate`, `DuplicateFrom`, …).
6103        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6104        c.deps = vec![
6105            Dep::simple("caixa-teia", "^0.1"),
6106            Dep::simple("caixa-teia", "^0.2"),
6107        ];
6108        let err = c.validate_deps().unwrap_err();
6109        assert!(
6110            matches!(
6111                err,
6112                crate::dep::DepError::DuplicateNome { ref nome, list }
6113                    if nome == "caixa-teia" && list == crate::render::DEP_AUTHOR_KEY_DEPS
6114            ),
6115            "got {err:?}"
6116        );
6117    }
6118
6119    #[test]
6120    fn validate_deps_rejects_duplicate_nome_in_deps_dev() {
6121        // Parity pin: `:deps-dev` runs through the same per-list
6122        // duplicate check as `:deps` — neither axis is a second-class
6123        // citizen of the set-not-multiset discipline.
6124        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6125        c.deps_dev = vec![
6126            Dep::simple("tatara-check", "*"),
6127            Dep::simple("tatara-check", "^0.1"),
6128        ];
6129        let err = c.validate_deps().unwrap_err();
6130        assert!(
6131            matches!(
6132                err,
6133                crate::dep::DepError::DuplicateNome { ref nome, list }
6134                    if nome == "tatara-check" && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
6135            ),
6136            "got {err:?}"
6137        );
6138    }
6139
6140    #[test]
6141    fn validate_deps_accepts_cross_list_same_nome() {
6142        // The Cargo `[dependencies]` + `[dev-dependencies]` override
6143        // convention is preserved: a name appearing in *both* lists is
6144        // valid (the dev-pin overrides at test/dev time). Only
6145        // within-list duplicates are structurally incoherent — pin the
6146        // permissive cross-list semantics so a future shortcut that
6147        // collapses the two seen-sets into one surfaces here as a test
6148        // failure.
6149        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6150        c.deps = vec![Dep::simple("caixa-teia", "^0.1")];
6151        c.deps_dev = vec![Dep::simple("caixa-teia", "^0.2")];
6152        c.validate_deps().unwrap();
6153    }
6154
6155    #[test]
6156    fn validate_deps_accepts_distinct_nome_in_both_lists() {
6157        // Positive control: distinct names within each list pass — the
6158        // gate's identity element on the canonical authoring shape.
6159        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6160        c.deps = vec![
6161            Dep::simple("caixa-teia", "^0.1"),
6162            Dep::simple("pleme-mesh", "*"),
6163        ];
6164        c.deps_dev = vec![
6165            Dep::simple("tatara-check", "*"),
6166            Dep::simple("dev-shim", "^0.1"),
6167        ];
6168        c.validate_deps().unwrap();
6169    }
6170
6171    #[test]
6172    fn validate_deps_per_entry_validate_fires_before_duplicate_in_deps() {
6173        // Diagnostic-precedence pin: a malformed `:versao` on the
6174        // duplicating entry surfaces its narrower `VersaoInvalid`
6175        // diagnostic first, before the cross-entry duplicate gate fires
6176        // — the canonical "per-entry shape before cross-entry uniqueness"
6177        // precedence every peer set-not-multiset gate establishes
6178        // (`*_invalid_fires_before_duplicate_check` pins on
6179        // `SupervisorSpec::validate`, `AplicacaoSpec::validate_membros`,
6180        // `validate_upgrade_from`).
6181        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6182        c.deps = vec![
6183            Dep::simple("caixa-teia", "^0.1"),
6184            Dep::simple("caixa-teia", "^bad-version"),
6185        ];
6186        let err = c.validate_deps().unwrap_err();
6187        assert!(
6188            matches!(
6189                err,
6190                crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
6191                    if nome == "caixa-teia" && versao == "^bad-version"
6192            ),
6193            "expected VersaoInvalid to surface before DuplicateNome, got {err:?}"
6194        );
6195    }
6196
6197    #[test]
6198    fn validate_deps_duplicate_diagnostic_names_first_collision() {
6199        // First-collision determinism pin: with three entries naming the
6200        // same caixa, the first colliding pair surfaces — not the last.
6201        // Mirrors the peer first-collision posture on every
6202        // duplicate-target gate
6203        // (`validate_upgrade_from_duplicate_diagnostic_names_second_collision`
6204        // — the second entry is the first collision; this gate uses the
6205        // same shape: the second entry's `:nome` lands in the diagnostic
6206        // because `seen.insert(first.nome)` already populated the set).
6207        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6208        c.deps = vec![
6209            Dep::simple("caixa-teia", "^0.1"),
6210            Dep::simple("caixa-teia", "^0.2"),
6211            Dep::simple("caixa-teia", "^0.3"),
6212        ];
6213        let err = c.validate_deps().unwrap_err();
6214        // The diagnostic carries the offending caixa name; the
6215        // implementation surfaces on the *second* entry (the first
6216        // collision), so the test pins the `:nome` value.
6217        assert!(
6218            matches!(
6219                err,
6220                crate::dep::DepError::DuplicateNome { ref nome, list }
6221                    if nome == "caixa-teia" && list == crate::render::DEP_AUTHOR_KEY_DEPS
6222            ),
6223            "got {err:?}"
6224        );
6225    }
6226
6227    #[test]
6228    fn validate_deps_duplicate_in_deps_fires_before_duplicate_in_deps_dev() {
6229        // Cross-list precedence pin: when both lists carry duplicates,
6230        // the `:deps` diagnostic surfaces first — same author-mental-
6231        // model ordering the `validate_deps_runs_deps_before_deps_dev`
6232        // pin establishes for malformed `:versao` (runtime axis before
6233        // dev axis).
6234        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6235        c.deps = vec![
6236            Dep::simple("runtime-dep", "^0.1"),
6237            Dep::simple("runtime-dep", "^0.2"),
6238        ];
6239        c.deps_dev = vec![Dep::simple("dev-dep", "*"), Dep::simple("dev-dep", "^0.1")];
6240        let err = c.validate_deps().unwrap_err();
6241        assert!(
6242            matches!(
6243                err,
6244                crate::dep::DepError::DuplicateNome { ref nome, list }
6245                    if nome == "runtime-dep" && list == crate::render::DEP_AUTHOR_KEY_DEPS
6246            ),
6247            "expected :deps duplicate to surface before :deps-dev duplicate, got {err:?}"
6248        );
6249    }
6250
6251    #[test]
6252    fn validate_deps_empty_lists_pass_duplicate_gate() {
6253        // Empty-set identity pin: the bare template (zero deps, zero
6254        // deps_dev) passes the duplicate gate as the gate's identity
6255        // element. A future tighten that conflates "empty" with
6256        // "missing" would regress this baseline.
6257        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6258        c.validate_deps().unwrap();
6259    }
6260
6261    #[test]
6262    fn validate_deps_duplicate_diagnostic_carries_list_tag() {
6263        // Diagnostic-shape pin: the `list:` field tags which list the
6264        // duplicate landed in (`:deps` vs `:deps-dev`) verbatim, so a
6265        // `feira lint` run can route the author to the right block in
6266        // their caixa.lisp without re-deriving the list from context.
6267        // Same self-locating shape every peer per-axis diagnostic
6268        // already exposes.
6269        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6270        c.deps_dev = vec![
6271            Dep::simple("dev-thing", "*"),
6272            Dep::simple("dev-thing", "^0.1"),
6273        ];
6274        let err = c.validate_deps().unwrap_err();
6275        let crate::dep::DepError::DuplicateNome { nome, list } = err else {
6276            panic!("expected DuplicateNome from :deps-dev walk");
6277        };
6278        assert_eq!(nome, "dev-thing");
6279        assert_eq!(list, crate::render::DEP_AUTHOR_KEY_DEPS_DEV);
6280    }
6281
6282    // ── validate_deps: per-entry :caracteristicas set-discipline gate ──
6283
6284    #[test]
6285    fn validate_deps_surfaces_caracteristicas_duplicate_in_deps_list() {
6286        // Thread-through pin on `:deps`: the per-entry
6287        // `Dep::validate_caracteristicas` gate fires inside
6288        // `Caixa::validate_deps`'s linear walk, so a malformed feature
6289        // list on any `:deps` entry surfaces as a `DepError` from
6290        // `validate_deps` — the same reachability shape every per-entry
6291        // `Dep::validate` arm threads through. Without this pin a future
6292        // shortcut that skips the per-entry `Dep::validate` call on the
6293        // cross-entry-uniqueness path would mask the within-entry
6294        // `:caracteristicas` gates.
6295        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6296        c.deps = vec![Dep {
6297            nome: "caixa-teia".into(),
6298            versao: "^0.1".into(),
6299            fonte: None,
6300            opcional: false,
6301            caracteristicas: vec!["http".into(), "http".into()],
6302        }];
6303        let err = c.validate_deps().unwrap_err();
6304        let crate::dep::DepError::CaracteristicaDuplicate {
6305            nome,
6306            caracteristica,
6307        } = err
6308        else {
6309            panic!("expected CaracteristicaDuplicate from :deps walk, got {err:?}");
6310        };
6311        assert_eq!(nome, "caixa-teia");
6312        assert_eq!(caracteristica, "http");
6313    }
6314
6315    #[test]
6316    fn validate_deps_surfaces_caracteristicas_empty_in_deps_dev_list() {
6317        // Peer thread-through pin on `:deps-dev`: same reachability as
6318        // the `:deps` arm above, on the dev-only authoring axis. Pins
6319        // that the `validate_deps` walk visits both lists' per-entry
6320        // gates uniformly. The empty-feature arm carries here so both
6321        // new `:caracteristicas` arms are surfaced via at least one
6322        // `validate_deps` thread-through.
6323        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6324        c.deps_dev = vec![Dep {
6325            nome: "caixa-teia".into(),
6326            versao: "^0.1".into(),
6327            fonte: None,
6328            opcional: false,
6329            caracteristicas: vec![String::new()],
6330        }];
6331        let err = c.validate_deps().unwrap_err();
6332        let crate::dep::DepError::CaracteristicaEmpty { nome } = err else {
6333            panic!("expected CaracteristicaEmpty from :deps-dev walk, got {err:?}");
6334        };
6335        assert_eq!(nome, "caixa-teia");
6336    }
6337
6338    #[test]
6339    fn validate_deps_surfaces_caracteristicas_invalid_in_deps_list() {
6340        // Thread-through pin on `:deps`: the per-entry
6341        // `Dep::validate_caracteristicas` value-shape gate (lifted via
6342        // `crate::render::is_cargo_feature_name`) fires inside
6343        // `Caixa::validate_deps`'s linear walk on the `:deps` list, so
6344        // a structurally invalid feature name on any `:deps` entry
6345        // surfaces as `DepError::CaracteristicaInvalid` from
6346        // `validate_deps` — the same reachability shape every per-entry
6347        // `Dep::validate` arm threads through. Without this pin a
6348        // future shortcut that skips the per-entry `Dep::validate` call
6349        // on the cross-entry-uniqueness path would mask the within-
6350        // entry `:caracteristicas` value-shape gate.
6351        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6352        c.deps = vec![Dep {
6353            nome: "caixa-teia".into(),
6354            versao: "^0.1".into(),
6355            fonte: None,
6356            opcional: false,
6357            caracteristicas: vec!["+http".into()],
6358        }];
6359        let err = c.validate_deps().unwrap_err();
6360        let crate::dep::DepError::CaracteristicaInvalid {
6361            nome,
6362            caracteristica,
6363            ..
6364        } = err
6365        else {
6366            panic!("expected CaracteristicaInvalid from :deps walk, got {err:?}");
6367        };
6368        assert_eq!(nome, "caixa-teia");
6369        assert_eq!(caracteristica, "+http");
6370    }
6371
6372    #[test]
6373    fn validate_deps_surfaces_caracteristicas_invalid_in_deps_dev_list() {
6374        // Peer thread-through pin on `:deps-dev`: same reachability as
6375        // the `:deps` arm above, on the dev-only authoring axis. The
6376        // `http/json` shape carries here so the segment-separator
6377        // diagnostic (the canonical Cargo `dep/feat` namespaced-dep
6378        // confusion footgun) is surfaced via the cross-entry walk too —
6379        // pinning that the `:deps-dev` list visits the same per-entry
6380        // value-shape gate as the `:deps` list.
6381        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6382        c.deps_dev = vec![Dep {
6383            nome: "caixa-teia".into(),
6384            versao: "^0.1".into(),
6385            fonte: None,
6386            opcional: false,
6387            caracteristicas: vec!["http/json".into()],
6388        }];
6389        let err = c.validate_deps().unwrap_err();
6390        let crate::dep::DepError::CaracteristicaInvalid {
6391            nome,
6392            caracteristica,
6393            ..
6394        } = err
6395        else {
6396            panic!("expected CaracteristicaInvalid from :deps-dev walk, got {err:?}");
6397        };
6398        assert_eq!(nome, "caixa-teia");
6399        assert_eq!(caracteristica, "http/json");
6400    }
6401
6402    #[test]
6403    fn to_lisp_preserves_deps() {
6404        let src = r#"
6405(defcaixa
6406  :nome "x"
6407  :versao "0.1.0"
6408  :kind Biblioteca
6409  :deps ((:nome "a" :versao "^0.1")
6410         (:nome "b" :versao "*" :fonte (:tipo git :repo "github:o/b" :tag "v1"))))
6411"#;
6412        let c1 = Caixa::from_lisp(src).unwrap();
6413        let emitted = c1.to_lisp();
6414        let c2 = Caixa::from_lisp(&emitted).expect("round trip");
6415        assert_eq!(c1.deps, c2.deps);
6416    }
6417
6418    // ── Caixa::validate_nome — top-level :nome value-shape gate ─────────
6419
6420    fn caixa_with_nome(nome: &str) -> Caixa {
6421        let mut c = Caixa::from_lisp(&Caixa::template("placeholder")).unwrap();
6422        c.nome = nome.to_string();
6423        c
6424    }
6425
6426    #[test]
6427    fn validate_nome_accepts_canonical_template() {
6428        // Positive control: the bare `feira init`-style template's
6429        // `:nome` ("demo") is a canonical DNS-1123 label; the gate must
6430        // not regress this baseline shape. A future tightening of the
6431        // accepted set surfaces here as a test failure first.
6432        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6433        c.validate_nome().unwrap();
6434    }
6435
6436    #[test]
6437    fn validate_nome_accepts_canonical_forms() {
6438        // Positive-set sweep: each realistic caixa-name shape the K8s
6439        // apiserver accepts as a `metadata.name` label must pass —
6440        // single-word, hyphen-joined, version-suffixed, single-char,
6441        // two-char, digit-start (DNS-1123 allows this; the stricter
6442        // DNS-1035 Service-name rule doesn't), version-suffix-bearing.
6443        // Mirrors `accepts_canonical_membro_caixa_forms` (3f9d7a0) on
6444        // the peer member-name axis.
6445        for nome in [
6446            "checkout",
6447            "cart-v2",
6448            "a",
6449            "db",
6450            "3rd-party-shim",
6451            "payment-retry",
6452            "0",
6453        ] {
6454            caixa_with_nome(nome)
6455                .validate_nome()
6456                .unwrap_or_else(|e| panic!("canonical :nome {nome:?} must validate, got {e:?}"));
6457        }
6458    }
6459
6460    #[test]
6461    fn validate_nome_rejects_empty() {
6462        // Fail-before-pass-after pin: `Caixa::from_lisp` does not refuse
6463        // an empty `:nome` (the derive macro stores the raw String);
6464        // the gate's empty arm names the offending axis with a narrower
6465        // diagnostic than the `NomeInvalid` parse arm would emit.
6466        let c = caixa_with_nome("");
6467        let err = c.validate_nome().unwrap_err();
6468        assert_eq!(err, ManifestError::NomeEmpty);
6469    }
6470
6471    #[test]
6472    fn validate_nome_rejects_uppercase() {
6473        // The canonical "I copied the TitleCase display name verbatim"
6474        // footgun. The K8s apiserver rejects `metadata.name: MyApp` at
6475        // admission on every derived artifact (Helm chart, ComputeUnit,
6476        // CNP, HTTPRoute, label values); the gate moves the diagnostic
6477        // to the source `caixa.lisp` and the reason suggests the
6478        // lowercased fix verbatim.
6479        let c = caixa_with_nome("MyApp");
6480        let err = c.validate_nome().unwrap_err();
6481        let ManifestError::NomeInvalid { nome, reason } = err else {
6482            panic!("expected NomeInvalid for uppercase :nome");
6483        };
6484        assert_eq!(nome, "MyApp");
6485        assert!(
6486            reason.contains("uppercase") && reason.contains("myapp"),
6487            "diagnostic must name the violation + the lowercased fix, got {reason:?}"
6488        );
6489    }
6490
6491    #[test]
6492    fn validate_nome_rejects_underscore() {
6493        // The Python-/Postgres-style `snake_case` leak. DNS-1123 forbids
6494        // `_`; the apiserver rejects on admission across every derived
6495        // artifact. Same fixture pinned for `:membros :caixa` (3f9d7a0)
6496        // and `:children :caixa` (31bfa43).
6497        let c = caixa_with_nome("my_app");
6498        let err = c.validate_nome().unwrap_err();
6499        assert!(
6500            matches!(
6501                err,
6502                ManifestError::NomeInvalid { ref nome, ref reason }
6503                    if nome == "my_app" && reason.contains('_')
6504            ),
6505            "got {err:?}"
6506        );
6507    }
6508
6509    #[test]
6510    fn validate_nome_rejects_dot() {
6511        // A `:nome` is a single DNS-1123 label, not a subdomain. The
6512        // "I want to namespace with `.`" footgun the gate redirects to
6513        // `-` via the shared predicate's reason wording.
6514        let c = caixa_with_nome("team.app");
6515        let err = c.validate_nome().unwrap_err();
6516        assert!(
6517            matches!(
6518                err,
6519                ManifestError::NomeInvalid { ref nome, ref reason }
6520                    if nome == "team.app" && reason.contains('.')
6521            ),
6522            "got {err:?}"
6523        );
6524    }
6525
6526    #[test]
6527    fn validate_nome_rejects_leading_hyphen() {
6528        // DNS-1123 boundary rule: the label must start with an ASCII
6529        // alphanumeric. Pin the leading-`-` arm explicitly.
6530        let c = caixa_with_nome("-app");
6531        let err = c.validate_nome().unwrap_err();
6532        assert!(
6533            matches!(
6534                err,
6535                ManifestError::NomeInvalid { ref nome, .. } if nome == "-app"
6536            ),
6537            "got {err:?}"
6538        );
6539    }
6540
6541    #[test]
6542    fn validate_nome_rejects_trailing_hyphen() {
6543        // Symmetric arm of the boundary rule, pinned separately so a
6544        // future relaxation that only checks the leading position
6545        // surfaces here. Mirrors `rejects_membro_caixa_with_trailing_hyphen`
6546        // and `_with_trailing_hyphen` on the supervisor / aplicacao
6547        // axes.
6548        let c = caixa_with_nome("app-");
6549        let err = c.validate_nome().unwrap_err();
6550        assert!(
6551            matches!(
6552                err,
6553                ManifestError::NomeInvalid { ref nome, .. } if nome == "app-"
6554            ),
6555            "got {err:?}"
6556        );
6557    }
6558
6559    #[test]
6560    fn validate_nome_rejects_unicode() {
6561        // IDN must be pre-encoded as Punycode (`xn--…`); raw Unicode
6562        // bytes are rejected by the K8s apiserver on every name axis.
6563        let c = caixa_with_nome("café");
6564        let err = c.validate_nome().unwrap_err();
6565        assert!(
6566            matches!(
6567                err,
6568                ManifestError::NomeInvalid { ref nome, .. } if nome == "café"
6569            ),
6570            "got {err:?}"
6571        );
6572    }
6573
6574    #[test]
6575    fn validate_nome_rejects_whitespace() {
6576        // The paste-from-sketch / paste-from-spec footgun. Internal
6577        // whitespace is rejected by every K8s name axis.
6578        let c = caixa_with_nome("my app");
6579        let err = c.validate_nome().unwrap_err();
6580        assert!(
6581            matches!(
6582                err,
6583                ManifestError::NomeInvalid { ref nome, .. } if nome == "my app"
6584            ),
6585            "got {err:?}"
6586        );
6587    }
6588
6589    #[test]
6590    fn validate_nome_rejects_too_long() {
6591        // 64-byte boundary pin: the K8s apiserver rejects any
6592        // `metadata.name` over 63 bytes at admission; the diagnostic
6593        // names both the 63-byte cap and the actual length so the
6594        // author can shorten in one edit. Mirrors `_too_long` on the
6595        // peer member-/cluster-/child-name axes.
6596        let over = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN + 1);
6597        let c = caixa_with_nome(&over);
6598        let err = c.validate_nome().unwrap_err();
6599        let ManifestError::NomeInvalid { nome, reason } = err else {
6600            panic!("expected NomeInvalid for over-cap :nome");
6601        };
6602        assert_eq!(nome.len(), crate::DNS_1123_LABEL_MAX_LEN + 1);
6603        assert!(
6604            reason.contains("63") && reason.contains("64"),
6605            "diagnostic must name the cap + actual length, got {reason:?}"
6606        );
6607    }
6608
6609    #[test]
6610    fn nome_max_length_validates() {
6611        // The 63-byte cap exactly — the boundary-accepting case pinned
6612        // alongside `validate_nome_rejects_too_long` so a future cap
6613        // shift surfaces both arms simultaneously. Mirrors
6614        // `membro_caixa_max_length_validates`,
6615        // `placement_cluster_max_length_validates`,
6616        // `child_caixa_max_length_validates`.
6617        let at_cap = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN);
6618        caixa_with_nome(&at_cap).validate_nome().unwrap();
6619    }
6620
6621    #[test]
6622    fn nome_empty_takes_precedence_over_invalid() {
6623        // Order pin: the empty arm fires before the predicate is
6624        // consulted. Empty < invalid in self-locating-ness — the
6625        // narrower `NomeEmpty` diagnostic doesn't carry a useless
6626        // `nome: ""` reference into the parser-shaped reason. Mirrors
6627        // `membro_caixa_empty_takes_precedence_over_invalid` on the
6628        // peer axis (3f9d7a0).
6629        let c = caixa_with_nome("");
6630        assert_eq!(c.validate_nome().unwrap_err(), ManifestError::NomeEmpty);
6631    }
6632
6633    #[test]
6634    fn nome_invalid_diagnostic_carries_offending_nome() {
6635        // Diagnostic-shape pin: the error names the offending `:nome`
6636        // verbatim with a non-empty parser-shaped reason, so a `feira
6637        // lint` run can render the diagnostic without re-parsing.
6638        // Mirrors `membro_caixa_invalid_diagnostic_carries_offending_caixa`.
6639        let c = caixa_with_nome("MyApp");
6640        let err = c.validate_nome().unwrap_err();
6641        let ManifestError::NomeInvalid { nome, reason } = err else {
6642            panic!("expected NomeInvalid variant");
6643        };
6644        assert_eq!(nome, "MyApp");
6645        assert!(
6646            !reason.is_empty(),
6647            "NomeInvalid `reason` must carry the predicate's wording verbatim"
6648        );
6649    }
6650
6651    // ── Caixa::validate_nome_chart_name_budget — joint-length on `:nome` ──
6652    //
6653    // The bare-`:nome` axis [`Caixa::validate_nome`] caps at 63 bytes
6654    // via DNS-1123; this second-axis gate caps the joint
6655    // `lareira-<nome>` chart name at the same 63-byte ceiling. The
6656    // canonical [`crate::lareira_chart_name`] helper's doc comment
6657    // (f7320d7, caixa-core/src/render.rs:3198) explicitly deferred:
6658    // "the M4 admission webhook will pin the joint-length invariant
6659    // when it lands". These tests pin it at the manifest-validate
6660    // layer instead, fail-before-pass-after on the 56-byte boundary.
6661
6662    #[test]
6663    fn validate_nome_chart_name_budget_accepts_canonical_template() {
6664        // Positive control: the bare `feira init`-style template's
6665        // `:nome` ("demo") sits far below the cap; the gate must not
6666        // regress this baseline. Same shape every peer
6667        // value-shape-gate baseline pin uses.
6668        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6669        c.validate_nome_chart_name_budget().unwrap();
6670    }
6671
6672    #[test]
6673    fn validate_nome_chart_name_budget_accepts_canonical_fixtures() {
6674        // Positive-set sweep across the canonical author surface every
6675        // in-tree fixture uses (`hello-rio`, `cart`, `checkout`,
6676        // `worker`, the `checkout-aplicacao` example members, the
6677        // `akeyless-attest` caixa-tatara fixture). Every value sits
6678        // far below the 55-byte per-`:nome` budget. Same shape every
6679        // peer per-axis baseline pin uses.
6680        for nome in [
6681            "hello-rio",
6682            "cart",
6683            "checkout",
6684            "worker",
6685            "akeyless-attest",
6686            "demo",
6687            "a",
6688        ] {
6689            caixa_with_nome(nome)
6690                .validate_nome_chart_name_budget()
6691                .unwrap_or_else(|e| {
6692                    panic!("canonical :nome {nome:?} must pass chart-name budget, got {e:?}")
6693                });
6694        }
6695    }
6696
6697    #[test]
6698    fn validate_nome_chart_name_budget_accepts_nome_at_cap() {
6699        // Boundary-accepting case at the 55-byte per-`:nome` budget —
6700        // the joint chart name is exactly 63 bytes, the DNS-1123 label
6701        // cap. Pinned alongside the rejecting-arm test so a future cap
6702        // shift surfaces both arms simultaneously. Mirrors
6703        // `nome_max_length_validates` on the peer bare-`:nome` axis.
6704        let at_cap = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN);
6705        caixa_with_nome(&at_cap)
6706            .validate_nome_chart_name_budget()
6707            .unwrap();
6708    }
6709
6710    #[test]
6711    fn validate_nome_chart_name_budget_rejects_nome_one_over_cap() {
6712        // Fail-before-pass-after pin on the 56-byte boundary: the
6713        // smallest `:nome` length that overflows the joint chart-name
6714        // cap. The inner [`is_dns_1123_label`] gate
6715        // (`Caixa::validate_nome`) accepts it (56 ≤ 63), so prior to
6716        // this gate it silently passed the manifest-validate cascade
6717        // and surfaced as a `helm lint` / apiserver rejection on the
6718        // rendered chart name far from the source `caixa.lisp`, with
6719        // no field naming the overflow. With this gate the diagnostic
6720        // names the offending `:nome` verbatim alongside the rendered
6721        // chart name and the budget, so the author can shorten in one
6722        // edit. Mirrors `validate_nome_rejects_too_long` on the peer
6723        // bare-`:nome` axis.
6724        let over = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
6725        let c = caixa_with_nome(&over);
6726        let err = c.validate_nome_chart_name_budget().unwrap_err();
6727        let ManifestError::NomeChartNameBudgetExceeded { nome, reason } = err else {
6728            panic!("expected NomeChartNameBudgetExceeded for over-budget :nome");
6729        };
6730        assert_eq!(nome.len(), crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
6731        assert_eq!(nome, over);
6732        assert!(
6733            reason.contains("63") && reason.contains("64") && reason.contains("55"),
6734            "diagnostic must name the DNS-1123 cap (63), the actual chart-name length (64), \
6735             and the per-`:nome` budget (55), got {reason:?}"
6736        );
6737    }
6738
6739    #[test]
6740    fn validate_nome_chart_name_budget_rejects_nome_at_bare_dns_cap() {
6741        // The 63-byte `:nome` boundary — passes the bare-`:nome`
6742        // [`is_dns_1123_label`] cap exactly, but produces a 71-byte
6743        // joint chart name that overflows the DNS-1123 label cap
6744        // structurally. The most stringent fail-before-pass-after
6745        // surface: every `:nome` in the 56..=63-byte range passed the
6746        // prior cascade and broke at admission.
6747        let bare_max = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN);
6748        let c = caixa_with_nome(&bare_max);
6749        // The bare-`:nome` gate accepts the 63-byte length.
6750        c.validate_nome().unwrap();
6751        // The new joint-length gate rejects it.
6752        let err = c.validate_nome_chart_name_budget().unwrap_err();
6753        assert!(
6754            matches!(
6755                err,
6756                ManifestError::NomeChartNameBudgetExceeded { ref nome, .. }
6757                    if nome.len() == crate::DNS_1123_LABEL_MAX_LEN
6758            ),
6759            "got {err:?}"
6760        );
6761    }
6762
6763    #[test]
6764    fn validate_nome_chart_name_budget_diagnostic_carries_offending_chart_name() {
6765        // Diagnostic-shape pin: the rendered `lareira-<nome>` chart
6766        // name appears verbatim in the diagnostic so the author sees
6767        // exactly the string the apiserver / `helm lint` would have
6768        // rejected — no re-derivation required to grep the source.
6769        // Peer with `nome_invalid_diagnostic_carries_offending_nome`
6770        // on the bare-`:nome` axis.
6771        let over = "x".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 5);
6772        let c = caixa_with_nome(&over);
6773        let err = c.validate_nome_chart_name_budget().unwrap_err();
6774        let ManifestError::NomeChartNameBudgetExceeded { nome, reason } = err else {
6775            panic!("expected NomeChartNameBudgetExceeded variant");
6776        };
6777        assert_eq!(nome, over);
6778        let expected_chart = crate::lareira_chart_name(&over);
6779        assert!(
6780            reason.contains(&expected_chart),
6781            "diagnostic must carry the rendered chart name {expected_chart:?} verbatim, \
6782             got {reason:?}"
6783        );
6784        assert!(
6785            reason.contains("lareira-"),
6786            "diagnostic must name the canonical chart-name prefix verbatim, got {reason:?}"
6787        );
6788    }
6789
6790    #[test]
6791    fn validate_nome_chart_name_budget_runs_after_nome_shape_via_layout_verify() {
6792        // Order pin on the layout cascade: the narrower
6793        // `NomeInvalid` (bare-DNS-1123 shape) fires before the
6794        // joint-length budget. A structurally-malformed `:nome` (here:
6795        // uppercase) surfaces its specific shape error rather than
6796        // the chart-name-budget error, even when the joint length
6797        // would also overflow — the narrower diagnostic is more
6798        // self-locating. Mirrors the cascade-precedence pins peer
6799        // gates already use (e.g. `EntradaParaEmpty` before
6800        // `EntradaParaInvalid`).
6801        let over = "A".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
6802        let c = caixa_with_nome(&over);
6803        // The bare-shape gate fires first.
6804        let err = c.validate_nome().unwrap_err();
6805        assert!(
6806            matches!(err, ManifestError::NomeInvalid { .. }),
6807            "bare-shape gate must fire before chart-name-budget gate; got {err:?}"
6808        );
6809        // And the layout verify cascade surfaces that diagnostic, not
6810        // the budget arm. Inject a path-exists oracle so the cascade
6811        // gets past the manifest-presence check and into the
6812        // value-shape gates.
6813        let layout = crate::StandardLayout::new().with_path_exists(|_| true);
6814        let err = crate::LayoutInvariants::verify(
6815            &layout,
6816            &c,
6817            std::path::Path::new("/tmp/caixa-test-fake-root"),
6818        )
6819        .unwrap_err();
6820        let issue = err.to_string();
6821        assert!(
6822            issue.contains("DNS-1123") || issue.contains("uppercase"),
6823            "layout cascade must surface the bare-DNS-1123 diagnostic on a \
6824             structurally-malformed :nome, not the chart-name-budget diagnostic; got {issue:?}"
6825        );
6826    }
6827
6828    #[test]
6829    fn layout_verify_routes_chart_name_budget_through_nome_violation() {
6830        // Cross-axis envelope pin: the layout cascade wraps both
6831        // bare-`:nome` and joint-length-`:nome` failures through the
6832        // same [`LayoutError::NomeViolation`] envelope, since both
6833        // arms are on the `:nome` axis. The user's diagnostic stays
6834        // self-locating ("which axis"), and a future consumer that
6835        // dispatches on the layout-error variant (e.g. a `feira lint`
6836        // exit-code mapping) sees a single per-axis envelope. The
6837        // wrapped `issue:` carries the full inner diagnostic.
6838        let over = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
6839        let c = caixa_with_nome(&over);
6840        // The bare-shape gate accepts.
6841        c.validate_nome().unwrap();
6842        let layout = crate::StandardLayout::new().with_path_exists(|_| true);
6843        let err = crate::LayoutInvariants::verify(
6844            &layout,
6845            &c,
6846            std::path::Path::new("/tmp/caixa-test-fake-root"),
6847        )
6848        .unwrap_err();
6849        let crate::LayoutError::NomeViolation { caixa, issue } = err else {
6850            panic!("expected LayoutError::NomeViolation, got {err:?}");
6851        };
6852        assert_eq!(caixa, over);
6853        assert!(
6854            issue.contains("lareira-") && issue.contains("63") && issue.contains("55"),
6855            "wrapped issue must carry the joint-length diagnostic verbatim, got {issue:?}"
6856        );
6857    }
6858
6859    // ── Caixa::validate_versao — top-level :versao value-shape gate ─────
6860
6861    fn caixa_with_versao(versao: &str) -> Caixa {
6862        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6863        c.versao = versao.to_string();
6864        c
6865    }
6866
6867    #[test]
6868    fn validate_versao_accepts_canonical_template() {
6869        // Positive control: the bare `feira init`-style template's
6870        // `:versao` ("0.1.0") is a canonical SemVer-2 literal; the gate
6871        // must not regress this baseline shape. A future tightening of
6872        // the accepted set surfaces here as a test failure first.
6873        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6874        c.validate_versao().unwrap();
6875    }
6876
6877    #[test]
6878    fn validate_versao_accepts_canonical_forms() {
6879        // Positive-set sweep: each realistic SemVer-2 shape the
6880        // substrate's downstream consumers accept must pass — bare
6881        // MAJOR.MINOR.PATCH, pre-release tags (`-rc.1`, `-alpha.0`),
6882        // build metadata (`+build.42`), the combined form, and the
6883        // `0.0.0` boundary case. Mirrors `accepts_canonical_forms` on
6884        // the peer `:nome` axis (6c992f8).
6885        for versao in [
6886            "0.1.0",
6887            "0.0.0",
6888            "1.0.0",
6889            "0.2.0-rc.1",
6890            "1.0.0-alpha.0",
6891            "1.0.0+build.42",
6892            "1.0.0-rc.1+build.42",
6893            "10.20.30",
6894        ] {
6895            caixa_with_versao(versao)
6896                .validate_versao()
6897                .unwrap_or_else(|e| {
6898                    panic!("canonical :versao {versao:?} must validate, got {e:?}")
6899                });
6900        }
6901    }
6902
6903    #[test]
6904    fn validate_versao_rejects_empty() {
6905        // Fail-before-pass-after pin: `Caixa::from_lisp` does not refuse
6906        // an empty `:versao` (the derive macro stores the raw String);
6907        // the gate's empty arm names the offending axis with a narrower
6908        // diagnostic than the `VersaoInvalid` parse arm would emit.
6909        // Mirrors `validate_nome_rejects_empty` (6c992f8).
6910        let c = caixa_with_versao("");
6911        let err = c.validate_versao().unwrap_err();
6912        assert_eq!(err, ManifestError::VersaoEmpty);
6913    }
6914
6915    #[test]
6916    fn validate_versao_rejects_git_tag_shape() {
6917        // The canonical "I copied the git tag verbatim" footgun —
6918        // `feira publish` *emits* `v<versao>` git tags, so a leaked
6919        // `v0.1.0` in `:versao` would render as `vv0.1.0` and silently
6920        // shift every downstream consumer's version axis. `semver`
6921        // rejects the leading `v` at parse time; the gate moves the
6922        // diagnostic to the source `caixa.lisp`.
6923        let c = caixa_with_versao("v0.1.0");
6924        let err = c.validate_versao().unwrap_err();
6925        let ManifestError::VersaoInvalid { versao, reason } = err else {
6926            panic!("expected VersaoInvalid for git-tag-shape :versao");
6927        };
6928        assert_eq!(versao, "v0.1.0");
6929        assert!(
6930            !reason.is_empty(),
6931            "VersaoInvalid `reason` must carry the parser's wording, got {reason:?}"
6932        );
6933    }
6934
6935    #[test]
6936    fn validate_versao_rejects_missing_patch() {
6937        // The canonical "I shortened it" footgun — SemVer-2 requires
6938        // three parts. Cargo's `version =` field accepts the shortened
6939        // form as a requirement, conflating the two leaks across the
6940        // typed `:deps :versao` vs top-level `:versao` axes; the gate
6941        // pins the top-level axis to the strict three-part shape.
6942        let c = caixa_with_versao("0.1");
6943        let err = c.validate_versao().unwrap_err();
6944        assert!(
6945            matches!(
6946                err,
6947                ManifestError::VersaoInvalid { ref versao, .. } if versao == "0.1"
6948            ),
6949            "got {err:?}"
6950        );
6951    }
6952
6953    #[test]
6954    fn validate_versao_rejects_requirement_shape() {
6955        // The canonical "I leaked a requirement into a version" footgun —
6956        // the typed `:deps :versao` / `:membros :versao` axes accept
6957        // `^0.1` (a `VersionReq`); the top-level `:versao` requires a
6958        // concrete `Version`. Without this gate the two typed surfaces
6959        // would silently overlap, and a top-level `^0.1` would surface
6960        // at `helm install` time as a Chart.yaml version rejection far
6961        // from the source `caixa.lisp`.
6962        let c = caixa_with_versao("^0.1");
6963        let err = c.validate_versao().unwrap_err();
6964        assert!(
6965            matches!(
6966                err,
6967                ManifestError::VersaoInvalid { ref versao, .. } if versao == "^0.1"
6968            ),
6969            "got {err:?}"
6970        );
6971    }
6972
6973    #[test]
6974    fn validate_versao_rejects_docker_tag_shape() {
6975        // The "I confused it with a docker tag" footgun — `latest`,
6976        // `main`, `stable` parse as identifiers, not SemVer-2 versions.
6977        // SemVer rejects at parse time; the gate moves the diagnostic
6978        // to the source `caixa.lisp`.
6979        for bad in ["latest", "main", "stable"] {
6980            let c = caixa_with_versao(bad);
6981            let err = c.validate_versao().unwrap_err();
6982            assert!(
6983                matches!(
6984                    err,
6985                    ManifestError::VersaoInvalid { ref versao, .. } if versao == bad
6986                ),
6987                "got {err:?} for {bad:?}"
6988            );
6989        }
6990    }
6991
6992    #[test]
6993    fn validate_versao_rejects_four_part_form() {
6994        // The Java/Microsoft "MAJOR.MINOR.PATCH.BUILD" convention
6995        // SemVer-2 forbids. A leak from a non-SemVer ecosystem; the
6996        // semver crate rejects the extra `.0` at parse time.
6997        let c = caixa_with_versao("0.1.0.0");
6998        let err = c.validate_versao().unwrap_err();
6999        assert!(
7000            matches!(
7001                err,
7002                ManifestError::VersaoInvalid { ref versao, .. } if versao == "0.1.0.0"
7003            ),
7004            "got {err:?}"
7005        );
7006    }
7007
7008    #[test]
7009    fn versao_empty_takes_precedence_over_invalid() {
7010        // Order pin: the empty arm fires before the parser is consulted.
7011        // Empty < invalid in self-locating-ness — the narrower
7012        // `VersaoEmpty` diagnostic doesn't carry a useless `versao: ""`
7013        // reference into the parser-shaped reason. Mirrors
7014        // `nome_empty_takes_precedence_over_invalid` (6c992f8) on the
7015        // peer axis.
7016        let c = caixa_with_versao("");
7017        assert_eq!(c.validate_versao().unwrap_err(), ManifestError::VersaoEmpty);
7018    }
7019
7020    #[test]
7021    fn versao_invalid_diagnostic_carries_offending_versao() {
7022        // Diagnostic-shape pin: the error names the offending `:versao`
7023        // verbatim with a non-empty parser-shaped reason, so a `feira
7024        // lint` run can render the diagnostic without re-parsing.
7025        // Mirrors `nome_invalid_diagnostic_carries_offending_nome`.
7026        let c = caixa_with_versao("v0.1.0");
7027        let err = c.validate_versao().unwrap_err();
7028        let ManifestError::VersaoInvalid { versao, reason } = err else {
7029            panic!("expected VersaoInvalid variant");
7030        };
7031        assert_eq!(versao, "v0.1.0");
7032        assert!(
7033            !reason.is_empty(),
7034            "VersaoInvalid `reason` must carry the parser's wording verbatim"
7035        );
7036    }
7037
7038    #[test]
7039    fn validate_versao_accepts_what_upgrade_from_from_accepts() {
7040        // Parity pin: every shape `UpgradeFromEntry::validate` accepts
7041        // for `:upgrade-from :from` must also pass `validate_versao` —
7042        // the two `:versao`-typed surfaces (top-level `:versao`,
7043        // `:upgrade-from :from`) consume the *same* `semver::Version`
7044        // parser, so they must agree on the accepted set. Without this
7045        // pin, a future tightening of one axis could silently diverge
7046        // from the other. Mirrors the `:versao` requirement-axis
7047        // parity (`:deps`/`:deps-dev`/`:membros`/`:children`) the prior
7048        // commits established.
7049        for versao in ["0.1.0", "0.2.0-rc.1", "1.0.0+build.42"] {
7050            // From the canonical UpgradeFromEntry round-trip fixture
7051            // (`upgrade::tests::round_trip_load_module` peers).
7052            let entry = crate::UpgradeFromEntry {
7053                from: versao.to_string(),
7054                instructions: Vec::new(),
7055            };
7056            entry
7057                .validate()
7058                .unwrap_or_else(|e| panic!(":from {versao:?} must validate, got {e:?}"));
7059            caixa_with_versao(versao)
7060                .validate_versao()
7061                .unwrap_or_else(|e| {
7062                    panic!(":versao {versao:?} must validate, got {e:?} — peer axis diverges")
7063                });
7064        }
7065    }
7066
7067    // ── Caixa::validate_restart_window — supervisor restart-window
7068    //    folds through the shared `supervisor::duration_codec` ────────
7069
7070    fn caixa_with_restart_window(window: Option<&str>) -> Caixa {
7071        let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
7072        c.kind = CaixaKind::Supervisor;
7073        c.restart_window = window.map(str::to_string);
7074        c
7075    }
7076
7077    #[test]
7078    fn validate_restart_window_accepts_none() {
7079        // The canonical "omit the slot to express no reset" shape — a
7080        // `None` raw string is the absence of the typed
7081        // `:restart-window` slot, which is exactly the SupervisorSpec
7082        // "never reset" semantics. The gate must be a no-op here; a
7083        // future tightening that rejected `None` would force every
7084        // supervisor caixa to authoring-time pin a window even when
7085        // the OTP semantics call for none.
7086        caixa_with_restart_window(None)
7087            .validate_restart_window()
7088            .unwrap();
7089    }
7090
7091    #[test]
7092    fn validate_restart_window_accepts_canonical_forms() {
7093        // Positive-set sweep across the canonical authoring units the
7094        // shared `supervisor::duration_codec::parse` accepts —
7095        // matches the codec-side `parse_accepts_integer_canonical_units`
7096        // pin in supervisor::tests so a future codec-side tightening
7097        // surfaces simultaneously on both axes.
7098        for window in ["60s", "5m", "1h", "500ms", "30", "0s"] {
7099            caixa_with_restart_window(Some(window))
7100                .validate_restart_window()
7101                .unwrap_or_else(|e| {
7102                    panic!("canonical :restart-window {window:?} must validate, got {e:?}")
7103                });
7104        }
7105    }
7106
7107    #[test]
7108    fn validate_restart_window_rejects_fractional_seconds() {
7109        // Fail-before-pass-after pin: the `"1.5s"` drift class (parses
7110        // as f64 to 1.5 → renders back as `"1500ms"` on first
7111        // serialize). Prior to the fold + this gate, the inline
7112        // `parse_window_inline` accepted f64 magnitudes and silently
7113        // produced a `Duration::from_secs_f64(1.5)`, divergent from
7114        // the shared codec's integer-magnitude discipline on the
7115        // serde-routed siblings. The gate now surfaces a self-locating
7116        // diagnostic at the manifest layer.
7117        let err = caixa_with_restart_window(Some("1.5s"))
7118            .validate_restart_window()
7119            .unwrap_err();
7120        let ManifestError::RestartWindowMalformed {
7121            restart_window,
7122            reason,
7123        } = err
7124        else {
7125            panic!("expected RestartWindowMalformed for fractional seconds");
7126        };
7127        assert_eq!(restart_window, "1.5s");
7128        assert!(
7129            reason.contains("\"1.5\"") && reason.contains("not a non-negative integer"),
7130            "diagnostic must carry shared-codec wording, got {reason:?}"
7131        );
7132    }
7133
7134    #[test]
7135    fn validate_restart_window_rejects_decimal_shaped_integer() {
7136        // The `"1.0s"` class — numerically `1s` exactly, but the
7137        // canonical form is `"1s"` not `"1.0s"`. Decimal-shape leak
7138        // gets the same canonical-form diagnostic.
7139        let err = caixa_with_restart_window(Some("1.0s"))
7140            .validate_restart_window()
7141            .unwrap_err();
7142        assert!(
7143            matches!(
7144                err,
7145                ManifestError::RestartWindowMalformed { ref restart_window, .. }
7146                    if restart_window == "1.0s"
7147            ),
7148            "got {err:?}"
7149        );
7150    }
7151
7152    #[test]
7153    fn validate_restart_window_rejects_half_unit_minute() {
7154        // `"0.5m"` is the unit-fraction footgun — author writes a
7155        // human-readable half-minute, the prior inline parser silently
7156        // produced `Duration::from_secs_f64(30.0)` and serde
7157        // re-emitted as `"30s"`, rewriting author intent. The gate
7158        // closes the loop at the manifest layer.
7159        let err = caixa_with_restart_window(Some("0.5m"))
7160            .validate_restart_window()
7161            .unwrap_err();
7162        let ManifestError::RestartWindowMalformed {
7163            restart_window,
7164            reason,
7165        } = err
7166        else {
7167            panic!("expected RestartWindowMalformed");
7168        };
7169        assert_eq!(restart_window, "0.5m");
7170        assert!(
7171            reason.contains("\"30s\""),
7172            "diagnostic must point at the canonical-form remediation, got {reason:?}"
7173        );
7174    }
7175
7176    #[test]
7177    fn validate_restart_window_rejects_leading_sign() {
7178        // `"+30s"` and `"-30s"` both round-tripped through f64 cleanly
7179        // on the prior parser (`+30` parses as `30.0`; `-30` parsed
7180        // and was caught by the `num < 0.0` arm which silently
7181        // returned `None`, dropping the author-supplied window). The
7182        // shared codec's digit-only gate rejects both with a unified
7183        // canonical-form diagnostic; the manifest-layer wrapper names
7184        // the offending value.
7185        for bad in ["+30s", "-30s"] {
7186            let err = caixa_with_restart_window(Some(bad))
7187                .validate_restart_window()
7188                .unwrap_err();
7189            assert!(
7190                matches!(
7191                    err,
7192                    ManifestError::RestartWindowMalformed { ref restart_window, .. }
7193                        if restart_window == bad
7194                ),
7195                "got {err:?} for {bad:?}"
7196            );
7197        }
7198    }
7199
7200    #[test]
7201    fn validate_restart_window_rejects_unknown_unit() {
7202        // `"30x"` — the typo / wrong-unit footgun. The shared codec's
7203        // unit dispatch surfaces an `unknown duration unit` reason;
7204        // the manifest-layer wrapper names the offending value.
7205        let err = caixa_with_restart_window(Some("30x"))
7206            .validate_restart_window()
7207            .unwrap_err();
7208        let ManifestError::RestartWindowMalformed {
7209            restart_window,
7210            reason,
7211        } = err
7212        else {
7213            panic!("expected RestartWindowMalformed for unknown unit");
7214        };
7215        assert_eq!(restart_window, "30x");
7216        assert!(
7217            reason.contains("unknown duration unit"),
7218            "diagnostic must carry shared-codec unit-rejection wording, got {reason:?}"
7219        );
7220    }
7221
7222    #[test]
7223    fn validate_restart_window_rejects_garbage() {
7224        // Pure non-numeric magnitude (`"abc"`) falls through to the
7225        // shared codec's narrower `"bad duration magnitude"` arm. Same
7226        // diagnostic shape as the codec-side
7227        // `parse_garbage_still_falls_through_to_bad_magnitude` pin.
7228        let err = caixa_with_restart_window(Some("abc"))
7229            .validate_restart_window()
7230            .unwrap_err();
7231        let ManifestError::RestartWindowMalformed {
7232            restart_window,
7233            reason,
7234        } = err
7235        else {
7236            panic!("expected RestartWindowMalformed for garbage");
7237        };
7238        assert_eq!(restart_window, "abc");
7239        assert!(
7240            reason.contains("bad duration magnitude"),
7241            "diagnostic must carry shared-codec garbage-rejection wording, got {reason:?}"
7242        );
7243    }
7244
7245    #[test]
7246    fn validate_restart_window_rejects_empty_string() {
7247        // The empty-after-trim edge case — distinct from the `None`
7248        // canonical "omit the slot" shape. The shared codec's
7249        // digit-only gate refuses an empty magnitude; the manifest
7250        // layer names the offending `""` so the author can grep for
7251        // the literal empty value in their `caixa.lisp` and either
7252        // remove the slot (the canonical "no reset" shape) or pin a
7253        // positive duration.
7254        let err = caixa_with_restart_window(Some(""))
7255            .validate_restart_window()
7256            .unwrap_err();
7257        assert!(
7258            matches!(
7259                err,
7260                ManifestError::RestartWindowMalformed { ref restart_window, .. }
7261                    if restart_window.is_empty()
7262            ),
7263            "got {err:?}"
7264        );
7265    }
7266
7267    #[test]
7268    fn validate_restart_window_diagnostic_carries_offending_value() {
7269        // Diagnostic-shape pin (peer with
7270        // `nome_invalid_diagnostic_carries_offending_nome` /
7271        // `versao_invalid_diagnostic_carries_offending_versao`): the
7272        // error names the offending raw `:restart-window` verbatim
7273        // with a non-empty shared-codec-shaped reason, so a `feira
7274        // lint` run can render the diagnostic without re-parsing.
7275        let err = caixa_with_restart_window(Some("1.5s"))
7276            .validate_restart_window()
7277            .unwrap_err();
7278        let ManifestError::RestartWindowMalformed {
7279            restart_window,
7280            reason,
7281        } = err
7282        else {
7283            panic!("expected RestartWindowMalformed variant");
7284        };
7285        assert_eq!(restart_window, "1.5s");
7286        assert!(
7287            !reason.is_empty(),
7288            "RestartWindowMalformed `reason` must carry the codec's wording verbatim"
7289        );
7290    }
7291
7292    #[test]
7293    fn supervisor_view_folds_through_shared_codec_on_canonical_form() {
7294        // Behavioral parity pin after the fold (`parse_window_inline`
7295        // deletion): the canonical `"60s"` still produces
7296        // `Duration::from_secs(60)` on the typed view — the fold is
7297        // semantically equivalent to the prior inline parser on the
7298        // accepted set. Mirrors the pre-fold `supervisor_view_returns_typed_shape`
7299        // pin, narrowed to the parser-side contract.
7300        let c = caixa_with_restart_window(Some("60s"));
7301        let view = c.supervisor_view().expect("Supervisor kind has a view");
7302        assert_eq!(
7303            view.restart_window,
7304            Some(std::time::Duration::from_secs(60))
7305        );
7306    }
7307
7308    #[test]
7309    fn supervisor_view_soft_swallows_what_validate_rejects() {
7310        // Parity pin between the view-construction path and the
7311        // manifest-level validator: the same `"1.5s"` that surfaces
7312        // `RestartWindowMalformed` at `validate_restart_window` time
7313        // becomes `restart_window: None` on the typed view (the fold
7314        // preserves the existing best-effort shape of `supervisor_view`).
7315        // The contract is: a layout-verifier / `feira lint` flow that
7316        // cares about the malformed-window axis MUST consult
7317        // `validate_restart_window` — relying solely on the view's
7318        // `None` swallows the diagnostic silently. This pin makes the
7319        // expectation a typed invariant.
7320        let c = caixa_with_restart_window(Some("1.5s"));
7321        let view = c.supervisor_view().expect("Supervisor kind has a view");
7322        assert_eq!(
7323            view.restart_window, None,
7324            "view-construction path soft-swallows the parse error to None"
7325        );
7326        // And the manifest-level validator does NOT soft-swallow:
7327        assert!(
7328            matches!(
7329                c.validate_restart_window().unwrap_err(),
7330                ManifestError::RestartWindowMalformed { ref restart_window, .. }
7331                    if restart_window == "1.5s"
7332            ),
7333            "validator must surface the offending value",
7334        );
7335    }
7336
7337    // ── validate_code_paths — per-entry shape on :bibliotecas / :exe / :servicos ──
7338
7339    fn caixa_with_code_paths(bibliotecas: Vec<&str>, exe: Vec<&str>, servicos: Vec<&str>) -> Caixa {
7340        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7341        c.bibliotecas = bibliotecas.into_iter().map(String::from).collect();
7342        c.exe = exe.into_iter().map(String::from).collect();
7343        c.servicos = servicos.into_iter().map(String::from).collect();
7344        c
7345    }
7346
7347    #[test]
7348    fn validate_code_paths_accepts_canonical_template() {
7349        // The bare `Caixa::template` shape is the gate's identity element
7350        // on the canonical authoring shape — `:bibliotecas
7351        // ("lib/demo.lisp")` + empty `:exe` + empty `:servicos`. Pins
7352        // that the gate is non-disruptive against every existing caixa.
7353        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7354        c.validate_code_paths().unwrap();
7355    }
7356
7357    #[test]
7358    fn validate_code_paths_accepts_explicit_relative_paths_on_every_slot() {
7359        // Positive control sweep: a canonical-shaped path on every slot
7360        // passes. Mirrors the peer
7361        // `behavior::validate_every_slot_relative_is_ok` pin.
7362        let c = caixa_with_code_paths(
7363            vec!["lib/demo.lisp", "lib/helpers.lisp"],
7364            vec!["exe/demo", "exe/tool"],
7365            vec!["servicos/demo.computeunit.yaml"],
7366        );
7367        c.validate_code_paths().unwrap();
7368    }
7369
7370    #[test]
7371    fn validate_code_paths_accepts_all_empty_lists() {
7372        // The empty-list identity element: every Caixa with no declared
7373        // code paths trivially passes (Supervisor / Aplicacao kinds rely
7374        // on this — the OwnCode gate already rejected them before the
7375        // path-shape gate runs in the layout, but the validator itself
7376        // must accept the empty shape).
7377        let c = caixa_with_code_paths(vec![], vec![], vec![]);
7378        c.validate_code_paths().unwrap();
7379    }
7380
7381    #[test]
7382    fn validate_code_paths_rejects_empty_bibliotecas_entry() {
7383        let c = caixa_with_code_paths(vec![""], vec![], vec![]);
7384        let err = c.validate_code_paths().unwrap_err();
7385        assert!(
7386            matches!(
7387                err,
7388                ManifestError::CodePathEmpty {
7389                    slot: ":bibliotecas"
7390                }
7391            ),
7392            "got {err:?}",
7393        );
7394    }
7395
7396    #[test]
7397    fn validate_code_paths_rejects_empty_exe_entry() {
7398        let c = caixa_with_code_paths(vec![], vec![""], vec![]);
7399        let err = c.validate_code_paths().unwrap_err();
7400        assert!(
7401            matches!(err, ManifestError::CodePathEmpty { slot: ":exe" }),
7402            "got {err:?}",
7403        );
7404    }
7405
7406    #[test]
7407    fn validate_code_paths_rejects_empty_servicos_entry() {
7408        let c = caixa_with_code_paths(vec![], vec![], vec![""]);
7409        let err = c.validate_code_paths().unwrap_err();
7410        assert!(
7411            matches!(err, ManifestError::CodePathEmpty { slot: ":servicos" }),
7412            "got {err:?}",
7413        );
7414    }
7415
7416    #[test]
7417    fn validate_code_paths_rejects_absolute_bibliotecas_entry() {
7418        // `:bibliotecas` has no `starts_with(<dir>)` fence downstream,
7419        // so an absolute path that resolves on disk silently passes the
7420        // layout's existence check — the canonical sandbox-escape on
7421        // the biblioteca axis.
7422        let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
7423        let err = c.validate_code_paths().unwrap_err();
7424        let ManifestError::CodePathAbsolute { slot, path } = err else {
7425            panic!("expected CodePathAbsolute, got {err:?}");
7426        };
7427        assert_eq!(slot, ":bibliotecas");
7428        assert_eq!(path, PathBuf::from("/etc/passwd"));
7429    }
7430
7431    #[test]
7432    fn validate_code_paths_rejects_absolute_exe_entry() {
7433        let c = caixa_with_code_paths(vec![], vec!["/usr/bin/env"], vec![]);
7434        let err = c.validate_code_paths().unwrap_err();
7435        let ManifestError::CodePathAbsolute { slot, path } = err else {
7436            panic!("expected CodePathAbsolute, got {err:?}");
7437        };
7438        assert_eq!(slot, ":exe");
7439        assert_eq!(path, PathBuf::from("/usr/bin/env"));
7440    }
7441
7442    #[test]
7443    fn validate_code_paths_rejects_absolute_servicos_entry() {
7444        let c = caixa_with_code_paths(vec![], vec![], vec!["/var/servicos/x.yaml"]);
7445        let err = c.validate_code_paths().unwrap_err();
7446        let ManifestError::CodePathAbsolute { slot, path } = err else {
7447            panic!("expected CodePathAbsolute, got {err:?}");
7448        };
7449        assert_eq!(slot, ":servicos");
7450        assert_eq!(path, PathBuf::from("/var/servicos/x.yaml"));
7451    }
7452
7453    #[test]
7454    fn validate_code_paths_rejects_parent_escape_bibliotecas_leading() {
7455        // Canonical "I want a lib from a sibling caixa" footgun on the
7456        // biblioteca axis. `:bibliotecas` has no `starts_with` fence
7457        // downstream, so a leading `..` traverses to the parent of the
7458        // caixa root with no diagnostic at layout time if the resolved
7459        // target exists.
7460        let c = caixa_with_code_paths(vec!["../sibling/x.lisp"], vec![], vec![]);
7461        let err = c.validate_code_paths().unwrap_err();
7462        let ManifestError::CodePathParentEscape { slot, path } = err else {
7463            panic!("expected CodePathParentEscape, got {err:?}");
7464        };
7465        assert_eq!(slot, ":bibliotecas");
7466        assert_eq!(path, PathBuf::from("../sibling/x.lisp"));
7467    }
7468
7469    #[test]
7470    fn validate_code_paths_rejects_parent_escape_exe_mid_path() {
7471        // Mid-path `..` defeats the layout's component-aware
7472        // `starts_with(exe_dir)` fence — `root.join("exe/../../escape")`
7473        // `starts_with(<root>/exe)` is true, but the canonical resolution
7474        // lives outside the caixa root. Caught regardless of where the
7475        // `..` sits — mirrors the peer
7476        // `behavior::validate_rejects_parent_escape_mid_path` pin.
7477        let c = caixa_with_code_paths(vec![], vec!["exe/../../escape"], vec![]);
7478        let err = c.validate_code_paths().unwrap_err();
7479        let ManifestError::CodePathParentEscape { slot, path } = err else {
7480            panic!("expected CodePathParentEscape, got {err:?}");
7481        };
7482        assert_eq!(slot, ":exe");
7483        assert_eq!(path, PathBuf::from("exe/../../escape"));
7484    }
7485
7486    #[test]
7487    fn validate_code_paths_rejects_parent_escape_servicos_trailing() {
7488        let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/foo/../../escape.yaml"]);
7489        let err = c.validate_code_paths().unwrap_err();
7490        let ManifestError::CodePathParentEscape { slot, path } = err else {
7491            panic!("expected CodePathParentEscape, got {err:?}");
7492        };
7493        assert_eq!(slot, ":servicos");
7494        assert_eq!(path, PathBuf::from("servicos/foo/../../escape.yaml"));
7495    }
7496
7497    #[test]
7498    fn validate_code_paths_cross_slot_precedence_bibliotecas_before_exe_before_servicos() {
7499        // Cross-slot precedence pin: `:bibliotecas` → `:exe` →
7500        // `:servicos`. A manifest with malformed entries on all three
7501        // surfaces surfaces the `:bibliotecas` defect first, mirroring
7502        // the canonical declaration order
7503        // `Caixa::declared_foreign_code_slots` already establishes for
7504        // the foreign-code-slot diagnostic.
7505        let c = caixa_with_code_paths(vec![""], vec![""], vec![""]);
7506        let err = c.validate_code_paths().unwrap_err();
7507        assert!(
7508            matches!(
7509                err,
7510                ManifestError::CodePathEmpty {
7511                    slot: ":bibliotecas"
7512                }
7513            ),
7514            "got {err:?}",
7515        );
7516    }
7517
7518    #[test]
7519    fn validate_code_paths_within_slot_precedence_empty_before_absolute_before_parent_escape() {
7520        // Within-slot precedence pin: empty → absolute → parent-escape,
7521        // matching the [`PathShapeViolation`] arm-ordering every peer
7522        // `is_sandboxed_relative_path` caller follows (b0c8389
7523        // BehaviorSpec, 26da2c7 UpgradeInstruction::StateChange). A
7524        // `:bibliotecas` list whose first entry is empty *and* whose
7525        // later entries are absolute/parent-escape surfaces the empty
7526        // arm first, on the lexicographically-earliest offending entry.
7527        let c = caixa_with_code_paths(vec!["", "/etc/passwd", "../escape.lisp"], vec![], vec![]);
7528        let err = c.validate_code_paths().unwrap_err();
7529        assert!(
7530            matches!(
7531                err,
7532                ManifestError::CodePathEmpty {
7533                    slot: ":bibliotecas"
7534                }
7535            ),
7536            "got {err:?}",
7537        );
7538    }
7539
7540    #[test]
7541    fn validate_code_paths_first_offender_per_slot_wins() {
7542        // Within a single slot, the first declaration-order offender
7543        // surfaces — pins that the gate is left-to-right deterministic
7544        // (peer of every `*_first_collision_*` pin on duplicate gates).
7545        let c = caixa_with_code_paths(
7546            vec!["lib/ok.lisp", "/etc/escape", "../also-escape"],
7547            vec![],
7548            vec![],
7549        );
7550        let err = c.validate_code_paths().unwrap_err();
7551        let ManifestError::CodePathAbsolute { slot, path } = err else {
7552            panic!("expected CodePathAbsolute, got {err:?}");
7553        };
7554        assert_eq!(slot, ":bibliotecas");
7555        assert_eq!(path, PathBuf::from("/etc/escape"));
7556    }
7557
7558    #[test]
7559    fn validate_code_paths_diagnostic_carries_offending_slot_and_path() {
7560        // Diagnostic-shape pin (peer with
7561        // `nome_invalid_diagnostic_carries_offending_nome` /
7562        // `versao_invalid_diagnostic_carries_offending_versao`): the
7563        // error's Display surfaces both the offending `:slot` tag and
7564        // the offending path verbatim, so a `feira lint` run can render
7565        // the diagnostic without re-parsing.
7566        let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
7567        let rendered = c.validate_code_paths().unwrap_err().to_string();
7568        assert!(
7569            rendered.contains(":bibliotecas"),
7570            "diagnostic must name the offending slot: {rendered}",
7571        );
7572        assert!(
7573            rendered.contains("/etc/passwd"),
7574            "diagnostic must quote the offending path: {rendered}",
7575        );
7576    }
7577
7578    #[test]
7579    fn validate_code_paths_rejects_duplicate_bibliotecas_entry() {
7580        // Canonical copy-paste-the-wrong-file footgun on the biblioteca
7581        // axis. Without the gate `feira build` re-parses the same lib
7582        // twice, wasting work and silently masking the author's intent
7583        // to declare a *second* biblioteca.
7584        let c = caixa_with_code_paths(vec!["lib/demo.lisp", "lib/demo.lisp"], vec![], vec![]);
7585        let err = c.validate_code_paths().unwrap_err();
7586        let ManifestError::CodePathDuplicate { slot, path } = err else {
7587            panic!("expected CodePathDuplicate, got {err:?}");
7588        };
7589        assert_eq!(slot, ":bibliotecas");
7590        assert_eq!(path, PathBuf::from("lib/demo.lisp"));
7591    }
7592
7593    #[test]
7594    fn validate_code_paths_rejects_duplicate_exe_entry() {
7595        // Same footgun on the Binario surface. The future `caixa-flake`
7596        // emitter that materializes each `:exe` entry as a flake
7597        // `packages.<name>` derivation would collide on the duplicate
7598        // package key — surfaced here at the typed-validate layer with a
7599        // self-locating diagnostic instead.
7600        let c = caixa_with_code_paths(vec![], vec!["exe/cli", "exe/cli"], vec![]);
7601        let err = c.validate_code_paths().unwrap_err();
7602        let ManifestError::CodePathDuplicate { slot, path } = err else {
7603            panic!("expected CodePathDuplicate, got {err:?}");
7604        };
7605        assert_eq!(slot, ":exe");
7606        assert_eq!(path, PathBuf::from("exe/cli"));
7607    }
7608
7609    #[test]
7610    fn validate_code_paths_rejects_duplicate_servicos_entry() {
7611        // Same footgun on the Servico surface. The peer caixa-helm /
7612        // caixa-flux renderers refuse `:servicos.len() != 1` with the
7613        // narrower `UnsupportedServicoCount` diagnostic, but that
7614        // diagnostic surfaces "too many servicos" without naming
7615        // "duplicate entry" — the typed self-locating framing only lands
7616        // at this gate.
7617        let c = caixa_with_code_paths(
7618            vec![],
7619            vec![],
7620            vec![
7621                "servicos/demo.computeunit.yaml",
7622                "servicos/demo.computeunit.yaml",
7623            ],
7624        );
7625        let err = c.validate_code_paths().unwrap_err();
7626        let ManifestError::CodePathDuplicate { slot, path } = err else {
7627            panic!("expected CodePathDuplicate, got {err:?}");
7628        };
7629        assert_eq!(slot, ":servicos");
7630        assert_eq!(path, PathBuf::from("servicos/demo.computeunit.yaml"));
7631    }
7632
7633    #[test]
7634    fn validate_code_paths_accepts_same_path_across_slots() {
7635        // Per-list scope pin: a `:bibliotecas` entry that happens to
7636        // collide with an `:exe` or `:servicos` entry as a *string* is
7637        // not a duplicate by this gate (each list gets its own HashSet),
7638        // mirroring the peer `:deps` ↔ `:deps-dev` per-list scope
7639        // (a `:nome` present in both lists is a legitimate dev-vs-runtime
7640        // shape on the dep axis). The structural `starts_with(<exe |
7641        // servicos>_dir)` fence at layout time prevents the realistic
7642        // cross-slot collision case from existing on disk, but the gate's
7643        // per-list scope is correct independent of that downstream fence.
7644        let c = caixa_with_code_paths(
7645            vec!["lib/x.lisp"],
7646            vec!["exe/x"],
7647            vec!["servicos/x.computeunit.yaml"],
7648        );
7649        c.validate_code_paths().unwrap();
7650    }
7651
7652    #[test]
7653    fn validate_code_paths_duplicate_fires_after_structural_checks_on_same_slot() {
7654        // Within-slot ordering pin: structural defects (empty / absolute
7655        // / parent-escape) fire before the duplicate gate on the same
7656        // slot. A `:bibliotecas ("" "lib/x.lisp" "lib/x.lisp")` shape
7657        // surfaces the narrower `CodePathEmpty` for the empty entry
7658        // first, not the duplicate on the later pair — same arm-ordering
7659        // every peer per-list duplicate gate uses (`:etiquetas` 360a499,
7660        // `:autores` 86c769b, `:deps` 359fba5).
7661        let c = caixa_with_code_paths(vec!["", "lib/x.lisp", "lib/x.lisp"], vec![], vec![]);
7662        let err = c.validate_code_paths().unwrap_err();
7663        assert!(
7664            matches!(
7665                err,
7666                ManifestError::CodePathEmpty {
7667                    slot: ":bibliotecas"
7668                }
7669            ),
7670            "got {err:?}",
7671        );
7672    }
7673
7674    #[test]
7675    fn validate_code_paths_duplicate_in_bibliotecas_fires_before_duplicate_in_exe() {
7676        // Cross-slot ordering pin on the duplicate arm: `:bibliotecas`
7677        // duplicates surface before `:exe` duplicates, matching the
7678        // canonical `:bibliotecas` → `:exe` → `:servicos` declaration
7679        // order every peer per-slot diagnostic on this surface follows.
7680        let c = caixa_with_code_paths(
7681            vec!["lib/x.lisp", "lib/x.lisp"],
7682            vec!["exe/y", "exe/y"],
7683            vec![],
7684        );
7685        let err = c.validate_code_paths().unwrap_err();
7686        let ManifestError::CodePathDuplicate { slot, path } = err else {
7687            panic!("expected CodePathDuplicate, got {err:?}");
7688        };
7689        assert_eq!(slot, ":bibliotecas");
7690        assert_eq!(path, PathBuf::from("lib/x.lisp"));
7691    }
7692
7693    #[test]
7694    fn validate_code_paths_duplicate_diagnostic_carries_offending_slot_and_path() {
7695        // Diagnostic-shape pin (peer with
7696        // `validate_code_paths_diagnostic_carries_offending_slot_and_path`
7697        // on the structural arm): the duplicate-arm Display surfaces both
7698        // the offending `:slot` tag and the offending path verbatim, so a
7699        // `feira lint` run can render the diagnostic without re-parsing.
7700        let c = caixa_with_code_paths(
7701            vec![],
7702            vec![],
7703            vec![
7704                "servicos/demo.computeunit.yaml",
7705                "servicos/demo.computeunit.yaml",
7706            ],
7707        );
7708        let rendered = c.validate_code_paths().unwrap_err().to_string();
7709        assert!(
7710            rendered.contains(":servicos"),
7711            "diagnostic must name the offending slot: {rendered}",
7712        );
7713        assert!(
7714            rendered.contains("servicos/demo.computeunit.yaml"),
7715            "diagnostic must quote the offending path: {rendered}",
7716        );
7717    }
7718
7719    // ── validate_code_paths — `.lisp` extension gate on :bibliotecas ──
7720    //
7721    // The lifted [`crate::render::is_lisp_extension`] predicate (33cc830)
7722    // now gates `:bibliotecas` entries on the tatara-lisp-source file-type
7723    // contract. The `feira build` loop (`caixa-feira/src/cmd/build.rs:33`)
7724    // reads every declared `:bibliotecas` entry through `tatara_lisp::read`
7725    // at parse time — the same downstream consumer the peer `:behavior
7726    // :on-*` (c97815a, [`crate::BehaviorError::NonLispExtension`]) and
7727    // `:upgrade-from :state-change :script` (33cc830,
7728    // [`crate::UpgradeError::NonLispExtensionScript`]) axes route through.
7729    // `:exe` and `:servicos` are deliberately excluded — `:exe` is the
7730    // nix-built executable surface (`"exe/<name>"` shape per the canonical
7731    // [`crate::LayoutError::ExeOutsideDir`] error message and every
7732    // in-tree `caixa_with_code_paths` positive control), and `:servicos`
7733    // is the `.computeunit.yaml` ComputeUnit-CR axis.
7734
7735    #[test]
7736    fn validate_code_paths_rejects_no_extension_bibliotecas_entry() {
7737        // Canonical "I dragged the wrong file from the workspace tree"
7738        // footgun on the biblioteca axis. Without the gate `feira build`
7739        // hands the extensionless path to `tatara_lisp::read` and fails
7740        // with a parser-shaped diagnostic far from the source caixa.lisp,
7741        // with no field naming the offending `:bibliotecas` entry.
7742        for relpath in ["lib/demo", "demo", "lib/handlers/inner"] {
7743            let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
7744            let err = c.validate_code_paths().unwrap_err();
7745            let ManifestError::CodePathNonLispExtension { slot, path } = err else {
7746                panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
7747            };
7748            assert_eq!(slot, ":bibliotecas");
7749            assert_eq!(path, PathBuf::from(relpath));
7750        }
7751    }
7752
7753    #[test]
7754    fn validate_code_paths_rejects_wrong_extension_bibliotecas_entry() {
7755        // Wrong-extension sweep across common authoring footguns. Same
7756        // sweep posture as the peer
7757        // `behavior::validate_rejects_wrong_extension` (c97815a) and
7758        // `upgrade::tests::state_change_rejects_wrong_extension_script`
7759        // (33cc830) cases.
7760        for relpath in [
7761            "lib/demo.rs",
7762            "lib/demo.txt",
7763            "lib/demo.md",
7764            "lib/demo.json",
7765            "lib/demo.yaml",
7766            "lib/demo.toml",
7767            "lib/demo.lisp.bak",
7768            "lib/demo.lispx",
7769            "lib/demo.lis",
7770        ] {
7771            let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
7772            let err = c.validate_code_paths().unwrap_err();
7773            let ManifestError::CodePathNonLispExtension { slot, path } = err else {
7774                panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
7775            };
7776            assert_eq!(slot, ":bibliotecas");
7777            assert_eq!(path, PathBuf::from(relpath));
7778        }
7779    }
7780
7781    #[test]
7782    fn validate_code_paths_rejects_case_folded_extension_bibliotecas_entry() {
7783        // Case-sensitivity sweep — pins the strict lowercase `.lisp`
7784        // contract. An uppercase `.LISP` shape that the layout's existence
7785        // check would (case-insensitively, on case-insensitive volumes)
7786        // match the on-disk file still mismatches the canonical form the
7787        // codec emits, breaking the THEORY.md §V.2.7 render-determinism
7788        // contract. Mirrors the peer
7789        // `behavior::validate_rejects_case_folded_extension` (c97815a) and
7790        // `upgrade::tests::state_change_rejects_case_folded_extension_script`
7791        // (33cc830) sweeps.
7792        for relpath in [
7793            "lib/demo.LISP",
7794            "lib/demo.Lisp",
7795            "lib/demo.LiSp",
7796            "lib/demo.lISP",
7797        ] {
7798            let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
7799            let err = c.validate_code_paths().unwrap_err();
7800            let ManifestError::CodePathNonLispExtension { slot, path } = err else {
7801                panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
7802            };
7803            assert_eq!(slot, ":bibliotecas");
7804            assert_eq!(path, PathBuf::from(relpath));
7805        }
7806    }
7807
7808    #[test]
7809    fn validate_code_paths_accepts_canonical_lisp_shapes() {
7810        // Positive-control sweep through every canonical authoring shape
7811        // every in-tree fixture and the `Caixa::template` scaffold use.
7812        // Mirrors the peer `behavior::validate_accepts_canonical_lisp_paths`
7813        // (c97815a) and the lifted predicate's own
7814        // `is_lisp_extension_accepts_canonical_shapes` sweep in render.rs
7815        // (33cc830).
7816        for relpath in [
7817            "lib/demo.lisp",
7818            "lib/handlers.lisp",
7819            "lib/migrations/v01-to-v02.lisp",
7820            "demo.lisp",
7821            "a.lisp",
7822            "./lib/demo.lisp",
7823            "lib/./handlers.lisp",
7824            "lib/migrations/v.0.1.lisp",
7825        ] {
7826            let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
7827            c.validate_code_paths()
7828                .unwrap_or_else(|e| panic!("canonical shape {relpath:?} must pass, got {e:?}"));
7829        }
7830    }
7831
7832    #[test]
7833    fn validate_code_paths_non_lisp_extension_does_not_fire_on_exe_or_servicos() {
7834        // The file-type gate is per-slot — only `:bibliotecas` carries the
7835        // tatara-lisp-source contract. An extensionless `:exe` entry
7836        // (`exe/demo`) and a `.computeunit.yaml` `:servicos` entry are the
7837        // canonical shapes every in-tree fixture uses, and must continue
7838        // to pass validate. Pins that a future tightening that broadens
7839        // the `.lisp` gate to either axis surfaces as a test failure
7840        // rather than as a silent breaking change to existing valid
7841        // manifests.
7842        let c = caixa_with_code_paths(
7843            vec![],
7844            vec!["exe/demo", "exe/tool"],
7845            vec!["servicos/demo.computeunit.yaml"],
7846        );
7847        c.validate_code_paths().unwrap();
7848    }
7849
7850    #[test]
7851    fn validate_code_paths_sandbox_shape_arms_precede_non_lisp_extension() {
7852        // Cross-arm precedence pin: a `:bibliotecas` entry that is *both*
7853        // sandbox-escaping and non-`.lisp` surfaces the more fundamental
7854        // sandbox-shape diagnostic first (the `.lisp` remediation would
7855        // be misleading when the offending path can never resolve under
7856        // the caixa root anyway). Mirrors the peer
7857        // `EmptyPath` → `AbsolutePath` → `ParentEscape` → `NonLispExtension`
7858        // ordering on `:behavior :on-*` (c97815a) and `EmptyScript` →
7859        // `AbsoluteScript` → `ParentEscapeScript` → `NonLispExtensionScript`
7860        // on `:upgrade-from :state-change :script` (33cc830).
7861        //
7862        // Empty wins (the strictly-smaller-scope structural arm).
7863        let c = caixa_with_code_paths(vec![""], vec![], vec![]);
7864        assert!(
7865            matches!(
7866                c.validate_code_paths().unwrap_err(),
7867                ManifestError::CodePathEmpty {
7868                    slot: ":bibliotecas"
7869                }
7870            ),
7871            "empty must win over non-lisp-extension",
7872        );
7873        // Absolute wins (the path can't resolve under the caixa root).
7874        let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
7875        let err = c.validate_code_paths().unwrap_err();
7876        let ManifestError::CodePathAbsolute { slot, .. } = err else {
7877            panic!("absolute must win over non-lisp-extension, got {err:?}");
7878        };
7879        assert_eq!(slot, ":bibliotecas");
7880        // ParentEscape wins (the path escapes the caixa root).
7881        let c = caixa_with_code_paths(vec!["../sibling/x.txt"], vec![], vec![]);
7882        let err = c.validate_code_paths().unwrap_err();
7883        let ManifestError::CodePathParentEscape { slot, .. } = err else {
7884            panic!("parent-escape must win over non-lisp-extension, got {err:?}");
7885        };
7886        assert_eq!(slot, ":bibliotecas");
7887    }
7888
7889    #[test]
7890    fn validate_code_paths_non_lisp_extension_precedes_duplicate() {
7891        // Within-slot precedence pin: the per-entry file-type shape gate
7892        // fires before the cross-entry duplicate gate, so the narrower
7893        // structural defect dominates the uniqueness diagnostic. A
7894        // `("lib/x.txt" "lib/x.txt")` shape surfaces
7895        // `CodePathNonLispExtension` on the first entry rather than
7896        // `CodePathDuplicate` on the pair — same posture every per-entry
7897        // shape-gate-precedes-duplicate cascade follows on this surface
7898        // (the empty / absolute / parent-escape arms already precede the
7899        // duplicate arm; the lifted file-type arm joins that set).
7900        let c = caixa_with_code_paths(vec!["lib/x.txt", "lib/x.txt"], vec![], vec![]);
7901        let err = c.validate_code_paths().unwrap_err();
7902        let ManifestError::CodePathNonLispExtension { slot, path } = err else {
7903            panic!("expected CodePathNonLispExtension, got {err:?}");
7904        };
7905        assert_eq!(slot, ":bibliotecas");
7906        assert_eq!(path, PathBuf::from("lib/x.txt"));
7907    }
7908
7909    #[test]
7910    fn validate_code_paths_non_lisp_extension_diagnostic_carries_offending_slot_and_path() {
7911        // Diagnostic-shape pin (peer with
7912        // `validate_code_paths_diagnostic_carries_offending_slot_and_path`
7913        // on the sandbox-shape arms and
7914        // `validate_code_paths_duplicate_diagnostic_carries_offending_slot_and_path`
7915        // on the duplicate arm): the file-type-arm Display surfaces both
7916        // the offending `:slot` tag, the offending path verbatim, and the
7917        // expected `.lisp` extension named in the remediation text, so a
7918        // `feira lint` run can render the diagnostic without re-parsing.
7919        let c = caixa_with_code_paths(vec!["lib/demo.rs"], vec![], vec![]);
7920        let rendered = c.validate_code_paths().unwrap_err().to_string();
7921        assert!(
7922            rendered.contains(":bibliotecas"),
7923            "diagnostic must name the offending slot: {rendered}",
7924        );
7925        assert!(
7926            rendered.contains("lib/demo.rs"),
7927            "diagnostic must quote the offending path: {rendered}",
7928        );
7929        assert!(
7930            rendered.contains(".lisp"),
7931            "diagnostic must name the expected extension: {rendered}",
7932        );
7933    }
7934
7935    // ── validate_code_paths — `.computeunit.yaml` compound-suffix gate on :servicos ──
7936    //
7937    // The lifted [`crate::render::is_computeunit_yaml_extension`] predicate
7938    // now gates `:servicos` entries on the ComputeUnit-CR YAML file-type
7939    // contract. The peer caixa-helm / caixa-flux renderers consume each
7940    // `:servicos` entry through `serde_yaml::from_str` as a typed
7941    // `ComputeUnit` CR — same downstream-consumer-shape lift as the peer
7942    // `:bibliotecas` `.lisp` gate (64772a9), here on the compound-suffix
7943    // axis `Path::extension` can't express on its own.
7944
7945    #[test]
7946    fn validate_code_paths_rejects_no_extension_servicos_entry() {
7947        // Canonical "I dragged the wrong file from the workspace tree"
7948        // footgun on the Servico axis. Without the gate the peer
7949        // caixa-helm / caixa-flux renderers hand the extensionless path
7950        // to `serde_yaml::from_str` and fail with a parser-shaped
7951        // diagnostic far from the source caixa.lisp, with no field
7952        // naming the offending `:servicos` entry.
7953        for relpath in ["servicos/demo", "demo", "servicos/sub/nested"] {
7954            let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
7955            let err = c.validate_code_paths().unwrap_err();
7956            let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
7957                panic!(
7958                    "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
7959                     got {err:?}"
7960                );
7961            };
7962            assert_eq!(slot, ":servicos");
7963            assert_eq!(path, PathBuf::from(relpath));
7964        }
7965    }
7966
7967    #[test]
7968    fn validate_code_paths_rejects_wrong_extension_servicos_entry() {
7969        // Wrong-extension sweep across common authoring footguns on the
7970        // Servico axis. Bare `.yaml` is the canonical "I forgot the
7971        // `.computeunit` segment" typo; the off-by-one-segment shapes
7972        // (`.computeunit-yaml` / `.computeunit_yaml`) silently pass the
7973        // bare `Path::extension` view but mismatch the typed compound
7974        // suffix the renderers' `serde_yaml::from_str` consumer demands.
7975        // Same sweep-posture as the peer
7976        // `validate_code_paths_rejects_wrong_extension_bibliotecas_entry`
7977        // (64772a9) on the sibling tatara-lisp-source axis.
7978        for relpath in [
7979            "servicos/demo.yaml",
7980            "servicos/demo.yml",
7981            "servicos/demo.json",
7982            "servicos/demo.toml",
7983            "servicos/demo.txt",
7984            "servicos/demo.computeunit.yaml.bak",
7985            "servicos/demo.computeunit.yam",
7986            "servicos/demo.computeunit",
7987            "servicos/demo-computeunit.yaml",
7988            "servicos/demo_computeunit.yaml",
7989        ] {
7990            let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
7991            let err = c.validate_code_paths().unwrap_err();
7992            let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
7993                panic!(
7994                    "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
7995                     got {err:?}"
7996                );
7997            };
7998            assert_eq!(slot, ":servicos");
7999            assert_eq!(path, PathBuf::from(relpath));
8000        }
8001    }
8002
8003    #[test]
8004    fn validate_code_paths_rejects_case_folded_extension_servicos_entry() {
8005        // Case-sensitivity sweep — pins the strict lowercase
8006        // `.computeunit.yaml` contract. A case-folded shape that the
8007        // layout's existence check would (case-insensitively, on
8008        // case-insensitive volumes) match the on-disk file still
8009        // mismatches the canonical form the codec emits, breaking the
8010        // THEORY.md §V.2.7 render-determinism contract. Mirrors the peer
8011        // `validate_code_paths_rejects_case_folded_extension_bibliotecas_entry`
8012        // (64772a9) sweep on the sibling tatara-lisp-source axis.
8013        for relpath in [
8014            "servicos/demo.ComputeUnit.yaml",
8015            "servicos/demo.COMPUTEUNIT.yaml",
8016            "servicos/demo.computeunit.YAML",
8017            "servicos/demo.computeunit.Yaml",
8018            "servicos/demo.COMPUTEUNIT.YAML",
8019        ] {
8020            let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
8021            let err = c.validate_code_paths().unwrap_err();
8022            let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
8023                panic!(
8024                    "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
8025                     got {err:?}"
8026                );
8027            };
8028            assert_eq!(slot, ":servicos");
8029            assert_eq!(path, PathBuf::from(relpath));
8030        }
8031    }
8032
8033    #[test]
8034    fn validate_code_paths_rejects_empty_stem_servicos_entry() {
8035        // Degenerate hidden-file shape: a file name exactly equal to the
8036        // suffix (`.computeunit.yaml` — no stem preceding the suffix) is
8037        // the structural "Servico declared with no identity" footgun.
8038        // The substrate identifies each ComputeUnit by the file-stem
8039        // segment that precedes `.computeunit.yaml` (the rendered
8040        // `lareira-<stem>` Helm chart, the per-Servico `metadata.name`,
8041        // the M3 `:contratos` membership lookup), so an empty stem
8042        // leaves the Servico unidentifiable. Pinned at the typed-axis
8043        // level so a future regression that drops the `name.len() >
8044        // SUFFIX.len()` bound at the predicate surfaces here, not
8045        // piecemeal as a `lareira-` chart-name collision at render time.
8046        for relpath in ["servicos/.computeunit.yaml"] {
8047            let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
8048            let err = c.validate_code_paths().unwrap_err();
8049            let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
8050                panic!(
8051                    "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
8052                     got {err:?}"
8053                );
8054            };
8055            assert_eq!(slot, ":servicos");
8056            assert_eq!(path, PathBuf::from(relpath));
8057        }
8058    }
8059
8060    #[test]
8061    fn validate_code_paths_accepts_canonical_computeunit_yaml_shapes() {
8062        // Positive-control sweep through every canonical authoring shape
8063        // every in-tree fixture and the `Caixa::template` scaffold use.
8064        // Mirrors the peer
8065        // `validate_code_paths_accepts_canonical_lisp_shapes` (64772a9)
8066        // and the lifted predicate's own
8067        // `computeunit_yaml_extension_accepts_canonical_shapes` sweep in
8068        // render.rs.
8069        for relpath in [
8070            "servicos/demo.computeunit.yaml",
8071            "servicos/hello-rio.computeunit.yaml",
8072            "servicos/my-service.computeunit.yaml",
8073            "servicos/a.computeunit.yaml",
8074            "./servicos/demo.computeunit.yaml",
8075            "servicos/./demo.computeunit.yaml",
8076            "servicos/sub/nested.computeunit.yaml",
8077            "servicos/v0.1.computeunit.yaml",
8078        ] {
8079            let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
8080            c.validate_code_paths()
8081                .unwrap_or_else(|e| panic!("canonical shape {relpath:?} must pass, got {e:?}"));
8082        }
8083    }
8084
8085    #[test]
8086    fn validate_code_paths_non_computeunit_yaml_extension_does_not_fire_on_bibliotecas_or_exe() {
8087        // The file-type gate is per-slot — only `:servicos` carries the
8088        // ComputeUnit-CR YAML contract. A canonical `.lisp` `:bibliotecas`
8089        // entry and an extensionless `:exe` entry are the canonical
8090        // shapes every in-tree fixture uses, and must continue to pass
8091        // validate. Peer of
8092        // `validate_code_paths_non_lisp_extension_does_not_fire_on_exe_or_servicos`
8093        // (64772a9) — together pin that the typed
8094        // [`CodePathFileType`] dispatch is exhaustively per-slot, with no
8095        // cross-axis leakage in either direction.
8096        let c = caixa_with_code_paths(
8097            vec!["lib/demo.lisp"],
8098            vec!["exe/demo", "exe/tool"],
8099            vec!["servicos/demo.computeunit.yaml"],
8100        );
8101        c.validate_code_paths().unwrap();
8102    }
8103
8104    #[test]
8105    fn validate_code_paths_sandbox_shape_arms_precede_non_computeunit_yaml_extension() {
8106        // Cross-arm precedence pin: a `:servicos` entry that is *both*
8107        // sandbox-escaping and wrong-extension surfaces the more
8108        // fundamental sandbox-shape diagnostic first (the
8109        // `.computeunit.yaml` remediation would be misleading when the
8110        // offending path can never resolve under the caixa root
8111        // anyway). Mirrors the peer
8112        // `validate_code_paths_sandbox_shape_arms_precede_non_lisp_extension`
8113        // (64772a9) ordering on the sibling `:bibliotecas` axis and the
8114        // peer `EmptyPath` → `AbsolutePath` → `ParentEscape` →
8115        // `NonComputeUnitYamlExtension` arm-ordering the dispatch
8116        // table establishes.
8117        //
8118        // Empty wins (the strictly-smaller-scope structural arm).
8119        let c = caixa_with_code_paths(vec![], vec![], vec![""]);
8120        assert!(
8121            matches!(
8122                c.validate_code_paths().unwrap_err(),
8123                ManifestError::CodePathEmpty { slot: ":servicos" }
8124            ),
8125            "empty must win over non-computeunit-yaml-extension",
8126        );
8127        // Absolute wins (the path can't resolve under the caixa root).
8128        let c = caixa_with_code_paths(vec![], vec![], vec!["/etc/foo.yaml"]);
8129        let err = c.validate_code_paths().unwrap_err();
8130        let ManifestError::CodePathAbsolute { slot, .. } = err else {
8131            panic!("absolute must win over non-computeunit-yaml-extension, got {err:?}");
8132        };
8133        assert_eq!(slot, ":servicos");
8134        // ParentEscape wins (the path escapes the caixa root).
8135        let c = caixa_with_code_paths(vec![], vec![], vec!["../sibling/x.yaml"]);
8136        let err = c.validate_code_paths().unwrap_err();
8137        let ManifestError::CodePathParentEscape { slot, .. } = err else {
8138            panic!("parent-escape must win over non-computeunit-yaml-extension, got {err:?}");
8139        };
8140        assert_eq!(slot, ":servicos");
8141    }
8142
8143    #[test]
8144    fn validate_code_paths_non_computeunit_yaml_extension_precedes_duplicate() {
8145        // Within-slot precedence pin: the per-entry file-type shape gate
8146        // fires before the cross-entry duplicate gate, so the narrower
8147        // structural defect dominates the uniqueness diagnostic. A
8148        // `("servicos/x.yaml" "servicos/x.yaml")` shape surfaces
8149        // `CodePathNonComputeUnitYamlExtension` on the first entry
8150        // rather than `CodePathDuplicate` on the pair — same posture
8151        // every per-entry shape-gate-precedes-duplicate cascade follows
8152        // on this surface, peer of the 64772a9 `:bibliotecas`
8153        // `("lib/x.txt" "lib/x.txt")` ordering.
8154        let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/x.yaml", "servicos/x.yaml"]);
8155        let err = c.validate_code_paths().unwrap_err();
8156        let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
8157            panic!("expected CodePathNonComputeUnitYamlExtension, got {err:?}");
8158        };
8159        assert_eq!(slot, ":servicos");
8160        assert_eq!(path, PathBuf::from("servicos/x.yaml"));
8161    }
8162
8163    #[test]
8164    fn validate_code_paths_non_computeunit_yaml_extension_diagnostic_carries_offending_slot_and_path()
8165     {
8166        // Diagnostic-shape pin (peer with
8167        // `validate_code_paths_non_lisp_extension_diagnostic_carries_offending_slot_and_path`
8168        // on the sibling tatara-lisp-source axis): the file-type-arm
8169        // Display surfaces both the offending `:slot` tag, the
8170        // offending path verbatim, and the expected
8171        // `.computeunit.yaml` compound suffix named in the remediation
8172        // text, so a `feira lint` run can render the diagnostic without
8173        // re-parsing.
8174        let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/demo.yaml"]);
8175        let rendered = c.validate_code_paths().unwrap_err().to_string();
8176        assert!(
8177            rendered.contains(":servicos"),
8178            "diagnostic must name the offending slot: {rendered}",
8179        );
8180        assert!(
8181            rendered.contains("servicos/demo.yaml"),
8182            "diagnostic must quote the offending path: {rendered}",
8183        );
8184        assert!(
8185            rendered.contains(".computeunit.yaml"),
8186            "diagnostic must name the expected compound suffix: {rendered}",
8187        );
8188    }
8189
8190    // ── validate_etiquetas — universal-axis registry-search-tag shape ──
8191
8192    fn caixa_with_etiquetas(etiquetas: Vec<&str>) -> Caixa {
8193        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8194        c.etiquetas = etiquetas.into_iter().map(String::from).collect();
8195        c
8196    }
8197
8198    #[test]
8199    fn validate_etiquetas_accepts_empty_list() {
8200        // The empty-list identity: every caixa with no declared tags
8201        // trivially passes — `Caixa::template` emits `:etiquetas ()`,
8202        // so the gate is non-disruptive against every existing manifest.
8203        let c = caixa_with_etiquetas(vec![]);
8204        c.validate_etiquetas().unwrap();
8205    }
8206
8207    #[test]
8208    fn validate_etiquetas_accepts_canonical_forms() {
8209        // Positive control sweep: a canonical-shaped non-empty distinct
8210        // tag list passes, mirroring the example checkout-aplicacao
8211        // (`:etiquetas ("example" "aplicacao" "mesh" "ecommerce" "demo")`)
8212        // and the hello-rio fixture (`("hello-world" "wasm" "rust")`).
8213        let c = caixa_with_etiquetas(vec!["example", "aplicacao", "mesh", "ecommerce", "demo"]);
8214        c.validate_etiquetas().unwrap();
8215    }
8216
8217    #[test]
8218    fn validate_etiquetas_rejects_empty_entry() {
8219        // Canonical paste-from-blank-doc footgun. Without the gate the
8220        // empty entry rendered as `keywords: [""]` in `Chart.yaml`, a
8221        // no-op tag indexing nothing in the future caixa-registry.
8222        let c = caixa_with_etiquetas(vec![""]);
8223        let err = c.validate_etiquetas().unwrap_err();
8224        assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
8225    }
8226
8227    #[test]
8228    fn validate_etiquetas_rejects_duplicate_entry() {
8229        // Canonical copy-paste-the-wrong-tag footgun. Without the gate
8230        // the duplicate was silently dedup'd by caixa-helm's BTreeSet
8231        // collect at chart render — a "second wins / one silently
8232        // disappears" shape divergent from every peer typed-graph set
8233        // gate. The duplicate-arm names the offending tag verbatim.
8234        let c = caixa_with_etiquetas(vec!["demo", "demo"]);
8235        let err = c.validate_etiquetas().unwrap_err();
8236        let ManifestError::EtiquetaDuplicate { etiqueta } = err else {
8237            panic!("expected EtiquetaDuplicate, got {err:?}");
8238        };
8239        assert_eq!(etiqueta, "demo");
8240    }
8241
8242    #[test]
8243    fn validate_etiquetas_empty_takes_precedence_over_duplicate() {
8244        // Empty-first cascade pin: `("" "demo" "demo")` surfaces
8245        // `EtiquetaEmpty` not `EtiquetaDuplicate` — the narrower
8246        // structural "this entry has no value" defect dominates the
8247        // cross-entry uniqueness diagnostic. Mirrors the peer
8248        // empty-before-duplicate cascades on `:caracteristicas`
8249        // (`CaracteristicaEmpty` before `CaracteristicaDuplicate`,
8250        // fc3b4d5) and `:membros :caixa` (`MembroCaixaEmpty` before
8251        // `MembroDuplicate`).
8252        let c = caixa_with_etiquetas(vec!["", "demo", "demo"]);
8253        let err = c.validate_etiquetas().unwrap_err();
8254        assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
8255    }
8256
8257    #[test]
8258    fn validate_etiquetas_duplicate_reports_first_collision() {
8259        // First-collision pin: `("a" "b" "a" "b")` surfaces the `"a"`
8260        // duplicate (the lexicographically-earliest offending position
8261        // — the second `"a"` at index 2 collides with the first `"a"`
8262        // at index 0), not the later `"b"` collision at index 3,
8263        // peer with every other first-collision diagnostic posture on
8264        // this surface (`validate_load_singularity_reports_first_collision`,
8265        // `validate_cleanup_singularity_reports_first_collision`).
8266        let c = caixa_with_etiquetas(vec!["a", "b", "a", "b"]);
8267        let err = c.validate_etiquetas().unwrap_err();
8268        let ManifestError::EtiquetaDuplicate { etiqueta } = err else {
8269            panic!("expected EtiquetaDuplicate, got {err:?}");
8270        };
8271        assert_eq!(etiqueta, "a");
8272    }
8273
8274    #[test]
8275    fn validate_etiquetas_case_sensitive() {
8276        // Case-sensitivity pin: `("Foo" "foo")` is two distinct entries,
8277        // mirroring the peer `:membros :caixa` / `:children :caixa`
8278        // exact-string-match discipline. The shape gate this routine
8279        // landed (`is_chart_keyword_shape`, Cargo crates.io keyword
8280        // grammar) accepts mixed case — crates.io's keyword rule is
8281        // "case-insensitive" at the index layer but admits mixed case
8282        // at the entry layer (the canonical Helm chart `keywords:`
8283        // shape is lowercase by convention, but the grammar admits
8284        // uppercase). Case-sensitivity at the duplicate-set layer
8285        // remains structural — two distinct strings are two distinct
8286        // entries.
8287        let c = caixa_with_etiquetas(vec!["Foo", "foo"]);
8288        c.validate_etiquetas().unwrap();
8289    }
8290
8291    #[test]
8292    fn validate_etiquetas_diagnostic_carries_offending_tag() {
8293        // Diagnostic-shape pin (peer with
8294        // `validate_code_paths_diagnostic_carries_offending_slot_and_path`):
8295        // the error's Display surfaces the offending tag verbatim, so a
8296        // `feira lint` run can render the diagnostic without re-parsing
8297        // and the author can grep their caixa.lisp for the offending
8298        // value.
8299        let c = caixa_with_etiquetas(vec!["demo", "demo"]);
8300        let rendered = c.validate_etiquetas().unwrap_err().to_string();
8301        assert!(
8302            rendered.contains(":etiquetas"),
8303            "diagnostic must name the offending slot: {rendered}",
8304        );
8305        assert!(
8306            rendered.contains("demo"),
8307            "diagnostic must quote the offending tag: {rendered}",
8308        );
8309    }
8310
8311    #[test]
8312    fn validate_etiquetas_rejects_leading_whitespace_entry() {
8313        // Canonical paste-from-aligned-doc footgun. Without the shape
8314        // gate `" mesh"` silently passed validate and landed as a
8315        // YAML plain-style scalar with leading whitespace in the
8316        // rendered Chart.yaml `keywords:` array — every YAML 1.2
8317        // dumper trims leading whitespace from plain-style scalars,
8318        // so the authored space round-tripped inconsistently back
8319        // through `caixa.lisp`. Mirrors the peer
8320        // `validate_autores_rejects_leading_whitespace_entry`.
8321        let c = caixa_with_etiquetas(vec![" mesh"]);
8322        let err = c.validate_etiquetas().unwrap_err();
8323        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8324            panic!("expected EtiquetaInvalid, got {err:?}");
8325        };
8326        assert_eq!(etiqueta, " mesh");
8327        assert!(reason.contains("whitespace"), "got: {reason}");
8328    }
8329
8330    #[test]
8331    fn validate_etiquetas_rejects_embedded_newline_entry() {
8332        // Canonical paste-from-multiline-doc footgun — the author
8333        // pasted a multi-tag block into one `:etiquetas` entry
8334        // instead of splitting into one entry per tag. Without the
8335        // shape gate `"mesh\nhttp"` silently passed validate and
8336        // landed as a YAML-illegal multi-line scalar in the rendered
8337        // Chart.yaml `keywords:` array.
8338        let c = caixa_with_etiquetas(vec!["mesh\nhttp"]);
8339        let err = c.validate_etiquetas().unwrap_err();
8340        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8341            panic!("expected EtiquetaInvalid, got {err:?}");
8342        };
8343        assert_eq!(etiqueta, "mesh\nhttp");
8344        assert!(reason.contains("newline"), "got: {reason}");
8345    }
8346
8347    #[test]
8348    fn validate_etiquetas_rejects_embedded_comma_entry() {
8349        // Canonical CSV-list-separator-confusion footgun: the author
8350        // confused the CSV-style separator convention with the
8351        // `:etiquetas` list grammar. Without the shape gate
8352        // `"mesh,http,grpc"` silently passed validate and landed as a
8353        // single malformed search tag in the rendered Chart.yaml
8354        // `keywords:` array — Artifact Hub's keyword index would
8355        // either silently drop the tag or index it as
8356        // `mesh,http,grpc` instead of three separate tags.
8357        let c = caixa_with_etiquetas(vec!["mesh,http,grpc"]);
8358        let err = c.validate_etiquetas().unwrap_err();
8359        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8360            panic!("expected EtiquetaInvalid, got {err:?}");
8361        };
8362        assert_eq!(etiqueta, "mesh,http,grpc");
8363        assert!(reason.contains('`'), "got: {reason}");
8364        assert!(reason.contains(','), "got: {reason}");
8365    }
8366
8367    #[test]
8368    fn validate_etiquetas_rejects_embedded_slash_entry() {
8369        // Canonical path-separator-confusion footgun: the author
8370        // confused namespace-path notation with the keyword grammar.
8371        let c = caixa_with_etiquetas(vec!["caixa/servico"]);
8372        let err = c.validate_etiquetas().unwrap_err();
8373        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8374            panic!("expected EtiquetaInvalid, got {err:?}");
8375        };
8376        assert_eq!(etiqueta, "caixa/servico");
8377        assert!(reason.contains('/'), "got: {reason}");
8378    }
8379
8380    #[test]
8381    fn validate_etiquetas_rejects_leading_digit_entry() {
8382        // Canonical paste-from-numbered-list footgun: the author
8383        // copied `1. mesh` from a numbered doc and the `1` leaked
8384        // into the tag.
8385        let c = caixa_with_etiquetas(vec!["1mesh"]);
8386        let err = c.validate_etiquetas().unwrap_err();
8387        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8388            panic!("expected EtiquetaInvalid, got {err:?}");
8389        };
8390        assert_eq!(etiqueta, "1mesh");
8391        assert!(reason.contains("digit"), "got: {reason}");
8392    }
8393
8394    #[test]
8395    fn validate_etiquetas_rejects_leading_hyphen_entry() {
8396        // Canonical kebab-leak footgun.
8397        let c = caixa_with_etiquetas(vec!["-foo"]);
8398        let err = c.validate_etiquetas().unwrap_err();
8399        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8400            panic!("expected EtiquetaInvalid, got {err:?}");
8401        };
8402        assert_eq!(etiqueta, "-foo");
8403        assert!(reason.contains('-'), "got: {reason}");
8404    }
8405
8406    #[test]
8407    fn validate_etiquetas_rejects_non_ascii_entry() {
8408        // Canonical paste-from-Unicode-doc footgun. Every legitimate
8409        // search tag is strict ASCII; raw non-ASCII silently
8410        // round-trips inconsistently across NFC/NFD normalization on
8411        // APFS / case-folding filesystems and breaks the Artifact Hub
8412        // keyword search index lookup.
8413        let c = caixa_with_etiquetas(vec!["café"]);
8414        let err = c.validate_etiquetas().unwrap_err();
8415        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8416            panic!("expected EtiquetaInvalid, got {err:?}");
8417        };
8418        assert_eq!(etiqueta, "café");
8419        assert!(reason.contains("non-ASCII"), "got: {reason}");
8420    }
8421
8422    #[test]
8423    fn validate_etiquetas_rejects_period_entry() {
8424        // Canonical namespace-confusion / version-suffix footgun
8425        // (`"http.1"` / `"v1.0"`): Cargo's crates.io keyword grammar
8426        // excludes `.` from the continuation set even though the
8427        // sibling `:caracteristicas` axis (Cargo's feature-name
8428        // grammar) admits it. Tighter than the sibling axis, peer
8429        // with Cargo's own crates.io keyword shape.
8430        let c = caixa_with_etiquetas(vec!["http.1"]);
8431        let err = c.validate_etiquetas().unwrap_err();
8432        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8433            panic!("expected EtiquetaInvalid, got {err:?}");
8434        };
8435        assert_eq!(etiqueta, "http.1");
8436        assert!(reason.contains('.'), "got: {reason}");
8437    }
8438
8439    #[test]
8440    fn validate_etiquetas_empty_takes_precedence_over_shape() {
8441        // Per-entry empty-first cascade pin: an entry that is both
8442        // empty *and* shape-invalid surfaces `EtiquetaEmpty` (the
8443        // narrower "this entry has no value" structural defect
8444        // dominates the broader shape-predicate diagnostic). The
8445        // empty arm fires before the shape predicate is consulted,
8446        // mirroring the peer `validate_autores_empty_takes_precedence_over_shape`
8447        // cascade established on the sibling universal-axis Vec<String>
8448        // surface.
8449        let c = caixa_with_etiquetas(vec![""]);
8450        let err = c.validate_etiquetas().unwrap_err();
8451        assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
8452    }
8453
8454    #[test]
8455    fn validate_etiquetas_shape_takes_precedence_over_duplicate() {
8456        // Per-entry shape-before-cross-entry-duplicate cascade pin: an
8457        // entry that is malformed surfaces `EtiquetaInvalid` even when
8458        // a later entry would have collided on duplicate. The
8459        // per-entry shape arm fires inside the same loop iteration as
8460        // the empty arm, before the seen-set insert at end-of-iteration
8461        // — structural per-entry defects dominate the cross-entry
8462        // uniqueness diagnostic. Mirrors the peer
8463        // `validate_autores_shape_takes_precedence_over_duplicate`.
8464        let c = caixa_with_etiquetas(vec!["mesh\nhttp", "mesh\nhttp"]);
8465        let err = c.validate_etiquetas().unwrap_err();
8466        assert!(
8467            matches!(err, ManifestError::EtiquetaInvalid { .. }),
8468            "got {err:?}",
8469        );
8470    }
8471
8472    #[test]
8473    fn validate_etiquetas_invalid_diagnostic_names_offending_slot_and_value() {
8474        // Diagnostic-shape pin on the new shape arm (peer with
8475        // `validate_autores_invalid_diagnostic_names_offending_slot_and_value`):
8476        // the rendered Display surfaces both the offending slot name
8477        // and the offending value verbatim, so a `feira lint` run
8478        // points the author at the exact `:etiquetas` entry to fix.
8479        let c = caixa_with_etiquetas(vec!["mesh\nhttp"]);
8480        let rendered = c.validate_etiquetas().unwrap_err().to_string();
8481        assert!(
8482            rendered.contains(":etiquetas"),
8483            "diagnostic must name the offending slot: {rendered}",
8484        );
8485        assert!(
8486            rendered.contains("mesh\\nhttp"),
8487            "diagnostic must quote the offending value (debug-escaped): {rendered}",
8488        );
8489    }
8490
8491    #[test]
8492    fn validate_etiquetas_rejects_at_21_byte_boundary() {
8493        // The 20-byte cap pin — boundary-exceeding case rejected,
8494        // boundary-accepting case passes. Mirrors the peer
8495        // `chart_keyword_shape_rejects_at_21_byte_boundary` substrate-
8496        // side pin, surfaced at the per-axis caller so the cap
8497        // propagates through validate end-to-end. Constructed as a
8498        // single all-`a` token so only the cap arm fires.
8499        let max_ok = "a".repeat(20);
8500        let c = caixa_with_etiquetas(vec![max_ok.as_str()]);
8501        c.validate_etiquetas().unwrap();
8502        let too_long = "a".repeat(21);
8503        let c = caixa_with_etiquetas(vec![too_long.as_str()]);
8504        let err = c.validate_etiquetas().unwrap_err();
8505        let ManifestError::EtiquetaInvalid { reason, .. } = err else {
8506            panic!("expected EtiquetaInvalid, got {err:?}");
8507        };
8508        assert!(reason.contains("20"), "got: {reason}");
8509        assert!(reason.contains("21"), "got: {reason}");
8510    }
8511
8512    #[test]
8513    fn validate_etiquetas_accepts_canonical_shaped_forms() {
8514        // Positive control sweep: every canonical-shaped tag from the
8515        // hello-rio / checkout-aplicacao / pangea-tatara-akeyless
8516        // example fixtures plus the substrate-fixed tags caixa-helm
8517        // unions in at chart render. Drift between this list and the
8518        // substrate-side `chart_keyword_shape_accepts_canonical_forms`
8519        // sweep surfaces here — one source of truth for the rule.
8520        let c = caixa_with_etiquetas(vec![
8521            "example",
8522            "aplicacao",
8523            "mesh",
8524            "ecommerce",
8525            "demo",
8526            "infrastructure",
8527            "aws",
8528            "akeyless",
8529            "pangea-native",
8530            "hello-world",
8531            "wasm",
8532            "rust",
8533            "tatara-lisp",
8534            "caixa-servico",
8535            "lareira",
8536        ]);
8537        c.validate_etiquetas().unwrap();
8538    }
8539
8540    // ── validate_autores — universal-axis maintainer shape ────────────
8541
8542    fn caixa_with_autores(autores: Vec<&str>) -> Caixa {
8543        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8544        c.autores = autores.into_iter().map(String::from).collect();
8545        c
8546    }
8547
8548    #[test]
8549    fn validate_autores_accepts_empty_list() {
8550        // The empty-list identity: `Caixa::template` emits `:autores ()`,
8551        // so the gate is non-disruptive against every existing manifest.
8552        let c = caixa_with_autores(vec![]);
8553        c.validate_autores().unwrap();
8554    }
8555
8556    #[test]
8557    fn validate_autores_accepts_canonical_forms() {
8558        // Positive control sweep: every canonical-shaped non-empty
8559        // distinct maintainer list passes — the hello-rio / checkout-
8560        // aplicacao fixtures' `:autores ("pleme-io")` shape, plus the
8561        // multi-author shape downstream packaging surfaces emit.
8562        let c = caixa_with_autores(vec!["pleme-io"]);
8563        c.validate_autores().unwrap();
8564        let c = caixa_with_autores(vec!["alice <alice@example.com>", "bob <bob@example.com>"]);
8565        c.validate_autores().unwrap();
8566    }
8567
8568    #[test]
8569    fn validate_autores_rejects_empty_entry() {
8570        // Canonical paste-from-blank-doc footgun. Without the gate the
8571        // empty entry rendered as `maintainers: [{name: "", email: null}]`
8572        // in `Chart.yaml`, a no-op maintainer the substrate cannot route
8573        // to.
8574        let c = caixa_with_autores(vec![""]);
8575        let err = c.validate_autores().unwrap_err();
8576        assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
8577    }
8578
8579    #[test]
8580    fn validate_autores_rejects_duplicate_entry() {
8581        // Canonical copy-paste-the-wrong-author footgun. Unlike the
8582        // `:etiquetas` peer (caixa-helm's `BTreeSet` collect silently
8583        // dedups the rendered `keywords:` array), the `maintainers:`
8584        // rendering has *no* dedup — duplicates stack verbatim. The
8585        // duplicate-arm names the offending author verbatim.
8586        let c = caixa_with_autores(vec!["pleme-io", "pleme-io"]);
8587        let err = c.validate_autores().unwrap_err();
8588        let ManifestError::AutorDuplicate { autor } = err else {
8589            panic!("expected AutorDuplicate, got {err:?}");
8590        };
8591        assert_eq!(autor, "pleme-io");
8592    }
8593
8594    #[test]
8595    fn validate_autores_empty_takes_precedence_over_duplicate() {
8596        // Empty-first cascade pin: `("" "pleme-io" "pleme-io")` surfaces
8597        // `AutorEmpty` not `AutorDuplicate` — the narrower structural
8598        // "this entry has no value" defect dominates the cross-entry
8599        // uniqueness diagnostic. Mirrors the peer empty-before-duplicate
8600        // cascades on `:etiquetas` (`EtiquetaEmpty` before
8601        // `EtiquetaDuplicate`, 360a499), `:caracteristicas`
8602        // (`CaracteristicaEmpty` before `CaracteristicaDuplicate`,
8603        // fc3b4d5), and `:membros :caixa` (`MembroCaixaEmpty` before
8604        // `MembroDuplicate`).
8605        let c = caixa_with_autores(vec!["", "pleme-io", "pleme-io"]);
8606        let err = c.validate_autores().unwrap_err();
8607        assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
8608    }
8609
8610    #[test]
8611    fn validate_autores_duplicate_reports_first_collision() {
8612        // First-collision pin: `("a" "b" "a" "b")` surfaces the `"a"`
8613        // duplicate (the lexicographically-earliest offending position
8614        // — the second `"a"` at index 2 collides with the first `"a"`
8615        // at index 0), not the later `"b"` collision at index 3,
8616        // peer with every other first-collision diagnostic posture on
8617        // this surface.
8618        let c = caixa_with_autores(vec!["a", "b", "a", "b"]);
8619        let err = c.validate_autores().unwrap_err();
8620        let ManifestError::AutorDuplicate { autor } = err else {
8621            panic!("expected AutorDuplicate, got {err:?}");
8622        };
8623        assert_eq!(autor, "a");
8624    }
8625
8626    #[test]
8627    fn validate_autores_case_sensitive() {
8628        // Case-sensitivity pin: `("Pleme-io" "pleme-io")` is two distinct
8629        // entries, mirroring the peer `:etiquetas` / `:membros :caixa`
8630        // / `:children :caixa` exact-string-match discipline.
8631        let c = caixa_with_autores(vec!["Pleme-io", "pleme-io"]);
8632        c.validate_autores().unwrap();
8633    }
8634
8635    #[test]
8636    fn validate_autores_diagnostic_carries_offending_author() {
8637        // Diagnostic-shape pin (peer with
8638        // `validate_etiquetas_diagnostic_carries_offending_tag`): the
8639        // error's Display surfaces the offending author verbatim, so a
8640        // `feira lint` run can render the diagnostic without re-parsing
8641        // and the author can grep their caixa.lisp for the offending
8642        // value.
8643        let c = caixa_with_autores(vec!["pleme-io", "pleme-io"]);
8644        let rendered = c.validate_autores().unwrap_err().to_string();
8645        assert!(
8646            rendered.contains(":autores"),
8647            "diagnostic must name the offending slot: {rendered}",
8648        );
8649        assert!(
8650            rendered.contains("pleme-io"),
8651            "diagnostic must quote the offending author: {rendered}",
8652        );
8653    }
8654
8655    #[test]
8656    fn validate_autores_rejects_leading_whitespace_entry() {
8657        // Canonical paste-from-aligned-doc footgun. Without the shape
8658        // gate `" pleme-io"` silently passed validate and landed as a
8659        // YAML plain-style scalar with leading whitespace in the
8660        // rendered Chart.yaml `maintainers:` array — every YAML 1.2
8661        // dumper trims leading whitespace from plain-style scalars, so
8662        // the authored space round-tripped inconsistently back through
8663        // `caixa.lisp`. Mirrors the peer
8664        // `validate_descricao_rejects_leading_whitespace`.
8665        let c = caixa_with_autores(vec![" pleme-io"]);
8666        let err = c.validate_autores().unwrap_err();
8667        let ManifestError::AutorInvalid { autor, reason } = err else {
8668            panic!("expected AutorInvalid, got {err:?}");
8669        };
8670        assert_eq!(autor, " pleme-io");
8671        assert!(reason.contains("whitespace"), "got: {reason}");
8672    }
8673
8674    #[test]
8675    fn validate_autores_rejects_trailing_whitespace_entry() {
8676        // Canonical paste-from-doc footgun.
8677        let c = caixa_with_autores(vec!["pleme-io "]);
8678        let err = c.validate_autores().unwrap_err();
8679        let ManifestError::AutorInvalid { autor, reason } = err else {
8680            panic!("expected AutorInvalid, got {err:?}");
8681        };
8682        assert_eq!(autor, "pleme-io ");
8683        assert!(reason.contains("whitespace"), "got: {reason}");
8684    }
8685
8686    #[test]
8687    fn validate_autores_rejects_embedded_newline_entry() {
8688        // Canonical paste-from-multiline-doc footgun — the author
8689        // pasted a multi-line block of author records into one
8690        // `:autores` entry instead of splitting into one entry per
8691        // author. Without the shape gate `"alice\nbob"` silently
8692        // passed validate and landed as a YAML-illegal multi-line
8693        // scalar in the rendered Chart.yaml `maintainers:` array.
8694        let c = caixa_with_autores(vec!["alice\nbob"]);
8695        let err = c.validate_autores().unwrap_err();
8696        let ManifestError::AutorInvalid { autor, reason } = err else {
8697            panic!("expected AutorInvalid, got {err:?}");
8698        };
8699        assert_eq!(autor, "alice\nbob");
8700        assert!(reason.contains("newline"), "got: {reason}");
8701    }
8702
8703    #[test]
8704    fn validate_autores_rejects_embedded_carriage_return_entry() {
8705        // Canonical paste-from-Windows-CRLF-doc footgun.
8706        let c = caixa_with_autores(vec!["alice\rbob"]);
8707        let err = c.validate_autores().unwrap_err();
8708        let ManifestError::AutorInvalid { autor, reason } = err else {
8709            panic!("expected AutorInvalid, got {err:?}");
8710        };
8711        assert_eq!(autor, "alice\rbob");
8712        assert!(reason.contains("carriage return"), "got: {reason}");
8713    }
8714
8715    #[test]
8716    fn validate_autores_rejects_embedded_tab_entry() {
8717        // Canonical tab-from-aligned-doc footgun.
8718        let c = caixa_with_autores(vec!["Pleme\tContributors"]);
8719        let err = c.validate_autores().unwrap_err();
8720        let ManifestError::AutorInvalid { autor, reason } = err else {
8721            panic!("expected AutorInvalid, got {err:?}");
8722        };
8723        assert_eq!(autor, "Pleme\tContributors");
8724        assert!(reason.contains("tab"), "got: {reason}");
8725    }
8726
8727    #[test]
8728    fn validate_autores_rejects_embedded_control_bytes_entry() {
8729        // Paste-from-binary-blob footguns: NUL, BEL, ESC, DEL all
8730        // surface the same control-byte arm.
8731        for entry in [
8732            "alice\x00bob",
8733            "alice\x07bob",
8734            "alice\x1bbob",
8735            "alice\x7fbob",
8736        ] {
8737            let c = caixa_with_autores(vec![entry]);
8738            let err = c.validate_autores().unwrap_err();
8739            let ManifestError::AutorInvalid { autor, reason } = err else {
8740                panic!("expected AutorInvalid for {entry:?}, got {err:?}");
8741            };
8742            assert_eq!(autor, entry);
8743            assert!(
8744                reason.contains("control character"),
8745                "{entry:?} reason: {reason}",
8746            );
8747        }
8748    }
8749
8750    #[test]
8751    fn validate_autores_accepts_unicode_entry() {
8752        // Unicode positive control: realistic maintainer names carry
8753        // Unicode (`François`, `日本語`, `naïve`). The predicate must
8754        // round-trip Unicode losslessly, peer with the
8755        // `chart_maintainer_name_shape_accepts_unicode` substrate-side
8756        // sweep.
8757        let c = caixa_with_autores(vec![
8758            "François Dupont",
8759            "日本語の名前",
8760            "naïve <naive@example.com>",
8761        ]);
8762        c.validate_autores().unwrap();
8763    }
8764
8765    #[test]
8766    fn validate_autores_empty_takes_precedence_over_shape() {
8767        // Per-entry empty-first cascade pin: an entry that is both
8768        // empty *and* shape-invalid surfaces `AutorEmpty` (the narrower
8769        // "this entry has no value" structural defect dominates the
8770        // broader shape-predicate diagnostic). The empty arm fires
8771        // before the shape predicate is consulted, mirroring the peer
8772        // `validate_repositorio_empty_takes_precedence_over_shape`
8773        // cascade on the universal `Option<String>` siblings — and now
8774        // established on the Vec<String> per-entry surface.
8775        let c = caixa_with_autores(vec![""]);
8776        let err = c.validate_autores().unwrap_err();
8777        assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
8778    }
8779
8780    #[test]
8781    fn validate_autores_shape_takes_precedence_over_duplicate() {
8782        // Per-entry shape-before-cross-entry-duplicate cascade pin: an
8783        // entry that is malformed surfaces `AutorInvalid` even when a
8784        // later entry would have collided on duplicate. The per-entry
8785        // shape arm fires inside the same loop iteration as the empty
8786        // arm, before the seen-set insert at end-of-iteration —
8787        // structural per-entry defects dominate the cross-entry
8788        // uniqueness diagnostic.
8789        let c = caixa_with_autores(vec!["alice\nbob", "alice\nbob"]);
8790        let err = c.validate_autores().unwrap_err();
8791        assert!(
8792            matches!(err, ManifestError::AutorInvalid { .. }),
8793            "got {err:?}",
8794        );
8795    }
8796
8797    #[test]
8798    fn validate_autores_invalid_diagnostic_names_offending_slot_and_value() {
8799        // Diagnostic-shape pin on the new shape arm (peer with
8800        // `validate_descricao_invalid_diagnostic_carries_offending_value`):
8801        // the rendered Display surfaces both the offending slot name
8802        // and the offending value verbatim, so a `feira lint` run
8803        // points the author at the exact `:autores` entry to fix.
8804        let c = caixa_with_autores(vec!["alice\nbob"]);
8805        let rendered = c.validate_autores().unwrap_err().to_string();
8806        assert!(
8807            rendered.contains(":autores"),
8808            "diagnostic must name the offending slot: {rendered}",
8809        );
8810        assert!(
8811            rendered.contains("alice\\nbob"),
8812            "diagnostic must quote the offending value (debug-escaped): {rendered}",
8813        );
8814    }
8815
8816    #[test]
8817    fn validate_autores_rejects_at_129_byte_boundary() {
8818        // The 128-byte cap pin — boundary-exceeding case rejected,
8819        // boundary-accepting case passes. Mirrors the peer
8820        // `chart_maintainer_name_shape_rejects_at_129_byte_boundary`
8821        // substrate-side pin, surfaced at the per-axis caller so the
8822        // cap propagates through validate end-to-end. Constructed as
8823        // a single all-`a` token so only the cap arm fires.
8824        let max_ok = "a".repeat(128);
8825        let c = caixa_with_autores(vec![max_ok.as_str()]);
8826        c.validate_autores().unwrap();
8827        let too_long = "a".repeat(129);
8828        let c = caixa_with_autores(vec![too_long.as_str()]);
8829        let err = c.validate_autores().unwrap_err();
8830        let ManifestError::AutorInvalid { reason, .. } = err else {
8831            panic!("expected AutorInvalid, got {err:?}");
8832        };
8833        assert!(reason.contains("128"), "got: {reason}");
8834        assert!(reason.contains("129"), "got: {reason}");
8835    }
8836
8837    // ── validate_repositorio — universal-axis git-repo-URL shape ──────
8838
8839    fn caixa_with_repositorio(repositorio: Option<&str>) -> Caixa {
8840        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8841        c.repositorio = repositorio.map(String::from);
8842        c
8843    }
8844
8845    #[test]
8846    fn validate_repositorio_accepts_none() {
8847        // The omit-the-slot identity: `:repositorio` is optional. The
8848        // gate is a no-op when the author didn't declare a value —
8849        // every caixa without a `:repositorio` line trivially passes,
8850        // and the substrate-side renderers fall back to their
8851        // documented placeholder (`caixa-helm`'s `home: None`,
8852        // `caixa-flux`'s `https://github.com/pleme-io/<nome>` derived
8853        // URL). Mirrors the peer `validate_restart_window_accepts_none`
8854        // posture on the other `Option<String>` Caixa slot.
8855        let c = caixa_with_repositorio(None);
8856        c.validate_repositorio().unwrap();
8857    }
8858
8859    #[test]
8860    fn validate_repositorio_accepts_canonical_forms() {
8861        // Positive control sweep across every documented `:repositorio`
8862        // authoring shape — the same union the shared
8863        // `crate::render::is_git_repo_url` predicate accepts and the
8864        // peer `:deps :fonte :repo` axis already routes through.
8865        // Covers the `github:` shorthand (the canonical pleme-io
8866        // convention used in the `:repositorio` field of every
8867        // manifest fixture across `caixa-helm` / `caixa-mesh` and the
8868        // `examples/`), the `https://…` URL the README quickstart uses,
8869        // the `ssh://`, `git://`, `git@host:path` scp-style SSH, and
8870        // `file://` URL schemes the shared predicate documents.
8871        for repo in [
8872            "github:pleme-io/hello-rio",
8873            "github:pleme-io/checkout",
8874            "https://github.com/pleme-io/hello-rio",
8875            "ssh://git@github.com/pleme-io/hello-rio.git",
8876            "git://github.com/pleme-io/hello-rio.git",
8877            "git@github.com:pleme-io/hello-rio.git",
8878            "file:///srv/pleme/hello-rio",
8879        ] {
8880            let c = caixa_with_repositorio(Some(repo));
8881            c.validate_repositorio()
8882                .unwrap_or_else(|err| panic!("canonical {repo:?} must pass: {err:?}"));
8883        }
8884    }
8885
8886    #[test]
8887    fn validate_repositorio_rejects_empty_some() {
8888        // Canonical paste-from-blank-doc footgun. The narrower
8889        // [`ManifestError::RepositorioEmpty`] arm fires before the
8890        // shape predicate is consulted, mirroring the empty-first
8891        // cascade every peer per-axis identity gate uses
8892        // (`NomeEmpty` → `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid`,
8893        // `FonteRepoEmpty` → `FonteRepoInvalid`). Without this gate
8894        // the empty `Some("")` silently passed the renderer's
8895        // `Option::unwrap_or_else(|| <fallback>)` (which only fires
8896        // on `None`) and landed as `home: ""` in `Chart.yaml` /
8897        // `url: ""` in the FluxCD `GitRepository`.
8898        let c = caixa_with_repositorio(Some(""));
8899        let err = c.validate_repositorio().unwrap_err();
8900        assert!(
8901            matches!(err, ManifestError::RepositorioEmpty),
8902            "got {err:?}",
8903        );
8904    }
8905
8906    #[test]
8907    fn validate_repositorio_rejects_whitespace() {
8908        // Paste-from-doc whitespace footgun. The shared
8909        // `is_git_repo_url` predicate refuses any whitespace byte; a
8910        // trailing space in a `:repositorio` value silently broke
8911        // `git clone '<value> '` at clone time. The diagnostic names
8912        // the offending value verbatim.
8913        let c = caixa_with_repositorio(Some("github:pleme-io/hello-rio "));
8914        let err = c.validate_repositorio().unwrap_err();
8915        let ManifestError::RepositorioInvalid { repositorio, .. } = err else {
8916            panic!("expected RepositorioInvalid, got {err:?}");
8917        };
8918        assert_eq!(repositorio, "github:pleme-io/hello-rio ");
8919    }
8920
8921    #[test]
8922    fn validate_repositorio_rejects_control_char() {
8923        // Paste-from-multiline-doc CRLF footgun — control characters
8924        // at the URL boundary are a class of subprocess-arg injection
8925        // and break git's URL parser at every porcelain entry point.
8926        let c = caixa_with_repositorio(Some("https://example.com/repo\n"));
8927        let err = c.validate_repositorio().unwrap_err();
8928        assert!(
8929            matches!(err, ManifestError::RepositorioInvalid { .. }),
8930            "got {err:?}",
8931        );
8932    }
8933
8934    #[test]
8935    fn validate_repositorio_rejects_leading_dash() {
8936        // Canonical CLI-argument-injection footgun: `git clone <repo>`
8937        // interprets a leading `-` as a CLI flag, so a
8938        // `-upload-pack=…` value escapes the subprocess argument
8939        // boundary. The shared predicate refuses every leading-`-`
8940        // shape at validate time.
8941        let c = caixa_with_repositorio(Some("-upload-pack=evil"));
8942        let err = c.validate_repositorio().unwrap_err();
8943        assert!(
8944            matches!(err, ManifestError::RepositorioInvalid { .. }),
8945            "got {err:?}",
8946        );
8947    }
8948
8949    #[test]
8950    fn validate_repositorio_rejects_missing_colon_separator() {
8951        // The bare `org/repo` ambiguity footgun — `git clone` reads
8952        // a no-`:` form as a relative filesystem path rather than the
8953        // GitHub-shorthand expansion the author probably intended.
8954        // The shared predicate refuses every shape without a `:`
8955        // separator.
8956        let c = caixa_with_repositorio(Some("pleme-io/hello-rio"));
8957        let err = c.validate_repositorio().unwrap_err();
8958        assert!(
8959            matches!(err, ManifestError::RepositorioInvalid { .. }),
8960            "got {err:?}",
8961        );
8962    }
8963
8964    #[test]
8965    fn validate_repositorio_rejects_fragment_anchor() {
8966        // Paste-from-browser-address-bar footgun on the
8967        // `:repositorio` axis — an author copies a GitHub permalink
8968        // to a README section / line-permalink and forgets to trim
8969        // the `#fragment` tail. The shared `is_git_repo_url`
8970        // predicate refuses the byte at the URL-grammar layer
8971        // (libcurl strips the fragment before opening the
8972        // transport, so the byte rides verbatim into the rendered
8973        // `Chart.yaml` `home:` and FluxCD `GitRepository` `url:`
8974        // fields but is silently dropped on the wire — two
8975        // manifest variants whose values differ only in their
8976        // fragment anchor lock to two distinct rendered artifacts
8977        // for the byte-identical clone, defeating the THEORY.md
8978        // §V.2 render-determinism contract on the `:repositorio`
8979        // axis the peer `:fonte :repo` axis already closes).
8980        let c = caixa_with_repositorio(Some("https://github.com/pleme-io/hello-rio#readme"));
8981        let err = c.validate_repositorio().unwrap_err();
8982        let ManifestError::RepositorioInvalid {
8983            repositorio,
8984            reason,
8985        } = err
8986        else {
8987            panic!("expected RepositorioInvalid, got {err:?}");
8988        };
8989        assert_eq!(repositorio, "https://github.com/pleme-io/hello-rio#readme");
8990        assert!(
8991            reason.contains("must not contain `#`"),
8992            "reason must surface the fragment-`#` arm, got {reason:?}"
8993        );
8994    }
8995
8996    #[test]
8997    fn validate_repositorio_rejects_query_string() {
8998        // Paste-from-browser-address-bar footgun on the
8999        // `:repositorio` axis (peer with the a68f818 fragment-`#`
9000        // arm on the same axis). An author copies a GitHub tab
9001        // deep-link out of the address bar and forgets to trim
9002        // the `?tab=…` query tail. The shared `is_git_repo_url`
9003        // predicate refuses the byte at the URL-grammar layer
9004        // (GitHub / GitLab / Bitbucket silently ignore the
9005        // `?query` tail and serve the same repo regardless, so
9006        // the byte rides verbatim into the rendered `Chart.yaml`
9007        // `home:` and FluxCD `GitRepository` `url:` fields but
9008        // is silently masked at the wire — two manifest variants
9009        // whose values differ only in their query tail lock to
9010        // two distinct rendered artifacts for the byte-identical
9011        // clone, defeating the THEORY.md §V.2 render-determinism
9012        // contract on the `:repositorio` axis the peer `:fonte
9013        // :repo` axis already closes).
9014        let c = caixa_with_repositorio(Some(
9015            "https://github.com/pleme-io/hello-rio?tab=readme-ov-file",
9016        ));
9017        let err = c.validate_repositorio().unwrap_err();
9018        let ManifestError::RepositorioInvalid {
9019            repositorio,
9020            reason,
9021        } = err
9022        else {
9023            panic!("expected RepositorioInvalid, got {err:?}");
9024        };
9025        assert_eq!(
9026            repositorio,
9027            "https://github.com/pleme-io/hello-rio?tab=readme-ov-file"
9028        );
9029        assert!(
9030            reason.contains("must not contain `?`"),
9031            "reason must surface the query-`?` arm, got {reason:?}"
9032        );
9033    }
9034
9035    #[test]
9036    fn validate_repositorio_rejects_embedded_backslash() {
9037        // Windows-file-path-confusion footgun on the `:repositorio`
9038        // axis (peer with the prior fragment-`#` / query-`?` arms on
9039        // the same axis, and peer with the new dep-level `:fonte :repo`
9040        // backslash arm on the URL-grammar trajectory). An author
9041        // pastes a Windows Explorer address-bar `file:///C:\Users\me\
9042        // hello-rio` into the `:repositorio` slot, expecting the
9043        // `lareira-<nome>` chart's `home:` field and the FluxCD
9044        // `GitRepository` `url:` field to render the canonical local
9045        // file-URI. The shared `is_git_repo_url` predicate refuses
9046        // the byte at the URL-grammar layer (libcurl silently
9047        // translates `\` → `/` on some platforms and refuses it on
9048        // others, so the byte rides verbatim into the rendered
9049        // artifacts but is silently rewritten or rejected at the wire
9050        // — two manifest variants whose values differ only in
9051        // backslash-vs-forward-slash lock to two distinct rendered
9052        // artifacts for the byte-identical clone, defeating the
9053        // THEORY.md §V.2 render-determinism contract on the
9054        // `:repositorio` axis the peer `:fonte :repo` axis already
9055        // closes).
9056        let c = caixa_with_repositorio(Some("file:///C:\\Users\\me\\hello-rio"));
9057        let err = c.validate_repositorio().unwrap_err();
9058        let ManifestError::RepositorioInvalid {
9059            repositorio,
9060            reason,
9061        } = err
9062        else {
9063            panic!("expected RepositorioInvalid, got {err:?}");
9064        };
9065        assert_eq!(repositorio, "file:///C:\\Users\\me\\hello-rio");
9066        assert!(
9067            reason.contains("must not contain `\\`"),
9068            "reason must surface the backslash-`\\` arm, got {reason:?}"
9069        );
9070    }
9071
9072    #[test]
9073    fn validate_repositorio_rejects_uri_template_placeholder() {
9074        // URI Template (RFC 6570) placeholder footgun on the
9075        // `:repositorio` axis (peer with the prior fragment-`#` /
9076        // query-`?` / backslash-`\` arms on the same axis, and peer
9077        // with the new dep-level `:fonte :repo` `{` / `}` arm on the
9078        // URL-grammar trajectory). An author pastes a quick-start
9079        // README snippet / OpenAPI `servers:` URL / Helm chart
9080        // `home:` template carrying unresolved `{org}` / `{repo}`
9081        // placeholders into the `:repositorio` slot, expecting the
9082        // substrate to resolve the placeholder downstream. The
9083        // shared `is_git_repo_url` predicate refuses the byte at the
9084        // URL-grammar layer (libcurl percent-encodes `{` / `}` to
9085        // `%7B` / `%7D` on the wire, so the byte round-trips
9086        // inconsistently between the rendered `Chart.yaml home:` /
9087        // FluxCD `GitRepository url:` and the resolver's `git clone`
9088        // invocation, defeating the THEORY.md §V.2 render-
9089        // determinism contract on the `:repositorio` axis the peer
9090        // `:fonte :repo` axis already closes; every git porcelain
9091        // entry-point additionally fetches a nonexistent literal-
9092        // `{placeholder}`-named path far from the source caixa.lisp).
9093        let c = caixa_with_repositorio(Some("https://github.com/{org}/hello-rio"));
9094        let err = c.validate_repositorio().unwrap_err();
9095        let ManifestError::RepositorioInvalid {
9096            repositorio,
9097            reason,
9098        } = err
9099        else {
9100            panic!("expected RepositorioInvalid, got {err:?}");
9101        };
9102        assert_eq!(repositorio, "https://github.com/{org}/hello-rio");
9103        assert!(
9104            reason.contains("must not contain `{`"),
9105            "reason must surface the open-brace `{{` arm, got {reason:?}"
9106        );
9107        assert!(
9108            reason.contains("URI Template") || reason.contains("RFC 6570"),
9109            "reason must name the RFC 6570 URI Template grammar, got {reason:?}"
9110        );
9111    }
9112
9113    #[test]
9114    fn validate_repositorio_empty_takes_precedence_over_shape() {
9115        // Empty-first cascade pin: the empty `Some("")` surfaces the
9116        // narrower `RepositorioEmpty` not the shape-predicate-wrapped
9117        // `RepositorioInvalid`, mirroring the peer
9118        // `NomeEmpty` → `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid`,
9119        // `FonteRepoEmpty` → `FonteRepoInvalid` cascades. The shared
9120        // `is_git_repo_url` predicate also rejects the empty input
9121        // (defensively, with its own `"must not be empty"` reason),
9122        // but the manifest-layer empty arm runs first to surface the
9123        // narrower diagnostic verbatim.
9124        let c = caixa_with_repositorio(Some(""));
9125        let err = c.validate_repositorio().unwrap_err();
9126        assert!(
9127            matches!(err, ManifestError::RepositorioEmpty),
9128            "got {err:?}",
9129        );
9130    }
9131
9132    #[test]
9133    fn validate_repositorio_diagnostic_carries_offending_value() {
9134        // Diagnostic-shape pin (peer with
9135        // `validate_autores_diagnostic_carries_offending_author`): the
9136        // error's Display surfaces the offending value + slot name
9137        // verbatim, so a `feira lint` run can render the diagnostic
9138        // without re-parsing and the author can grep their caixa.lisp
9139        // for the offending `:repositorio` value.
9140        let c = caixa_with_repositorio(Some("pleme-io/hello-rio"));
9141        let rendered = c.validate_repositorio().unwrap_err().to_string();
9142        assert!(
9143            rendered.contains(":repositorio"),
9144            "diagnostic must name the offending slot: {rendered}",
9145        );
9146        assert!(
9147            rendered.contains("pleme-io/hello-rio"),
9148            "diagnostic must quote the offending value: {rendered}",
9149        );
9150    }
9151
9152    // ── validate_descricao — universal-axis Chart.yaml description shape ──
9153
9154    fn caixa_with_descricao(descricao: Option<&str>) -> Caixa {
9155        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9156        c.descricao = descricao.map(String::from);
9157        c
9158    }
9159
9160    #[test]
9161    fn validate_descricao_accepts_none() {
9162        // The omit-the-slot identity: `:descricao` is optional. The
9163        // gate is a no-op when the author didn't declare a value —
9164        // every caixa without a `:descricao` line trivially passes,
9165        // and the substrate-side renderers fall back to their
9166        // documented `caixa.nome`-derived placeholder. Mirrors the
9167        // peer `validate_repositorio_accepts_none` posture on the
9168        // sibling `Option<String>` Caixa slot.
9169        let c = caixa_with_descricao(None);
9170        c.validate_descricao().unwrap();
9171    }
9172
9173    #[test]
9174    fn validate_descricao_accepts_canonical_summary() {
9175        // Positive control: the canonical pleme-io descricao shape —
9176        // a short free-form prose summary — passes the gate. Covers
9177        // the fixture shapes the `caixa-helm` / `caixa-flux` /
9178        // `caixa-mesh` test fixtures use (`"Canonical Rust→wasm32-
9179        // wasip2 caixa Servico."`, `"Checkout flow."`).
9180        for desc in [
9181            "Canonical Rust→wasm32-wasip2 caixa Servico.",
9182            "Checkout flow.",
9183            "AWS provider caixa for tatara-lisp",
9184            "FIXME — describe this caixa",
9185            "x",
9186        ] {
9187            let c = caixa_with_descricao(Some(desc));
9188            c.validate_descricao()
9189                .unwrap_or_else(|err| panic!("canonical {desc:?} must pass: {err:?}"));
9190        }
9191    }
9192
9193    #[test]
9194    fn validate_descricao_rejects_empty_some() {
9195        // Canonical paste-from-blank-doc footgun. Without this gate
9196        // the empty `Some("")` silently passed the renderer's
9197        // `Option::unwrap_or_else(|| <fallback>)` (which only fires
9198        // on `None`) and landed as `description: ""` in `Chart.yaml`
9199        // and a blank `README.md` header. Mirrors the peer
9200        // [`ManifestError::RepositorioEmpty`] empty-arm on the
9201        // sibling `Option<String>` Caixa slot.
9202        let c = caixa_with_descricao(Some(""));
9203        let err = c.validate_descricao().unwrap_err();
9204        assert!(matches!(err, ManifestError::DescricaoEmpty), "got {err:?}",);
9205    }
9206
9207    #[test]
9208    fn validate_descricao_rejects_leading_whitespace() {
9209        // Paste-from-aligned-doc footgun: a leading ASCII space the
9210        // bare empty-arm gate accepted, the shape predicate now
9211        // refuses. The diagnostic carries the offending value
9212        // verbatim (with the leading space preserved) so the author
9213        // can grep their caixa.lisp for the exact `:descricao` line
9214        // and fix the round-trip-inconsistent leading whitespace.
9215        // Mirrors the peer
9216        // `validate_licenca_rejects_leading_whitespace` arm on the
9217        // sibling `:licenca` axis.
9218        let c = caixa_with_descricao(Some(" Checkout flow."));
9219        let err = c.validate_descricao().unwrap_err();
9220        let ManifestError::DescricaoInvalid { descricao, reason } = err else {
9221            panic!("expected DescricaoInvalid, got {err:?}");
9222        };
9223        assert_eq!(descricao, " Checkout flow.");
9224        assert!(reason.contains("whitespace"), "got: {reason:?}");
9225    }
9226
9227    #[test]
9228    fn validate_descricao_rejects_trailing_whitespace() {
9229        // Paste-from-doc footgun: a trailing ASCII space the bare
9230        // empty-arm gate accepted, the shape predicate now refuses.
9231        let c = caixa_with_descricao(Some("Checkout flow. "));
9232        let err = c.validate_descricao().unwrap_err();
9233        let ManifestError::DescricaoInvalid { descricao, reason } = err else {
9234            panic!("expected DescricaoInvalid, got {err:?}");
9235        };
9236        assert_eq!(descricao, "Checkout flow. ");
9237        assert!(reason.contains("whitespace"), "got: {reason:?}");
9238    }
9239
9240    #[test]
9241    fn validate_descricao_rejects_embedded_newline() {
9242        // Paste-from-multiline-doc footgun: an embedded LF the bare
9243        // empty-arm gate accepted, the shape predicate now refuses.
9244        // Without this gate the embedded newline silently landed in
9245        // the rendered Chart.yaml as a multi-line YAML block scalar,
9246        // and every chart-aware UI (`helm list`, `helm search`,
9247        // Artifact Hub) renders the description in a single-line
9248        // column so the embedded newline is silently dropped at
9249        // every downstream consumer.
9250        let c = caixa_with_descricao(Some("Checkout\nflow."));
9251        let err = c.validate_descricao().unwrap_err();
9252        assert!(
9253            matches!(err, ManifestError::DescricaoInvalid { .. }),
9254            "got {err:?}",
9255        );
9256        assert!(err.to_string().contains("newline"), "got {err}");
9257    }
9258
9259    #[test]
9260    fn validate_descricao_rejects_embedded_carriage_return() {
9261        // Paste-from-Windows-CRLF-doc footgun.
9262        let c = caixa_with_descricao(Some("Checkout\rflow."));
9263        let err = c.validate_descricao().unwrap_err();
9264        assert!(
9265            matches!(err, ManifestError::DescricaoInvalid { .. }),
9266            "got {err:?}",
9267        );
9268        assert!(err.to_string().contains("carriage return"), "got {err}");
9269    }
9270
9271    #[test]
9272    fn validate_descricao_rejects_embedded_tab() {
9273        // Tab-from-aligned-doc footgun.
9274        let c = caixa_with_descricao(Some("Checkout\tflow."));
9275        let err = c.validate_descricao().unwrap_err();
9276        assert!(
9277            matches!(err, ManifestError::DescricaoInvalid { .. }),
9278            "got {err:?}",
9279        );
9280        assert!(err.to_string().contains("tab"), "got {err}");
9281    }
9282
9283    #[test]
9284    fn validate_descricao_rejects_embedded_control_bytes() {
9285        // Paste-from-binary-blob footgun: every other control byte
9286        // (NUL, BEL, ESC, DEL) is refused at validate time. Mirrors
9287        // the peer SPDX-expression control-byte arm.
9288        for s in [
9289            "Checkout\x00flow.",
9290            "Checkout\x07flow.",
9291            "Checkout\x1bflow.",
9292            "Checkout\x7fflow.",
9293        ] {
9294            let c = caixa_with_descricao(Some(s));
9295            let err = c.validate_descricao().unwrap_err();
9296            assert!(
9297                matches!(err, ManifestError::DescricaoInvalid { .. }),
9298                "{s:?} got {err:?}",
9299            );
9300            assert!(
9301                err.to_string().contains("control character"),
9302                "{s:?} got {err}",
9303            );
9304        }
9305    }
9306
9307    #[test]
9308    fn validate_descricao_accepts_unicode_prose() {
9309        // Positive control: Unicode prose is accepted — the
9310        // canonical fixtures carry `→` (U+2192) and `—` (U+2014),
9311        // and `Caixa::template`'s `"FIXME — describe this caixa"`
9312        // scaffold every `feira init` emits must continue to pass.
9313        for s in [
9314            "Canonical Rust→wasm32-wasip2 caixa Servico.",
9315            "FIXME — describe this caixa",
9316            "Caixa pour le projet tâche",
9317            "日本語の説明",
9318        ] {
9319            let c = caixa_with_descricao(Some(s));
9320            c.validate_descricao()
9321                .unwrap_or_else(|err| panic!("Unicode {s:?} must pass: {err:?}"));
9322        }
9323    }
9324
9325    #[test]
9326    fn validate_descricao_empty_takes_precedence_over_shape() {
9327        // Cascade pin: a `Some("")` surfaces the narrower
9328        // `DescricaoEmpty` arm, not the broader `DescricaoInvalid`
9329        // shape-predicate arm. Mirrors the peer
9330        // `validate_licenca_empty_takes_precedence_over_shape` pin
9331        // on the sibling `:licenca` axis.
9332        let c = caixa_with_descricao(Some(""));
9333        let err = c.validate_descricao().unwrap_err();
9334        assert!(matches!(err, ManifestError::DescricaoEmpty), "got {err:?}",);
9335    }
9336
9337    #[test]
9338    fn validate_descricao_invalid_diagnostic_carries_offending_value_and_slot() {
9339        // Diagnostic-shape pin: the error's Display surfaces both
9340        // the `:descricao` slot name and the offending value
9341        // verbatim, so a `feira lint` run can render the diagnostic
9342        // without re-parsing and the author can grep their caixa.lisp
9343        // for the offending `:descricao` line. Mirrors the peer
9344        // `validate_licenca_invalid_diagnostic_carries_offending_value_and_slot`
9345        // pin (ee2e888) on the sibling `:licenca` axis.
9346        // The `{descricao:?}` Debug format escapes embedded control
9347        // bytes; the quoted offending value surfaces as
9348        // `"Checkout\nflow."` (literal backslash-n) in the rendered
9349        // diagnostic. The author can grep their caixa.lisp for the
9350        // literal `Checkout` summary prefix.
9351        let c = caixa_with_descricao(Some("Checkout\nflow."));
9352        let rendered = c.validate_descricao().unwrap_err().to_string();
9353        assert!(
9354            rendered.contains(":descricao"),
9355            "diagnostic must name the offending slot: {rendered}",
9356        );
9357        assert!(
9358            rendered.contains("Checkout\\nflow."),
9359            "diagnostic must quote the offending value (debug-escaped): {rendered}",
9360        );
9361    }
9362
9363    #[test]
9364    fn validate_descricao_template_passes() {
9365        // Round-trip pin: the bare `Caixa::template` shape carries
9366        // `:descricao "FIXME — describe this caixa"` (a non-empty
9367        // sentinel), so the template-derived Caixa passes the gate by
9368        // construction. A future template-shape change that omits or
9369        // empties `:descricao` would surface here as a regression.
9370        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9371        c.validate_descricao().unwrap();
9372    }
9373
9374    #[test]
9375    fn validate_descricao_diagnostic_names_offending_slot() {
9376        // Diagnostic-shape pin (peer with
9377        // `validate_repositorio_diagnostic_carries_offending_value`):
9378        // the error's Display surfaces the `:descricao` slot name
9379        // verbatim, so a `feira lint` run can render the diagnostic
9380        // without re-parsing and the author can grep their caixa.lisp
9381        // for the offending `:descricao` line.
9382        let c = caixa_with_descricao(Some(""));
9383        let rendered = c.validate_descricao().unwrap_err().to_string();
9384        assert!(
9385            rendered.contains(":descricao"),
9386            "diagnostic must name the offending slot: {rendered}",
9387        );
9388    }
9389
9390    // ── validate_licenca — universal-axis chart README license shape ──
9391
9392    fn caixa_with_licenca(licenca: Option<&str>) -> Caixa {
9393        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9394        c.licenca = licenca.map(String::from);
9395        c
9396    }
9397
9398    #[test]
9399    fn validate_licenca_accepts_none() {
9400        // The omit-the-slot identity: `:licenca` is optional. The
9401        // gate is a no-op when the author didn't declare a value —
9402        // every caixa without a `:licenca` line trivially passes,
9403        // and the substrate-side `caixa-helm` renderer falls back to
9404        // the documented `"MIT"` placeholder. Mirrors the peer
9405        // `validate_descricao_accepts_none` posture on the sibling
9406        // `Option<String>` Caixa slot.
9407        let c = caixa_with_licenca(None);
9408        c.validate_licenca().unwrap();
9409    }
9410
9411    #[test]
9412    fn validate_licenca_accepts_canonical_expressions() {
9413        // Positive control: every canonical SPDX expression shape
9414        // pleme-io carries in its existing fixtures + the canonical
9415        // SPDX dual-license / with-exception / `+`-suffix / grouped /
9416        // user-defined-reference shapes all pass the gate. Covers
9417        // the single-license, `OR`-compound, `AND`-compound,
9418        // `WITH`-exception, parenthesis-grouped, `+`-suffix, and
9419        // `LicenseRef-` / `DocumentRef-:LicenseRef-` shapes — every
9420        // production the SPDX 2.1 expression grammar admits that
9421        // sits within the alphabet floor the
9422        // `is_spdx_expression_shape` predicate enforces.
9423        for lic in [
9424            "MIT",
9425            "Apache-2.0",
9426            "Apache-2.0 OR MIT",
9427            "Apache-2.0 AND MIT",
9428            "BSD-3-Clause",
9429            "MPL-2.0",
9430            "GPL-3.0-or-later",
9431            "GPL-2.0+",
9432            "Apache-2.0 WITH LLVM-exception",
9433            "(MIT OR Apache-2.0) AND BSD-3-Clause",
9434            "(MIT OR Apache-2.0) AND BSD-3-Clause AND ISC",
9435            "LicenseRef-MyLicense",
9436            "DocumentRef-spdx-tool:LicenseRef-MIT-Style",
9437            "x",
9438        ] {
9439            let c = caixa_with_licenca(Some(lic));
9440            c.validate_licenca()
9441                .unwrap_or_else(|err| panic!("canonical {lic:?} must pass: {err:?}"));
9442        }
9443    }
9444
9445    #[test]
9446    fn validate_licenca_rejects_trailing_whitespace() {
9447        // Paste-from-doc whitespace footgun. A trailing space in the
9448        // `:licenca` value would silently break a downstream SPDX
9449        // parser that splits on exact `AND` / `OR` / `WITH` keyword
9450        // boundaries. The shape predicate refuses every trailing
9451        // whitespace byte by construction. Peer with
9452        // `validate_repositorio_rejects_whitespace` and
9453        // `validate_edicao_rejects_trailing_whitespace`.
9454        let c = caixa_with_licenca(Some("MIT "));
9455        let err = c.validate_licenca().unwrap_err();
9456        let ManifestError::LicencaInvalid { licenca, .. } = err else {
9457            panic!("expected LicencaInvalid, got {err:?}");
9458        };
9459        assert_eq!(licenca, "MIT ");
9460    }
9461
9462    #[test]
9463    fn validate_licenca_rejects_leading_whitespace() {
9464        // Symmetric paste-from-doc whitespace footgun on the leading
9465        // boundary — the gate refuses every shape that starts with a
9466        // space byte by construction. Peer with
9467        // `validate_edicao_rejects_leading_whitespace`.
9468        let c = caixa_with_licenca(Some(" MIT"));
9469        let err = c.validate_licenca().unwrap_err();
9470        assert!(
9471            matches!(err, ManifestError::LicencaInvalid { .. }),
9472            "got {err:?}",
9473        );
9474    }
9475
9476    #[test]
9477    fn validate_licenca_rejects_control_char() {
9478        // Paste-from-multiline-doc CRLF footgun — control characters
9479        // at the value boundary land as a malformed line in the
9480        // rendered chart `README.md` `## License` section. Peer with
9481        // `validate_repositorio_rejects_control_char` and
9482        // `validate_edicao_rejects_control_char`.
9483        for lic in ["MIT\n", "MIT\r\n", "MIT\rApache-2.0"] {
9484            let c = caixa_with_licenca(Some(lic));
9485            let err = c.validate_licenca().unwrap_err();
9486            assert!(
9487                matches!(err, ManifestError::LicencaInvalid { .. }),
9488                "expected LicencaInvalid on {lic:?}, got {err:?}",
9489            );
9490        }
9491    }
9492
9493    #[test]
9494    fn validate_licenca_rejects_tab() {
9495        // Tab-from-aligned-doc footgun — SPDX expressions use a
9496        // single ASCII space between tokens; a tab breaks every
9497        // downstream SPDX parser that splits on exact `" "`
9498        // boundaries.
9499        let c = caixa_with_licenca(Some("MIT\tOR Apache-2.0"));
9500        let err = c.validate_licenca().unwrap_err();
9501        assert!(
9502            matches!(err, ManifestError::LicencaInvalid { .. }),
9503            "got {err:?}",
9504        );
9505    }
9506
9507    #[test]
9508    fn validate_licenca_rejects_non_ascii() {
9509        // Smart-quote / non-ASCII paste footgun — SPDX identifiers
9510        // are ASCII per the `idstring = 1*(ALPHA / DIGIT / "-" /
9511        // ".")` production. The shape predicate refuses every
9512        // non-ASCII byte by construction; peer with
9513        // `validate_edicao_rejects_non_ascii_lookalike`.
9514        for lic in ["MIT\u{a0}OR Apache-2.0", "MIT\u{2013}1.0", "Café-1.0"] {
9515            let c = caixa_with_licenca(Some(lic));
9516            let err = c.validate_licenca().unwrap_err();
9517            assert!(
9518                matches!(err, ManifestError::LicencaInvalid { .. }),
9519                "expected LicencaInvalid on {lic:?}, got {err:?}",
9520            );
9521        }
9522    }
9523
9524    #[test]
9525    fn validate_licenca_rejects_underscore() {
9526        // Underscore-instead-of-hyphen typo footgun — `Apache_2.0` /
9527        // `MIT_Style` / `BSD_3_Clause` are familiar shapes from
9528        // snake-case identifier conventions that don't apply to the
9529        // SPDX `idstring` grammar (which admits only `ALPHA / DIGIT /
9530        // "-" / "."`). The shape predicate refuses every underscore
9531        // byte by construction.
9532        for lic in ["Apache_2.0", "MIT_Style", "BSD_3_Clause"] {
9533            let c = caixa_with_licenca(Some(lic));
9534            let err = c.validate_licenca().unwrap_err();
9535            assert!(
9536                matches!(err, ManifestError::LicencaInvalid { .. }),
9537                "expected LicencaInvalid on {lic:?}, got {err:?}",
9538            );
9539        }
9540    }
9541
9542    #[test]
9543    fn validate_licenca_rejects_comma_separator() {
9544        // Comma-instead-of-`OR`-keyword colloquial idiom footgun —
9545        // SPDX expressions compose multiple licenses via `AND` / `OR`
9546        // keywords, not the comma separator. The shape predicate
9547        // refuses every comma byte by construction.
9548        for lic in ["MIT, Apache-2.0", "MIT,Apache-2.0"] {
9549            let c = caixa_with_licenca(Some(lic));
9550            let err = c.validate_licenca().unwrap_err();
9551            assert!(
9552                matches!(err, ManifestError::LicencaInvalid { .. }),
9553                "expected LicencaInvalid on {lic:?}, got {err:?}",
9554            );
9555        }
9556    }
9557
9558    #[test]
9559    fn validate_licenca_rejects_slash_dual_license() {
9560        // Slash-dual-license colloquial idiom footgun — the
9561        // `MIT/Apache-2.0` shape is common in Cargo's pre-SPDX
9562        // `package.license` field but non-SPDX; the SPDX equivalent
9563        // is `MIT OR Apache-2.0`. The shape predicate refuses every
9564        // forward-slash byte by construction.
9565        for lic in ["MIT/Apache-2.0", "MIT/BSD-3-Clause"] {
9566            let c = caixa_with_licenca(Some(lic));
9567            let err = c.validate_licenca().unwrap_err();
9568            assert!(
9569                matches!(err, ManifestError::LicencaInvalid { .. }),
9570                "expected LicencaInvalid on {lic:?}, got {err:?}",
9571            );
9572        }
9573    }
9574
9575    #[test]
9576    fn validate_licenca_rejects_semicolon_separator() {
9577        // Semicolon-list-separator confusion footgun — adjacent to
9578        // the comma-separator idiom, every list-separator-belongs-
9579        // to-list-grammar confusion lands here.
9580        let c = caixa_with_licenca(Some("MIT; Apache-2.0"));
9581        let err = c.validate_licenca().unwrap_err();
9582        assert!(
9583            matches!(err, ManifestError::LicencaInvalid { .. }),
9584            "got {err:?}",
9585        );
9586    }
9587
9588    #[test]
9589    fn validate_licenca_empty_takes_precedence_over_shape() {
9590        // Empty-first cascade pin: the empty `Some("")` surfaces the
9591        // narrower `LicencaEmpty` not the shape-predicate-wrapped
9592        // `LicencaInvalid`, mirroring the peer
9593        // `validate_edicao_empty_takes_precedence_over_shape` and
9594        // `validate_repositorio_empty_takes_precedence_over_shape`
9595        // (`RepositorioEmpty` → `RepositorioInvalid`), `NomeEmpty` →
9596        // `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid` cascades.
9597        // The shape predicate also refuses the empty input
9598        // (defensively — `"must not be empty"`), but the manifest-
9599        // layer empty arm runs first to surface the narrower
9600        // diagnostic verbatim.
9601        let c = caixa_with_licenca(Some(""));
9602        let err = c.validate_licenca().unwrap_err();
9603        assert!(matches!(err, ManifestError::LicencaEmpty), "got {err:?}",);
9604    }
9605
9606    #[test]
9607    fn validate_licenca_invalid_diagnostic_carries_offending_value() {
9608        // Diagnostic-shape pin on the shape-predicate arm (peer with
9609        // `validate_edicao_invalid_diagnostic_carries_offending_value`
9610        // and `validate_repositorio_diagnostic_carries_offending_value`):
9611        // the error's Display surfaces the offending value + slot
9612        // name verbatim, so a `feira lint` run can render the
9613        // diagnostic without re-parsing and the author can grep
9614        // their caixa.lisp for the offending `:licenca` value.
9615        let c = caixa_with_licenca(Some("Apache_2.0"));
9616        let rendered = c.validate_licenca().unwrap_err().to_string();
9617        assert!(
9618            rendered.contains(":licenca"),
9619            "diagnostic must name the offending slot: {rendered}",
9620        );
9621        assert!(
9622            rendered.contains("Apache_2.0"),
9623            "diagnostic must quote the offending value: {rendered}",
9624        );
9625    }
9626
9627    #[test]
9628    fn validate_licenca_rejects_empty_some() {
9629        // Canonical paste-from-blank-doc footgun. Without this gate
9630        // the empty `Some("")` silently passed the renderer's
9631        // `Option::unwrap_or_else(|| "MIT".into())` (which only
9632        // fires on `None`) and landed as a bare trailing period in
9633        // the rendered chart `README.md` `## License` section.
9634        // Mirrors the peer [`ManifestError::DescricaoEmpty`] empty-
9635        // arm on the sibling `Option<String>` Caixa slot.
9636        let c = caixa_with_licenca(Some(""));
9637        let err = c.validate_licenca().unwrap_err();
9638        assert!(matches!(err, ManifestError::LicencaEmpty), "got {err:?}",);
9639    }
9640
9641    #[test]
9642    fn validate_licenca_template_passes() {
9643        // Round-trip pin: the bare `Caixa::template` shape (whether
9644        // it carries `:licenca` or omits it) passes the gate by
9645        // construction. A future template-shape change that
9646        // introduced `(:licenca "")` would surface here as a
9647        // regression. Mirrors the peer
9648        // `validate_descricao_template_passes` pin.
9649        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9650        c.validate_licenca().unwrap();
9651    }
9652
9653    #[test]
9654    fn validate_licenca_diagnostic_names_offending_slot() {
9655        // Diagnostic-shape pin (peer with
9656        // `validate_descricao_diagnostic_names_offending_slot`):
9657        // the error's Display surfaces the `:licenca` slot name
9658        // verbatim, so a `feira lint` run can render the diagnostic
9659        // without re-parsing and the author can grep their caixa.lisp
9660        // for the offending `:licenca` line.
9661        let c = caixa_with_licenca(Some(""));
9662        let rendered = c.validate_licenca().unwrap_err().to_string();
9663        assert!(
9664            rendered.contains(":licenca"),
9665            "diagnostic must name the offending slot: {rendered}",
9666        );
9667    }
9668
9669    // ── Caixa::licenca — outer top-level Option<&str> scalar accessor ──
9670
9671    #[test]
9672    fn licenca_returns_licenca_byte_string_verbatim_across_permutations() {
9673        // The canonical per-`Caixa` `:licenca` SPDX-expression scalar
9674        // pin: [`Caixa::licenca`] must return the `:licenca` typed
9675        // byte-string verbatim as an `Option<&str>`, byte-equal to the
9676        // raw `self.licenca.as_deref()` access across every
9677        // representative value in the accept-set — `None` (the "omit
9678        // the slot to defer to the caixa-helm renderer's `MIT`
9679        // fallback" arm every existing fixture without a `:licenca`
9680        // line carries), `Some("")` (a past-the-guard sentinel that
9681        // pins the accessor doesn't perform a silent
9682        // `Some("") → None` collapse on the empty arm — validate
9683        // rejects `Some("")` through `LicencaEmpty` but the accessor
9684        // must ship the raw slot verbatim so a validate-time gate
9685        // regression surfaces at the caixa-helm emit boundary rather
9686        // than being silently absorbed into the fallback), `Some("MIT")`
9687        // (the canonical single-license shape every `feira init`
9688        // template scaffolds), `Some("Apache-2.0 OR MIT")` (the
9689        // canonical `OR`-compound shape the peer
9690        // `validate_licenca_accepts_canonical_expressions` positive
9691        // sweep exercises), `Some("(MIT OR Apache-2.0) AND
9692        // BSD-3-Clause")` (the canonical parenthesis-grouped shape),
9693        // `Some("MIT ")` / `Some(" MIT")` / `Some("MIT\n")` /
9694        // `Some("Apache_2.0")` / `Some("MIT,Apache-2.0")` (past-the-
9695        // guard sentinels — validate rejects each through
9696        // `LicencaInvalid` but the accessor must ship the raw slot
9697        // verbatim).
9698        //
9699        // First outer top-level [`Caixa`] `Option<&str>`-return scalar
9700        // accessor pin on the substrate primitive — opens the "outer
9701        // [`Caixa`] `Option<&str>` scalar" projection pattern the
9702        // sibling per-`Caixa` `:descricao` / `:repositorio` / `:edicao`
9703        // future lifts fold on. Sibling in shape to the peer per-`:placement`
9704        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
9705        // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
9706        // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
9707        // axes, extended onto the outer top-level [`Caixa`] universal-
9708        // axis surface. Pins against a future silent detour that
9709        // returned an owned `Option<String>` (which would type-check
9710        // but silently allocate on every accessor call, breaking the
9711        // zero-cost projection every peer sibling accessor carries), a
9712        // `Some("") → None` collapse (which would silently absorb the
9713        // `LicencaEmpty` refusal case at the accessor boundary and the
9714        // caixa-helm emit path would silently fall back to `"MIT"` on
9715        // a struct-literal `Caixa { licenca: Some(""), .. }`), or a
9716        // `None → Some("MIT")` collapse (which would silently reify
9717        // the caixa-helm renderer's `"MIT"` fallback at the accessor
9718        // boundary and every downstream consumer keying off the
9719        // `Option::is_none()` discriminator would lose the "author
9720        // omitted the slot" signal).
9721        for licenca in [
9722            None,
9723            Some(""),
9724            Some("MIT"),
9725            Some("Apache-2.0 OR MIT"),
9726            Some("(MIT OR Apache-2.0) AND BSD-3-Clause"),
9727            Some("MIT "),
9728            Some(" MIT"),
9729            Some("MIT\n"),
9730            Some("Apache_2.0"),
9731            Some("MIT,Apache-2.0"),
9732        ] {
9733            let c = caixa_with_licenca(licenca);
9734            assert_eq!(
9735                c.licenca(),
9736                licenca,
9737                "Caixa::licenca must return :licenca verbatim (got {:?}, \
9738                 expected {licenca:?})",
9739                c.licenca(),
9740            );
9741            assert_eq!(
9742                c.licenca(),
9743                c.licenca.as_deref(),
9744                "Caixa::licenca must byte-equal the raw \
9745                 `self.licenca.as_deref()` field access across every \
9746                 value in the Option<&str> accept-set",
9747            );
9748        }
9749    }
9750
9751    #[test]
9752    fn validate_licenca_empty_arm_routes_through_accessor() {
9753        // Composition pin: [`Caixa::validate_licenca`]'s empty-arm gate
9754        // must key off [`Caixa::licenca`], not the raw
9755        // `self.licenca.as_deref()` field access. Structurally: a
9756        // `Caixa { licenca: Some(""), .. }` must surface the
9757        // `LicencaEmpty` refusal exactly, and a
9758        // `Caixa { licenca: Some("MIT"), .. }` (the canonical
9759        // single-license form) must pass validate. The pair jointly
9760        // pins the accessor + validate-gate composition: any future
9761        // silent detour that had the accessor return `None` on the
9762        // empty arm (a `.filter(|s| !s.is_empty())` collapse) would
9763        // silently absorb the `LicencaEmpty` refusal at the accessor
9764        // boundary and the validate gate would accept a struct-literal
9765        // `Caixa { licenca: Some(""), .. }` — the composition pin
9766        // catches that at caixa-core build time.
9767        //
9768        // Peer of the per-`:politicas :circuit-breaker`
9769        // [`crate::aplicacao::CircuitBreaker::max_failures`] (3a74062)
9770        // accessor-composition pin
9771        // (`validate_politicas_max_failures_zero_floor_arm_routes_through_accessor`)
9772        // on the sibling per-M3-mesh-slot required-`u32` axis — same
9773        // "the validate / shape-gate predicate must route through the
9774        // substrate-primitive typed dispatch" discipline extended onto
9775        // the outer top-level [`Caixa`] universal-axis
9776        // `Option<&str>`-composition surface.
9777        let c = caixa_with_licenca(Some(""));
9778        assert!(
9779            matches!(c.validate_licenca(), Err(ManifestError::LicencaEmpty)),
9780            "validate_licenca must reject licenca == Some(\"\") with \
9781             LicencaEmpty — the accessor and the validate gate must \
9782             route through the same substrate-primitive typed dispatch \
9783             on the :licenca empty arm",
9784        );
9785        let c = caixa_with_licenca(Some("MIT"));
9786        assert!(
9787            c.validate_licenca().is_ok(),
9788            "validate_licenca must accept licenca == Some(\"MIT\") \
9789             (the canonical single-license SPDX shape)",
9790        );
9791    }
9792
9793    #[test]
9794    fn licenca_projects_option_str_by_borrow() {
9795        // The by-borrow pin: [`Caixa::licenca`] returns
9796        // `Option<&str>` by borrow — the `&str` borrows the underlying
9797        // `String` storage of the `Option<String>` slot and the
9798        // accessor must not allocate a fresh `String` on every call.
9799        // Peer of the per-`:placement`
9800        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
9801        // borrow pin on the peer per-M3-mesh-slot
9802        // `Option<&str>`-return axis, extended onto the outer top-
9803        // level [`Caixa`] universal-axis `Option<&str>` shape — the
9804        // accessor's returned `&str` must borrow from `&self` (the
9805        // returned reference's lifetime is tied to `&self`), and
9806        // calling the accessor twice on the same [`Caixa`] must yield
9807        // the same `Option<&str>` verbatim (idempotent, no side
9808        // effects on `&self`).
9809        //
9810        // Pins against a future silent detour that returned an owned
9811        // `Option<String>` (which would type-check but silently
9812        // allocate on every call, breaking the zero-cost projection
9813        // every peer sibling accessor carries), or a one-arm-only
9814        // accessor that returned a saturating value on some sentinel
9815        // input (breaking the pass-through invariant the sibling
9816        // required-scalar accessors carry).
9817        for licenca in [None, Some(""), Some("MIT"), Some("Apache-2.0 OR MIT")] {
9818            let c = caixa_with_licenca(licenca);
9819            let first = c.licenca();
9820            let second = c.licenca();
9821            assert_eq!(
9822                first, second,
9823                "Caixa::licenca must be idempotent — two successive \
9824                 calls on the same &self must return the same \
9825                 Option<&str>",
9826            );
9827            assert_eq!(
9828                first, licenca,
9829                "Caixa::licenca must return :licenca verbatim by \
9830                 borrow — got {first:?}, expected {licenca:?}",
9831            );
9832        }
9833    }
9834
9835    // ── Caixa::repositorio — outer top-level Option<&str> scalar accessor ──
9836
9837    #[test]
9838    fn repositorio_returns_repositorio_byte_string_verbatim_across_permutations() {
9839        // The canonical per-`Caixa` `:repositorio` git-repo-URL scalar
9840        // pin: [`Caixa::repositorio`] must return the `:repositorio`
9841        // typed byte-string verbatim as an `Option<&str>`, byte-equal
9842        // to the raw `self.repositorio.as_deref()` access across every
9843        // representative value in the accept-set — `None` (the "omit
9844        // the slot to defer to the per-renderer placeholder" arm every
9845        // existing fixture without a `:repositorio` line carries),
9846        // `Some("")` (a past-the-guard sentinel that pins the accessor
9847        // doesn't perform a silent `Some("") → None` collapse on the
9848        // empty arm — validate rejects `Some("")` through
9849        // `RepositorioEmpty` but the accessor must ship the raw slot
9850        // verbatim so a validate-time gate regression surfaces at the
9851        // caixa-helm / caixa-flux emit boundary rather than being
9852        // silently absorbed into the per-renderer fallback),
9853        // `Some("github:pleme-io/hello-rio")` (the canonical `github:`
9854        // shorthand every existing manifest fixture across
9855        // `caixa-helm` / `caixa-mesh` and the `examples/` uses),
9856        // `Some("https://github.com/pleme-io/checkout")` (the canonical
9857        // `https://` URL the README quickstart uses),
9858        // `Some("ssh://git@github.com/pleme-io/checkout.git")` /
9859        // `Some("git://github.com/pleme-io/checkout.git")` /
9860        // `Some("git@github.com:pleme-io/checkout.git")` /
9861        // `Some("file:///opt/mirrors/pleme-io/checkout")` (every non-
9862        // github scheme the shared `is_git_repo_url` predicate
9863        // documents), and five past-the-guard sentinels for the
9864        // `RepositorioInvalid` refusal cases (`Some("pleme-io/checkout")`
9865        // missing-colon, `Some("-upload-pack=evil")` leading-dash, /
9866        // `Some("github:pleme-io/checkout?ref=main")` query-string, /
9867        // `Some("github:pleme-io/checkout#main")` fragment-anchor, /
9868        // `Some("github:pleme-io/{tpl}")` URI-template-placeholder — the
9869        // sentinels pin the accessor doesn't silently absorb the
9870        // refusal cases into a fallback).
9871        //
9872        // Second outer top-level [`Caixa`] `Option<&str>`-return scalar
9873        // accessor pin on the substrate primitive — sibling of the peer
9874        // [`Caixa::licenca`] (6d5bc28) pin
9875        // (`licenca_returns_licenca_byte_string_verbatim_across_permutations`)
9876        // that opened the "outer [`Caixa`] `Option<&str>` scalar"
9877        // projection pin pattern this pin folds on. Sibling in shape to
9878        // the peer per-`:placement`
9879        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
9880        // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
9881        // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
9882        // axes, extended onto the outer top-level [`Caixa`] universal-
9883        // axis surface. Pins against a future silent detour that
9884        // returned an owned `Option<String>` (which would type-check
9885        // but silently allocate on every accessor call, breaking the
9886        // zero-cost projection every peer sibling accessor carries), a
9887        // `Some("") → None` collapse (which would silently absorb the
9888        // `RepositorioEmpty` refusal case at the accessor boundary and
9889        // the caixa-helm `Chart.yaml` `home:` fold would silently
9890        // render a `home: null` / omitted field on a struct-literal
9891        // `Caixa { repositorio: Some(""), .. }`), or a
9892        // `None → Some(<default>)` collapse (which would silently reify
9893        // the per-renderer fallback at the accessor boundary and every
9894        // downstream consumer keying off the `Option::is_none()`
9895        // discriminator would lose the "author omitted the slot"
9896        // signal).
9897        for repositorio in [
9898            None,
9899            Some(""),
9900            Some("github:pleme-io/hello-rio"),
9901            Some("https://github.com/pleme-io/checkout"),
9902            Some("ssh://git@github.com/pleme-io/checkout.git"),
9903            Some("git://github.com/pleme-io/checkout.git"),
9904            Some("git@github.com:pleme-io/checkout.git"),
9905            Some("file:///opt/mirrors/pleme-io/checkout"),
9906            Some("pleme-io/checkout"),
9907            Some("-upload-pack=evil"),
9908            Some("github:pleme-io/checkout?ref=main"),
9909            Some("github:pleme-io/checkout#main"),
9910            Some("github:pleme-io/{tpl}"),
9911        ] {
9912            let c = caixa_with_repositorio(repositorio);
9913            assert_eq!(
9914                c.repositorio(),
9915                repositorio,
9916                "Caixa::repositorio must return :repositorio verbatim \
9917                 (got {:?}, expected {repositorio:?})",
9918                c.repositorio(),
9919            );
9920            assert_eq!(
9921                c.repositorio(),
9922                c.repositorio.as_deref(),
9923                "Caixa::repositorio must byte-equal the raw \
9924                 `self.repositorio.as_deref()` field access across every \
9925                 value in the Option<&str> accept-set",
9926            );
9927        }
9928    }
9929
9930    #[test]
9931    fn validate_repositorio_empty_arm_routes_through_accessor() {
9932        // Composition pin: [`Caixa::validate_repositorio`]'s empty-arm
9933        // gate must key off [`Caixa::repositorio`], not the raw
9934        // `self.repositorio.as_deref()` field access. Structurally: a
9935        // `Caixa { repositorio: Some(""), .. }` must surface the
9936        // `RepositorioEmpty` refusal exactly, and a
9937        // `Caixa { repositorio: Some("github:pleme-io/hello-rio"), .. }`
9938        // (the canonical `github:` shorthand form) must pass validate.
9939        // The pair jointly pins the accessor + validate-gate
9940        // composition: any future silent detour that had the accessor
9941        // return `None` on the empty arm (a `.filter(|s| !s.is_empty())`
9942        // collapse) would silently absorb the `RepositorioEmpty` refusal
9943        // at the accessor boundary and the validate gate would accept a
9944        // struct-literal `Caixa { repositorio: Some(""), .. }` — the
9945        // composition pin catches that at caixa-core build time.
9946        //
9947        // Peer of the [`Caixa::licenca`] (6d5bc28)
9948        // `validate_licenca_empty_arm_routes_through_accessor`
9949        // composition pin on the sibling outer top-level [`Caixa`]
9950        // `Option<&str>` universal-axis surface — same "the validate /
9951        // shape-gate predicate must route through the substrate-
9952        // primitive typed dispatch" discipline extended onto the second
9953        // outer top-level [`Caixa`] universal-axis `Option<&str>`-
9954        // composition surface.
9955        let c = caixa_with_repositorio(Some(""));
9956        assert!(
9957            matches!(
9958                c.validate_repositorio(),
9959                Err(ManifestError::RepositorioEmpty),
9960            ),
9961            "validate_repositorio must reject repositorio == Some(\"\") \
9962             with RepositorioEmpty — the accessor and the validate gate \
9963             must route through the same substrate-primitive typed \
9964             dispatch on the :repositorio empty arm",
9965        );
9966        let c = caixa_with_repositorio(Some("github:pleme-io/hello-rio"));
9967        assert!(
9968            c.validate_repositorio().is_ok(),
9969            "validate_repositorio must accept repositorio == \
9970             Some(\"github:pleme-io/hello-rio\") (the canonical \
9971             `github:` shorthand git-repo-URL shape)",
9972        );
9973    }
9974
9975    #[test]
9976    fn repositorio_projects_option_str_by_borrow() {
9977        // The by-borrow pin: [`Caixa::repositorio`] returns
9978        // `Option<&str>` by borrow — the `&str` borrows the underlying
9979        // `String` storage of the `Option<String>` slot and the
9980        // accessor must not allocate a fresh `String` on every call.
9981        // Peer of the per-`:placement`
9982        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) and the
9983        // [`Caixa::licenca`] (6d5bc28) by-borrow pins on the peer
9984        // `Option<&str>`-return axes, extended onto the second outer
9985        // top-level [`Caixa`] universal-axis `Option<&str>` shape —
9986        // the accessor's returned `&str` must borrow from `&self` (the
9987        // returned reference's lifetime is tied to `&self`), and
9988        // calling the accessor twice on the same [`Caixa`] must yield
9989        // the same `Option<&str>` verbatim (idempotent, no side effects
9990        // on `&self`).
9991        //
9992        // Pins against a future silent detour that returned an owned
9993        // `Option<String>` (which would type-check but silently
9994        // allocate on every call, breaking the zero-cost projection
9995        // every peer sibling accessor carries), or a one-arm-only
9996        // accessor that returned a saturating value on some sentinel
9997        // input (breaking the pass-through invariant the sibling
9998        // required-scalar accessors carry).
9999        for repositorio in [
10000            None,
10001            Some(""),
10002            Some("github:pleme-io/hello-rio"),
10003            Some("https://github.com/pleme-io/checkout"),
10004        ] {
10005            let c = caixa_with_repositorio(repositorio);
10006            let first = c.repositorio();
10007            let second = c.repositorio();
10008            assert_eq!(
10009                first, second,
10010                "Caixa::repositorio must be idempotent — two successive \
10011                 calls on the same &self must return the same \
10012                 Option<&str>",
10013            );
10014            assert_eq!(
10015                first, repositorio,
10016                "Caixa::repositorio must return :repositorio verbatim by \
10017                 borrow — got {first:?}, expected {repositorio:?}",
10018            );
10019        }
10020    }
10021
10022    // ── Caixa::descricao — outer top-level Option<&str> scalar accessor ──
10023
10024    #[test]
10025    fn descricao_returns_descricao_byte_string_verbatim_across_permutations() {
10026        // The canonical per-`Caixa` `:descricao` free-form-prose scalar
10027        // pin: [`Caixa::descricao`] must return the `:descricao` typed
10028        // byte-string verbatim as an `Option<&str>`, byte-equal to the
10029        // raw `self.descricao.as_deref()` access across every
10030        // representative value in the accept-set — `None` (the "omit
10031        // the slot to defer to the per-renderer `caixa.nome`-derived
10032        // fallback" arm every existing fixture without a `:descricao`
10033        // line carries), `Some("")` (a past-the-guard sentinel that
10034        // pins the accessor doesn't perform a silent `Some("") → None`
10035        // collapse on the empty arm — validate rejects `Some("")`
10036        // through `DescricaoEmpty` but the accessor must ship the raw
10037        // slot verbatim so a validate-time gate regression surfaces at
10038        // the caixa-helm / caixa-feira emit boundary rather than being
10039        // silently absorbed into the per-renderer `caixa.nome`-derived
10040        // fallback), `Some("Checkout flow.")` (the canonical one-line
10041        // prose descriptor the peer
10042        // `validate_descricao_accepts_canonical_value` positive sweep
10043        // exercises), `Some("Canonical Rust→wasm32-wasip2 caixa
10044        // Servico.")` (the multi-byte Unicode continuation-byte shape
10045        // the `hello-rio` fixture carries), `Some("→ — · ✓")` (a
10046        // multi-glyph Unicode shape the peer
10047        // `is_chart_description_shape` predicate accepts), and five
10048        // past-the-guard sentinels for the `DescricaoInvalid` refusal
10049        // cases (`Some(" Checkout flow.")` leading-whitespace,
10050        // `Some("Checkout flow. ")` trailing-whitespace,
10051        // `Some("Checkout\nflow.")` embedded-LF,
10052        // `Some("Checkout\tflow.")` embedded-TAB, and
10053        // `Some("Checkout\x00flow.")` embedded-NUL — the sentinels pin
10054        // the accessor doesn't silently absorb the refusal cases into
10055        // a fallback).
10056        //
10057        // Third outer top-level [`Caixa`] `Option<&str>`-return scalar
10058        // accessor pin on the substrate primitive — sibling of the peer
10059        // [`Caixa::licenca`] (6d5bc28) and [`Caixa::repositorio`]
10060        // (cc7332d) pins that opened the "outer [`Caixa`]
10061        // `Option<&str>` scalar" projection pin pattern this pin folds
10062        // on. Sibling in shape to the peer per-`:placement`
10063        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
10064        // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
10065        // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
10066        // axes, extended onto the outer top-level [`Caixa`] universal-
10067        // axis surface. Pins against a future silent detour that
10068        // returned an owned `Option<String>` (which would type-check
10069        // but silently allocate on every accessor call, breaking the
10070        // zero-cost projection every peer sibling accessor carries), a
10071        // `Some("") → None` collapse (which would silently absorb the
10072        // `DescricaoEmpty` refusal case at the accessor boundary and
10073        // the caixa-helm `Chart.yaml` `description:` fold would
10074        // silently render a `caixa.nome`-derived fallback on a
10075        // struct-literal `Caixa { descricao: Some(""), .. }`), or a
10076        // `None → Some(<default>)` collapse (which would silently
10077        // reify the per-renderer `caixa.nome`-derived fallback at the
10078        // accessor boundary and every downstream consumer keying off
10079        // the `Option::is_none()` discriminator would lose the "author
10080        // omitted the slot" signal).
10081        for descricao in [
10082            None,
10083            Some(""),
10084            Some("Checkout flow."),
10085            Some("Canonical Rust→wasm32-wasip2 caixa Servico."),
10086            Some("→ — · ✓"),
10087            Some(" Checkout flow."),
10088            Some("Checkout flow. "),
10089            Some("Checkout\nflow."),
10090            Some("Checkout\tflow."),
10091            Some("Checkout\x00flow."),
10092        ] {
10093            let c = caixa_with_descricao(descricao);
10094            assert_eq!(
10095                c.descricao(),
10096                descricao,
10097                "Caixa::descricao must return :descricao verbatim (got \
10098                 {:?}, expected {descricao:?})",
10099                c.descricao(),
10100            );
10101            assert_eq!(
10102                c.descricao(),
10103                c.descricao.as_deref(),
10104                "Caixa::descricao must byte-equal the raw \
10105                 `self.descricao.as_deref()` field access across every \
10106                 value in the Option<&str> accept-set",
10107            );
10108        }
10109    }
10110
10111    #[test]
10112    fn validate_descricao_empty_arm_routes_through_accessor() {
10113        // Composition pin: [`Caixa::validate_descricao`]'s empty-arm
10114        // gate must key off [`Caixa::descricao`], not the raw
10115        // `self.descricao.as_deref()` field access. Structurally: a
10116        // `Caixa { descricao: Some(""), .. }` must surface the
10117        // `DescricaoEmpty` refusal exactly, and a
10118        // `Caixa { descricao: Some("Checkout flow."), .. }` (the
10119        // canonical one-line-prose form) must pass validate. The pair
10120        // jointly pins the accessor + validate-gate composition: any
10121        // future silent detour that had the accessor return `None` on
10122        // the empty arm (a `.filter(|s| !s.is_empty())` collapse) would
10123        // silently absorb the `DescricaoEmpty` refusal at the accessor
10124        // boundary and the validate gate would accept a struct-literal
10125        // `Caixa { descricao: Some(""), .. }` — the composition pin
10126        // catches that at caixa-core build time.
10127        //
10128        // Peer of the [`Caixa::licenca`] (6d5bc28)
10129        // `validate_licenca_empty_arm_routes_through_accessor` and
10130        // [`Caixa::repositorio`] (cc7332d)
10131        // `validate_repositorio_empty_arm_routes_through_accessor`
10132        // composition pins on the sibling outer top-level [`Caixa`]
10133        // `Option<&str>` universal-axis surface — same "the validate /
10134        // shape-gate predicate must route through the substrate-
10135        // primitive typed dispatch" discipline extended onto the third
10136        // outer top-level [`Caixa`] universal-axis `Option<&str>`-
10137        // composition surface.
10138        let c = caixa_with_descricao(Some(""));
10139        assert!(
10140            matches!(c.validate_descricao(), Err(ManifestError::DescricaoEmpty),),
10141            "validate_descricao must reject descricao == Some(\"\") \
10142             with DescricaoEmpty — the accessor and the validate gate \
10143             must route through the same substrate-primitive typed \
10144             dispatch on the :descricao empty arm",
10145        );
10146        let c = caixa_with_descricao(Some("Checkout flow."));
10147        assert!(
10148            c.validate_descricao().is_ok(),
10149            "validate_descricao must accept descricao == \
10150             Some(\"Checkout flow.\") (the canonical one-line-prose \
10151             chart-description shape)",
10152        );
10153    }
10154
10155    #[test]
10156    fn descricao_projects_option_str_by_borrow() {
10157        // The by-borrow pin: [`Caixa::descricao`] returns
10158        // `Option<&str>` by borrow — the `&str` borrows the underlying
10159        // `String` storage of the `Option<String>` slot and the
10160        // accessor must not allocate a fresh `String` on every call.
10161        // Peer of the [`Caixa::licenca`] (6d5bc28) and
10162        // [`Caixa::repositorio`] (cc7332d) by-borrow pins on the peer
10163        // outer top-level [`Caixa`] `Option<&str>`-return axes, and of
10164        // the per-`:placement`
10165        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
10166        // borrow pin on the peer per-M3-mesh-slot `Option<&str>`-
10167        // return axis, extended onto the third outer top-level
10168        // [`Caixa`] universal-axis `Option<&str>` shape — the
10169        // accessor's returned `&str` must borrow from `&self` (the
10170        // returned reference's lifetime is tied to `&self`), and
10171        // calling the accessor twice on the same [`Caixa`] must yield
10172        // the same `Option<&str>` verbatim (idempotent, no side
10173        // effects on `&self`).
10174        //
10175        // Pins against a future silent detour that returned an owned
10176        // `Option<String>` (which would type-check but silently
10177        // allocate on every call, breaking the zero-cost projection
10178        // every peer sibling accessor carries), or a one-arm-only
10179        // accessor that returned a saturating value on some sentinel
10180        // input (breaking the pass-through invariant the sibling
10181        // required-scalar accessors carry).
10182        for descricao in [
10183            None,
10184            Some(""),
10185            Some("Checkout flow."),
10186            Some("Canonical Rust→wasm32-wasip2 caixa Servico."),
10187        ] {
10188            let c = caixa_with_descricao(descricao);
10189            let first = c.descricao();
10190            let second = c.descricao();
10191            assert_eq!(
10192                first, second,
10193                "Caixa::descricao must be idempotent — two successive \
10194                 calls on the same &self must return the same \
10195                 Option<&str>",
10196            );
10197            assert_eq!(
10198                first, descricao,
10199                "Caixa::descricao must return :descricao verbatim by \
10200                 borrow — got {first:?}, expected {descricao:?}",
10201            );
10202        }
10203    }
10204
10205    // ── validate_edicao — universal-axis language-edition shape ──
10206
10207    fn caixa_with_edicao(edicao: Option<&str>) -> Caixa {
10208        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10209        c.edicao = edicao.map(String::from);
10210        c
10211    }
10212
10213    #[test]
10214    fn validate_edicao_accepts_none() {
10215        // The omit-the-slot identity: `:edicao` is optional. The
10216        // gate is a no-op when the author didn't declare a value —
10217        // every caixa without an `:edicao` line trivially passes,
10218        // and the substrate-side build pipeline falls back to the
10219        // documented default edition. Mirrors the peer
10220        // `validate_licenca_accepts_none` posture on the sibling
10221        // `Option<String>` Caixa slot.
10222        let c = caixa_with_edicao(None);
10223        c.validate_edicao().unwrap();
10224    }
10225
10226    #[test]
10227    fn validate_edicao_accepts_canonical_value() {
10228        // Positive control: the canonical `"2026"` edition every
10229        // existing renderer-side fixture (`caixa-helm`, `caixa-flux`,
10230        // `caixa-mesh`) carries by construction passes the gate.
10231        // Future-introduced sibling editions (`"2027"`, `"2030"`,
10232        // `"2049"`) that match the same 4-digit ASCII decimal year
10233        // shape must also trivially pass — the structural shape
10234        // predicate accepts every well-formed year regardless of
10235        // whether the substrate yet understands the specific value
10236        // (a future known-edition allowlist tightens that).
10237        for ed in ["2026", "2027", "2030", "2049"] {
10238            let c = caixa_with_edicao(Some(ed));
10239            c.validate_edicao()
10240                .unwrap_or_else(|err| panic!("canonical {ed:?} must pass: {err:?}"));
10241        }
10242    }
10243
10244    #[test]
10245    fn validate_edicao_rejects_empty_some() {
10246        // Canonical paste-from-blank-doc footgun. Without this gate
10247        // the empty `Some("")` silently lands as `(:edicao "")` in
10248        // the rendered caixa.lisp and a future renderer-side
10249        // consumer's `Option::unwrap_or_else` (which only fires on
10250        // `None`) skips its fallback. Mirrors the peer
10251        // [`ManifestError::LicencaEmpty`] empty-arm on the sibling
10252        // `Option<String>` Caixa slot.
10253        let c = caixa_with_edicao(Some(""));
10254        let err = c.validate_edicao().unwrap_err();
10255        assert!(matches!(err, ManifestError::EdicaoEmpty), "got {err:?}",);
10256    }
10257
10258    #[test]
10259    fn validate_edicao_rejects_free_form_non_year() {
10260        // Free-form non-year footgun: the bare `"x"` / `"latest"` /
10261        // `"nightly"` shapes carry no operational meaning on the
10262        // substrate's build-time edition selector. Until this gate
10263        // landed the bare empty-arm check let every such value
10264        // through and broke far from the source caixa.lisp. Peer
10265        // with the shape-predicate cascade
10266        // `validate_repositorio_rejects_missing_colon_separator`
10267        // establishes past its own empty arm.
10268        for ed in ["x", "latest", "nightly", "stable"] {
10269            let c = caixa_with_edicao(Some(ed));
10270            let err = c.validate_edicao().unwrap_err();
10271            assert!(
10272                matches!(err, ManifestError::EdicaoInvalid { .. }),
10273                "expected EdicaoInvalid on {ed:?}, got {err:?}",
10274            );
10275        }
10276    }
10277
10278    #[test]
10279    fn validate_edicao_rejects_trailing_whitespace() {
10280        // Paste-from-doc whitespace footgun. A trailing space in
10281        // the `:edicao` value would silently break the substrate's
10282        // build-time edition match-table lookup at the rendered
10283        // artifact's edition-selector consumer. The shape predicate
10284        // refuses every whitespace byte by construction (any byte
10285        // outside `0-9` fails `is_ascii_digit`). Peer with
10286        // `validate_repositorio_rejects_whitespace`.
10287        let c = caixa_with_edicao(Some("2026 "));
10288        let err = c.validate_edicao().unwrap_err();
10289        let ManifestError::EdicaoInvalid { edicao, .. } = err else {
10290            panic!("expected EdicaoInvalid, got {err:?}");
10291        };
10292        assert_eq!(edicao, "2026 ");
10293    }
10294
10295    #[test]
10296    fn validate_edicao_rejects_leading_whitespace() {
10297        // Symmetric paste-from-doc whitespace footgun on the leading
10298        // boundary — the gate refuses every shape with a non-digit
10299        // byte by construction.
10300        let c = caixa_with_edicao(Some(" 2026"));
10301        let err = c.validate_edicao().unwrap_err();
10302        assert!(
10303            matches!(err, ManifestError::EdicaoInvalid { .. }),
10304            "got {err:?}",
10305        );
10306    }
10307
10308    #[test]
10309    fn validate_edicao_rejects_control_char() {
10310        // Paste-from-multiline-doc CRLF footgun — control characters
10311        // at the value boundary break the substrate's build-time
10312        // edition-selector parser. Peer with
10313        // `validate_repositorio_rejects_control_char`.
10314        let c = caixa_with_edicao(Some("2026\n"));
10315        let err = c.validate_edicao().unwrap_err();
10316        assert!(
10317            matches!(err, ManifestError::EdicaoInvalid { .. }),
10318            "got {err:?}",
10319        );
10320    }
10321
10322    #[test]
10323    fn validate_edicao_rejects_non_ascii_lookalike() {
10324        // Fullwidth-keyboard look-alike footgun — `"2026"` is
10325        // the U+FF12 U+FF10 U+FF12 U+FF16 sequence (CJK fullwidth
10326        // digits), 4 codepoints but 12 UTF-8 bytes; the substrate's
10327        // edition selector wants an ASCII year, and the gate
10328        // refuses every non-ASCII shape by construction (length in
10329        // bytes is 12 ≠ 4, *and* every byte falls outside
10330        // `is_ascii_digit`'s `0-9` range).
10331        let c = caixa_with_edicao(Some("2026"));
10332        let err = c.validate_edicao().unwrap_err();
10333        assert!(
10334            matches!(err, ManifestError::EdicaoInvalid { .. }),
10335            "got {err:?}",
10336        );
10337    }
10338
10339    #[test]
10340    fn validate_edicao_rejects_version_tag_prefix() {
10341        // Common version-tag idiom footgun — `"v2026"` / `"e2026"`
10342        // / `"r2026"` are familiar shapes from git-tag / Rust
10343        // edition / release-tag conventions that don't apply to
10344        // the year-shaped edition axis. The shape predicate refuses
10345        // every leading non-digit prefix.
10346        for ed in ["v2026", "e2026", "r2026"] {
10347            let c = caixa_with_edicao(Some(ed));
10348            let err = c.validate_edicao().unwrap_err();
10349            assert!(
10350                matches!(err, ManifestError::EdicaoInvalid { .. }),
10351                "expected EdicaoInvalid on {ed:?}, got {err:?}",
10352            );
10353        }
10354    }
10355
10356    #[test]
10357    fn validate_edicao_rejects_decimal_shape() {
10358        // Decimal-shaped pseudo-version footgun — `"2026.1"` /
10359        // `"2026.0"` are familiar shapes from semver / float
10360        // conventions that don't apply to the year-shaped edition
10361        // axis. The shape predicate refuses every non-digit byte
10362        // (`.` falls outside `is_ascii_digit`).
10363        for ed in ["2026.1", "2026.0", "2026.0.1"] {
10364            let c = caixa_with_edicao(Some(ed));
10365            let err = c.validate_edicao().unwrap_err();
10366            assert!(
10367                matches!(err, ManifestError::EdicaoInvalid { .. }),
10368                "expected EdicaoInvalid on {ed:?}, got {err:?}",
10369            );
10370        }
10371    }
10372
10373    #[test]
10374    fn validate_edicao_rejects_wrong_length_numeric() {
10375        // Wrong-length numeric footgun — `"26"` (truncated) /
10376        // `"202"` (truncated) / `"20260"` (extra digit) / `"00026"`
10377        // (zero-padded too wide) all parse as integers but don't
10378        // name a 4-digit year. The shape predicate refuses every
10379        // value whose length isn't exactly 4 bytes.
10380        for ed in ["26", "202", "20260", "00026", "9"] {
10381            let c = caixa_with_edicao(Some(ed));
10382            let err = c.validate_edicao().unwrap_err();
10383            assert!(
10384                matches!(err, ManifestError::EdicaoInvalid { .. }),
10385                "expected EdicaoInvalid on {ed:?}, got {err:?}",
10386            );
10387        }
10388    }
10389
10390    #[test]
10391    fn validate_edicao_empty_takes_precedence_over_shape() {
10392        // Empty-first cascade pin: the empty `Some("")` surfaces
10393        // the narrower `EdicaoEmpty` not the shape-predicate-
10394        // wrapped `EdicaoInvalid`, mirroring the peer
10395        // `validate_repositorio_empty_takes_precedence_over_shape`
10396        // (`RepositorioEmpty` → `RepositorioInvalid`),
10397        // `NomeEmpty` → `NomeInvalid`, `VersaoEmpty` →
10398        // `VersaoInvalid`, `FonteRepoEmpty` → `FonteRepoInvalid`
10399        // cascades. The shape predicate also refuses the empty
10400        // input (defensively — `s.len() != 4`), but the
10401        // manifest-layer empty arm runs first to surface the
10402        // narrower diagnostic verbatim.
10403        let c = caixa_with_edicao(Some(""));
10404        let err = c.validate_edicao().unwrap_err();
10405        assert!(matches!(err, ManifestError::EdicaoEmpty), "got {err:?}",);
10406    }
10407
10408    #[test]
10409    fn validate_edicao_template_passes() {
10410        // Round-trip pin: the bare `Caixa::template` shape (which
10411        // carries `:edicao "2026"` verbatim) passes the gate by
10412        // construction. A future template-shape change that
10413        // introduced `(:edicao "")` or a non-year value would
10414        // surface here as a regression. Mirrors the peer
10415        // `validate_licenca_template_passes` pin.
10416        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10417        c.validate_edicao().unwrap();
10418    }
10419
10420    #[test]
10421    fn validate_edicao_diagnostic_names_offending_slot() {
10422        // Diagnostic-shape pin (peer with
10423        // `validate_licenca_diagnostic_names_offending_slot`): the
10424        // error's Display surfaces the `:edicao` slot name verbatim,
10425        // so a `feira lint` run can render the diagnostic without
10426        // re-parsing and the author can grep their caixa.lisp for
10427        // the offending `:edicao` line.
10428        let c = caixa_with_edicao(Some(""));
10429        let rendered = c.validate_edicao().unwrap_err().to_string();
10430        assert!(
10431            rendered.contains(":edicao"),
10432            "diagnostic must name the offending slot: {rendered}",
10433        );
10434    }
10435
10436    #[test]
10437    fn validate_edicao_invalid_diagnostic_carries_offending_value() {
10438        // Diagnostic-shape pin on the shape-predicate arm (peer
10439        // with `validate_repositorio_diagnostic_carries_offending_value`):
10440        // the error's Display surfaces the offending value + slot
10441        // name verbatim, so a `feira lint` run can render the
10442        // diagnostic without re-parsing and the author can grep
10443        // their caixa.lisp for the offending `:edicao` value.
10444        let c = caixa_with_edicao(Some("v2026"));
10445        let rendered = c.validate_edicao().unwrap_err().to_string();
10446        assert!(
10447            rendered.contains(":edicao"),
10448            "diagnostic must name the offending slot: {rendered}",
10449        );
10450        assert!(
10451            rendered.contains("v2026"),
10452            "diagnostic must quote the offending value: {rendered}",
10453        );
10454    }
10455
10456    // ── Caixa::edicao — outer top-level Option<&str> scalar accessor ──
10457
10458    #[test]
10459    fn edicao_returns_edicao_byte_string_verbatim_across_permutations() {
10460        // The canonical per-`Caixa` `:edicao` language-edition scalar
10461        // pin: [`Caixa::edicao`] must return the `:edicao` typed
10462        // byte-string verbatim as an `Option<&str>`, byte-equal to the
10463        // raw `self.edicao.as_deref()` access across every representative
10464        // value in the accept-set — `None` (the "omit the slot to defer
10465        // to the substrate's default edition" arm every existing
10466        // [`caixa-resolver`] fixture without an `:edicao` line carries),
10467        // `Some("")` (a past-the-guard sentinel that pins the accessor
10468        // doesn't perform a silent `Some("") → None` collapse on the
10469        // empty arm — validate rejects `Some("")` through `EdicaoEmpty`
10470        // but the accessor must ship the raw slot verbatim so a
10471        // validate-time gate regression surfaces at any future edition-
10472        // aware consumer's boundary rather than being silently absorbed
10473        // into the substrate's default edition), `Some("2026")` (the
10474        // canonical 4-digit-ASCII-decimal-year shape every `feira init`
10475        // template scaffolds via [`Caixa::template`] and every
10476        // renderer-side fixture at `caixa-helm/src/lib.rs:978` /
10477        // `caixa-flux/src/lib.rs:2319` / `caixa-mesh/src/lib.rs:3208`
10478        // carries by construction), `Some("2018")` / `Some("2021")` /
10479        // `Some("2024")` (canonical 4-digit-ASCII-decimal-year shapes
10480        // peer with Cargo's `[package] edition` grammar every future-
10481        // introduced sibling to `"2026"` will follow), and eight
10482        // past-the-guard sentinels for the `EdicaoInvalid` refusal cases
10483        // (`Some("2026 ")` trailing-whitespace, `Some(" 2026")` leading-
10484        // whitespace, `Some("2026\n")` embedded-LF, `Some("2026")`
10485        // fullwidth-non-ASCII-lookalike, `Some("v2026")` version-tag-
10486        // prefix, `Some("2026.1")` decimal-shape, `Some("26")` wrong-
10487        // length-numeric, `Some("latest")` free-form-non-year — the
10488        // sentinels pin the accessor doesn't silently absorb the
10489        // refusal cases into a substrate-default-edition fallback).
10490        //
10491        // Fourth and final outer top-level [`Caixa`] `Option<&str>`-
10492        // return scalar accessor pin on the substrate primitive —
10493        // sibling of the peer [`Caixa::licenca`] (6d5bc28),
10494        // [`Caixa::repositorio`] (cc7332d), and [`Caixa::descricao`]
10495        // (3f16e2f) pins that opened the "outer [`Caixa`]
10496        // `Option<&str>` scalar" projection pin pattern this pin folds
10497        // on. Sibling in shape to the peer per-`:placement`
10498        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
10499        // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
10500        // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
10501        // axes, extended onto the outer top-level [`Caixa`] universal-
10502        // axis surface's last unlifted `Option<String>` slot. Pins
10503        // against a future silent detour that returned an owned
10504        // `Option<String>` (which would type-check but silently
10505        // allocate on every accessor call, breaking the zero-cost
10506        // projection every peer sibling accessor carries), a
10507        // `Some("") → None` collapse (which would silently absorb the
10508        // `EdicaoEmpty` refusal case at the accessor boundary and any
10509        // future edition-aware consumer would silently fall back to
10510        // the substrate's default edition on a struct-literal
10511        // `Caixa { edicao: Some(""), .. }`), or a
10512        // `None → Some("2026")` collapse (which would silently reify
10513        // the substrate's default edition at the accessor boundary
10514        // and every downstream consumer keying off the
10515        // `Option::is_none()` discriminator would lose the "author
10516        // omitted the slot" signal).
10517        for edicao in [
10518            None,
10519            Some(""),
10520            Some("2026"),
10521            Some("2018"),
10522            Some("2021"),
10523            Some("2024"),
10524            Some("2026 "),
10525            Some(" 2026"),
10526            Some("2026\n"),
10527            Some("2026"),
10528            Some("v2026"),
10529            Some("2026.1"),
10530            Some("26"),
10531            Some("latest"),
10532        ] {
10533            let c = caixa_with_edicao(edicao);
10534            assert_eq!(
10535                c.edicao(),
10536                edicao,
10537                "Caixa::edicao must return :edicao verbatim (got {:?}, \
10538                 expected {edicao:?})",
10539                c.edicao(),
10540            );
10541            assert_eq!(
10542                c.edicao(),
10543                c.edicao.as_deref(),
10544                "Caixa::edicao must byte-equal the raw \
10545                 `self.edicao.as_deref()` field access across every \
10546                 value in the Option<&str> accept-set",
10547            );
10548        }
10549    }
10550
10551    #[test]
10552    fn validate_edicao_empty_arm_routes_through_accessor() {
10553        // Composition pin: [`Caixa::validate_edicao`]'s empty-arm gate
10554        // must key off [`Caixa::edicao`], not the raw
10555        // `self.edicao.as_deref()` field access. Structurally: a
10556        // `Caixa { edicao: Some(""), .. }` must surface the
10557        // `EdicaoEmpty` refusal exactly, and a
10558        // `Caixa { edicao: Some("2026"), .. }` (the canonical
10559        // 4-digit-ASCII-decimal-year form) must pass validate. The
10560        // pair jointly pins the accessor + validate-gate composition:
10561        // any future silent detour that had the accessor return `None`
10562        // on the empty arm (a `.filter(|s| !s.is_empty())` collapse)
10563        // would silently absorb the `EdicaoEmpty` refusal at the
10564        // accessor boundary and the validate gate would accept a
10565        // struct-literal `Caixa { edicao: Some(""), .. }` — the
10566        // composition pin catches that at caixa-core build time.
10567        //
10568        // Peer of the [`Caixa::licenca`] (6d5bc28)
10569        // `validate_licenca_empty_arm_routes_through_accessor`,
10570        // [`Caixa::repositorio`] (cc7332d)
10571        // `validate_repositorio_empty_arm_routes_through_accessor`,
10572        // and [`Caixa::descricao`] (3f16e2f)
10573        // `validate_descricao_empty_arm_routes_through_accessor`
10574        // composition pins on the sibling outer top-level [`Caixa`]
10575        // `Option<&str>` universal-axis surface — same "the validate /
10576        // shape-gate predicate must route through the substrate-
10577        // primitive typed dispatch" discipline extended onto the
10578        // fourth and final outer top-level [`Caixa`] universal-axis
10579        // `Option<&str>`-composition surface, closing the accessor-
10580        // composition family.
10581        let c = caixa_with_edicao(Some(""));
10582        assert!(
10583            matches!(c.validate_edicao(), Err(ManifestError::EdicaoEmpty)),
10584            "validate_edicao must reject edicao == Some(\"\") with \
10585             EdicaoEmpty — the accessor and the validate gate must \
10586             route through the same substrate-primitive typed dispatch \
10587             on the :edicao empty arm",
10588        );
10589        let c = caixa_with_edicao(Some("2026"));
10590        assert!(
10591            c.validate_edicao().is_ok(),
10592            "validate_edicao must accept edicao == Some(\"2026\") \
10593             (the canonical 4-digit-ASCII-decimal-year shape)",
10594        );
10595    }
10596
10597    #[test]
10598    fn edicao_projects_option_str_by_borrow() {
10599        // The by-borrow pin: [`Caixa::edicao`] returns
10600        // `Option<&str>` by borrow — the `&str` borrows the underlying
10601        // `String` storage of the `Option<String>` slot and the
10602        // accessor must not allocate a fresh `String` on every call.
10603        // Peer of the [`Caixa::licenca`] (6d5bc28),
10604        // [`Caixa::repositorio`] (cc7332d), and [`Caixa::descricao`]
10605        // (3f16e2f) by-borrow pins on the peer outer top-level
10606        // [`Caixa`] `Option<&str>`-return axes, and of the
10607        // per-`:placement`
10608        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
10609        // borrow pin on the peer per-M3-mesh-slot `Option<&str>`-
10610        // return axis, extended onto the fourth and final outer top-
10611        // level [`Caixa`] universal-axis `Option<&str>` shape — the
10612        // accessor's returned `&str` must borrow from `&self` (the
10613        // returned reference's lifetime is tied to `&self`), and
10614        // calling the accessor twice on the same [`Caixa`] must yield
10615        // the same `Option<&str>` verbatim (idempotent, no side
10616        // effects on `&self`).
10617        //
10618        // Pins against a future silent detour that returned an owned
10619        // `Option<String>` (which would type-check but silently
10620        // allocate on every call, breaking the zero-cost projection
10621        // every peer sibling accessor carries), or a one-arm-only
10622        // accessor that returned a saturating value on some sentinel
10623        // input (breaking the pass-through invariant the sibling
10624        // required-scalar accessors carry).
10625        for edicao in [None, Some(""), Some("2026"), Some("2018")] {
10626            let c = caixa_with_edicao(edicao);
10627            let first = c.edicao();
10628            let second = c.edicao();
10629            assert_eq!(
10630                first, second,
10631                "Caixa::edicao must be idempotent — two successive \
10632                 calls on the same &self must return the same \
10633                 Option<&str>",
10634            );
10635            assert_eq!(
10636                first, edicao,
10637                "Caixa::edicao must return :edicao verbatim by \
10638                 borrow — got {first:?}, expected {edicao:?}",
10639            );
10640        }
10641    }
10642
10643    #[test]
10644    fn nome_returns_nome_byte_string_verbatim_across_permutations() {
10645        // The canonical per-`Caixa` `:nome` universal-axis DNS-1123-
10646        // label caixa-identity scalar pin: [`Caixa::nome`] must return
10647        // the `:nome` typed `String` verbatim as `&str`, byte-equal to
10648        // the raw field access across every representative value in
10649        // the accept-set — the canonical `"demo"` template baseline
10650        // (the same `feira init`-scaffolded default the sibling
10651        // `validate_nome_accepts_canonical_template` positive-control
10652        // gate pins), plus every sibling per-typed-slot atom accessor's
10653        // canonical positive-arm byte-string (`"catalog"` per
10654        // [`crate::aplicacao::Membro::nome`], `"cart"` per the peer
10655        // per-`:contratos` `:de`, `"hello-rio"` per the canonical
10656        // `caixa-helm`/`caixa-flux` cross-crate integration-test
10657        // fixture, `"checkout"` per the M3 mesh-slot Aplicacao
10658        // canonical example), plus every past-the-guard sentinel for
10659        // the `NomeEmpty` / `NomeInvalid` / `NomeChartNameBudgetExceeded`
10660        // refusal cases (`""`, `"Bad_Name"`, `"a"` × 56 — 56 bytes fits
10661        // the bare DNS-1123 63-byte cap but overflows the joint
10662        // `lareira-<nome>` chart-name budget the sibling
10663        // [`Caixa::validate_nome_chart_name_budget`] gate closes on).
10664        //
10665        // The past-the-guard sentinels pin the accessor doesn't
10666        // silently absorb the refusal cases into a template-derived
10667        // fallback (a future `.nome().is_empty().then(|| "demo")`
10668        // collapse would silently absorb the `NomeEmpty` refusal at
10669        // the accessor boundary and the validate gate would accept a
10670        // struct-literal `Caixa { nome: "".into(), .. }` — the pin
10671        // catches that at caixa-core build time).
10672        //
10673        // First outer top-level [`Caixa`] `&str`-return required-
10674        // scalar accessor pin — opens the "outer [`Caixa`] `&str`
10675        // required-scalar" projection pattern the sibling per-`Caixa`
10676        // `:versao` future lift folds on. Sibling in shape to the peer
10677        // per-`:membros` [`crate::aplicacao::Membro::nome`] (4a32abf)
10678        // required-`String`-carry accessor pin on the sibling per-
10679        // sub-struct required-axis, extended onto the outer top-level
10680        // [`Caixa`] universal-axis required-`String`-carry axis.
10681        for nome in [
10682            "demo",
10683            "catalog",
10684            "cart",
10685            "hello-rio",
10686            "checkout",
10687            "",
10688            "Bad_Name",
10689            "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
10690        ] {
10691            let c = caixa_with_nome(nome);
10692            assert_eq!(
10693                c.nome(),
10694                nome,
10695                "Caixa::nome must return :nome verbatim (got {}, \
10696                 expected {nome})",
10697                c.nome(),
10698            );
10699            assert_eq!(
10700                c.nome(),
10701                c.nome.as_str(),
10702                "Caixa::nome must byte-equal the raw .nome field \
10703                 access across every value in the String accept-set",
10704            );
10705        }
10706    }
10707
10708    #[test]
10709    fn validate_nome_empty_arm_routes_through_accessor() {
10710        // Composition pin: [`Caixa::validate_nome`]'s empty-arm must
10711        // key off [`Caixa::nome`], not the raw `.nome` field access.
10712        // Structurally: a `Caixa { nome: "".into(), .. }` must surface
10713        // the `NomeEmpty` refusal exactly, and the canonical `"demo"`
10714        // template baseline (the peer positive-arm the sibling
10715        // `validate_nome_accepts_canonical_template` gate carves out)
10716        // must pass validate. The pair jointly pins the accessor +
10717        // validate-gate composition: any future silent detour that
10718        // had the accessor return a fresh `"demo"` on the empty arm
10719        // (a `.nome().is_empty().then(|| "demo")` fallback collapse)
10720        // would silently absorb the `NomeEmpty` refusal at the
10721        // accessor boundary and the validate gate would accept a
10722        // struct-literal `Caixa { nome: "".into(), .. }` — the
10723        // composition pin catches that at caixa-core build time.
10724        //
10725        // Peer of the sibling per-`Caixa`
10726        // `validate_licenca_empty_arm_routes_through_accessor` (6d5bc28)
10727        // / `validate_repositorio_empty_arm_routes_through_accessor`
10728        // (cc7332d) / `validate_descricao_empty_arm_routes_through_accessor`
10729        // (3f16e2f) / `validate_edicao_empty_arm_routes_through_accessor`
10730        // (2641cbd) composition pins on the sibling outer top-level
10731        // [`Caixa`] `Option<&str>` axes — same "the validate /
10732        // shape-gate predicate must route through the substrate-
10733        // primitive typed dispatch" discipline extended onto the peer
10734        // outer top-level [`Caixa`] required-`&str` composition axis.
10735        let c = caixa_with_nome("");
10736        assert!(
10737            matches!(c.validate_nome(), Err(ManifestError::NomeEmpty)),
10738            "validate_nome must reject nome == \"\" with NomeEmpty — \
10739             the accessor and the validate gate must route through the \
10740             same substrate-primitive typed dispatch on the :nome \
10741             empty-arm",
10742        );
10743        let c = caixa_with_nome("demo");
10744        assert!(
10745            c.validate_nome().is_ok(),
10746            "validate_nome must accept nome == \"demo\" (the canonical \
10747             DNS-1123-label template baseline)",
10748        );
10749    }
10750
10751    #[test]
10752    fn nome_projects_str_by_borrow() {
10753        // The by-borrow pin: [`Caixa::nome`] returns `&str` by borrow
10754        // — the `&str` borrows the underlying `String` storage of the
10755        // required `nome` slot and the accessor must not allocate a
10756        // fresh `String` on every call. Peer of the [`Caixa::licenca`]
10757        // (6d5bc28) / [`Caixa::repositorio`] (cc7332d) /
10758        // [`Caixa::descricao`] (3f16e2f) / [`Caixa::edicao`] (2641cbd)
10759        // by-borrow pins on the peer outer top-level [`Caixa`]
10760        // `Option<&str>`-return axes, extended onto the first outer
10761        // top-level [`Caixa`] required-`&str`-return axis — the
10762        // accessor's returned `&str` must borrow from `&self` (the
10763        // returned reference's lifetime is tied to `&self`), and
10764        // calling the accessor twice on the same [`Caixa`] must yield
10765        // the same `&str` verbatim (idempotent, no side effects on
10766        // `&self`).
10767        //
10768        // Pins against a future silent detour that returned an owned
10769        // `String` (which would type-check but silently allocate on
10770        // every call, breaking the zero-cost projection every peer
10771        // sibling accessor carries), an accidental
10772        // `.nome.to_lowercase()` detour that returned a fresh
10773        // allocation through an already-DNS-1123-lowercase-only
10774        // string (breaking a future `const fn` regression), or a
10775        // one-arm-only accessor that returned a canonicalized value
10776        // on some sentinel input (breaking the pass-through invariant
10777        // the sibling required-scalar accessors carry).
10778        for nome in ["demo", "catalog", "hello-rio", "checkout"] {
10779            let c = caixa_with_nome(nome);
10780            let first = c.nome();
10781            let second = c.nome();
10782            assert_eq!(
10783                first, second,
10784                "Caixa::nome must be idempotent — two successive calls \
10785                 on the same &self must return the same &str",
10786            );
10787            assert_eq!(
10788                first, nome,
10789                "Caixa::nome must return :nome verbatim by borrow — \
10790                 got {first}, expected {nome}",
10791            );
10792        }
10793    }
10794
10795    #[test]
10796    fn versao_returns_versao_byte_string_verbatim_across_permutations() {
10797        // The canonical per-`Caixa` `:versao` universal-axis SemVer-2
10798        // pinned-version scalar pin: [`Caixa::versao`] must return the
10799        // `:versao` typed `String` verbatim as `&str`, byte-equal to the
10800        // raw `.versao` field access across every representative value
10801        // in the accept-set — the canonical `"0.1.0"` template baseline
10802        // (the same `feira init`-scaffolded default the sibling
10803        // `validate_versao_accepts_canonical_template` positive-control
10804        // gate pins), plus every canonical SemVer-2 shape the sibling
10805        // `validate_versao_accepts_canonical_forms` positive-arm sweep
10806        // covers (`"0.0.0"`, `"1.0.0"`, `"0.2.0-rc.1"`,
10807        // `"1.0.0-alpha.0"`, `"1.0.0+build.42"`, `"1.0.0-rc.1+build.42"`,
10808        // `"10.20.30"`), plus every past-the-guard sentinel for the
10809        // `VersaoEmpty` / `VersaoInvalid` refusal cases (`""` the empty
10810        // arm, `"v0.1.0"` the git-tag-shape-leak footgun, `"0.1"` the
10811        // missing-patch footgun, `"^0.1"` the requirement-shape-leak
10812        // footgun, `"0.1.0.0"` the four-part-Java-convention footgun,
10813        // `"latest"` the docker-tag-shape footgun — the sentinels pin
10814        // the accessor doesn't silently absorb the refusal cases into a
10815        // template-derived fallback like `"0.1.0"`).
10816        //
10817        // The past-the-guard sentinels pin the accessor doesn't silently
10818        // absorb the refusal cases into a template-derived fallback (a
10819        // future `.versao().is_empty().then(|| "0.1.0")` collapse would
10820        // silently absorb the `VersaoEmpty` refusal at the accessor
10821        // boundary and the validate gate would accept a struct-literal
10822        // `Caixa { versao: "".into(), .. }` — the pin catches that at
10823        // caixa-core build time).
10824        //
10825        // Second outer top-level [`Caixa`] `&str`-return required-scalar
10826        // accessor pin — folds on the "outer [`Caixa`] `&str` required-
10827        // scalar" projection pattern the sibling per-`Caixa`
10828        // [`Caixa::nome`] (e6b7d97) opened. Sibling in shape to the peer
10829        // per-`:membros` [`crate::aplicacao::Membro::versao_requirement`]
10830        // (4127bb6) / per-`:children`
10831        // [`crate::supervisor::ChildSpec::versao_requirement`] (2c053c8)
10832        // / per-`:upgrade-from`
10833        // [`crate::UpgradeFromEntry::prior_versao`] (75d27a8) per-sub-
10834        // struct `:versao`-shaped `&str`-return accessor pins on the
10835        // sibling per-typed-slot version-carrier axes, extended onto the
10836        // second outer top-level [`Caixa`] universal-axis required-
10837        // `String`-carry axis so the two universal-axis identity-
10838        // carrying scalars every `defcaixa` form supplies (`:nome` +
10839        // `:versao`) share the same "one typed dispatch per axis" pin
10840        // discipline.
10841        for versao in [
10842            "0.1.0",
10843            "0.0.0",
10844            "1.0.0",
10845            "0.2.0-rc.1",
10846            "1.0.0-alpha.0",
10847            "1.0.0+build.42",
10848            "1.0.0-rc.1+build.42",
10849            "10.20.30",
10850            "",
10851            "v0.1.0",
10852            "0.1",
10853            "^0.1",
10854            "0.1.0.0",
10855            "latest",
10856        ] {
10857            let c = caixa_with_versao(versao);
10858            assert_eq!(
10859                c.versao(),
10860                versao,
10861                "Caixa::versao must return :versao verbatim (got {}, \
10862                 expected {versao})",
10863                c.versao(),
10864            );
10865            assert_eq!(
10866                c.versao(),
10867                c.versao.as_str(),
10868                "Caixa::versao must byte-equal the raw .versao field \
10869                 access across every value in the String accept-set",
10870            );
10871        }
10872    }
10873
10874    #[test]
10875    fn validate_versao_empty_arm_routes_through_accessor() {
10876        // Composition pin: [`Caixa::validate_versao`]'s empty-arm gate
10877        // must key off [`Caixa::versao`], not the raw `.versao` field
10878        // access. Structurally: a `Caixa { versao: "".into(), .. }` must
10879        // surface the `VersaoEmpty` refusal exactly, and the canonical
10880        // `"0.1.0"` template baseline (the peer positive-arm the sibling
10881        // `validate_versao_accepts_canonical_template` gate carves out)
10882        // must pass validate. The pair jointly pins the accessor +
10883        // validate-gate composition: any future silent detour that had
10884        // the accessor return a fresh `"0.1.0"` on the empty arm
10885        // (a `.versao().is_empty().then(|| "0.1.0")` fallback collapse)
10886        // would silently absorb the `VersaoEmpty` refusal at the
10887        // accessor boundary and the validate gate would accept a
10888        // struct-literal `Caixa { versao: "".into(), .. }` — the
10889        // composition pin catches that at caixa-core build time.
10890        //
10891        // Peer of the sibling per-`Caixa`
10892        // `validate_nome_empty_arm_routes_through_accessor` (e6b7d97)
10893        // composition pin on the sibling outer top-level [`Caixa`]
10894        // required-`&str` universal-axis surface — same "the validate /
10895        // shape-gate predicate must route through the substrate-
10896        // primitive typed dispatch" discipline extended onto the peer
10897        // outer top-level [`Caixa`] required-`&str` universal-axis
10898        // pinned-version composition axis, closing the second
10899        // coordinate of the "one canonical typed dispatch per per-Caixa
10900        // required-`&str` universal-axis" discipline.
10901        let c = caixa_with_versao("");
10902        assert!(
10903            matches!(c.validate_versao(), Err(ManifestError::VersaoEmpty)),
10904            "validate_versao must reject versao == \"\" with VersaoEmpty — \
10905             the accessor and the validate gate must route through the \
10906             same substrate-primitive typed dispatch on the :versao \
10907             empty-arm",
10908        );
10909        let c = caixa_with_versao("0.1.0");
10910        assert!(
10911            c.validate_versao().is_ok(),
10912            "validate_versao must accept versao == \"0.1.0\" (the \
10913             canonical SemVer-2 template baseline)",
10914        );
10915    }
10916
10917    #[test]
10918    fn versao_projects_str_by_borrow() {
10919        // The by-borrow pin: [`Caixa::versao`] returns `&str` by borrow
10920        // — the `&str` borrows the underlying `String` storage of the
10921        // required `versao` slot and the accessor must not allocate a
10922        // fresh `String` on every call. Peer of the [`Caixa::nome`]
10923        // (e6b7d97) by-borrow pin on the sibling outer top-level
10924        // [`Caixa`] required-`&str`-return axis, extended onto the
10925        // second outer top-level [`Caixa`] required-`&str`-return
10926        // universal-axis pinned-version surface — the accessor's
10927        // returned `&str` must borrow from `&self` (the returned
10928        // reference's lifetime is tied to `&self`), and calling the
10929        // accessor twice on the same [`Caixa`] must yield the same
10930        // `&str` verbatim (idempotent, no side effects on `&self`).
10931        //
10932        // Pins against a future silent detour that returned an owned
10933        // `String` (which would type-check but silently allocate on
10934        // every call, breaking the zero-cost projection every peer
10935        // sibling accessor carries), an accidental
10936        // `semver::Version::parse(&self.versao).unwrap().to_string()`
10937        // detour that returned a canonicalized fresh allocation through
10938        // an already-canonical byte-string (breaking a future `const fn`
10939        // regression and silently absorbing the `VersaoInvalid` refusal
10940        // at the accessor boundary), or a one-arm-only accessor that
10941        // returned a canonicalized value on some sentinel input
10942        // (breaking the pass-through invariant the sibling required-
10943        // scalar accessors carry).
10944        for versao in ["0.1.0", "1.0.0", "0.2.0-rc.1", "1.0.0+build.42"] {
10945            let c = caixa_with_versao(versao);
10946            let first = c.versao();
10947            let second = c.versao();
10948            assert_eq!(
10949                first, second,
10950                "Caixa::versao must be idempotent — two successive \
10951                 calls on the same &self must return the same &str",
10952            );
10953            assert_eq!(
10954                first, versao,
10955                "Caixa::versao must return :versao verbatim by borrow \
10956                 — got {first}, expected {versao}",
10957            );
10958        }
10959    }
10960
10961    fn caixa_with_kind(kind: CaixaKind) -> Caixa {
10962        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10963        c.kind = kind;
10964        c
10965    }
10966
10967    #[test]
10968    fn kind_returns_kind_variant_verbatim_across_permutations() {
10969        // The canonical per-`Caixa` `:kind` universal-axis closed-set-
10970        // enum discriminant pin: [`Caixa::kind`] must return the `:kind`
10971        // typed [`CaixaKind`] variant verbatim by `Copy`, byte-equal to
10972        // the raw `.kind` field access across every variant in the
10973        // closed accept-set (`Biblioteca` — the library kind that
10974        // exports lisp forms; `Binario` — the nix-built executable kind
10975        // under `exe/`; `Servico` — the wasm-component daemon kind
10976        // under `servicos/`; `Supervisor` — the OTP-shaped hierarchical
10977        // reconciliation kind; `Aplicacao` — the M3 typed-mesh
10978        // composition kind).
10979        //
10980        // Pins against a future silent detour that re-derived the kind
10981        // from a peer axis (an accidental fallback to
10982        // `if !servicos.is_empty() { Servico } else if
10983        // !membros.is_empty() { Aplicacao } else { Biblioteca }`
10984        // collapse that read the code-surface / mesh-slot columns into
10985        // the kind discriminator), a variant remap the operator
10986        // authors on one consumer without the other, or a stale-derive
10987        // detour that substituted [`CaixaKind::Biblioteca`] as the
10988        // default when the field held any other variant (which would
10989        // silently collapse the distinction between "author explicitly
10990        // declared `:kind Servico`" and "author declared any other
10991        // kind" every downstream renderer-dispatch site depends on).
10992        //
10993        // First outer top-level [`Caixa`] `Copy`-return required-enum-
10994        // discriminant accessor pin — opens the "outer [`Caixa`]
10995        // `Copy`-return required-discriminant" projection pattern.
10996        // Sibling in shape to the peer per-`:supervisor`
10997        // [`crate::supervisor::SupervisorSpec::estrategia`] (eafb619),
10998        // per-`:placement` [`crate::aplicacao::Placement::estrategia`]
10999        // (921fe1b), and per-`:children`
11000        // [`crate::supervisor::ChildSpec::restart`] (dfb4a81)
11001        // `Copy`-return closed-set-enum discriminant accessor pins on
11002        // the sibling nested-spec typed-slot discriminator axes,
11003        // extended here to the outer top-level [`Caixa`] universal-
11004        // axis surface.
11005        for kind in [
11006            CaixaKind::Biblioteca,
11007            CaixaKind::Binario,
11008            CaixaKind::Servico,
11009            CaixaKind::Supervisor,
11010            CaixaKind::Aplicacao,
11011        ] {
11012            let c = caixa_with_kind(kind);
11013            assert_eq!(
11014                c.kind(),
11015                kind,
11016                "Caixa::kind must return :kind verbatim (got {:?}, \
11017                 expected {kind:?})",
11018                c.kind(),
11019            );
11020            assert_eq!(
11021                c.kind(),
11022                c.kind,
11023                "Caixa::kind accessor and .kind field access must \
11024                 byte-equal — the accessor is the substrate-primitive \
11025                 typed dispatch every downstream kind-gate consumer \
11026                 must route through",
11027            );
11028        }
11029    }
11030
11031    #[test]
11032    fn require_kind_reads_through_lifted_kind_accessor() {
11033        // Two-consumer coherence pin: the [`crate::render::require_kind`]
11034        // entry-gate predicate (the canonical two-line
11035        // `require_kind(caixa, Servico)?` prelude every per-Servico /
11036        // per-Aplicacao renderer runs at its entry-point) and the
11037        // sibling [`crate::render::KindMismatch`] error carrier's
11038        // `actual:` field (which names the offending caixa's variant
11039        // in the diagnostic) must both key off the lifted accessor, so
11040        // any future rebrand on the typed slot's reader shape lands at
11041        // exactly one place. Pins the two-site coherence by exercising
11042        // every off-diagonal `(actual, expected)` pair across the
11043        // closed accept-set — the `KindMismatch { actual, expected }`
11044        // surfaced on the mismatch arm must byte-equal the pair the
11045        // accessor returns for each side.
11046        //
11047        // Peer of the sibling per-`:placement`
11048        // `validate_placement_reads_through_lifted_estrategia_accessor`
11049        // (921fe1b) two-arm consumer-coherence pin on the M3 mesh-slot
11050        // `Copy`-return discriminant axis — same "the entry-gate
11051        // predicate and the error carrier's `actual:` field must route
11052        // through the substrate-primitive typed dispatch" discipline
11053        // extended onto the outer top-level [`Caixa`] universal-axis
11054        // discriminant surface.
11055        for expected in [
11056            CaixaKind::Biblioteca,
11057            CaixaKind::Binario,
11058            CaixaKind::Servico,
11059            CaixaKind::Supervisor,
11060            CaixaKind::Aplicacao,
11061        ] {
11062            for actual in [
11063                CaixaKind::Biblioteca,
11064                CaixaKind::Binario,
11065                CaixaKind::Servico,
11066                CaixaKind::Supervisor,
11067                CaixaKind::Aplicacao,
11068            ] {
11069                let c = caixa_with_kind(actual);
11070                let result = crate::render::require_kind(&c, expected);
11071                if expected == actual {
11072                    assert!(
11073                        result.is_ok(),
11074                        "require_kind must accept when actual == expected \
11075                         (actual={actual:?}, expected={expected:?})",
11076                    );
11077                } else {
11078                    let err = result.expect_err("require_kind must reject when actual != expected");
11079                    assert_eq!(
11080                        err.actual,
11081                        c.kind(),
11082                        "KindMismatch.actual must byte-equal Caixa::kind() \
11083                         — the error carrier's `actual:` field reads \
11084                         through the lifted accessor",
11085                    );
11086                    assert_eq!(
11087                        err.expected, expected,
11088                        "KindMismatch.expected must byte-equal the \
11089                         expected variant passed to require_kind",
11090                    );
11091                }
11092            }
11093        }
11094    }
11095
11096    #[test]
11097    fn aplicacao_view_kind_gate_routes_through_accessor() {
11098        // Composition pin: [`Caixa::aplicacao_view`]'s kind-gate arm
11099        // must key off [`Caixa::kind`], not the raw `.kind` field
11100        // access. Structurally: a `Caixa { kind: X, .. }` for any
11101        // non-`Aplicacao` variant must fold to `None` on the
11102        // `aplicacao_view` composer (the "kind mismatch → no typed
11103        // view" contract every downstream Aplicacao consumer keys off
11104        // via `?`), and a `Caixa { kind: Aplicacao, .. }` must fold to
11105        // `Some(_)`. The pair jointly pins the accessor + view-gate
11106        // composition: any future silent detour that had the accessor
11107        // return a fresh [`CaixaKind::Aplicacao`] on some sentinel
11108        // input would silently absorb the kind-mismatch case at the
11109        // accessor boundary and every per-Aplicacao renderer would
11110        // silently render a non-Aplicacao caixa's mesh slots — the
11111        // composition pin catches that at caixa-core build time.
11112        //
11113        // Peer of the sibling per-`Caixa`
11114        // `validate_nome_empty_arm_routes_through_accessor` (e6b7d97) /
11115        // `validate_versao_empty_arm_routes_through_accessor` (20c0539)
11116        // composition pins on the sibling outer top-level [`Caixa`]
11117        // required-`&str` universal-axis surfaces — same "the
11118        // composer / validate gate must route through the substrate-
11119        // primitive typed dispatch" discipline extended onto the
11120        // outer top-level [`Caixa`] `Copy`-return required-
11121        // discriminant composition axis.
11122        for kind in [
11123            CaixaKind::Biblioteca,
11124            CaixaKind::Binario,
11125            CaixaKind::Servico,
11126            CaixaKind::Supervisor,
11127        ] {
11128            let c = caixa_with_kind(kind);
11129            assert!(
11130                c.aplicacao_view().is_none(),
11131                "aplicacao_view must return None on non-Aplicacao \
11132                 kind {kind:?} — the composer's kind-gate must route \
11133                 through Caixa::kind()",
11134            );
11135        }
11136        let c = caixa_with_kind(CaixaKind::Aplicacao);
11137        assert!(
11138            c.aplicacao_view().is_some(),
11139            "aplicacao_view must return Some on kind Aplicacao — \
11140             the composer's kind-gate must accept the matching arm \
11141             through Caixa::kind()",
11142        );
11143    }
11144
11145    #[test]
11146    fn supervisor_view_kind_gate_routes_through_accessor() {
11147        // Composition pin (mirror of the sibling
11148        // `aplicacao_view_kind_gate_routes_through_accessor` on the
11149        // second `_view` composer): [`Caixa::supervisor_view`]'s kind-
11150        // gate arm must key off [`Caixa::kind`], not the raw `.kind`
11151        // field access. A `Caixa { kind: X, .. }` for any non-
11152        // `Supervisor` variant must fold to `None` on the
11153        // `supervisor_view` composer, and a `Caixa { kind:
11154        // Supervisor, .. }` must fold to `Some(_)`. Same peer
11155        // composition pin discipline on the second `_view` composer
11156        // axis.
11157        for kind in [
11158            CaixaKind::Biblioteca,
11159            CaixaKind::Binario,
11160            CaixaKind::Servico,
11161            CaixaKind::Aplicacao,
11162        ] {
11163            let c = caixa_with_kind(kind);
11164            assert!(
11165                c.supervisor_view().is_none(),
11166                "supervisor_view must return None on non-Supervisor \
11167                 kind {kind:?} — the composer's kind-gate must route \
11168                 through Caixa::kind()",
11169            );
11170        }
11171        let mut c = caixa_with_kind(CaixaKind::Supervisor);
11172        // A Supervisor caixa needs a strategy + at least one child to
11173        // fold to a Some(_) that also validates; the composer itself
11174        // requires only the kind arm, so bare kind flip is enough to
11175        // pin the `Some(_)` return, but we populate the minimum
11176        // supervisor shape so a future strengthening of the composer
11177        // to reject an empty spec doesn't false-positive this pin.
11178        c.estrategia = Some(crate::supervisor::RestartStrategy::OneForOne);
11179        c.children = vec![crate::supervisor::ChildSpec {
11180            caixa: "child".into(),
11181            versao: "^0.1".into(),
11182            restart: crate::supervisor::RestartPolicy::Permanent,
11183        }];
11184        assert!(
11185            c.supervisor_view().is_some(),
11186            "supervisor_view must return Some on kind Supervisor — \
11187             the composer's kind-gate must accept the matching arm \
11188             through Caixa::kind()",
11189        );
11190    }
11191
11192    #[test]
11193    fn kind_projects_by_copy() {
11194        // The by-`Copy` pin: [`Caixa::kind`] returns a fresh
11195        // [`CaixaKind`] by `Copy` — the accessor must not borrow from
11196        // `&self` (the returned value is owned, `Copy`-projected from
11197        // the underlying [`CaixaKind`] storage; two calls on the same
11198        // [`Caixa`] must yield byte-equal values). Peer of the peer
11199        // per-`:placement` `Placement::estrategia` / per-`:supervisor`
11200        // `SupervisorSpec::estrategia` / per-`:children`
11201        // `ChildSpec::restart` `Copy`-return discriminant accessor
11202        // pins on the sibling nested-spec typed-slot discriminator
11203        // axes, extended onto the first outer top-level [`Caixa`]
11204        // required-`Copy`-return axis — pins against a future silent
11205        // detour that returned `&CaixaKind` (which would type-check
11206        // but silently constrain every consumer's callsite to a
11207        // borrow-shaped dispatch, breaking the zero-cost `Copy`
11208        // projection every peer sibling accessor carries).
11209        for kind in [
11210            CaixaKind::Biblioteca,
11211            CaixaKind::Binario,
11212            CaixaKind::Servico,
11213            CaixaKind::Supervisor,
11214            CaixaKind::Aplicacao,
11215        ] {
11216            let c = caixa_with_kind(kind);
11217            let first: CaixaKind = c.kind();
11218            let second: CaixaKind = c.kind();
11219            assert_eq!(
11220                first, second,
11221                "Caixa::kind must be idempotent — two successive \
11222                 calls on the same &self must return the same \
11223                 CaixaKind variant",
11224            );
11225            assert_eq!(
11226                first, kind,
11227                "Caixa::kind must return :kind verbatim by Copy — \
11228                 got {first:?}, expected {kind:?}",
11229            );
11230        }
11231    }
11232
11233    // ── Caixa::autores — outer top-level &[T] slice accessor ──────────
11234
11235    #[test]
11236    fn autores_returns_autores_slice_verbatim_across_permutations() {
11237        // The canonical per-`Caixa` `:autores` universal-axis maintainer-
11238        // name-list slice pin: [`Caixa::autores`] must return the
11239        // `:autores` typed [`Vec<String>`] list verbatim as a
11240        // `&[String]`, byte-equal to the raw `self.autores.as_slice()`
11241        // access across every representative value in the accept-set —
11242        // `[]` (the "no maintainers declared" arm every existing
11243        // fixture without an `:autores` line carries), `[""]` (a past-
11244        // the-guard sentinel that pins the accessor doesn't perform a
11245        // silent `[""] → []` collapse on the empty-entry arm — validate
11246        // rejects `[""]` through `AutorEmpty` but the accessor must
11247        // ship the raw slot verbatim so a validate-time gate regression
11248        // surfaces at the caixa-helm emit boundary rather than being
11249        // silently absorbed into a maintainer-drop), `["pleme-io"]` (the
11250        // canonical single-maintainer form every `feira init` template
11251        // scaffolds), `["alice", "bob"]` (a canonical multi-maintainer
11252        // form), `["alice <alice@example.com>", "bob <bob@example.com>"]`
11253        // (the canonical RFC-5322 `<name> <email>` form the
11254        // `is_chart_maintainer_name_shape` predicate accepts), and
11255        // `["pleme-io", "pleme-io"]` (a past-the-guard duplicate
11256        // sentinel — validate rejects through `AutorDuplicate` but the
11257        // accessor must ship the raw slot verbatim).
11258        //
11259        // First outer top-level [`Caixa`] `&[T]`-return slice accessor
11260        // pin on the substrate primitive — opens the "outer [`Caixa`]
11261        // `&[T]` slice" projection pattern the sibling per-`Caixa`
11262        // `:etiquetas` / `:deps` / `:deps-dev` / `:exe` / `:bibliotecas`
11263        // / `:servicos` / `:upgrade-from` / `:children` future lifts
11264        // fold on. Sibling in shape to the peer per-`:supervisor`
11265        // [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
11266        // per-`:placement` [`crate::aplicacao::Placement::clusters`]
11267        // (a6e18d7), per-`:membros`
11268        // [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
11269        // per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
11270        // (0dcc926), and per-`:upgrade-from :instructions`
11271        // [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
11272        // `&[T]`-return slice accessor pins on the sibling per-M2 /
11273        // per-M3 typed-slot list axes, extended onto the outer top-
11274        // level [`Caixa`] universal-axis surface. Pins against a future
11275        // silent detour that returned an owned `Vec<String>` (which
11276        // would type-check but silently clone on every accessor call,
11277        // breaking the zero-cost projection every peer sibling slice
11278        // accessor carries), a `[""] → []` collapse (which would
11279        // silently absorb the `AutorEmpty` refusal case at the accessor
11280        // boundary), or a `["a", "a"] → ["a"]` dedup collapse (which
11281        // would silently absorb the `AutorDuplicate` refusal case at
11282        // the accessor boundary and the caixa-helm `maintainers:` fold
11283        // would silently render a dedupped list on a struct-literal
11284        // `Caixa { autores: vec!["a".into(), "a".into()], .. }`).
11285        for autores in [
11286            vec![],
11287            vec![""],
11288            vec!["pleme-io"],
11289            vec!["alice", "bob"],
11290            vec!["alice <alice@example.com>", "bob <bob@example.com>"],
11291            vec!["pleme-io", "pleme-io"],
11292        ] {
11293            let c = caixa_with_autores(autores.clone());
11294            let expected: Vec<String> = autores.iter().map(|s| (*s).to_string()).collect();
11295            assert_eq!(
11296                c.autores(),
11297                expected.as_slice(),
11298                "Caixa::autores must return :autores verbatim (got {:?}, \
11299                 expected {expected:?})",
11300                c.autores(),
11301            );
11302            assert_eq!(
11303                c.autores(),
11304                c.autores.as_slice(),
11305                "Caixa::autores must byte-equal the raw \
11306                 `self.autores.as_slice()` field access across every \
11307                 value in the Vec<String> accept-set",
11308            );
11309        }
11310    }
11311
11312    #[test]
11313    fn validate_autores_empty_entry_arm_routes_through_accessor() {
11314        // Composition pin: [`Caixa::validate_autores`]'s per-entry
11315        // empty-arm gate must key off [`Caixa::autores`], not the raw
11316        // `&self.autores` field-borrow walk. Structurally: a
11317        // `Caixa { autores: vec!["".into()], .. }` must surface the
11318        // `AutorEmpty` refusal exactly, and a
11319        // `Caixa { autores: vec!["pleme-io".into()], .. }` (the
11320        // canonical single-maintainer form) must pass validate. The
11321        // pair jointly pins the accessor + validate-gate composition:
11322        // any future silent detour that had the accessor return an
11323        // empty slice on the `[""]` arm (a
11324        // `.iter().filter(|s| !s.is_empty()).collect()` collapse)
11325        // would silently absorb the `AutorEmpty` refusal at the
11326        // accessor boundary and the validate gate would accept a
11327        // struct-literal `Caixa { autores: vec!["".into()], .. }` —
11328        // the composition pin catches that at caixa-core build time.
11329        //
11330        // Peer of the per-`Caixa` [`Caixa::validate_licenca`] (6d5bc28)
11331        // accessor-composition pin
11332        // (`validate_licenca_empty_arm_routes_through_accessor`) on the
11333        // sibling `Option<&str>`-composition axis and the
11334        // per-`:politicas :circuit-breaker`
11335        // [`crate::aplicacao::CircuitBreaker::max_failures`] (3a74062)
11336        // accessor-composition pin
11337        // (`validate_politicas_max_failures_zero_floor_arm_routes_through_accessor`)
11338        // on the sibling required-`u32`-composition axis — same "the
11339        // validate / shape-gate predicate must route through the
11340        // substrate-primitive typed dispatch" discipline extended onto
11341        // the outer top-level [`Caixa`] universal-axis `&[T]`-
11342        // composition surface.
11343        let c = caixa_with_autores(vec![""]);
11344        assert!(
11345            matches!(c.validate_autores(), Err(ManifestError::AutorEmpty)),
11346            "validate_autores must reject autores == vec![\"\"] with \
11347             AutorEmpty — the accessor and the validate gate must \
11348             route through the same substrate-primitive typed dispatch \
11349             on the :autores per-entry empty arm",
11350        );
11351        let c = caixa_with_autores(vec!["pleme-io"]);
11352        assert!(
11353            c.validate_autores().is_ok(),
11354            "validate_autores must accept autores == vec![\"pleme-io\"] \
11355             (the canonical single-maintainer shape every `feira init` \
11356             template scaffolds)",
11357        );
11358    }
11359
11360    #[test]
11361    fn autores_projects_slice_by_borrow() {
11362        // The by-borrow pin: [`Caixa::autores`] returns `&[String]` by
11363        // borrow — the returned slice borrows the underlying
11364        // `Vec<String>` storage of the `:autores` slot and the
11365        // accessor must not clone the backing `Vec` on every call.
11366        // Peer of the per-`:membros`
11367        // [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36) /
11368        // per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
11369        // (0dcc926) / per-`:placement`
11370        // [`crate::aplicacao::Placement::clusters`] (a6e18d7) /
11371        // per-`:supervisor` [`crate::supervisor::SupervisorSpec::children`]
11372        // (bc92bce) by-borrow pins on the sibling per-M2 / per-M3
11373        // typed-slot `&[T]`-return axes, extended onto the outer top-
11374        // level [`Caixa`] universal-axis `&[String]` shape — the
11375        // accessor's returned slice must borrow from `&self` (the
11376        // returned reference's lifetime is tied to `&self`), and
11377        // calling the accessor twice on the same [`Caixa`] must yield
11378        // slices that are pointer-equal (the underlying byte-buffer is
11379        // the storage `Vec`'s allocation, not a fresh copy) as well as
11380        // value-equal (idempotent, no side effects on `&self`).
11381        //
11382        // Pins against a future silent detour that returned an owned
11383        // `Vec<String>` (which would type-check but silently clone on
11384        // every call, breaking the zero-cost projection every peer
11385        // sibling slice accessor carries), a `&Vec<String>` return
11386        // (which would leak the backing `Vec`'s grow/push/reserve
11387        // surface no downstream consumer reaches for), or a one-arm-
11388        // only accessor that returned a saturating value on some
11389        // sentinel input (breaking the pass-through invariant the
11390        // sibling slice accessors carry).
11391        for autores in [
11392            vec![],
11393            vec!["pleme-io"],
11394            vec!["alice", "bob"],
11395            vec!["pleme-io", "pleme-io"],
11396        ] {
11397            let c = caixa_with_autores(autores.clone());
11398            let expected: Vec<String> = autores.iter().map(|s| (*s).to_string()).collect();
11399            let first = c.autores();
11400            let second = c.autores();
11401            assert_eq!(
11402                first, second,
11403                "Caixa::autores must be idempotent — two successive \
11404                 calls on the same &self must return the same \
11405                 &[String]",
11406            );
11407            assert_eq!(
11408                first.as_ptr(),
11409                second.as_ptr(),
11410                "Caixa::autores must borrow the underlying Vec<String> \
11411                 storage — two successive calls must return slices \
11412                 with the same backing pointer (a fresh Vec<String> \
11413                 clone would change the pointer on every call)",
11414            );
11415            assert_eq!(
11416                first,
11417                expected.as_slice(),
11418                "Caixa::autores must return :autores verbatim by \
11419                 borrow — got {first:?}, expected {expected:?}",
11420            );
11421        }
11422    }
11423
11424    // ── Caixa::etiquetas — outer top-level &[T] slice accessor ────────
11425
11426    #[test]
11427    fn etiquetas_returns_etiquetas_slice_verbatim_across_permutations() {
11428        // The canonical per-`Caixa` `:etiquetas` universal-axis
11429        // registry-search-tag-list slice pin: [`Caixa::etiquetas`] must
11430        // return the `:etiquetas` typed [`Vec<String>`] list verbatim
11431        // as a `&[String]`, byte-equal to the raw
11432        // `self.etiquetas.as_slice()` access across every representative
11433        // value in the accept-set — `[]` (the "no tags declared" arm
11434        // every existing fixture without an `:etiquetas` line carries),
11435        // `[""]` (a past-the-guard sentinel that pins the accessor
11436        // doesn't perform a silent `[""] → []` collapse on the empty-
11437        // entry arm — validate rejects `[""]` through `EtiquetaEmpty`
11438        // but the accessor must ship the raw slot verbatim so a
11439        // validate-time gate regression surfaces at the caixa-helm emit
11440        // boundary rather than being silently absorbed into a keyword-
11441        // drop), `["demo"]` (the canonical single-tag form every
11442        // `feira init` template scaffolds), `["example", "aplicacao",
11443        // "mesh", "ecommerce", "demo"]` (the canonical multi-tag form
11444        // the checkout-aplicacao fixture emits), and `["demo", "demo"]`
11445        // (a past-the-guard duplicate sentinel — validate rejects
11446        // through `EtiquetaDuplicate` but the accessor must ship the
11447        // raw slot verbatim so the caixa-helm `BTreeSet::collect` dedup
11448        // at chart-render time isn't silently promoted into the
11449        // accessor boundary and struct-literal
11450        // `Caixa { etiquetas: vec!["demo".into(), "demo".into()], .. }`
11451        // fixtures continue to expose the duplicate at the accessor).
11452        //
11453        // Second outer top-level [`Caixa`] `&[T]`-return slice accessor
11454        // pin on the substrate primitive — folds on the "outer
11455        // [`Caixa`] `&[T]` slice" projection pattern
11456        // `autores_returns_autores_slice_verbatim_across_permutations`
11457        // (b5d813f) opened, sibling in shape and idiom. Pins against a
11458        // future silent detour that returned an owned `Vec<String>`
11459        // (which would type-check but silently clone on every accessor
11460        // call, breaking the zero-cost projection every peer sibling
11461        // slice accessor carries), a `[""] → []` collapse (which would
11462        // silently absorb the `EtiquetaEmpty` refusal case at the
11463        // accessor boundary), or a `["a", "a"] → ["a"]` dedup collapse
11464        // (which would silently absorb the `EtiquetaDuplicate` refusal
11465        // case at the accessor boundary — the caixa-helm chart-render
11466        // `BTreeSet::collect` dedup is downstream of the accessor and
11467        // must not be silently promoted into it).
11468        for etiquetas in [
11469            vec![],
11470            vec![""],
11471            vec!["demo"],
11472            vec!["example", "aplicacao", "mesh", "ecommerce", "demo"],
11473            vec!["demo", "demo"],
11474        ] {
11475            let c = caixa_with_etiquetas(etiquetas.clone());
11476            let expected: Vec<String> = etiquetas.iter().map(|s| (*s).to_string()).collect();
11477            assert_eq!(
11478                c.etiquetas(),
11479                expected.as_slice(),
11480                "Caixa::etiquetas must return :etiquetas verbatim (got \
11481                 {:?}, expected {expected:?})",
11482                c.etiquetas(),
11483            );
11484            assert_eq!(
11485                c.etiquetas(),
11486                c.etiquetas.as_slice(),
11487                "Caixa::etiquetas must byte-equal the raw \
11488                 `self.etiquetas.as_slice()` field access across every \
11489                 value in the Vec<String> accept-set",
11490            );
11491        }
11492    }
11493
11494    #[test]
11495    fn validate_etiquetas_empty_entry_arm_routes_through_accessor() {
11496        // Composition pin: [`Caixa::validate_etiquetas`]'s per-entry
11497        // empty-arm gate must key off [`Caixa::etiquetas`], not the raw
11498        // `&self.etiquetas` field-borrow walk. Structurally: a
11499        // `Caixa { etiquetas: vec!["".into()], .. }` must surface the
11500        // `EtiquetaEmpty` refusal exactly, and a
11501        // `Caixa { etiquetas: vec!["demo".into()], .. }` (the canonical
11502        // single-tag form) must pass validate. The pair jointly pins
11503        // the accessor + validate-gate composition: any future silent
11504        // detour that had the accessor return an empty slice on the
11505        // `[""]` arm (a
11506        // `.iter().filter(|s| !s.is_empty()).collect()` collapse) would
11507        // silently absorb the `EtiquetaEmpty` refusal at the accessor
11508        // boundary and the validate gate would accept a struct-literal
11509        // `Caixa { etiquetas: vec!["".into()], .. }` — the composition
11510        // pin catches that at caixa-core build time.
11511        //
11512        // Peer of the per-`Caixa` `validate_autores_empty_arm_routes_
11513        // through_accessor` (b5d813f) accessor-composition pin on the
11514        // sibling `&[T]`-composition axis — same "the validate / shape-
11515        // gate predicate must route through the substrate-primitive
11516        // typed dispatch" discipline extended onto the sibling outer
11517        // top-level [`Caixa`] `&[T]`-composition surface.
11518        let c = caixa_with_etiquetas(vec![""]);
11519        assert!(
11520            matches!(c.validate_etiquetas(), Err(ManifestError::EtiquetaEmpty)),
11521            "validate_etiquetas must reject etiquetas == vec![\"\"] \
11522             with EtiquetaEmpty — the accessor and the validate gate \
11523             must route through the same substrate-primitive typed \
11524             dispatch on the :etiquetas per-entry empty arm",
11525        );
11526        let c = caixa_with_etiquetas(vec!["demo"]);
11527        assert!(
11528            c.validate_etiquetas().is_ok(),
11529            "validate_etiquetas must accept etiquetas == vec![\"demo\"] \
11530             (the canonical single-tag shape every `feira init` \
11531             template scaffolds)",
11532        );
11533    }
11534
11535    #[test]
11536    fn etiquetas_projects_slice_by_borrow() {
11537        // The by-borrow pin: [`Caixa::etiquetas`] returns `&[String]`
11538        // by borrow — the returned slice borrows the underlying
11539        // `Vec<String>` storage of the `:etiquetas` slot and the
11540        // accessor must not clone the backing `Vec` on every call.
11541        // Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
11542        // (b5d813f) by-borrow pin on the sibling outer top-level
11543        // [`Caixa`] `&[String]`-return axis — the accessor's returned
11544        // slice must borrow from `&self` (the returned reference's
11545        // lifetime is tied to `&self`), and calling the accessor twice
11546        // on the same [`Caixa`] must yield slices that are pointer-
11547        // equal (the underlying byte-buffer is the storage `Vec`'s
11548        // allocation, not a fresh copy) as well as value-equal
11549        // (idempotent, no side effects on `&self`).
11550        //
11551        // Pins against a future silent detour that returned an owned
11552        // `Vec<String>` (which would type-check but silently clone on
11553        // every call, breaking the zero-cost projection every peer
11554        // sibling slice accessor carries), a `&Vec<String>` return
11555        // (which would leak the backing `Vec`'s grow/push/reserve
11556        // surface no downstream consumer reaches for), or a one-arm-
11557        // only accessor that returned a saturating value on some
11558        // sentinel input (breaking the pass-through invariant the
11559        // sibling slice accessors carry).
11560        for etiquetas in [
11561            vec![],
11562            vec!["demo"],
11563            vec!["example", "aplicacao", "mesh"],
11564            vec!["demo", "demo"],
11565        ] {
11566            let c = caixa_with_etiquetas(etiquetas.clone());
11567            let expected: Vec<String> = etiquetas.iter().map(|s| (*s).to_string()).collect();
11568            let first = c.etiquetas();
11569            let second = c.etiquetas();
11570            assert_eq!(
11571                first, second,
11572                "Caixa::etiquetas must be idempotent — two successive \
11573                 calls on the same &self must return the same \
11574                 &[String]",
11575            );
11576            assert_eq!(
11577                first.as_ptr(),
11578                second.as_ptr(),
11579                "Caixa::etiquetas must borrow the underlying \
11580                 Vec<String> storage — two successive calls must \
11581                 return slices with the same backing pointer (a fresh \
11582                 Vec<String> clone would change the pointer on every \
11583                 call)",
11584            );
11585            assert_eq!(
11586                first,
11587                expected.as_slice(),
11588                "Caixa::etiquetas must return :etiquetas verbatim by \
11589                 borrow — got {first:?}, expected {expected:?}",
11590            );
11591        }
11592    }
11593
11594    // ── Caixa::bibliotecas — outer top-level &[T] slice accessor ──────
11595
11596    #[test]
11597    fn bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations() {
11598        // The canonical per-`Caixa` `:bibliotecas` universal-axis
11599        // library-source-path-list slice pin: [`Caixa::bibliotecas`]
11600        // must return the `:bibliotecas` typed [`Vec<String>`] list
11601        // verbatim as a `&[String]`, byte-equal to the raw
11602        // `self.bibliotecas.as_slice()` access across every
11603        // representative value in the accept-set — `[]` (the "no
11604        // libraries declared" arm every `:kind` other than `Biblioteca`
11605        // + every `Biblioteca` relying on the canonical
11606        // `lib/<nome>.lisp` implicit-default path carries; the
11607        // layout's [`crate::LayoutInvariants`] `MissingLib` arm-gate
11608        // fires exactly on this empty-slot + `Biblioteca`-kind
11609        // combination), `[""]` (a past-the-guard sentinel that pins
11610        // the accessor doesn't perform a silent `[""] → []` collapse
11611        // on the empty-entry arm — validate rejects `[""]` through
11612        // `CodePathEmpty { slot: ":bibliotecas" }` but the accessor
11613        // must ship the raw slot verbatim so a validate-time gate
11614        // regression surfaces at the `feira build` phase-1 parse
11615        // boundary rather than being silently absorbed into a
11616        // library-drop), `["lib/demo.lisp"]` (the canonical single-
11617        // entry form `Caixa::template` scaffolds and every `feira init`
11618        // template emits), `["lib/demo.lisp", "lib/helpers.lisp"]`
11619        // (the canonical multi-library form the
11620        // `validate_code_paths_accepts_explicit_relative_paths_on_
11621        // every_slot` fixture emits), and `["lib/foo.lisp",
11622        // "lib/foo.lisp"]` (a past-the-guard duplicate sentinel —
11623        // validate rejects through `CodePathDuplicate { slot:
11624        // ":bibliotecas" }` per the per-slot set-not-multiset gate,
11625        // but the accessor must ship the raw slot verbatim so the
11626        // `feira build` `for entry in caixa.bibliotecas()` parse walk
11627        // sees the duplicate at the accessor boundary and struct-
11628        // literal `Caixa { bibliotecas: vec!["lib/foo.lisp".into(),
11629        // "lib/foo.lisp".into()], .. }` fixtures continue to expose
11630        // the duplicate at the accessor).
11631        //
11632        // Third outer top-level [`Caixa`] `&[T]`-return slice accessor
11633        // pin on the substrate primitive — folds on the "outer
11634        // [`Caixa`] `&[T]` slice" projection pattern
11635        // `autores_returns_autores_slice_verbatim_across_permutations`
11636        // (b5d813f) opened and
11637        // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
11638        // (78c7d3c) folded on, sibling in shape and idiom. Pins
11639        // against a future silent detour that returned an owned
11640        // `Vec<String>` (which would type-check but silently clone on
11641        // every accessor call, breaking the zero-cost projection
11642        // every peer sibling slice accessor carries), a `[""] → []`
11643        // collapse (which would silently absorb the `CodePathEmpty`
11644        // refusal case at the accessor boundary), or a `["lib/foo.lisp",
11645        // "lib/foo.lisp"] → ["lib/foo.lisp"]` dedup collapse (which
11646        // would silently absorb the `CodePathDuplicate` refusal case
11647        // at the accessor boundary — the per-slot set-not-multiset
11648        // gate is downstream of the accessor and must not be silently
11649        // promoted into it).
11650        for bibliotecas in [
11651            vec![],
11652            vec![""],
11653            vec!["lib/demo.lisp"],
11654            vec!["lib/demo.lisp", "lib/helpers.lisp"],
11655            vec!["lib/foo.lisp", "lib/foo.lisp"],
11656        ] {
11657            let c = caixa_with_code_paths(bibliotecas.clone(), vec![], vec![]);
11658            let expected: Vec<String> = bibliotecas.iter().map(|s| (*s).to_string()).collect();
11659            assert_eq!(
11660                c.bibliotecas(),
11661                expected.as_slice(),
11662                "Caixa::bibliotecas must return :bibliotecas verbatim \
11663                 (got {:?}, expected {expected:?})",
11664                c.bibliotecas(),
11665            );
11666            assert_eq!(
11667                c.bibliotecas(),
11668                c.bibliotecas.as_slice(),
11669                "Caixa::bibliotecas must byte-equal the raw \
11670                 `self.bibliotecas.as_slice()` field access across \
11671                 every value in the Vec<String> accept-set",
11672            );
11673        }
11674    }
11675
11676    #[test]
11677    fn validate_code_paths_bibliotecas_empty_arm_routes_through_accessor() {
11678        // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
11679        // empty-arm gate on the `:bibliotecas` slot must key off
11680        // [`Caixa::bibliotecas`], not a divergent raw
11681        // `&self.bibliotecas` field-borrow walk. Structurally: a
11682        // `Caixa { bibliotecas: vec!["".into()], .. }` must surface
11683        // the `CodePathEmpty { slot: ":bibliotecas" }` refusal
11684        // exactly, and a `Caixa { bibliotecas: vec!["lib/demo.lisp".
11685        // into()], .. }` (the canonical single-library form
11686        // `Caixa::template` scaffolds) must pass validate. The pair
11687        // jointly pins the accessor + validate-gate composition: any
11688        // future silent detour that had the accessor return an empty
11689        // slice on the `[""]` arm (a `.iter().filter(|s|
11690        // !s.is_empty()).collect()` collapse) would silently absorb
11691        // the `CodePathEmpty` refusal at the accessor boundary and
11692        // the validate gate would accept a struct-literal
11693        // `Caixa { bibliotecas: vec!["".into()], .. }` — the
11694        // composition pin catches that at caixa-core build time.
11695        //
11696        // Peer of the per-`Caixa` `validate_autores_empty_arm_routes_
11697        // through_accessor` (b5d813f) and
11698        // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
11699        // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
11700        // composition axes — same "the validate / shape-gate
11701        // predicate must route through the substrate-primitive typed
11702        // dispatch" discipline extended onto the sibling outer top-
11703        // level [`Caixa`] `&[T]`-composition surface. Nominally the
11704        // in-tree `validate_code_paths` production body still keys
11705        // off the internal `[(":bibliotecas", &self.bibliotecas,
11706        // CodePathFileType::LispSource), (":exe", &self.exe, ..),
11707        // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
11708        // (the tuple's homogeneous slice-typed shape blocks a per-
11709        // element accessor swap in isolation — a future companion
11710        // lift for `:exe` and `:servicos` on the same outer-`Caixa`
11711        // `&[T]` slice-accessor axis closes that tuple onto the
11712        // triple of typed dispatches as a unit); the composition pin
11713        // catches any future accessor-side silent filter drop against
11714        // that eventual tuple-closure regardless of whether the
11715        // `:bibliotecas` slot is threaded through the accessor or the
11716        // raw field access at the tuple's construction site.
11717        let c = caixa_with_code_paths(vec![""], vec![], vec![]);
11718        assert!(
11719            matches!(
11720                c.validate_code_paths(),
11721                Err(ManifestError::CodePathEmpty {
11722                    slot: ":bibliotecas"
11723                })
11724            ),
11725            "validate_code_paths must reject bibliotecas == vec![\"\"] \
11726             with CodePathEmpty {{ slot: \":bibliotecas\" }} — the \
11727             accessor and the validate gate must route through the \
11728             same substrate-primitive typed dispatch on the \
11729             :bibliotecas per-entry empty arm",
11730        );
11731        let c = caixa_with_code_paths(vec!["lib/demo.lisp"], vec![], vec![]);
11732        assert!(
11733            c.validate_code_paths().is_ok(),
11734            "validate_code_paths must accept bibliotecas == \
11735             vec![\"lib/demo.lisp\"] (the canonical single-library \
11736             shape every `feira init` template scaffolds)",
11737        );
11738    }
11739
11740    #[test]
11741    fn bibliotecas_projects_slice_by_borrow() {
11742        // The by-borrow pin: [`Caixa::bibliotecas`] returns
11743        // `&[String]` by borrow — the returned slice borrows the
11744        // underlying `Vec<String>` storage of the `:bibliotecas` slot
11745        // and the accessor must not clone the backing `Vec` on every
11746        // call. Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
11747        // (b5d813f) and `etiquetas_projects_slice_by_borrow` (78c7d3c)
11748        // by-borrow pins on the sibling outer top-level [`Caixa`]
11749        // `&[String]`-return axes — the accessor's returned slice
11750        // must borrow from `&self` (the returned reference's lifetime
11751        // is tied to `&self`), and calling the accessor twice on the
11752        // same [`Caixa`] must yield slices that are pointer-equal
11753        // (the underlying byte-buffer is the storage `Vec`'s
11754        // allocation, not a fresh copy) as well as value-equal
11755        // (idempotent, no side effects on `&self`).
11756        //
11757        // Pins against a future silent detour that returned an owned
11758        // `Vec<String>` (which would type-check but silently clone on
11759        // every call, breaking the zero-cost projection every peer
11760        // sibling slice accessor carries), a `&Vec<String>` return
11761        // (which would leak the backing `Vec`'s grow/push/reserve
11762        // surface no downstream consumer reaches for), or a one-arm-
11763        // only accessor that returned a saturating value on some
11764        // sentinel input (breaking the pass-through invariant the
11765        // sibling slice accessors carry).
11766        for bibliotecas in [
11767            vec![],
11768            vec!["lib/demo.lisp"],
11769            vec!["lib/demo.lisp", "lib/helpers.lisp"],
11770            vec!["lib/foo.lisp", "lib/foo.lisp"],
11771        ] {
11772            let c = caixa_with_code_paths(bibliotecas.clone(), vec![], vec![]);
11773            let expected: Vec<String> = bibliotecas.iter().map(|s| (*s).to_string()).collect();
11774            let first = c.bibliotecas();
11775            let second = c.bibliotecas();
11776            assert_eq!(
11777                first, second,
11778                "Caixa::bibliotecas must be idempotent — two \
11779                 successive calls on the same &self must return the \
11780                 same &[String]",
11781            );
11782            assert_eq!(
11783                first.as_ptr(),
11784                second.as_ptr(),
11785                "Caixa::bibliotecas must borrow the underlying \
11786                 Vec<String> storage — two successive calls must \
11787                 return slices with the same backing pointer (a \
11788                 fresh Vec<String> clone would change the pointer on \
11789                 every call)",
11790            );
11791            assert_eq!(
11792                first,
11793                expected.as_slice(),
11794                "Caixa::bibliotecas must return :bibliotecas verbatim \
11795                 by borrow — got {first:?}, expected {expected:?}",
11796            );
11797        }
11798    }
11799
11800    // ── Caixa::exe — outer top-level &[T] slice accessor ──────────────
11801
11802    #[test]
11803    fn exe_returns_exe_slice_verbatim_across_permutations() {
11804        // The canonical per-`Caixa` `:exe` universal-axis
11805        // nix-built-executable-entry-path-list slice pin: [`Caixa::exe`]
11806        // must return the `:exe` typed [`Vec<String>`] list verbatim as
11807        // a `&[String]`, byte-equal to the raw `self.exe.as_slice()`
11808        // access across every representative value in the accept-set —
11809        // `[]` (the "no executable declared" arm every `:kind` other
11810        // than `Binario` carries; the layout's [`crate::LayoutInvariants`]
11811        // `BinarioWithoutExe` arm-gate fires exactly on this empty-slot
11812        // + `Binario`-kind combination), `[""]` (a past-the-guard
11813        // sentinel that pins the accessor doesn't perform a silent
11814        // `[""] → []` collapse on the empty-entry arm — validate rejects
11815        // `[""]` through `CodePathEmpty { slot: ":exe" }` but the
11816        // accessor must ship the raw slot verbatim so a validate-time
11817        // gate regression surfaces at the layout / `feira nix` boundary
11818        // rather than being silently absorbed into an executable-drop),
11819        // `["exe/cli"]` (the canonical single-entry Binario form every
11820        // in-tree `caixa_with_code_paths` positive control uses),
11821        // `["exe/cli", "exe/serve"]` (the canonical multi-executable
11822        // form the `validate_code_paths_accepts_explicit_relative_paths_
11823        // on_every_slot` fixture emits), and `["exe/cli", "exe/cli"]`
11824        // (a past-the-guard duplicate sentinel — validate rejects
11825        // through `CodePathDuplicate { slot: ":exe" }` per the per-slot
11826        // set-not-multiset gate, but the accessor must ship the raw
11827        // slot verbatim so struct-literal `Caixa { exe: vec!["exe/cli".
11828        // into(), "exe/cli".into()], .. }` fixtures continue to expose
11829        // the duplicate at the accessor).
11830        //
11831        // Fourth outer top-level [`Caixa`] `&[T]`-return slice accessor
11832        // pin on the substrate primitive — folds on the "outer
11833        // [`Caixa`] `&[T]` slice" projection pattern
11834        // `autores_returns_autores_slice_verbatim_across_permutations`
11835        // (b5d813f) opened,
11836        // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
11837        // (78c7d3c) folded on, and
11838        // `bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
11839        // (8a36c23) closed the universal-axis text-tag family of.
11840        // Opens the outer-`Caixa` foreign-code-slot `&[T]` sub-family
11841        // the sibling `:servicos` future lift closes onto. Pins against
11842        // a future silent detour that returned an owned `Vec<String>`
11843        // (which would type-check but silently clone on every accessor
11844        // call, breaking the zero-cost projection every peer sibling
11845        // slice accessor carries), a `[""] → []` collapse (which would
11846        // silently absorb the `CodePathEmpty` refusal case at the
11847        // accessor boundary), or an `["exe/cli", "exe/cli"] →
11848        // ["exe/cli"]` dedup collapse (which would silently absorb the
11849        // `CodePathDuplicate` refusal case at the accessor boundary —
11850        // the per-slot set-not-multiset gate is downstream of the
11851        // accessor and must not be silently promoted into it).
11852        for exe in [
11853            vec![],
11854            vec![""],
11855            vec!["exe/cli"],
11856            vec!["exe/cli", "exe/serve"],
11857            vec!["exe/cli", "exe/cli"],
11858        ] {
11859            let c = caixa_with_code_paths(vec![], exe.clone(), vec![]);
11860            let expected: Vec<String> = exe.iter().map(|s| (*s).to_string()).collect();
11861            assert_eq!(
11862                c.exe(),
11863                expected.as_slice(),
11864                "Caixa::exe must return :exe verbatim (got {:?}, \
11865                 expected {expected:?})",
11866                c.exe(),
11867            );
11868            assert_eq!(
11869                c.exe(),
11870                c.exe.as_slice(),
11871                "Caixa::exe must byte-equal the raw \
11872                 `self.exe.as_slice()` field access across every value \
11873                 in the Vec<String> accept-set",
11874            );
11875        }
11876    }
11877
11878    #[test]
11879    fn validate_code_paths_exe_empty_arm_routes_through_accessor() {
11880        // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
11881        // empty-arm gate on the `:exe` slot must key off
11882        // [`Caixa::exe`], not a divergent raw `&self.exe` field-borrow
11883        // walk. Structurally: a `Caixa { exe: vec!["".into()], .. }`
11884        // must surface the `CodePathEmpty { slot: ":exe" }` refusal
11885        // exactly, and a `Caixa { exe: vec!["exe/cli".into()], .. }`
11886        // (the canonical single-executable form every in-tree
11887        // `caixa_with_code_paths` positive control uses) must pass
11888        // validate. The pair jointly pins the accessor + validate-gate
11889        // composition: any future silent detour that had the accessor
11890        // return an empty slice on the `[""]` arm (a
11891        // `.iter().filter(|s| !s.is_empty()).collect()` collapse) would
11892        // silently absorb the `CodePathEmpty` refusal at the accessor
11893        // boundary and the validate gate would accept a struct-literal
11894        // `Caixa { exe: vec!["".into()], .. }` — the composition pin
11895        // catches that at caixa-core build time.
11896        //
11897        // Peer of the per-`Caixa`
11898        // `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
11899        // (8a36c23), `validate_autores_empty_arm_routes_through_accessor`
11900        // (b5d813f), and
11901        // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
11902        // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
11903        // composition axes — same "the validate / shape-gate predicate
11904        // must route through the substrate-primitive typed dispatch"
11905        // discipline extended onto the sibling outer top-level [`Caixa`]
11906        // `&[T]`-composition surface. Nominally the in-tree
11907        // `validate_code_paths` production body still keys off the
11908        // internal `[(":bibliotecas", &self.bibliotecas,
11909        // CodePathFileType::LispSource), (":exe", &self.exe, ..),
11910        // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
11911        // (the tuple's homogeneous slice-typed shape blocks a per-
11912        // element accessor swap in isolation — a future companion lift
11913        // for `:servicos` on the same outer-`Caixa` `&[T]` slice-
11914        // accessor axis closes that tuple onto the triple of typed
11915        // dispatches as a unit); the composition pin catches any future
11916        // accessor-side silent filter drop against that eventual tuple-
11917        // closure regardless of whether the `:exe` slot is threaded
11918        // through the accessor or the raw field access at the tuple's
11919        // construction site.
11920        let c = caixa_with_code_paths(vec![], vec![""], vec![]);
11921        assert!(
11922            matches!(
11923                c.validate_code_paths(),
11924                Err(ManifestError::CodePathEmpty { slot: ":exe" })
11925            ),
11926            "validate_code_paths must reject exe == vec![\"\"] \
11927             with CodePathEmpty {{ slot: \":exe\" }} — the \
11928             accessor and the validate gate must route through the \
11929             same substrate-primitive typed dispatch on the \
11930             :exe per-entry empty arm",
11931        );
11932        let c = caixa_with_code_paths(vec![], vec!["exe/cli"], vec![]);
11933        assert!(
11934            c.validate_code_paths().is_ok(),
11935            "validate_code_paths must accept exe == vec![\"exe/cli\"] \
11936             (the canonical single-executable shape every in-tree \
11937             `caixa_with_code_paths` positive control uses)",
11938        );
11939    }
11940
11941    #[test]
11942    fn exe_projects_slice_by_borrow() {
11943        // The by-borrow pin: [`Caixa::exe`] returns `&[String]` by
11944        // borrow — the returned slice borrows the underlying
11945        // `Vec<String>` storage of the `:exe` slot and the accessor
11946        // must not clone the backing `Vec` on every call. Peer of the
11947        // per-`Caixa` `autores_projects_slice_by_borrow` (b5d813f),
11948        // `etiquetas_projects_slice_by_borrow` (78c7d3c), and
11949        // `bibliotecas_projects_slice_by_borrow` (8a36c23) by-borrow
11950        // pins on the sibling outer top-level [`Caixa`] `&[String]`-
11951        // return axes — the accessor's returned slice must borrow from
11952        // `&self` (the returned reference's lifetime is tied to
11953        // `&self`), and calling the accessor twice on the same
11954        // [`Caixa`] must yield slices that are pointer-equal (the
11955        // underlying byte-buffer is the storage `Vec`'s allocation,
11956        // not a fresh copy) as well as value-equal (idempotent, no
11957        // side effects on `&self`).
11958        //
11959        // Pins against a future silent detour that returned an owned
11960        // `Vec<String>` (which would type-check but silently clone on
11961        // every call, breaking the zero-cost projection every peer
11962        // sibling slice accessor carries), a `&Vec<String>` return
11963        // (which would leak the backing `Vec`'s grow/push/reserve
11964        // surface no downstream consumer reaches for), or a one-arm-
11965        // only accessor that returned a saturating value on some
11966        // sentinel input (breaking the pass-through invariant the
11967        // sibling slice accessors carry).
11968        for exe in [
11969            vec![],
11970            vec!["exe/cli"],
11971            vec!["exe/cli", "exe/serve"],
11972            vec!["exe/cli", "exe/cli"],
11973        ] {
11974            let c = caixa_with_code_paths(vec![], exe.clone(), vec![]);
11975            let expected: Vec<String> = exe.iter().map(|s| (*s).to_string()).collect();
11976            let first = c.exe();
11977            let second = c.exe();
11978            assert_eq!(
11979                first, second,
11980                "Caixa::exe must be idempotent — two successive calls \
11981                 on the same &self must return the same &[String]",
11982            );
11983            assert_eq!(
11984                first.as_ptr(),
11985                second.as_ptr(),
11986                "Caixa::exe must borrow the underlying Vec<String> \
11987                 storage — two successive calls must return slices \
11988                 with the same backing pointer (a fresh Vec<String> \
11989                 clone would change the pointer on every call)",
11990            );
11991            assert_eq!(
11992                first,
11993                expected.as_slice(),
11994                "Caixa::exe must return :exe verbatim by borrow — \
11995                 got {first:?}, expected {expected:?}",
11996            );
11997        }
11998    }
11999
12000    // ── Caixa::servicos — outer top-level &[T] slice accessor ─────────
12001
12002    #[test]
12003    fn servicos_returns_servicos_slice_verbatim_across_permutations() {
12004        // The canonical per-`Caixa` `:servicos` universal-axis
12005        // ComputeUnit-CR-YAML-entry-path-list slice pin:
12006        // [`Caixa::servicos`] must return the `:servicos` typed
12007        // [`Vec<String>`] list verbatim as a `&[String]`, byte-equal to
12008        // the raw `self.servicos.as_slice()` access across every
12009        // representative value in the accept-set — `[]` (the "no
12010        // ComputeUnit-CR declared" arm every `:kind` other than
12011        // `Servico` carries; the layout's [`crate::LayoutInvariants`]
12012        // `ServicoWithoutServicos` arm-gate fires exactly on this
12013        // empty-slot + `Servico`-kind combination), `[""]` (a past-the-
12014        // guard sentinel that pins the accessor doesn't perform a
12015        // silent `[""] → []` collapse on the empty-entry arm — validate
12016        // rejects `[""]` through `CodePathEmpty { slot: ":servicos" }`
12017        // but the accessor must ship the raw slot verbatim so a
12018        // validate-time gate regression surfaces at the layout /
12019        // per-Servico renderer boundary rather than being silently
12020        // absorbed into a component-drop),
12021        // `["servicos/demo.computeunit.yaml"]` (the canonical
12022        // singleton V0-shape every in-tree `caixa_with_code_paths`
12023        // positive control uses; the same shape
12024        // [`crate::require_single_servico`] admits),
12025        // `["servicos/a.computeunit.yaml", "servicos/b.computeunit.
12026        // yaml"]` (a past-the-guard `len != 1` sentinel — the V0
12027        // singularity gate rejects through `ServicoCountMismatch
12028        // { count: 2 }` but the accessor must ship the raw slot
12029        // verbatim so struct-literal `Caixa { servicos: vec![...,
12030        // ...], .. }` fixtures continue to expose the count at the
12031        // accessor), and `["servicos/a.computeunit.yaml",
12032        // "servicos/a.computeunit.yaml"]` (a past-the-guard duplicate
12033        // sentinel — validate rejects through
12034        // `CodePathDuplicate { slot: ":servicos" }` per the per-slot
12035        // set-not-multiset gate, but the accessor must ship the raw
12036        // slot verbatim so struct-literal fixtures continue to expose
12037        // the duplicate at the accessor).
12038        //
12039        // Fifth and final outer top-level [`Caixa`] `&[T]`-return
12040        // slice accessor pin on the substrate primitive — folds on the
12041        // "outer [`Caixa`] `&[T]` slice" projection pattern
12042        // `autores_returns_autores_slice_verbatim_across_permutations`
12043        // (b5d813f) opened,
12044        // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
12045        // (78c7d3c) folded on,
12046        // `bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
12047        // (8a36c23) closed the universal-axis text-tag family of, and
12048        // `exe_returns_exe_slice_verbatim_across_permutations`
12049        // (65d9527) opened the foreign-code-slot sub-family of. Closes
12050        // the outer-`Caixa` foreign-code-slot `&[T]` sub-family — the
12051        // trio of code-surface list slots (`:bibliotecas` + `:exe` +
12052        // `:servicos`) now each carries a substrate-canonical slice
12053        // accessor. Pins against a future silent detour that returned
12054        // an owned `Vec<String>` (which would type-check but silently
12055        // clone on every accessor call, breaking the zero-cost
12056        // projection every peer sibling slice accessor carries), a
12057        // `[""] → []` collapse (which would silently absorb the
12058        // `CodePathEmpty` refusal case at the accessor boundary), an
12059        // `[a, a] → [a]` dedup collapse (which would silently absorb
12060        // the `CodePathDuplicate` refusal case at the accessor
12061        // boundary — the per-slot set-not-multiset gate is downstream
12062        // of the accessor and must not be silently promoted into it),
12063        // or a `[a, b] → [a]` singleton collapse (which would silently
12064        // absorb the V0 `ServicoCountMismatch` refusal case at the
12065        // accessor boundary — the V0 singularity gate is downstream of
12066        // the accessor and must not be silently promoted into it).
12067        for servicos in [
12068            vec![],
12069            vec![""],
12070            vec!["servicos/demo.computeunit.yaml"],
12071            vec!["servicos/a.computeunit.yaml", "servicos/b.computeunit.yaml"],
12072            vec!["servicos/a.computeunit.yaml", "servicos/a.computeunit.yaml"],
12073        ] {
12074            let c = caixa_with_code_paths(vec![], vec![], servicos.clone());
12075            let expected: Vec<String> = servicos.iter().map(|s| (*s).to_string()).collect();
12076            assert_eq!(
12077                c.servicos(),
12078                expected.as_slice(),
12079                "Caixa::servicos must return :servicos verbatim (got \
12080                 {:?}, expected {expected:?})",
12081                c.servicos(),
12082            );
12083            assert_eq!(
12084                c.servicos(),
12085                c.servicos.as_slice(),
12086                "Caixa::servicos must byte-equal the raw \
12087                 `self.servicos.as_slice()` field access across every \
12088                 value in the Vec<String> accept-set",
12089            );
12090        }
12091    }
12092
12093    #[test]
12094    fn validate_code_paths_servicos_empty_arm_routes_through_accessor() {
12095        // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
12096        // empty-arm gate on the `:servicos` slot must key off
12097        // [`Caixa::servicos`], not a divergent raw `&self.servicos`
12098        // field-borrow walk. Structurally: a `Caixa { servicos:
12099        // vec!["".into()], .. }` must surface the `CodePathEmpty
12100        // { slot: ":servicos" }` refusal exactly, and a `Caixa
12101        // { servicos: vec!["servicos/demo.computeunit.yaml".into()],
12102        // .. }` (the canonical singleton V0-shape every in-tree
12103        // `caixa_with_code_paths` positive control uses) must pass
12104        // validate. The pair jointly pins the accessor + validate-gate
12105        // composition: any future silent detour that had the accessor
12106        // return an empty slice on the `[""]` arm (a `.iter().filter
12107        // (|s| !s.is_empty()).collect()` collapse) would silently
12108        // absorb the `CodePathEmpty` refusal at the accessor boundary
12109        // and the validate gate would accept a struct-literal
12110        // `Caixa { servicos: vec!["".into()], .. }` — the composition
12111        // pin catches that at caixa-core build time.
12112        //
12113        // Peer of the per-`Caixa`
12114        // `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
12115        // (8a36c23), `validate_code_paths_exe_empty_arm_routes_through_accessor`
12116        // (65d9527), `validate_autores_empty_arm_routes_through_accessor`
12117        // (b5d813f), and
12118        // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
12119        // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
12120        // composition axes — same "the validate / shape-gate predicate
12121        // must route through the substrate-primitive typed dispatch"
12122        // discipline extended onto the sibling outer top-level
12123        // [`Caixa`] `&[T]`-composition surface, closing the trio of
12124        // code-surface accessor-composition pins on the same axis.
12125        // Nominally the in-tree `validate_code_paths` production body
12126        // still keys off the internal
12127        // `[(":bibliotecas", &self.bibliotecas,
12128        // CodePathFileType::LispSource), (":exe", &self.exe, ..),
12129        // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
12130        // (the tuple's homogeneous `&Vec<String>`-typed shape blocks a
12131        // per-element accessor swap in isolation — a future companion
12132        // lift promotes the tuple's element type to `&[String]` and
12133        // threads the triple of typed dispatches through as a unit);
12134        // the composition pin catches any future accessor-side silent
12135        // filter drop against that eventual tuple-closure regardless
12136        // of whether the `:servicos` slot is threaded through the
12137        // accessor or the raw field access at the tuple's construction
12138        // site.
12139        let c = caixa_with_code_paths(vec![], vec![], vec![""]);
12140        assert!(
12141            matches!(
12142                c.validate_code_paths(),
12143                Err(ManifestError::CodePathEmpty { slot: ":servicos" })
12144            ),
12145            "validate_code_paths must reject servicos == vec![\"\"] \
12146             with CodePathEmpty {{ slot: \":servicos\" }} — the \
12147             accessor and the validate gate must route through the \
12148             same substrate-primitive typed dispatch on the \
12149             :servicos per-entry empty arm",
12150        );
12151        let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/demo.computeunit.yaml"]);
12152        assert!(
12153            c.validate_code_paths().is_ok(),
12154            "validate_code_paths must accept servicos == \
12155             vec![\"servicos/demo.computeunit.yaml\"] (the canonical \
12156             singleton V0-shape every in-tree `caixa_with_code_paths` \
12157             positive control uses)",
12158        );
12159    }
12160
12161    #[test]
12162    fn servicos_projects_slice_by_borrow() {
12163        // The by-borrow pin: [`Caixa::servicos`] returns `&[String]` by
12164        // borrow — the returned slice borrows the underlying
12165        // `Vec<String>` storage of the `:servicos` slot and the
12166        // accessor must not clone the backing `Vec` on every call.
12167        // Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
12168        // (b5d813f), `etiquetas_projects_slice_by_borrow` (78c7d3c),
12169        // `bibliotecas_projects_slice_by_borrow` (8a36c23), and
12170        // `exe_projects_slice_by_borrow` (65d9527) by-borrow pins on
12171        // the sibling outer top-level [`Caixa`] `&[String]`-return
12172        // axes — the accessor's returned slice must borrow from
12173        // `&self` (the returned reference's lifetime is tied to
12174        // `&self`), and calling the accessor twice on the same
12175        // [`Caixa`] must yield slices that are pointer-equal (the
12176        // underlying byte-buffer is the storage `Vec`'s allocation,
12177        // not a fresh copy) as well as value-equal (idempotent, no
12178        // side effects on `&self`).
12179        //
12180        // Pins against a future silent detour that returned an owned
12181        // `Vec<String>` (which would type-check but silently clone on
12182        // every call, breaking the zero-cost projection every peer
12183        // sibling slice accessor carries), a `&Vec<String>` return
12184        // (which would leak the backing `Vec`'s grow/push/reserve
12185        // surface no downstream consumer reaches for), or a one-arm-
12186        // only accessor that returned a saturating value on some
12187        // sentinel input (breaking the pass-through invariant the
12188        // sibling slice accessors carry).
12189        for servicos in [
12190            vec![],
12191            vec!["servicos/demo.computeunit.yaml"],
12192            vec!["servicos/a.computeunit.yaml", "servicos/b.computeunit.yaml"],
12193            vec!["servicos/a.computeunit.yaml", "servicos/a.computeunit.yaml"],
12194        ] {
12195            let c = caixa_with_code_paths(vec![], vec![], servicos.clone());
12196            let expected: Vec<String> = servicos.iter().map(|s| (*s).to_string()).collect();
12197            let first = c.servicos();
12198            let second = c.servicos();
12199            assert_eq!(
12200                first, second,
12201                "Caixa::servicos must be idempotent — two successive \
12202                 calls on the same &self must return the same &[String]",
12203            );
12204            assert_eq!(
12205                first.as_ptr(),
12206                second.as_ptr(),
12207                "Caixa::servicos must borrow the underlying \
12208                 Vec<String> storage — two successive calls must \
12209                 return slices with the same backing pointer (a fresh \
12210                 Vec<String> clone would change the pointer on every \
12211                 call)",
12212            );
12213            assert_eq!(
12214                first,
12215                expected.as_slice(),
12216                "Caixa::servicos must return :servicos verbatim by \
12217                 borrow — got {first:?}, expected {expected:?}",
12218            );
12219        }
12220    }
12221
12222    // ── Caixa::deps — outer top-level &[Dep] slice accessor ───────────
12223
12224    fn caixa_with_deps(deps: Vec<Dep>) -> Caixa {
12225        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12226        c.deps = deps;
12227        c
12228    }
12229
12230    #[test]
12231    fn deps_returns_deps_slice_verbatim_across_permutations() {
12232        // The canonical per-`Caixa` `:deps` universal-axis runtime-
12233        // dependency-declaration-list slice pin: [`Caixa::deps`] must
12234        // return the `:deps` typed [`Vec<Dep>`] list verbatim as a
12235        // `&[Dep]`, element-equal to the raw `self.deps.as_slice()`
12236        // access across every representative value in the accept-set —
12237        // `[]` (the "no runtime deps declared" arm every existing
12238        // fixture without a `:deps` line carries; the
12239        // [`Caixa::template`] scaffold emits `:deps ()`), a canonical
12240        // single-entry list (the shape most consumer caixas carry), a
12241        // canonical two-entry list (the multi-dep runtime closure), and
12242        // two past-the-guard sentinels — a `[""]`-`:nome` entry
12243        // ([`Self::validate_deps`] rejects through `NomeEmpty` /
12244        // `NomeInvalid` but the accessor must ship the raw slot
12245        // verbatim) and a `[a, a]` duplicate (validate rejects through
12246        // `DuplicateNome { list: ":deps" }` but the accessor must ship
12247        // the raw slot verbatim so struct-literal fixtures continue to
12248        // expose the duplicate at the accessor).
12249        //
12250        // First outer top-level [`Caixa`] `&[Dep]`-return slice accessor
12251        // pin on the substrate primitive — opens the outer-`Caixa`
12252        // dependency-slot `&[Dep]` sub-family the sibling `:deps-dev`
12253        // future lift closes on. Peer of the closed outer-`Caixa`
12254        // foreign-code-slot `&[String]` sub-family
12255        // (`bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
12256        // 8a36c23, `exe_returns_exe_slice_verbatim_across_permutations`
12257        // 65d9527, `servicos_returns_servicos_slice_verbatim_across_permutations`
12258        // 611f78b) and the outer-`Caixa` universal-axis text-tag family
12259        // (`autores_returns_autores_slice_verbatim_across_permutations`
12260        // b5d813f, `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
12261        // 78c7d3c) — extends the "outer [`Caixa`] `&[T]` slice"
12262        // projection pattern onto a novel element-type axis (`Dep`
12263        // composite vs the prior sibling family's `String` scalar).
12264        // Pins against a future silent detour that returned an owned
12265        // `Vec<Dep>` (which would type-check but silently clone on every
12266        // accessor call, breaking the zero-cost projection every peer
12267        // sibling slice accessor carries), a `[""] → []` collapse (which
12268        // would silently absorb the `NomeEmpty` refusal case at the
12269        // accessor boundary), or a `[a, a] → [a]` dedup collapse (which
12270        // would silently absorb the `DuplicateNome` refusal case at the
12271        // accessor boundary).
12272        for deps in [
12273            vec![],
12274            vec![Dep::simple("", "^0.1")],
12275            vec![Dep::simple("caixa-teia", "^0.1")],
12276            vec![
12277                Dep::simple("caixa-teia", "^0.1"),
12278                Dep::simple("caixa-core", "^0.1"),
12279            ],
12280            vec![
12281                Dep::simple("caixa-teia", "^0.1"),
12282                Dep::simple("caixa-teia", "^0.2"),
12283            ],
12284        ] {
12285            let c = caixa_with_deps(deps.clone());
12286            assert_eq!(
12287                c.deps(),
12288                deps.as_slice(),
12289                "Caixa::deps must return :deps verbatim (got {:?}, \
12290                 expected {deps:?})",
12291                c.deps(),
12292            );
12293            assert_eq!(
12294                c.deps(),
12295                c.deps.as_slice(),
12296                "Caixa::deps must element-equal the raw \
12297                 `self.deps.as_slice()` field access across every \
12298                 value in the Vec<Dep> accept-set",
12299            );
12300        }
12301    }
12302
12303    #[test]
12304    fn validate_deps_duplicate_arm_routes_through_accessor() {
12305        // Composition pin: [`Caixa::validate_deps`]'s within-`:deps`
12306        // duplicate-`:nome` gate must key off [`Caixa::deps`], not the
12307        // raw `&self.deps` field-borrow walk. Structurally: a `Caixa
12308        // { deps: vec![Dep::simple("d", "^0.1"), Dep::simple("d",
12309        // "^0.2")], .. }` must surface the `DuplicateNome { list:
12310        // ":deps" }` refusal exactly, and a `Caixa { deps: vec![
12311        // Dep::simple("d", "^0.1")], .. }` (the canonical single-entry
12312        // form) must pass validate. The pair jointly pins the accessor +
12313        // validate-gate composition: any future silent detour that had
12314        // the accessor return a dedupped slice on the `[a, a]` arm (a
12315        // `.iter().unique_by(|d| d.nome.as_str()).collect()` collapse)
12316        // would silently absorb the `DuplicateNome` refusal at the
12317        // accessor boundary and the validate gate would accept a
12318        // struct-literal `Caixa` carrying the drift — the composition
12319        // pin catches that at caixa-core build time.
12320        //
12321        // Peer of the per-`Caixa`
12322        // `validate_autores_empty_entry_arm_routes_through_accessor`
12323        // (b5d813f), `validate_etiquetas_empty_entry_arm_routes_through_accessor`
12324        // (78c7d3c), `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
12325        // (8a36c23), `validate_code_paths_exe_empty_arm_routes_through_accessor`
12326        // (65d9527), and `validate_code_paths_servicos_empty_arm_routes_through_accessor`
12327        // (611f78b) accessor-composition pins on the sibling `&[T]`-
12328        // composition axes — same "the validate gate must route through
12329        // the substrate-primitive typed dispatch" discipline extended
12330        // onto the sibling outer top-level [`Caixa`] `&[Dep]`-
12331        // composition surface, opening the outer-`Caixa` dependency-slot
12332        // arm of the composition-pin family.
12333        let c = caixa_with_deps(vec![Dep::simple("d", "^0.1"), Dep::simple("d", "^0.2")]);
12334        let err = c.validate_deps().unwrap_err();
12335        assert!(
12336            matches!(
12337                err,
12338                DepError::DuplicateNome { ref nome, list } if nome == "d"
12339                    && list == crate::render::DEP_AUTHOR_KEY_DEPS
12340            ),
12341            "validate_deps must reject deps == \
12342             vec![Dep(\"d\",\"^0.1\"), Dep(\"d\",\"^0.2\")] with \
12343             DuplicateNome {{ nome: \"d\", list: \":deps\" }} — the \
12344             accessor and the validate gate must route through the \
12345             same substrate-primitive typed dispatch on the :deps \
12346             within-list duplicate arm (got {err:?})",
12347        );
12348        let c = caixa_with_deps(vec![Dep::simple("d", "^0.1")]);
12349        assert!(
12350            c.validate_deps().is_ok(),
12351            "validate_deps must accept deps == vec![Dep(\"d\",\"^0.1\")] \
12352             (the canonical single-entry form)",
12353        );
12354    }
12355
12356    #[test]
12357    fn deps_projects_slice_by_borrow() {
12358        // The by-borrow pin: [`Caixa::deps`] returns `&[Dep]` by borrow
12359        // — the returned slice borrows the underlying `Vec<Dep>` storage
12360        // of the `:deps` slot and the accessor must not clone the
12361        // backing `Vec` on every call. Peer of the per-`Caixa`
12362        // `autores_projects_slice_by_borrow` (b5d813f),
12363        // `etiquetas_projects_slice_by_borrow` (78c7d3c),
12364        // `bibliotecas_projects_slice_by_borrow` (8a36c23),
12365        // `exe_projects_slice_by_borrow` (65d9527), and
12366        // `servicos_projects_slice_by_borrow` (611f78b) by-borrow pins
12367        // on the sibling outer top-level [`Caixa`] `&[String]`-return
12368        // axes — the accessor's returned slice must borrow from `&self`
12369        // (the returned reference's lifetime is tied to `&self`), and
12370        // calling the accessor twice on the same [`Caixa`] must yield
12371        // slices that are pointer-equal (the underlying byte-buffer is
12372        // the storage `Vec`'s allocation, not a fresh copy) as well as
12373        // value-equal (idempotent, no side effects on `&self`).
12374        //
12375        // Pins against a future silent detour that returned an owned
12376        // `Vec<Dep>` (which would type-check but silently clone on
12377        // every call), a `&Vec<Dep>` return (which would leak the
12378        // backing `Vec`'s grow/push/reserve surface no downstream
12379        // consumer reaches for), or a one-arm-only accessor that
12380        // returned a saturating value on some sentinel input.
12381        for deps in [
12382            vec![],
12383            vec![Dep::simple("caixa-teia", "^0.1")],
12384            vec![
12385                Dep::simple("caixa-teia", "^0.1"),
12386                Dep::simple("caixa-core", "^0.1"),
12387            ],
12388        ] {
12389            let c = caixa_with_deps(deps.clone());
12390            let first = c.deps();
12391            let second = c.deps();
12392            assert_eq!(
12393                first, second,
12394                "Caixa::deps must be idempotent — two successive calls \
12395                 on the same &self must return the same &[Dep]",
12396            );
12397            assert_eq!(
12398                first.as_ptr(),
12399                second.as_ptr(),
12400                "Caixa::deps must borrow the underlying Vec<Dep> \
12401                 storage — two successive calls must return slices \
12402                 with the same backing pointer (a fresh Vec<Dep> clone \
12403                 would change the pointer on every call)",
12404            );
12405            assert_eq!(
12406                first,
12407                deps.as_slice(),
12408                "Caixa::deps must return :deps verbatim by borrow — \
12409                 got {first:?}, expected {deps:?}",
12410            );
12411        }
12412    }
12413
12414    // ── Caixa::deps_dev — outer top-level &[Dep] slice accessor ──────
12415
12416    fn caixa_with_deps_dev(deps_dev: Vec<Dep>) -> Caixa {
12417        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12418        c.deps_dev = deps_dev;
12419        c
12420    }
12421
12422    #[test]
12423    fn deps_dev_returns_deps_dev_slice_verbatim_across_permutations() {
12424        // The canonical per-`Caixa` `:deps-dev` universal-axis dev-only-
12425        // dependency-declaration-list slice pin: [`Caixa::deps_dev`]
12426        // must return the `:deps-dev` typed [`Vec<Dep>`] list verbatim as
12427        // a `&[Dep]`, element-equal to the raw `self.deps_dev.as_slice()`
12428        // access across every representative value in the accept-set —
12429        // `[]` (the "no dev deps declared" arm every existing fixture
12430        // without a `:deps-dev` line carries; the [`Caixa::template`]
12431        // scaffold emits `:deps-dev ()`), a canonical single-entry list
12432        // (the shape most consumer caixas carry — a `tatara-check` dev
12433        // pin), a canonical two-entry list (the multi-dev-dep closure),
12434        // and two past-the-guard sentinels — a `[""]`-`:nome` entry
12435        // ([`Self::validate_deps`] rejects through `NomeEmpty` /
12436        // `NomeInvalid` but the accessor must ship the raw slot
12437        // verbatim) and a `[a, a]` duplicate (validate rejects through
12438        // `DuplicateNome { list: ":deps-dev" }` but the accessor must
12439        // ship the raw slot verbatim so struct-literal fixtures continue
12440        // to expose the duplicate at the accessor).
12441        //
12442        // Second outer top-level [`Caixa`] `&[Dep]`-return slice-accessor
12443        // pin on the substrate primitive — closes the outer-`Caixa`
12444        // dependency-slot `&[Dep]` sub-family the sibling
12445        // `deps_returns_deps_slice_verbatim_across_permutations`
12446        // (ad34b4e) opened on. Folds the "outer [`Caixa`] `&[Dep]`
12447        // slice" projection pattern onto the sibling dev-dep axis —
12448        // pins against a future silent detour that returned an owned
12449        // `Vec<Dep>` (which would type-check but silently clone on every
12450        // accessor call, breaking the zero-cost projection every peer
12451        // sibling slice accessor carries), a `[""] → []` collapse (which
12452        // would silently absorb the `NomeEmpty` refusal case at the
12453        // accessor boundary), or a `[a, a] → [a]` dedup collapse (which
12454        // would silently absorb the `DuplicateNome` refusal case at the
12455        // accessor boundary).
12456        for deps_dev in [
12457            vec![],
12458            vec![Dep::simple("", "^0.1")],
12459            vec![Dep::simple("tatara-check", "^0.1")],
12460            vec![
12461                Dep::simple("tatara-check", "^0.1"),
12462                Dep::simple("caixa-lint", "^0.1"),
12463            ],
12464            vec![
12465                Dep::simple("tatara-check", "^0.1"),
12466                Dep::simple("tatara-check", "^0.2"),
12467            ],
12468        ] {
12469            let c = caixa_with_deps_dev(deps_dev.clone());
12470            assert_eq!(
12471                c.deps_dev(),
12472                deps_dev.as_slice(),
12473                "Caixa::deps_dev must return :deps-dev verbatim (got \
12474                 {:?}, expected {deps_dev:?})",
12475                c.deps_dev(),
12476            );
12477            assert_eq!(
12478                c.deps_dev(),
12479                c.deps_dev.as_slice(),
12480                "Caixa::deps_dev must element-equal the raw \
12481                 `self.deps_dev.as_slice()` field access across every \
12482                 value in the Vec<Dep> accept-set",
12483            );
12484        }
12485    }
12486
12487    #[test]
12488    fn validate_deps_duplicate_deps_dev_arm_routes_through_accessor() {
12489        // Composition pin: [`Caixa::validate_deps`]'s within-`:deps-dev`
12490        // duplicate-`:nome` gate must key off [`Caixa::deps_dev`], not
12491        // the raw `&self.deps_dev` field-borrow walk. Structurally: a
12492        // `Caixa { deps_dev: vec![Dep::simple("d", "^0.1"),
12493        // Dep::simple("d", "^0.2")], .. }` must surface the
12494        // `DuplicateNome { list: ":deps-dev" }` refusal exactly, and a
12495        // `Caixa { deps_dev: vec![Dep::simple("d", "^0.1")], .. }` (the
12496        // canonical single-entry form) must pass validate. The pair
12497        // jointly pins the accessor + validate-gate composition: any
12498        // future silent detour that had the accessor return a dedupped
12499        // slice on the `[a, a]` arm (a
12500        // `.iter().unique_by(|d| d.nome.as_str()).collect()` collapse)
12501        // would silently absorb the `DuplicateNome` refusal at the
12502        // accessor boundary and the validate gate would accept a
12503        // struct-literal `Caixa` carrying the drift — the composition
12504        // pin catches that at caixa-core build time.
12505        //
12506        // Peer of `validate_deps_duplicate_arm_routes_through_accessor`
12507        // (ad34b4e) on the sibling `:deps` axis — same "the validate
12508        // gate must route through the substrate-primitive typed
12509        // dispatch" discipline folded onto the sibling `:deps-dev`
12510        // axis, closing the two-list dep-graph composition-pin family.
12511        // The `:deps-dev` diagnostic must carry the
12512        // `DEP_AUTHOR_KEY_DEPS_DEV` list-tag (not
12513        // `DEP_AUTHOR_KEY_DEPS`) so the emitted error names the
12514        // offending list unambiguously.
12515        let c = caixa_with_deps_dev(vec![Dep::simple("d", "^0.1"), Dep::simple("d", "^0.2")]);
12516        let err = c.validate_deps().unwrap_err();
12517        assert!(
12518            matches!(
12519                err,
12520                DepError::DuplicateNome { ref nome, list } if nome == "d"
12521                    && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
12522            ),
12523            "validate_deps must reject deps_dev == \
12524             vec![Dep(\"d\",\"^0.1\"), Dep(\"d\",\"^0.2\")] with \
12525             DuplicateNome {{ nome: \"d\", list: \":deps-dev\" }} — the \
12526             accessor and the validate gate must route through the \
12527             same substrate-primitive typed dispatch on the :deps-dev \
12528             within-list duplicate arm (got {err:?})",
12529        );
12530        let c = caixa_with_deps_dev(vec![Dep::simple("d", "^0.1")]);
12531        assert!(
12532            c.validate_deps().is_ok(),
12533            "validate_deps must accept deps_dev == \
12534             vec![Dep(\"d\",\"^0.1\")] (the canonical single-entry form)",
12535        );
12536    }
12537
12538    #[test]
12539    fn deps_dev_projects_slice_by_borrow() {
12540        // The by-borrow pin: [`Caixa::deps_dev`] returns `&[Dep]` by
12541        // borrow — the returned slice borrows the underlying `Vec<Dep>`
12542        // storage of the `:deps-dev` slot and the accessor must not
12543        // clone the backing `Vec` on every call. Peer of
12544        // `deps_projects_slice_by_borrow` (ad34b4e) on the sibling
12545        // `:deps` axis, and of the per-`Caixa`
12546        // `autores_projects_slice_by_borrow` (b5d813f),
12547        // `etiquetas_projects_slice_by_borrow` (78c7d3c),
12548        // `bibliotecas_projects_slice_by_borrow` (8a36c23),
12549        // `exe_projects_slice_by_borrow` (65d9527), and
12550        // `servicos_projects_slice_by_borrow` (611f78b) by-borrow pins
12551        // on the sibling outer top-level [`Caixa`] `&[String]`-return
12552        // axes — the accessor's returned slice must borrow from `&self`
12553        // (the returned reference's lifetime is tied to `&self`), and
12554        // calling the accessor twice on the same [`Caixa`] must yield
12555        // slices that are pointer-equal (the underlying byte-buffer is
12556        // the storage `Vec`'s allocation, not a fresh copy) as well as
12557        // value-equal (idempotent, no side effects on `&self`).
12558        //
12559        // Pins against a future silent detour that returned an owned
12560        // `Vec<Dep>` (which would type-check but silently clone on
12561        // every call), a `&Vec<Dep>` return (which would leak the
12562        // backing `Vec`'s grow/push/reserve surface no downstream
12563        // consumer reaches for), or a one-arm-only accessor that
12564        // returned a saturating value on some sentinel input.
12565        for deps_dev in [
12566            vec![],
12567            vec![Dep::simple("tatara-check", "^0.1")],
12568            vec![
12569                Dep::simple("tatara-check", "^0.1"),
12570                Dep::simple("caixa-lint", "^0.1"),
12571            ],
12572        ] {
12573            let c = caixa_with_deps_dev(deps_dev.clone());
12574            let first = c.deps_dev();
12575            let second = c.deps_dev();
12576            assert_eq!(
12577                first, second,
12578                "Caixa::deps_dev must be idempotent — two successive \
12579                 calls on the same &self must return the same &[Dep]",
12580            );
12581            assert_eq!(
12582                first.as_ptr(),
12583                second.as_ptr(),
12584                "Caixa::deps_dev must borrow the underlying Vec<Dep> \
12585                 storage — two successive calls must return slices \
12586                 with the same backing pointer (a fresh Vec<Dep> clone \
12587                 would change the pointer on every call)",
12588            );
12589            assert_eq!(
12590                first,
12591                deps_dev.as_slice(),
12592                "Caixa::deps_dev must return :deps-dev verbatim by \
12593                 borrow — got {first:?}, expected {deps_dev:?}",
12594            );
12595        }
12596    }
12597
12598    // ── Caixa::limits — outer top-level Option<&LimitsSpec> composite-reference accessor ──
12599
12600    fn caixa_with_limits(limits: Option<crate::LimitsSpec>) -> Caixa {
12601        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12602        c.limits = limits;
12603        c
12604    }
12605
12606    #[test]
12607    fn limits_returns_limits_option_ref_verbatim_across_permutations() {
12608        // The canonical per-`Caixa` `:limits` M2 typed-slot outer-
12609        // composite optional-composite-reference-shape pin:
12610        // [`Caixa::limits`] must return the `:limits` typed
12611        // `Option<LimitsSpec>` verbatim as an `Option<&LimitsSpec>`
12612        // reference over the same backing storage the raw
12613        // `self.limits.as_ref()` field access borrows from, byte-equal
12614        // across every representative fixture in the accept-set — the
12615        // author-omitted `None` shape (the "engine-default applies"
12616        // partition every downstream Servico M2 overlay emitter treats
12617        // as "emit nothing"), the empty-composite `Some(LimitsSpec {
12618        // .. default })` shape ([`LimitsSpec::is_empty`] holds — every
12619        // per-axis cap is `None`, so the peer M2 overlay emitter's
12620        // `.is_empty()`-gated projection still emits nothing but the
12621        // outer presence-bit is `Some`, so [`Caixa::declared_servico_slots`]
12622        // still pushes the `M2_AUTHOR_KEY_LIMITS` label), a single-axis
12623        // fixture (only `:memory` set — the canonical shape most
12624        // memory-heavy Servicos carry), and a fully-populated composite
12625        // (every per-axis cap set — the canonical shape a
12626        // sandboxed-by-default Servico carries).
12627        //
12628        // Pins against a future silent detour that returned a fresh-
12629        // cloned [`LimitsSpec`] copy (which would type-check via the
12630        // `Clone` impl but silently break every downstream caller that
12631        // relied on the reference sharing the composite's backing
12632        // identity), a reference to an operator-resolved overlay (the
12633        // future per-cluster `:limits-overrides` slot — its resolution
12634        // must land at exactly this accessor body, not silently divert
12635        // the raw slot away from a second consumer), a
12636        // `None` → `Some(LimitsSpec::default)` cluster-default
12637        // projection (which would collapse the load-bearing
12638        // "author-omitted `:limits` ⇒ engine-default applies" partition
12639        // the peer [`crate::render::servico_m2_overlay`] emitter and
12640        // the peer [`Caixa::declared_servico_slots`] enumerator both
12641        // read), or an axis-shuffled projection (a future detour that
12642        // swapped `memory` and `fuel` through the accessor would
12643        // silently split the paired [`crate::StandardLayout::verify`]
12644        // per-`:limits` shape gate's traversal input from the peer
12645        // `servico_m2_overlay` emitter's projection input).
12646        //
12647        // First outer top-level [`Caixa`] `Option<&Composite>`-return
12648        // composite-reference accessor pin on the substrate primitive
12649        // — opens the outer-`Caixa` `Option<&Composite>` composite-
12650        // reference projection pattern the sibling `:behavior`
12651        // [`crate::BehaviorSpec`] / `:politicas`
12652        // [`crate::aplicacao::MeshPolicy`] / `:placement`
12653        // [`crate::aplicacao::Placement`] / `:entrada`
12654        // [`crate::aplicacao::Entrada`] future outer-composite lifts
12655        // fold on. Peer of the closed M3 outer-composite family the
12656        // sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
12657        // [`crate::AplicacaoSpec::placement`] (9abb8f0) /
12658        // [`crate::AplicacaoSpec::entrada`] (d32111c) composite-
12659        // reference accessor pins already carry on the outer
12660        // [`crate::AplicacaoSpec`] altitude — extends the outer-
12661        // accessor byte-equal-projection discipline onto the outer
12662        // top-level [`Caixa`] M2 Servico-runtime slot altitude.
12663        use crate::LimitsSpec;
12664        use std::time::Duration;
12665        let fixtures: Vec<Option<LimitsSpec>> = vec![
12666            None,
12667            Some(LimitsSpec::default()),
12668            Some(LimitsSpec {
12669                memory: Some(64 * 1024 * 1024),
12670                ..Default::default()
12671            }),
12672            Some(LimitsSpec {
12673                memory: Some(64 * 1024 * 1024),
12674                fuel: Some(1_000_000),
12675                wall_clock: Some(Duration::from_secs(30)),
12676                cpu: Some(500),
12677            }),
12678        ];
12679        for limits in fixtures {
12680            let c = caixa_with_limits(limits.clone());
12681            assert_eq!(
12682                c.limits(),
12683                limits.as_ref(),
12684                "Caixa::limits must return :limits verbatim (got {:?}, \
12685                 expected {:?})",
12686                c.limits(),
12687                limits.as_ref(),
12688            );
12689            match (c.limits(), c.limits.as_ref()) {
12690                (Some(a), Some(b)) => assert!(
12691                    std::ptr::eq(a, b),
12692                    "Caixa::limits accessor and self.limits.as_ref() \
12693                     field access must borrow the same backing storage \
12694                     — the accessor is the substrate-primitive typed \
12695                     dispatch every downstream Servico-M2-overlay \
12696                     composite consumer must route through, and a \
12697                     reference-identity split would silently break \
12698                     every consumer that relied on the borrow sharing \
12699                     the composite's storage",
12700                ),
12701                (None, None) => {}
12702                _ => panic!(
12703                    "Caixa::limits presence bit must byte-equal \
12704                     self.limits.is_some() — a presence-bit drift would \
12705                     silently split the paired StandardLayout::verify \
12706                     per-`:limits` shape gate's traversal head from \
12707                     the peer render::servico_m2_overlay M2 overlay \
12708                     emitter's traversal head from the peer \
12709                     Caixa::declared_servico_slots M2 declared-slot \
12710                     enumerator's presence probe",
12711                ),
12712            }
12713            assert_eq!(
12714                c.limits().is_some(),
12715                c.limits.is_some(),
12716                "Caixa::limits().is_some() must byte-equal \
12717                 self.limits.is_some() — a presence-bit drift would \
12718                 silently split every downstream Option<&LimitsSpec> \
12719                 consumer's partition on the engine-default arm",
12720            );
12721        }
12722    }
12723
12724    #[test]
12725    fn declared_servico_slots_limits_arm_routes_through_accessor() {
12726        // Composition pin: [`Caixa::declared_servico_slots`]'s
12727        // `:limits` presence-probe arm must key off [`Caixa::limits`],
12728        // not the raw `self.limits.is_some()` field-probe. Structurally:
12729        // a `Caixa { limits: Some(LimitsSpec::default()), .. }` must
12730        // still push `M2_AUTHOR_KEY_LIMITS` onto the declared-slot list
12731        // (the presence bit is `Some`, so the M2 kind-coherence gate
12732        // must surface the slot as "declared" even when every per-axis
12733        // cap is unset), and a `Caixa { limits: None, .. }` must NOT
12734        // push the label (the "author omitted the slot entirely"
12735        // partition). The pair jointly pins the accessor + declared-
12736        // slot enumerator composition: any future silent detour that
12737        // had the accessor collapse `Some(LimitsSpec::default())` to
12738        // `None` (a `.filter(|l| !l.is_empty())` projection) would
12739        // silently absorb the "declared but empty" arm at the
12740        // accessor boundary and the [`crate::LayoutError::ServicoSlotsOnNonServico`]
12741        // kind-coherence gate would silently accept a
12742        // struct-literal `Caixa` carrying the drift.
12743        //
12744        // Peer of the sibling per-`Caixa`
12745        // `validate_deps_duplicate_arm_routes_through_accessor` (ad34b4e)
12746        // and `validate_deps_duplicate_deps_dev_arm_routes_through_accessor`
12747        // (f7fd81e) accessor-composition pins on the sibling `:deps` /
12748        // `:deps-dev` outer-`&[Dep]`-composition axes — same "the
12749        // enumerator gate must route through the substrate-primitive
12750        // typed dispatch" discipline extended onto the outer top-level
12751        // [`Caixa`] `Option<&LimitsSpec>`-composition surface, opening
12752        // the outer-`Caixa` M2 Servico-runtime-slot arm of the
12753        // composition-pin family.
12754        use crate::LimitsSpec;
12755        let c = caixa_with_limits(Some(LimitsSpec::default()));
12756        let slots = c.declared_servico_slots();
12757        assert!(
12758            slots.contains(&crate::render::M2_AUTHOR_KEY_LIMITS),
12759            "declared_servico_slots must push M2_AUTHOR_KEY_LIMITS \
12760             when `:limits` is Some (even for LimitsSpec::default()) \
12761             — the accessor and the enumerator gate must route through \
12762             the same substrate-primitive typed dispatch on the outer \
12763             :limits presence bit (got slots={slots:?})",
12764        );
12765        let c = caixa_with_limits(None);
12766        let slots = c.declared_servico_slots();
12767        assert!(
12768            !slots.contains(&crate::render::M2_AUTHOR_KEY_LIMITS),
12769            "declared_servico_slots must NOT push M2_AUTHOR_KEY_LIMITS \
12770             when `:limits` is None — the author-omitted arm must \
12771             route through the accessor's None-return unchanged (got \
12772             slots={slots:?})",
12773        );
12774    }
12775
12776    #[test]
12777    fn servico_m2_overlay_limits_arm_routes_through_accessor() {
12778        // Composition pin: [`crate::render::servico_m2_overlay`]'s
12779        // per-`:limits` M2 overlay emit arm must key off
12780        // [`Caixa::limits`], not the raw `&caixa.limits` field-borrow.
12781        // Structurally: a `Caixa { limits: Some(LimitsSpec { memory:
12782        // Some(64 MiB), .. default }), .. }` must surface the
12783        // `M2_KEY_LIMITS` key with the per-axis
12784        // `memory: "64MiB"` sub-mapping in the overlay, a `Caixa {
12785        // limits: Some(LimitsSpec::default()), .. }` must omit the
12786        // key entirely (the `.is_empty()`-gated inner arm elides an
12787        // empty composite even when the outer presence bit is `Some`),
12788        // and a `Caixa { limits: None, .. }` must also omit the key
12789        // (the "author omitted the slot entirely" partition). The
12790        // three-fixture family jointly pins the accessor + M2 overlay
12791        // emitter composition: any future silent detour that had the
12792        // accessor return a fresh-cloned copy on the `Some` arm (a
12793        // `LimitsSpec::clone()` projection) would silently break the
12794        // reference-identity pin the peer per-axis
12795        // `serde_yaml::to_value(limits)` projection reads from.
12796        use crate::LimitsSpec;
12797        use crate::render::{M2_KEY_LIMITS, servico_m2_overlay};
12798        let c = caixa_with_limits(Some(LimitsSpec {
12799            memory: Some(64 * 1024 * 1024),
12800            ..Default::default()
12801        }));
12802        let overlay = servico_m2_overlay(&c).unwrap();
12803        assert!(
12804            overlay.contains_key(M2_KEY_LIMITS),
12805            "servico_m2_overlay must surface M2_KEY_LIMITS when \
12806             `:limits` carries a non-empty composite — the accessor \
12807             and the M2 overlay emitter must route through the same \
12808             substrate-primitive typed dispatch on the outer :limits \
12809             composite (got overlay={overlay:?})",
12810        );
12811        let c = caixa_with_limits(Some(LimitsSpec::default()));
12812        let overlay = servico_m2_overlay(&c).unwrap();
12813        assert!(
12814            !overlay.contains_key(M2_KEY_LIMITS),
12815            "servico_m2_overlay must omit M2_KEY_LIMITS when \
12816             `:limits` is Some(LimitsSpec::default()) — the empty \
12817             composite's `.is_empty()`-gated inner arm must elide \
12818             the key regardless of the outer presence bit (got \
12819             overlay={overlay:?})",
12820        );
12821        let c = caixa_with_limits(None);
12822        let overlay = servico_m2_overlay(&c).unwrap();
12823        assert!(
12824            !overlay.contains_key(M2_KEY_LIMITS),
12825            "servico_m2_overlay must omit M2_KEY_LIMITS when \
12826             `:limits` is None — the author-omitted arm must route \
12827             through the accessor's None-return unchanged (got \
12828             overlay={overlay:?})",
12829        );
12830    }
12831
12832    #[test]
12833    fn limits_projects_option_ref_by_borrow() {
12834        // The by-borrow pin: [`Caixa::limits`] returns
12835        // `Option<&LimitsSpec>` by borrow — the returned reference
12836        // borrows the underlying `Option<LimitsSpec>` storage of the
12837        // `:limits` slot and the accessor must not clone the backing
12838        // composite on every call. Peer of the sibling
12839        // `deps_projects_slice_by_borrow` (ad34b4e) /
12840        // `deps_dev_projects_slice_by_borrow` (f7fd81e) by-borrow pins
12841        // on the outer top-level [`Caixa`] `&[Dep]`-return axes —
12842        // extended here to the outer [`Caixa`] `Option<&Composite>`-
12843        // return axis: the accessor's returned reference must borrow
12844        // from `&self` (the returned reference's lifetime is tied to
12845        // `&self`), and calling the accessor twice on the same
12846        // [`Caixa`] must yield references that are pointer-equal (the
12847        // underlying byte-buffer is the storage `LimitsSpec`'s
12848        // allocation, not a fresh copy) as well as value-equal
12849        // (idempotent, no side effects on `&self`).
12850        //
12851        // Pins against a future silent detour that returned an owned
12852        // `LimitsSpec` (which would type-check via the `Clone` impl
12853        // but silently clone on every call), a `&LimitsSpec` panic-
12854        // return on the `None` arm (which would collapse the load-
12855        // bearing `Option` presence-bit into a runtime panic), or a
12856        // one-arm-only accessor that returned a saturating composite
12857        // on some sentinel input.
12858        use crate::LimitsSpec;
12859        use std::time::Duration;
12860        for limits in [
12861            Some(LimitsSpec::default()),
12862            Some(LimitsSpec {
12863                memory: Some(64 * 1024 * 1024),
12864                fuel: Some(1_000_000),
12865                wall_clock: Some(Duration::from_secs(30)),
12866                cpu: Some(500),
12867            }),
12868        ] {
12869            let c = caixa_with_limits(limits.clone());
12870            let first = c.limits().unwrap();
12871            let second = c.limits().unwrap();
12872            assert_eq!(
12873                first, second,
12874                "Caixa::limits must be idempotent — two successive \
12875                 calls on the same &self must return the same \
12876                 &LimitsSpec",
12877            );
12878            assert!(
12879                std::ptr::eq(first, second),
12880                "Caixa::limits must borrow the underlying \
12881                 Option<LimitsSpec> storage — two successive calls \
12882                 must return references with the same backing pointer \
12883                 (a fresh LimitsSpec clone would change the pointer \
12884                 on every call)",
12885            );
12886            assert_eq!(
12887                Some(first),
12888                limits.as_ref(),
12889                "Caixa::limits must return :limits verbatim by borrow \
12890                 — got {first:?}, expected {:?}",
12891                limits.as_ref(),
12892            );
12893        }
12894        let c = caixa_with_limits(None);
12895        assert!(
12896            c.limits().is_none(),
12897            "Caixa::limits must return None when :limits is absent — \
12898             the author-omitted arm must project through the \
12899             accessor's Option::None unchanged",
12900        );
12901    }
12902
12903    // ── Caixa::behavior — outer top-level Option<&BehaviorSpec> composite-reference accessor ──
12904
12905    fn caixa_with_behavior(behavior: Option<crate::BehaviorSpec>) -> Caixa {
12906        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12907        c.behavior = behavior;
12908        c
12909    }
12910
12911    #[test]
12912    fn behavior_returns_behavior_option_ref_verbatim_across_permutations() {
12913        // The canonical per-`Caixa` `:behavior` M2 typed-slot outer-
12914        // composite optional-composite-reference-shape pin:
12915        // [`Caixa::behavior`] must return the `:behavior` typed
12916        // `Option<BehaviorSpec>` verbatim as an `Option<&BehaviorSpec>`
12917        // reference over the same backing storage the raw
12918        // `self.behavior.as_ref()` field access borrows from, byte-equal
12919        // across every representative fixture in the accept-set — the
12920        // author-omitted `None` shape (the "runtime-default applies"
12921        // partition every downstream Servico M2 overlay emitter treats
12922        // as "emit nothing"), the empty-composite `Some(BehaviorSpec {
12923        // .. default })` shape ([`BehaviorSpec::is_empty`] holds —
12924        // every per-callback path is `None`, so the peer M2 overlay
12925        // emitter's `.is_empty()`-gated projection still emits nothing
12926        // but the outer presence-bit is `Some`, so
12927        // [`Caixa::declared_servico_slots`] still pushes the
12928        // `M2_AUTHOR_KEY_BEHAVIOR` label), a single-callback fixture
12929        // (only `:on-state-change` set — the canonical shape a caixa
12930        // that only wires the hot-upgrade migration path carries), and
12931        // a fully-populated composite (every per-callback path set —
12932        // the canonical shape a fully-instrumented gen_server-shaped
12933        // Servico carries).
12934        //
12935        // Peer of the sibling
12936        // `limits_returns_limits_option_ref_verbatim_across_permutations`
12937        // (b2bd9d7) opening fixture-family + reference-identity +
12938        // presence-bit tetrad pin on the outer top-level [`Caixa`]
12939        // `Option<&Composite>`-return sub-family — extended here to the
12940        // second axis of that sub-family so both of the currently-lifted
12941        // M2 Servico-runtime `Option<&Composite>` slots (`:limits` /
12942        // `:behavior`) carry the same "byte-equal, borrow-shared,
12943        // presence-bit-preserved" outer-accessor discipline.
12944        //
12945        // Pins against a future silent detour that returned a fresh-
12946        // cloned [`crate::BehaviorSpec`] copy (which would type-check
12947        // via the `Clone` impl but silently break every downstream
12948        // caller that relied on the reference sharing the composite's
12949        // backing identity), a reference to an operator-resolved
12950        // overlay (a future per-cluster `:behavior-overrides` slot —
12951        // its resolution must land at exactly this accessor body, not
12952        // silently divert the raw slot away from a second consumer), a
12953        // `None` → `Some(BehaviorSpec::default)` cluster-default
12954        // projection (which would collapse the load-bearing
12955        // "author-omitted `:behavior` ⇒ runtime-default applies"
12956        // partition the peer [`crate::render::servico_m2_overlay`]
12957        // emitter, the peer [`Caixa::declared_servico_slots`]
12958        // enumerator, and the cross-slot
12959        // [`crate::upgrade::validate_upgrade_from_against_behavior`]
12960        // gate all read), or a callback-shuffled projection (a future
12961        // detour that swapped `on_init` and `on_terminate` through the
12962        // accessor would silently split the paired
12963        // [`crate::StandardLayout::verify`] per-`:behavior` shape gate's
12964        // traversal input from the peer `servico_m2_overlay` emitter's
12965        // projection input from the cross-slot `:state-change`
12966        // composition gate's traversal input).
12967        use crate::BehaviorSpec;
12968        use std::path::PathBuf;
12969        let fixtures: Vec<Option<BehaviorSpec>> = vec![
12970            None,
12971            Some(BehaviorSpec::default()),
12972            Some(BehaviorSpec {
12973                on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
12974                ..Default::default()
12975            }),
12976            Some(BehaviorSpec {
12977                on_init: Some(PathBuf::from("lib/init.lisp")),
12978                on_call: Some(PathBuf::from("lib/handlers.lisp")),
12979                on_cast: Some(PathBuf::from("lib/handlers.lisp")),
12980                on_info: Some(PathBuf::from("lib/handlers.lisp")),
12981                on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
12982                on_terminate: Some(PathBuf::from("lib/cleanup.lisp")),
12983            }),
12984        ];
12985        for behavior in fixtures {
12986            let c = caixa_with_behavior(behavior.clone());
12987            assert_eq!(
12988                c.behavior(),
12989                behavior.as_ref(),
12990                "Caixa::behavior must return :behavior verbatim (got \
12991                 {:?}, expected {:?})",
12992                c.behavior(),
12993                behavior.as_ref(),
12994            );
12995            match (c.behavior(), c.behavior.as_ref()) {
12996                (Some(a), Some(b)) => assert!(
12997                    std::ptr::eq(a, b),
12998                    "Caixa::behavior accessor and self.behavior.as_ref() \
12999                     field access must borrow the same backing storage \
13000                     — the accessor is the substrate-primitive typed \
13001                     dispatch every downstream Servico-M2-overlay \
13002                     composite consumer must route through, and a \
13003                     reference-identity split would silently break \
13004                     every consumer that relied on the borrow sharing \
13005                     the composite's storage",
13006                ),
13007                (None, None) => {}
13008                _ => panic!(
13009                    "Caixa::behavior presence bit must byte-equal \
13010                     self.behavior.is_some() — a presence-bit drift \
13011                     would silently split the paired \
13012                     StandardLayout::verify per-`:behavior` shape \
13013                     gate's traversal head from the peer \
13014                     render::servico_m2_overlay M2 overlay emitter's \
13015                     traversal head from the cross-slot \
13016                     validate_upgrade_from_against_behavior \
13017                     composition gate's traversal head from the peer \
13018                     Caixa::declared_servico_slots M2 declared-slot \
13019                     enumerator's presence probe",
13020                ),
13021            }
13022            assert_eq!(
13023                c.behavior().is_some(),
13024                c.behavior.is_some(),
13025                "Caixa::behavior().is_some() must byte-equal \
13026                 self.behavior.is_some() — a presence-bit drift would \
13027                 silently split every downstream Option<&BehaviorSpec> \
13028                 consumer's partition on the runtime-default arm",
13029            );
13030        }
13031    }
13032
13033    #[test]
13034    fn declared_servico_slots_behavior_arm_routes_through_accessor() {
13035        // Composition pin: [`Caixa::declared_servico_slots`]'s
13036        // `:behavior` presence-probe arm must key off
13037        // [`Caixa::behavior`], not the raw `self.behavior.is_some()`
13038        // field-probe. Structurally: a `Caixa { behavior:
13039        // Some(BehaviorSpec::default()), .. }` must still push
13040        // `M2_AUTHOR_KEY_BEHAVIOR` onto the declared-slot list (the
13041        // presence bit is `Some`, so the M2 kind-coherence gate must
13042        // surface the slot as "declared" even when every per-callback
13043        // path is unset), and a `Caixa { behavior: None, .. }` must
13044        // NOT push the label (the "author omitted the slot entirely"
13045        // partition). The pair jointly pins the accessor + declared-
13046        // slot enumerator composition: any future silent detour that
13047        // had the accessor collapse `Some(BehaviorSpec::default())`
13048        // to `None` (a `.filter(|b| !b.is_empty())` projection) would
13049        // silently absorb the "declared but empty" arm at the
13050        // accessor boundary and the
13051        // [`crate::LayoutError::ServicoSlotsOnNonServico`]
13052        // kind-coherence gate would silently accept a struct-literal
13053        // `Caixa` carrying the drift.
13054        //
13055        // Peer of the sibling
13056        // `declared_servico_slots_limits_arm_routes_through_accessor`
13057        // (b2bd9d7) composition pin on the sibling `:limits` outer-
13058        // `Option<&LimitsSpec>` arm of the same
13059        // [`Caixa::declared_servico_slots`] M2 declared-slot
13060        // enumerator's traversal — same "the enumerator gate must
13061        // route through the substrate-primitive typed dispatch"
13062        // discipline extended onto the outer top-level [`Caixa`]
13063        // `Option<&BehaviorSpec>`-composition surface.
13064        use crate::BehaviorSpec;
13065        let c = caixa_with_behavior(Some(BehaviorSpec::default()));
13066        let slots = c.declared_servico_slots();
13067        assert!(
13068            slots.contains(&crate::render::M2_AUTHOR_KEY_BEHAVIOR),
13069            "declared_servico_slots must push M2_AUTHOR_KEY_BEHAVIOR \
13070             when `:behavior` is Some (even for BehaviorSpec::default()) \
13071             — the accessor and the enumerator gate must route through \
13072             the same substrate-primitive typed dispatch on the outer \
13073             :behavior presence bit (got slots={slots:?})",
13074        );
13075        let c = caixa_with_behavior(None);
13076        let slots = c.declared_servico_slots();
13077        assert!(
13078            !slots.contains(&crate::render::M2_AUTHOR_KEY_BEHAVIOR),
13079            "declared_servico_slots must NOT push M2_AUTHOR_KEY_BEHAVIOR \
13080             when `:behavior` is None — the author-omitted arm must \
13081             route through the accessor's None-return unchanged (got \
13082             slots={slots:?})",
13083        );
13084    }
13085
13086    #[test]
13087    fn servico_m2_overlay_behavior_arm_routes_through_accessor() {
13088        // Composition pin: [`crate::render::servico_m2_overlay`]'s
13089        // per-`:behavior` M2 overlay emit arm must key off
13090        // [`Caixa::behavior`], not the raw `&caixa.behavior`
13091        // field-borrow. Structurally: a `Caixa { behavior:
13092        // Some(BehaviorSpec { on_state_change: Some(...), .. default
13093        // }), .. }` must surface the `M2_KEY_BEHAVIOR` key with the
13094        // per-callback `onStateChange` sub-mapping in the overlay, a
13095        // `Caixa { behavior: Some(BehaviorSpec::default()), .. }`
13096        // must omit the key entirely (the `.is_empty()`-gated inner
13097        // arm elides an empty composite even when the outer presence
13098        // bit is `Some`), and a `Caixa { behavior: None, .. }` must
13099        // also omit the key (the "author omitted the slot entirely"
13100        // partition). The three-fixture family jointly pins the
13101        // accessor + M2 overlay emitter composition: any future
13102        // silent detour that had the accessor return a fresh-cloned
13103        // copy on the `Some` arm (a `BehaviorSpec::clone()`
13104        // projection) would silently break the reference-identity
13105        // pin the peer per-callback `serde_yaml::to_value(behavior)`
13106        // projection reads from.
13107        //
13108        // Peer of the sibling
13109        // `servico_m2_overlay_limits_arm_routes_through_accessor`
13110        // (b2bd9d7) composition pin on the sibling `:limits` outer-
13111        // `Option<&LimitsSpec>` arm of the same
13112        // [`crate::render::servico_m2_overlay`] M2 overlay emitter's
13113        // traversal — same "the emitter must route through the
13114        // substrate-primitive typed dispatch on the outer composite"
13115        // discipline extended onto the outer top-level [`Caixa`]
13116        // `Option<&BehaviorSpec>`-composition surface.
13117        use crate::BehaviorSpec;
13118        use crate::render::{M2_KEY_BEHAVIOR, servico_m2_overlay};
13119        use std::path::PathBuf;
13120        let c = caixa_with_behavior(Some(BehaviorSpec {
13121            on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
13122            ..Default::default()
13123        }));
13124        let overlay = servico_m2_overlay(&c).unwrap();
13125        assert!(
13126            overlay.contains_key(M2_KEY_BEHAVIOR),
13127            "servico_m2_overlay must surface M2_KEY_BEHAVIOR when \
13128             `:behavior` carries a non-empty composite — the accessor \
13129             and the M2 overlay emitter must route through the same \
13130             substrate-primitive typed dispatch on the outer :behavior \
13131             composite (got overlay={overlay:?})",
13132        );
13133        let c = caixa_with_behavior(Some(BehaviorSpec::default()));
13134        let overlay = servico_m2_overlay(&c).unwrap();
13135        assert!(
13136            !overlay.contains_key(M2_KEY_BEHAVIOR),
13137            "servico_m2_overlay must omit M2_KEY_BEHAVIOR when \
13138             `:behavior` is Some(BehaviorSpec::default()) — the empty \
13139             composite's `.is_empty()`-gated inner arm must elide the \
13140             key regardless of the outer presence bit (got \
13141             overlay={overlay:?})",
13142        );
13143        let c = caixa_with_behavior(None);
13144        let overlay = servico_m2_overlay(&c).unwrap();
13145        assert!(
13146            !overlay.contains_key(M2_KEY_BEHAVIOR),
13147            "servico_m2_overlay must omit M2_KEY_BEHAVIOR when \
13148             `:behavior` is None — the author-omitted arm must route \
13149             through the accessor's None-return unchanged (got \
13150             overlay={overlay:?})",
13151        );
13152    }
13153
13154    #[test]
13155    fn behavior_projects_option_ref_by_borrow() {
13156        // The by-borrow pin: [`Caixa::behavior`] returns
13157        // `Option<&BehaviorSpec>` by borrow — the returned reference
13158        // borrows the underlying `Option<BehaviorSpec>` storage of the
13159        // `:behavior` slot and the accessor must not clone the backing
13160        // composite on every call. Peer of the sibling
13161        // `limits_projects_option_ref_by_borrow` (b2bd9d7) by-borrow
13162        // pin on the outer top-level [`Caixa`] `Option<&Composite>`-
13163        // return sub-family — extended here to the second axis of the
13164        // same sub-family: the accessor's returned reference must
13165        // borrow from `&self` (the returned reference's lifetime is
13166        // tied to `&self`), and calling the accessor twice on the same
13167        // [`Caixa`] must yield references that are pointer-equal (the
13168        // underlying byte-buffer is the storage `BehaviorSpec`'s
13169        // allocation, not a fresh copy) as well as value-equal
13170        // (idempotent, no side effects on `&self`).
13171        //
13172        // Pins against a future silent detour that returned an owned
13173        // `BehaviorSpec` (which would type-check via the `Clone` impl
13174        // but silently clone on every call), a `&BehaviorSpec` panic-
13175        // return on the `None` arm (which would collapse the load-
13176        // bearing `Option` presence-bit into a runtime panic), or a
13177        // one-arm-only accessor that returned a saturating composite
13178        // on some sentinel input.
13179        use crate::BehaviorSpec;
13180        use std::path::PathBuf;
13181        for behavior in [
13182            Some(BehaviorSpec::default()),
13183            Some(BehaviorSpec {
13184                on_init: Some(PathBuf::from("lib/init.lisp")),
13185                on_call: Some(PathBuf::from("lib/handlers.lisp")),
13186                on_cast: Some(PathBuf::from("lib/handlers.lisp")),
13187                on_info: Some(PathBuf::from("lib/handlers.lisp")),
13188                on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
13189                on_terminate: Some(PathBuf::from("lib/cleanup.lisp")),
13190            }),
13191        ] {
13192            let c = caixa_with_behavior(behavior.clone());
13193            let first = c.behavior().unwrap();
13194            let second = c.behavior().unwrap();
13195            assert_eq!(
13196                first, second,
13197                "Caixa::behavior must be idempotent — two successive \
13198                 calls on the same &self must return the same \
13199                 &BehaviorSpec",
13200            );
13201            assert!(
13202                std::ptr::eq(first, second),
13203                "Caixa::behavior must borrow the underlying \
13204                 Option<BehaviorSpec> storage — two successive calls \
13205                 must return references with the same backing pointer \
13206                 (a fresh BehaviorSpec clone would change the pointer \
13207                 on every call)",
13208            );
13209            assert_eq!(
13210                Some(first),
13211                behavior.as_ref(),
13212                "Caixa::behavior must return :behavior verbatim by \
13213                 borrow — got {first:?}, expected {:?}",
13214                behavior.as_ref(),
13215            );
13216        }
13217        let c = caixa_with_behavior(None);
13218        assert!(
13219            c.behavior().is_none(),
13220            "Caixa::behavior must return None when :behavior is absent \
13221             — the author-omitted arm must project through the \
13222             accessor's Option::None unchanged",
13223        );
13224    }
13225
13226    // ── Caixa::politicas — outer top-level Option<&MeshPolicy> composite-reference accessor ──
13227
13228    fn caixa_aplicacao_with_politicas(politicas: Option<crate::aplicacao::MeshPolicy>) -> Caixa {
13229        use crate::aplicacao::{Membro, WitContract};
13230        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
13231        c.kind = CaixaKind::Aplicacao;
13232        c.membros = vec![Membro {
13233            caixa: "a".into(),
13234            versao: "^0.1".into(),
13235        }];
13236        c.contratos = vec![WitContract {
13237            de: "a".into(),
13238            para: "a".into(),
13239            wit: "wasi:http/proxy".into(),
13240            endpoint: Some("/x".into()),
13241            subject: None,
13242            slot: None,
13243        }];
13244        c.politicas = politicas;
13245        c
13246    }
13247
13248    #[test]
13249    fn politicas_returns_politicas_option_ref_verbatim_across_permutations() {
13250        // The canonical per-`Caixa` `:politicas` M3 mesh-slot outer-
13251        // composite optional-composite-reference-shape pin:
13252        // [`Caixa::politicas`] must return the `:politicas` typed
13253        // `Option<MeshPolicy>` verbatim as an `Option<&MeshPolicy>`
13254        // reference over the same backing storage the raw
13255        // `self.politicas.as_ref()` field access borrows from,
13256        // byte-equal across every representative fixture in the
13257        // accept-set — the author-omitted `None` shape (the "cluster-
13258        // default applies" partition every downstream mesh-artifact
13259        // emitter treats as "emit no `:politicas` overlay"), the
13260        // empty-composite `Some(MeshPolicy { .. default })` shape
13261        // ([`crate::aplicacao::MeshPolicy::is_empty`] holds — every
13262        // per-axis mesh-policy scalar is `None`, so the peer inner
13263        // [`crate::AplicacaoSpec::politicas`] `.is_empty()`-gated
13264        // caixa-mesh overlay elides every per-axis emit but the outer
13265        // presence-bit is `Some`, so [`Caixa::declared_mesh_slots`]
13266        // still pushes the `M3_AUTHOR_KEY_POLITICAS` label), a
13267        // single-axis fixture (only `:timeout` set — the canonical
13268        // shape a latency-sensitive Aplicacao carries), and a
13269        // fully-populated composite (every per-axis mesh-policy
13270        // scalar set — the canonical shape a fully-governed
13271        // Aplicacao carries).
13272        //
13273        // Pins against a future silent detour that returned a fresh-
13274        // cloned [`crate::aplicacao::MeshPolicy`] copy (which would
13275        // type-check via the `Clone` impl but silently break every
13276        // downstream caller that relied on the reference sharing the
13277        // composite's backing identity), a reference to an operator-
13278        // resolved overlay (the future per-cluster
13279        // `:politicas-overrides` slot — its resolution must land at
13280        // exactly this accessor body, not silently divert the raw
13281        // slot away from the peer [`Caixa::declared_mesh_slots`]
13282        // enumerator's presence probe), a
13283        // `None` → `Some(MeshPolicy::default)` cluster-default
13284        // projection (which would collapse the load-bearing
13285        // "author-omitted `:politicas` ⇒ cluster-default applies"
13286        // partition the peer [`Caixa::declared_mesh_slots`]
13287        // enumerator and the peer [`Caixa::aplicacao_view`]
13288        // Aplicacao-composition seed both read), or an axis-shuffled
13289        // projection (a future detour that swapped `timeout` and
13290        // `retries` through the accessor would silently split the
13291        // paired [`Caixa::aplicacao_view`] seed's fold input from the
13292        // sibling M3 mesh-artifact emitter's projection input).
13293        //
13294        // Third outer top-level [`Caixa`] `Option<&Composite>`-return
13295        // composite-reference accessor pin on the substrate primitive
13296        // — peer of the sibling
13297        // `limits_returns_limits_option_ref_verbatim_across_permutations`
13298        // (b2bd9d7) and
13299        // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
13300        // (35d8b52) opening tetrad pins on the outer top-level
13301        // [`Caixa`] `Option<&Composite>`-return sub-family — extended
13302        // here to the first of the three M3 mesh-slot axes so the
13303        // opening third of the outer `Option<&Composite>` sub-family
13304        // carries the same "byte-equal, borrow-shared, presence-bit-
13305        // preserved" outer-accessor discipline.
13306        use crate::aplicacao::{CircuitBreaker, MeshPolicy, RateLimit};
13307        use std::time::Duration;
13308        let fixtures: Vec<Option<MeshPolicy>> = vec![
13309            None,
13310            Some(MeshPolicy::default()),
13311            Some(MeshPolicy {
13312                timeout: Some(Duration::from_secs(30)),
13313                ..Default::default()
13314            }),
13315            Some(MeshPolicy {
13316                timeout: Some(Duration::from_secs(30)),
13317                retries: Some(3),
13318                circuit_breaker: Some(CircuitBreaker {
13319                    max_failures: 5,
13320                    window: Duration::from_secs(60),
13321                }),
13322                mtls_required: Some(true),
13323                rate_limit: Some(RateLimit {
13324                    rate: 100,
13325                    window: Duration::from_secs(1),
13326                }),
13327            }),
13328        ];
13329        for politicas in fixtures {
13330            let c = caixa_aplicacao_with_politicas(politicas.clone());
13331            assert_eq!(
13332                c.politicas(),
13333                politicas.as_ref(),
13334                "Caixa::politicas must return :politicas verbatim (got \
13335                 {:?}, expected {:?})",
13336                c.politicas(),
13337                politicas.as_ref(),
13338            );
13339            match (c.politicas(), c.politicas.as_ref()) {
13340                (Some(a), Some(b)) => assert!(
13341                    std::ptr::eq(a, b),
13342                    "Caixa::politicas accessor and self.politicas.as_ref() \
13343                     field access must borrow the same backing storage \
13344                     — the accessor is the substrate-primitive typed \
13345                     dispatch every downstream Aplicacao-mesh-overlay \
13346                     composite consumer must route through, and a \
13347                     reference-identity split would silently break \
13348                     every consumer that relied on the borrow sharing \
13349                     the composite's storage",
13350                ),
13351                (None, None) => {}
13352                _ => panic!(
13353                    "Caixa::politicas presence bit must byte-equal \
13354                     self.politicas.is_some() — a presence-bit drift \
13355                     would silently split the paired \
13356                     Caixa::aplicacao_view Aplicacao-composition seed's \
13357                     traversal head from the peer \
13358                     Caixa::declared_mesh_slots M3 declared-slot \
13359                     enumerator's presence probe",
13360                ),
13361            }
13362            assert_eq!(
13363                c.politicas().is_some(),
13364                c.politicas.is_some(),
13365                "Caixa::politicas().is_some() must byte-equal \
13366                 self.politicas.is_some() — a presence-bit drift would \
13367                 silently split every downstream Option<&MeshPolicy> \
13368                 consumer's partition on the cluster-default arm",
13369            );
13370        }
13371    }
13372
13373    #[test]
13374    fn declared_mesh_slots_politicas_arm_routes_through_accessor() {
13375        // Composition pin: [`Caixa::declared_mesh_slots`]'s
13376        // `:politicas` presence-probe arm must key off
13377        // [`Caixa::politicas`], not the raw `self.politicas.is_some()`
13378        // field-probe. Structurally: a `Caixa { politicas:
13379        // Some(MeshPolicy::default()), .. }` must still push
13380        // `M3_AUTHOR_KEY_POLITICAS` onto the declared-slot list (the
13381        // presence bit is `Some`, so the M3 kind-coherence gate must
13382        // surface the slot as "declared" even when every per-axis
13383        // scalar is unset), and a `Caixa { politicas: None, .. }` must
13384        // NOT push the label (the "author omitted the slot entirely"
13385        // partition). The pair jointly pins the accessor + declared-
13386        // slot enumerator composition: any future silent detour that
13387        // had the accessor collapse `Some(MeshPolicy::default())` to
13388        // `None` (a `.filter(|p| !p.is_empty())` projection) would
13389        // silently absorb the "declared but empty" arm at the
13390        // accessor boundary and the
13391        // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
13392        // coherence gate would silently accept a struct-literal
13393        // `Caixa` carrying the drift.
13394        //
13395        // Peer of the sibling
13396        // `declared_servico_slots_limits_arm_routes_through_accessor`
13397        // (b2bd9d7) and
13398        // `declared_servico_slots_behavior_arm_routes_through_accessor`
13399        // (35d8b52) composition pins on the sibling `:limits` /
13400        // `:behavior` outer-`Option<&Composite>` arms of the peer
13401        // [`Caixa::declared_servico_slots`] M2 declared-slot
13402        // enumerator's traversal — same "the enumerator gate must
13403        // route through the substrate-primitive typed dispatch"
13404        // discipline extended onto the outer top-level [`Caixa`] M3
13405        // mesh-slot family so the [`Caixa::declared_mesh_slots`]
13406        // enumerator carries the same routing invariant as its M2
13407        // sibling.
13408        use crate::aplicacao::MeshPolicy;
13409        let c = caixa_aplicacao_with_politicas(Some(MeshPolicy::default()));
13410        let slots = c.declared_mesh_slots();
13411        assert!(
13412            slots.contains(&crate::render::M3_AUTHOR_KEY_POLITICAS),
13413            "declared_mesh_slots must push M3_AUTHOR_KEY_POLITICAS \
13414             when `:politicas` is Some (even for MeshPolicy::default()) \
13415             — the accessor and the enumerator gate must route through \
13416             the same substrate-primitive typed dispatch on the outer \
13417             :politicas presence bit (got slots={slots:?})",
13418        );
13419        let c = caixa_aplicacao_with_politicas(None);
13420        let slots = c.declared_mesh_slots();
13421        assert!(
13422            !slots.contains(&crate::render::M3_AUTHOR_KEY_POLITICAS),
13423            "declared_mesh_slots must NOT push M3_AUTHOR_KEY_POLITICAS \
13424             when `:politicas` is None — the author-omitted arm must \
13425             route through the accessor's None-return unchanged (got \
13426             slots={slots:?})",
13427        );
13428    }
13429
13430    #[test]
13431    fn aplicacao_view_politicas_arm_folds_through_accessor() {
13432        // Composition pin: [`Caixa::aplicacao_view`]'s per-`:politicas`
13433        // Aplicacao-composition seed must fold through
13434        // [`Caixa::politicas`], not the raw
13435        // `self.politicas.clone().unwrap_or_default()` field-borrow.
13436        // Structurally: a `Caixa { politicas: Some(MeshPolicy {
13437        // timeout: Some(30s), .. default }), kind: Aplicacao, .. }`
13438        // must surface a projected [`crate::AplicacaoSpec`] whose
13439        // `politicas().timeout()` field byte-equals the outer
13440        // composite's `timeout` scalar (the fold must project the
13441        // authored composite verbatim), a `Caixa { politicas:
13442        // Some(MeshPolicy::default()), kind: Aplicacao, .. }` must
13443        // surface an [`crate::AplicacaoSpec`] whose `politicas()`
13444        // byte-equals [`crate::aplicacao::MeshPolicy::default`] (the
13445        // fold's empty-composite arm collapses to the same default the
13446        // author-omitted arm does), and a `Caixa { politicas: None,
13447        // kind: Aplicacao, .. }` must surface an
13448        // [`crate::AplicacaoSpec`] whose `politicas()` byte-equals
13449        // [`crate::aplicacao::MeshPolicy::default`] (the "author
13450        // omitted the slot entirely" arm folds through the
13451        // `unwrap_or_default` onto the cluster-default). The triad
13452        // jointly pins the accessor + Aplicacao-composition seed
13453        // composition: any future silent detour that had the accessor
13454        // divert the raw slot away from the seed's fold (an operator-
13455        // resolved overlay's default-fold arm silently differing from
13456        // the raw slot's default-fold arm) would silently split the
13457        // build-time mesh-artifact emission gate from the caixa-mesh
13458        // renderer's Aplicacao-view input at the composition boundary.
13459        use crate::aplicacao::MeshPolicy;
13460        use std::time::Duration;
13461        let c = caixa_aplicacao_with_politicas(Some(MeshPolicy {
13462            timeout: Some(Duration::from_secs(30)),
13463            ..Default::default()
13464        }));
13465        let view = c.aplicacao_view().unwrap();
13466        assert_eq!(
13467            view.politicas().timeout(),
13468            Some(Duration::from_secs(30)),
13469            "Caixa::aplicacao_view must fold the authored :politicas \
13470             :timeout scalar through the accessor verbatim onto the \
13471             projected AplicacaoSpec — a future silent detour at the \
13472             seed's fold arm would surface here as a projected-scalar \
13473             drift (got {:?})",
13474            view.politicas().timeout(),
13475        );
13476        let c = caixa_aplicacao_with_politicas(Some(MeshPolicy::default()));
13477        let view = c.aplicacao_view().unwrap();
13478        assert_eq!(
13479            view.politicas(),
13480            &MeshPolicy::default(),
13481            "Caixa::aplicacao_view must fold Some(MeshPolicy::default()) \
13482             through the accessor onto MeshPolicy::default — the empty- \
13483             composite arm collapses to the same default the author- \
13484             omitted arm does (got {:?})",
13485            view.politicas(),
13486        );
13487        let c = caixa_aplicacao_with_politicas(None);
13488        let view = c.aplicacao_view().unwrap();
13489        assert_eq!(
13490            view.politicas(),
13491            &MeshPolicy::default(),
13492            "Caixa::aplicacao_view must fold None through the accessor's \
13493             unwrap_or_default onto MeshPolicy::default — the author- \
13494             omitted arm must route through the accessor's None-return \
13495             unchanged (got {:?})",
13496            view.politicas(),
13497        );
13498    }
13499
13500    #[test]
13501    fn politicas_projects_option_ref_by_borrow() {
13502        // The by-borrow pin: [`Caixa::politicas`] returns
13503        // `Option<&MeshPolicy>` by borrow — the returned reference
13504        // borrows the underlying `Option<MeshPolicy>` storage of the
13505        // `:politicas` slot and the accessor must not clone the
13506        // backing composite on every call. Peer of the sibling
13507        // `limits_projects_option_ref_by_borrow` (b2bd9d7) and
13508        // `behavior_projects_option_ref_by_borrow` (35d8b52) by-borrow
13509        // pins on the outer top-level [`Caixa`]
13510        // `Option<&Composite>`-return sub-family — extended here to
13511        // the third axis of the same sub-family: the accessor's
13512        // returned reference must borrow from `&self` (the returned
13513        // reference's lifetime is tied to `&self`), and calling the
13514        // accessor twice on the same [`Caixa`] must yield references
13515        // that are pointer-equal (the underlying byte-buffer is the
13516        // storage `MeshPolicy`'s allocation, not a fresh copy) as
13517        // well as value-equal (idempotent, no side effects on
13518        // `&self`).
13519        //
13520        // Pins against a future silent detour that returned an owned
13521        // `MeshPolicy` (which would type-check via the `Clone` impl
13522        // but silently clone on every call), a `&MeshPolicy` 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::aplicacao::{CircuitBreaker, MeshPolicy, RateLimit};
13528        use std::time::Duration;
13529        for politicas in [
13530            Some(MeshPolicy::default()),
13531            Some(MeshPolicy {
13532                timeout: Some(Duration::from_secs(30)),
13533                retries: Some(3),
13534                circuit_breaker: Some(CircuitBreaker {
13535                    max_failures: 5,
13536                    window: Duration::from_secs(60),
13537                }),
13538                mtls_required: Some(true),
13539                rate_limit: Some(RateLimit {
13540                    rate: 100,
13541                    window: Duration::from_secs(1),
13542                }),
13543            }),
13544        ] {
13545            let c = caixa_aplicacao_with_politicas(politicas.clone());
13546            let first = c.politicas().unwrap();
13547            let second = c.politicas().unwrap();
13548            assert_eq!(
13549                first, second,
13550                "Caixa::politicas must be idempotent — two successive \
13551                 calls on the same &self must return the same \
13552                 &MeshPolicy",
13553            );
13554            assert!(
13555                std::ptr::eq(first, second),
13556                "Caixa::politicas must borrow the underlying \
13557                 Option<MeshPolicy> storage — two successive calls \
13558                 must return references with the same backing pointer \
13559                 (a fresh MeshPolicy clone would change the pointer on \
13560                 every call)",
13561            );
13562            assert_eq!(
13563                Some(first),
13564                politicas.as_ref(),
13565                "Caixa::politicas must return :politicas verbatim by \
13566                 borrow — got {first:?}, expected {:?}",
13567                politicas.as_ref(),
13568            );
13569        }
13570        let c = caixa_aplicacao_with_politicas(None);
13571        assert!(
13572            c.politicas().is_none(),
13573            "Caixa::politicas must return None when :politicas is \
13574             absent — the author-omitted arm must project through the \
13575             accessor's Option::None unchanged",
13576        );
13577    }
13578
13579    // ── Caixa::placement — outer top-level Option<&Placement> composite-reference accessor ──
13580
13581    fn caixa_aplicacao_with_placement(placement: Option<crate::aplicacao::Placement>) -> Caixa {
13582        use crate::aplicacao::{Membro, WitContract};
13583        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
13584        c.kind = CaixaKind::Aplicacao;
13585        c.membros = vec![Membro {
13586            caixa: "a".into(),
13587            versao: "^0.1".into(),
13588        }];
13589        c.contratos = vec![WitContract {
13590            de: "a".into(),
13591            para: "a".into(),
13592            wit: "wasi:http/proxy".into(),
13593            endpoint: Some("/x".into()),
13594            subject: None,
13595            slot: None,
13596        }];
13597        c.placement = placement;
13598        c
13599    }
13600
13601    #[test]
13602    fn placement_returns_placement_option_ref_verbatim_across_permutations() {
13603        // The canonical per-`Caixa` `:placement` M3 mesh-slot outer-
13604        // composite optional-composite-reference-shape pin:
13605        // [`Caixa::placement`] must return the `:placement` typed
13606        // `Option<Placement>` verbatim as an `Option<&Placement>`
13607        // reference over the same backing storage the raw
13608        // `self.placement.as_ref()` field access borrows from,
13609        // byte-equal across every representative fixture in the
13610        // accept-set — the author-omitted `None` shape (the
13611        // "cluster-default applies" partition every downstream mesh-
13612        // artifact emitter treats as "emit no `:placement` overlay"),
13613        // the empty-composite `Some(Placement { .. default })` shape
13614        // (`estrategia: SingleNode`, empty clusters, no shard-key /
13615        // affinity — the outer presence-bit is `Some` so
13616        // [`Caixa::declared_mesh_slots`] still pushes the
13617        // `M3_AUTHOR_KEY_PLACEMENT` label), a single-axis
13618        // `Replicated`-on-two-clusters fixture (the canonical shape a
13619        // stateless HTTP Aplicacao carries), and a fully-populated
13620        // `Sharded`-with-shard-key-and-affinity fixture (the canonical
13621        // shape a stateful Akka-style cluster-sharding Aplicacao
13622        // carries).
13623        //
13624        // Pins against a future silent detour that returned a fresh-
13625        // cloned [`crate::aplicacao::Placement`] copy (which would
13626        // type-check via the `Clone` impl but silently break every
13627        // downstream caller that relied on the reference sharing the
13628        // composite's backing identity), a reference to an operator-
13629        // resolved overlay (the future per-cluster
13630        // `:placement-overrides` slot — its resolution must land at
13631        // exactly this accessor body, not silently divert the raw
13632        // slot away from the peer [`Caixa::declared_mesh_slots`]
13633        // enumerator's presence probe), a `None` →
13634        // `Some(Placement::default)` cluster-default projection (which
13635        // would collapse the load-bearing "author-omitted `:placement`
13636        // ⇒ cluster-default applies" partition the peer
13637        // [`Caixa::declared_mesh_slots`] enumerator and the peer
13638        // [`Caixa::aplicacao_view`] Aplicacao-composition seed both
13639        // read), or an axis-shuffled projection (a future detour that
13640        // swapped `clusters` and `affinity` through the accessor would
13641        // silently split the paired [`Caixa::aplicacao_view`] seed's
13642        // fold input from the sibling M3 mesh-artifact emitter's
13643        // projection input).
13644        //
13645        // Fourth outer top-level [`Caixa`] `Option<&Composite>`-return
13646        // composite-reference accessor pin on the substrate primitive
13647        // — peer of the sibling
13648        // `limits_returns_limits_option_ref_verbatim_across_permutations`
13649        // (b2bd9d7),
13650        // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
13651        // (35d8b52), and
13652        // `politicas_returns_politicas_option_ref_verbatim_across_permutations`
13653        // (5d23d29) opening triad pins on the outer top-level
13654        // [`Caixa`] `Option<&Composite>`-return sub-family — extended
13655        // here to the second of the three M3 mesh-slot axes so the
13656        // opening four-fifths of the outer `Option<&Composite>` sub-
13657        // family carries the same "byte-equal, borrow-shared,
13658        // presence-bit-preserved" outer-accessor discipline.
13659        use crate::aplicacao::{Placement, PlacementStrategy};
13660        let fixtures: Vec<Option<Placement>> = vec![
13661            None,
13662            Some(Placement::default()),
13663            Some(Placement {
13664                estrategia: PlacementStrategy::Replicated,
13665                clusters: vec!["rio".into(), "sao-paulo".into()],
13666                affinity: None,
13667                shard_key: None,
13668            }),
13669            Some(Placement {
13670                estrategia: PlacementStrategy::Sharded,
13671                clusters: vec!["rio".into(), "sao-paulo".into(), "brasilia".into()],
13672                affinity: Some("data-locality".into()),
13673                shard_key: Some("$tenantId".into()),
13674            }),
13675        ];
13676        for placement in fixtures {
13677            let c = caixa_aplicacao_with_placement(placement.clone());
13678            assert_eq!(
13679                c.placement(),
13680                placement.as_ref(),
13681                "Caixa::placement must return :placement verbatim (got \
13682                 {:?}, expected {:?})",
13683                c.placement(),
13684                placement.as_ref(),
13685            );
13686            match (c.placement(), c.placement.as_ref()) {
13687                (Some(a), Some(b)) => assert!(
13688                    std::ptr::eq(a, b),
13689                    "Caixa::placement accessor and self.placement.as_ref() \
13690                     field access must borrow the same backing storage \
13691                     — the accessor is the substrate-primitive typed \
13692                     dispatch every downstream Aplicacao-distribution- \
13693                     overlay composite consumer must route through, and \
13694                     a reference-identity split would silently break \
13695                     every consumer that relied on the borrow sharing \
13696                     the composite's storage",
13697                ),
13698                (None, None) => {}
13699                _ => panic!(
13700                    "Caixa::placement presence bit must byte-equal \
13701                     self.placement.is_some() — a presence-bit drift \
13702                     would silently split the paired \
13703                     Caixa::aplicacao_view Aplicacao-composition seed's \
13704                     traversal head from the peer \
13705                     Caixa::declared_mesh_slots M3 declared-slot \
13706                     enumerator's presence probe",
13707                ),
13708            }
13709            assert_eq!(
13710                c.placement().is_some(),
13711                c.placement.is_some(),
13712                "Caixa::placement().is_some() must byte-equal \
13713                 self.placement.is_some() — a presence-bit drift would \
13714                 silently split every downstream Option<&Placement> \
13715                 consumer's partition on the cluster-default arm",
13716            );
13717        }
13718    }
13719
13720    #[test]
13721    fn declared_mesh_slots_placement_arm_routes_through_accessor() {
13722        // Composition pin: [`Caixa::declared_mesh_slots`]'s
13723        // `:placement` presence-probe arm must key off
13724        // [`Caixa::placement`], not the raw `self.placement.is_some()`
13725        // field-probe. Structurally: a `Caixa { placement:
13726        // Some(Placement::default()), .. }` must still push
13727        // `M3_AUTHOR_KEY_PLACEMENT` onto the declared-slot list (the
13728        // presence bit is `Some`, so the M3 kind-coherence gate must
13729        // surface the slot as "declared" even when every per-axis
13730        // scalar defers to the cluster-default arm), and a `Caixa {
13731        // placement: None, .. }` must NOT push the label (the "author
13732        // omitted the slot entirely" partition). The pair jointly pins
13733        // the accessor + declared-slot enumerator composition: any
13734        // future silent detour that had the accessor collapse
13735        // `Some(Placement::default())` to `None` (a `.filter(|p|
13736        // p.clusters().is_empty().not())` projection) would silently
13737        // absorb the "declared but empty" arm at the accessor boundary
13738        // and the [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
13739        // kind-coherence gate would silently accept a struct-literal
13740        // `Caixa` carrying the drift.
13741        //
13742        // Peer of the sibling
13743        // `declared_servico_slots_limits_arm_routes_through_accessor`
13744        // (b2bd9d7),
13745        // `declared_servico_slots_behavior_arm_routes_through_accessor`
13746        // (35d8b52), and
13747        // `declared_mesh_slots_politicas_arm_routes_through_accessor`
13748        // (5d23d29) composition pins on the sibling `:limits` /
13749        // `:behavior` / `:politicas` outer-`Option<&Composite>` arms
13750        // — same "the enumerator gate must route through the
13751        // substrate-primitive typed dispatch" discipline extended onto
13752        // the second of the three M3 mesh-slot axes so the
13753        // [`Caixa::declared_mesh_slots`] enumerator carries the same
13754        // routing invariant on the `:placement` arm as the peer
13755        // `:politicas` arm.
13756        use crate::aplicacao::Placement;
13757        let c = caixa_aplicacao_with_placement(Some(Placement::default()));
13758        let slots = c.declared_mesh_slots();
13759        assert!(
13760            slots.contains(&crate::render::M3_AUTHOR_KEY_PLACEMENT),
13761            "declared_mesh_slots must push M3_AUTHOR_KEY_PLACEMENT \
13762             when `:placement` is Some (even for Placement::default()) \
13763             — the accessor and the enumerator gate must route through \
13764             the same substrate-primitive typed dispatch on the outer \
13765             :placement presence bit (got slots={slots:?})",
13766        );
13767        let c = caixa_aplicacao_with_placement(None);
13768        let slots = c.declared_mesh_slots();
13769        assert!(
13770            !slots.contains(&crate::render::M3_AUTHOR_KEY_PLACEMENT),
13771            "declared_mesh_slots must NOT push M3_AUTHOR_KEY_PLACEMENT \
13772             when `:placement` is None — the author-omitted arm must \
13773             route through the accessor's None-return unchanged (got \
13774             slots={slots:?})",
13775        );
13776    }
13777
13778    #[test]
13779    fn aplicacao_view_placement_arm_folds_through_accessor() {
13780        // Composition pin: [`Caixa::aplicacao_view`]'s per-`:placement`
13781        // Aplicacao-composition seed must fold through
13782        // [`Caixa::placement`], not the raw
13783        // `self.placement.clone().unwrap_or_default()` field-borrow.
13784        // Structurally: a `Caixa { placement: Some(Placement {
13785        // estrategia: Replicated, clusters: ["rio"], .. default }),
13786        // kind: Aplicacao, .. }` must surface a projected
13787        // [`crate::AplicacaoSpec`] whose `placement().estrategia()` +
13788        // `placement().clusters()` byte-equal the outer composite's
13789        // authored values (the fold must project the authored
13790        // composite verbatim), a `Caixa { placement:
13791        // Some(Placement::default()), kind: Aplicacao, .. }` must
13792        // surface an [`crate::AplicacaoSpec`] whose `placement()`
13793        // byte-equals [`crate::aplicacao::Placement::default`] (the
13794        // fold's empty-composite arm collapses to the same default
13795        // the author-omitted arm does), and a `Caixa { placement:
13796        // None, kind: Aplicacao, .. }` must surface an
13797        // [`crate::AplicacaoSpec`] whose `placement()` byte-equals
13798        // [`crate::aplicacao::Placement::default`] (the "author
13799        // omitted the slot entirely" arm folds through the
13800        // `unwrap_or_default` onto the cluster-default). The triad
13801        // jointly pins the accessor + Aplicacao-composition seed
13802        // composition: any future silent detour that had the accessor
13803        // divert the raw slot away from the seed's fold (an operator-
13804        // resolved overlay's default-fold arm silently differing from
13805        // the raw slot's default-fold arm) would silently split the
13806        // build-time distribution-artifact emission gate from the
13807        // caixa-mesh renderer's Aplicacao-view input at the
13808        // composition boundary.
13809        use crate::aplicacao::{Placement, PlacementStrategy};
13810        let c = caixa_aplicacao_with_placement(Some(Placement {
13811            estrategia: PlacementStrategy::Replicated,
13812            clusters: vec!["rio".into()],
13813            affinity: None,
13814            shard_key: None,
13815        }));
13816        let view = c.aplicacao_view().unwrap();
13817        assert_eq!(
13818            view.placement().estrategia(),
13819            PlacementStrategy::Replicated,
13820            "Caixa::aplicacao_view must fold the authored :placement \
13821             :estrategia scalar through the accessor verbatim onto the \
13822             projected AplicacaoSpec — a future silent detour at the \
13823             seed's fold arm would surface here as a projected-scalar \
13824             drift (got {:?})",
13825            view.placement().estrategia(),
13826        );
13827        assert_eq!(
13828            view.placement().clusters(),
13829            &["rio"],
13830            "Caixa::aplicacao_view must fold the authored :placement \
13831             :clusters list through the accessor verbatim onto the \
13832             projected AplicacaoSpec — a future silent detour at the \
13833             seed's fold arm would surface here as a projected-list \
13834             drift (got {:?})",
13835            view.placement().clusters(),
13836        );
13837        let c = caixa_aplicacao_with_placement(Some(Placement::default()));
13838        let view = c.aplicacao_view().unwrap();
13839        assert_eq!(
13840            view.placement(),
13841            &Placement::default(),
13842            "Caixa::aplicacao_view must fold Some(Placement::default()) \
13843             through the accessor onto Placement::default — the empty- \
13844             composite arm collapses to the same default the author- \
13845             omitted arm does (got {:?})",
13846            view.placement(),
13847        );
13848        let c = caixa_aplicacao_with_placement(None);
13849        let view = c.aplicacao_view().unwrap();
13850        assert_eq!(
13851            view.placement(),
13852            &Placement::default(),
13853            "Caixa::aplicacao_view must fold None through the accessor's \
13854             unwrap_or_default onto Placement::default — the author- \
13855             omitted arm must route through the accessor's None-return \
13856             unchanged (got {:?})",
13857            view.placement(),
13858        );
13859    }
13860
13861    #[test]
13862    fn placement_projects_option_ref_by_borrow() {
13863        // The by-borrow pin: [`Caixa::placement`] returns
13864        // `Option<&Placement>` by borrow — the returned reference
13865        // borrows the underlying `Option<Placement>` storage of the
13866        // `:placement` slot and the accessor must not clone the
13867        // backing composite on every call. Peer of the sibling
13868        // `limits_projects_option_ref_by_borrow` (b2bd9d7),
13869        // `behavior_projects_option_ref_by_borrow` (35d8b52), and
13870        // `politicas_projects_option_ref_by_borrow` (5d23d29) by-borrow
13871        // pins on the outer top-level [`Caixa`]
13872        // `Option<&Composite>`-return sub-family — extended here to
13873        // the fourth axis of the same sub-family: the accessor's
13874        // returned reference must borrow from `&self` (the returned
13875        // reference's lifetime is tied to `&self`), and calling the
13876        // accessor twice on the same [`Caixa`] must yield references
13877        // that are pointer-equal (the underlying byte-buffer is the
13878        // storage `Placement`'s allocation, not a fresh copy) as well
13879        // as value-equal (idempotent, no side effects on `&self`).
13880        //
13881        // Pins against a future silent detour that returned an owned
13882        // `Placement` (which would type-check via the `Clone` impl
13883        // but silently clone on every call), a `&Placement` panic-
13884        // return on the `None` arm (which would collapse the load-
13885        // bearing `Option` presence-bit into a runtime panic), or a
13886        // one-arm-only accessor that returned a saturating composite
13887        // on some sentinel input.
13888        use crate::aplicacao::{Placement, PlacementStrategy};
13889        for placement in [
13890            Some(Placement::default()),
13891            Some(Placement {
13892                estrategia: PlacementStrategy::Sharded,
13893                clusters: vec!["rio".into(), "sao-paulo".into()],
13894                affinity: Some("data-locality".into()),
13895                shard_key: Some("$tenantId".into()),
13896            }),
13897        ] {
13898            let c = caixa_aplicacao_with_placement(placement.clone());
13899            let first = c.placement().unwrap();
13900            let second = c.placement().unwrap();
13901            assert_eq!(
13902                first, second,
13903                "Caixa::placement must be idempotent — two successive \
13904                 calls on the same &self must return the same \
13905                 &Placement",
13906            );
13907            assert!(
13908                std::ptr::eq(first, second),
13909                "Caixa::placement must borrow the underlying \
13910                 Option<Placement> storage — two successive calls \
13911                 must return references with the same backing pointer \
13912                 (a fresh Placement clone would change the pointer on \
13913                 every call)",
13914            );
13915            assert_eq!(
13916                Some(first),
13917                placement.as_ref(),
13918                "Caixa::placement must return :placement verbatim by \
13919                 borrow — got {first:?}, expected {:?}",
13920                placement.as_ref(),
13921            );
13922        }
13923        let c = caixa_aplicacao_with_placement(None);
13924        assert!(
13925            c.placement().is_none(),
13926            "Caixa::placement must return None when :placement is \
13927             absent — the author-omitted arm must project through the \
13928             accessor's Option::None unchanged",
13929        );
13930    }
13931
13932    // ── Caixa::entrada — outer top-level Option<&Entrada> composite-reference accessor ──
13933
13934    fn caixa_aplicacao_with_entrada(entrada: Option<crate::aplicacao::Entrada>) -> Caixa {
13935        use crate::aplicacao::{Membro, WitContract};
13936        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
13937        c.kind = CaixaKind::Aplicacao;
13938        c.membros = vec![Membro {
13939            caixa: "a".into(),
13940            versao: "^0.1".into(),
13941        }];
13942        c.contratos = vec![WitContract {
13943            de: "a".into(),
13944            para: "a".into(),
13945            wit: "wasi:http/proxy".into(),
13946            endpoint: Some("/x".into()),
13947            subject: None,
13948            slot: None,
13949        }];
13950        c.entrada = entrada;
13951        c
13952    }
13953
13954    #[test]
13955    fn entrada_returns_entrada_option_ref_verbatim_across_permutations() {
13956        // The canonical per-`Caixa` `:entrada` M3 mesh-slot outer-
13957        // composite optional-composite-reference-shape pin:
13958        // [`Caixa::entrada`] must return the `:entrada` typed
13959        // `Option<Entrada>` verbatim as an `Option<&Entrada>`
13960        // reference over the same backing storage the raw
13961        // `self.entrada.as_ref()` field access borrows from,
13962        // byte-equal across every representative fixture in the
13963        // accept-set — the author-omitted `None` shape (the
13964        // "cluster-internal Aplicacao" partition every downstream
13965        // Gateway-API emitter treats as "emit no listener + no
13966        // HTTPRoute"), a bare-`host`/`para` minimum-composite fixture
13967        // (empty `paths` — the resolved-paths fallback the peer
13968        // [`crate::aplicacao::Entrada::resolved_paths`] cascade folds
13969        // onto the substrate catch-all), and a fully-populated
13970        // multi-path-with-non-default-port fixture (the canonical
13971        // shape a public HTTP Aplicacao carries).
13972        //
13973        // Pins against a future silent detour that returned a fresh-
13974        // cloned [`crate::aplicacao::Entrada`] copy (which would
13975        // type-check via the `Clone` impl but silently break every
13976        // downstream caller that relied on the reference sharing the
13977        // composite's backing identity), a reference to an operator-
13978        // resolved overlay (the future per-cluster
13979        // `:entrada-overrides` slot — its resolution must land at
13980        // exactly this accessor body, not silently divert the raw
13981        // slot away from the peer [`Caixa::declared_mesh_slots`]
13982        // enumerator's presence probe), or an axis-shuffled projection
13983        // (a future detour that swapped `host` and `para` through the
13984        // accessor would silently split the paired
13985        // [`Caixa::aplicacao_view`] seed's forward input from the
13986        // sibling M3 gateway-artifact emitter's projection input).
13987        //
13988        // Fifth and final outer top-level [`Caixa`]
13989        // `Option<&Composite>`-return composite-reference accessor pin
13990        // on the substrate primitive — peer of the sibling
13991        // `limits_returns_limits_option_ref_verbatim_across_permutations`
13992        // (b2bd9d7),
13993        // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
13994        // (35d8b52),
13995        // `politicas_returns_politicas_option_ref_verbatim_across_permutations`
13996        // (5d23d29), and
13997        // `placement_returns_placement_option_ref_verbatim_across_permutations`
13998        // (4fb8074) opening tetrad pins on the outer top-level
13999        // [`Caixa`] `Option<&Composite>`-return sub-family — extended
14000        // here to the third and final M3 mesh-slot axis so the closed
14001        // outer `Option<&Composite>` sub-family carries the same
14002        // "byte-equal, borrow-shared, presence-bit-preserved" outer-
14003        // accessor discipline across all five arms.
14004        use crate::aplicacao::Entrada;
14005        let fixtures: Vec<Option<Entrada>> = vec![
14006            None,
14007            Some(Entrada {
14008                host: "checkout.quero.cloud".into(),
14009                para: "gateway".into(),
14010                paths: Vec::new(),
14011                port: crate::DEFAULT_SERVICO_PORT,
14012            }),
14013            Some(Entrada {
14014                host: "api.pleme.io".into(),
14015                para: "public-api".into(),
14016                paths: vec!["/v1".into(), "/v2".into()],
14017                port: 8080,
14018            }),
14019        ];
14020        for entrada in fixtures {
14021            let c = caixa_aplicacao_with_entrada(entrada.clone());
14022            assert_eq!(
14023                c.entrada(),
14024                entrada.as_ref(),
14025                "Caixa::entrada must return :entrada verbatim (got \
14026                 {:?}, expected {:?})",
14027                c.entrada(),
14028                entrada.as_ref(),
14029            );
14030            match (c.entrada(), c.entrada.as_ref()) {
14031                (Some(a), Some(b)) => assert!(
14032                    std::ptr::eq(a, b),
14033                    "Caixa::entrada accessor and self.entrada.as_ref() \
14034                     field access must borrow the same backing storage \
14035                     — the accessor is the substrate-primitive typed \
14036                     dispatch every downstream Aplicacao-external- \
14037                     gateway composite consumer must route through, and \
14038                     a reference-identity split would silently break \
14039                     every consumer that relied on the borrow sharing \
14040                     the composite's storage",
14041                ),
14042                (None, None) => {}
14043                _ => panic!(
14044                    "Caixa::entrada presence bit must byte-equal \
14045                     self.entrada.is_some() — a presence-bit drift \
14046                     would silently split the paired \
14047                     Caixa::aplicacao_view Aplicacao-composition seed's \
14048                     traversal head from the peer \
14049                     Caixa::declared_mesh_slots M3 declared-slot \
14050                     enumerator's presence probe",
14051                ),
14052            }
14053            assert_eq!(
14054                c.entrada().is_some(),
14055                c.entrada.is_some(),
14056                "Caixa::entrada().is_some() must byte-equal \
14057                 self.entrada.is_some() — a presence-bit drift would \
14058                 silently split every downstream Option<&Entrada> \
14059                 consumer's partition on the cluster-internal arm",
14060            );
14061        }
14062    }
14063
14064    #[test]
14065    fn declared_mesh_slots_entrada_arm_routes_through_accessor() {
14066        // Composition pin: [`Caixa::declared_mesh_slots`]'s `:entrada`
14067        // presence-probe arm must key off [`Caixa::entrada`], not the
14068        // raw `self.entrada.is_some()` field-probe. Structurally: a
14069        // `Caixa { entrada: Some(Entrada { host: "...", para: "...",
14070        // paths: [], port: DEFAULT_SERVICO_PORT }), .. }` must push
14071        // `M3_AUTHOR_KEY_ENTRADA` onto the declared-slot list (the
14072        // presence bit is `Some`, so the M3 kind-coherence gate must
14073        // surface the slot as "declared" even when every per-axis
14074        // scalar defers to the substrate catch-all / default port),
14075        // and a `Caixa { entrada: None, .. }` must NOT push the label
14076        // (the "author omitted the slot entirely" partition). The pair
14077        // jointly pins the accessor + declared-slot enumerator
14078        // composition: any future silent detour that had the accessor
14079        // collapse `Some(Entrada { paths: [], .. })` to `None` (a
14080        // `.filter(|e| !e.paths.is_empty())` projection) would silently
14081        // absorb the "declared but empty-paths" arm at the accessor
14082        // boundary and the
14083        // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
14084        // coherence gate would silently accept a struct-literal
14085        // `Caixa` carrying the drift.
14086        //
14087        // Peer of the sibling
14088        // `declared_servico_slots_limits_arm_routes_through_accessor`
14089        // (b2bd9d7),
14090        // `declared_servico_slots_behavior_arm_routes_through_accessor`
14091        // (35d8b52),
14092        // `declared_mesh_slots_politicas_arm_routes_through_accessor`
14093        // (5d23d29), and
14094        // `declared_mesh_slots_placement_arm_routes_through_accessor`
14095        // (4fb8074) composition pins on the sibling `:limits` /
14096        // `:behavior` / `:politicas` / `:placement` outer-
14097        // `Option<&Composite>` arms — same "the enumerator gate must
14098        // route through the substrate-primitive typed dispatch"
14099        // discipline extended onto the third and final M3 mesh-slot
14100        // axis so the [`Caixa::declared_mesh_slots`] enumerator now
14101        // carries the routing invariant on every M3 mesh-slot arm.
14102        use crate::aplicacao::Entrada;
14103        let c = caixa_aplicacao_with_entrada(Some(Entrada {
14104            host: "checkout.quero.cloud".into(),
14105            para: "gateway".into(),
14106            paths: Vec::new(),
14107            port: crate::DEFAULT_SERVICO_PORT,
14108        }));
14109        let slots = c.declared_mesh_slots();
14110        assert!(
14111            slots.contains(&crate::render::M3_AUTHOR_KEY_ENTRADA),
14112            "declared_mesh_slots must push M3_AUTHOR_KEY_ENTRADA when \
14113             `:entrada` is Some (even for empty-paths / default-port) \
14114             — the accessor and the enumerator gate must route through \
14115             the same substrate-primitive typed dispatch on the outer \
14116             :entrada presence bit (got slots={slots:?})",
14117        );
14118        let c = caixa_aplicacao_with_entrada(None);
14119        let slots = c.declared_mesh_slots();
14120        assert!(
14121            !slots.contains(&crate::render::M3_AUTHOR_KEY_ENTRADA),
14122            "declared_mesh_slots must NOT push M3_AUTHOR_KEY_ENTRADA \
14123             when `:entrada` is None — the author-omitted arm must \
14124             route through the accessor's None-return unchanged (got \
14125             slots={slots:?})",
14126        );
14127    }
14128
14129    #[test]
14130    fn aplicacao_view_entrada_arm_folds_through_accessor() {
14131        // Composition pin: [`Caixa::aplicacao_view`]'s per-`:entrada`
14132        // Aplicacao-composition seed must fold through
14133        // [`Caixa::entrada`], not the raw `self.entrada.clone()` field-
14134        // borrow. Structurally: a `Caixa { entrada: Some(Entrada {
14135        // host: "api.pleme.io", para: "public-api", paths: ["/v1"],
14136        // port: 8080 }), kind: Aplicacao, .. }` must surface a projected
14137        // [`crate::AplicacaoSpec`] whose `entrada().unwrap()` byte-
14138        // equals the outer composite's authored value (the fold must
14139        // project the authored composite verbatim), and a `Caixa {
14140        // entrada: None, kind: Aplicacao, .. }` must surface an
14141        // [`crate::AplicacaoSpec`] whose `entrada()` is `None` (the
14142        // "author omitted the slot entirely" arm folds through the
14143        // accessor's `Option::cloned` onto the same `None` presence
14144        // bit — unlike the peer `:politicas` / `:placement` arms
14145        // `:entrada` has no cluster-default fold, the omitted arm
14146        // stays omitted). The pair jointly pins the accessor +
14147        // Aplicacao-composition seed composition: any future silent
14148        // detour that had the accessor divert the raw slot away from
14149        // the seed's fold (an operator-resolved overlay's forward arm
14150        // silently differing from the raw slot's forward arm) would
14151        // silently split the build-time gateway-artifact emission gate
14152        // from the caixa-mesh renderer's Aplicacao-view input at the
14153        // composition boundary.
14154        use crate::aplicacao::Entrada;
14155        let authored = Entrada {
14156            host: "api.pleme.io".into(),
14157            para: "public-api".into(),
14158            paths: vec!["/v1".into()],
14159            port: 8080,
14160        };
14161        let c = caixa_aplicacao_with_entrada(Some(authored.clone()));
14162        let view = c.aplicacao_view().unwrap();
14163        assert_eq!(
14164            view.entrada(),
14165            Some(&authored),
14166            "Caixa::aplicacao_view must fold the authored :entrada \
14167             composite through the accessor verbatim onto the \
14168             projected AplicacaoSpec — a future silent detour at the \
14169             seed's fold arm would surface here as a projected- \
14170             composite drift (got {:?})",
14171            view.entrada(),
14172        );
14173        let c = caixa_aplicacao_with_entrada(None);
14174        let view = c.aplicacao_view().unwrap();
14175        assert!(
14176            view.entrada().is_none(),
14177            "Caixa::aplicacao_view must fold None through the \
14178             accessor's Option::cloned onto None — the author- \
14179             omitted arm must route through the accessor's None-return \
14180             unchanged (got {:?})",
14181            view.entrada(),
14182        );
14183    }
14184
14185    #[test]
14186    fn entrada_projects_option_ref_by_borrow() {
14187        // The by-borrow pin: [`Caixa::entrada`] returns
14188        // `Option<&Entrada>` by borrow — the returned reference
14189        // borrows the underlying `Option<Entrada>` storage of the
14190        // `:entrada` slot and the accessor must not clone the backing
14191        // composite on every call. Peer of the sibling
14192        // `limits_projects_option_ref_by_borrow` (b2bd9d7),
14193        // `behavior_projects_option_ref_by_borrow` (35d8b52),
14194        // `politicas_projects_option_ref_by_borrow` (5d23d29), and
14195        // `placement_projects_option_ref_by_borrow` (4fb8074) by-
14196        // borrow pins on the outer top-level [`Caixa`]
14197        // `Option<&Composite>`-return sub-family — extended here to
14198        // the fifth and final axis of the same sub-family, closing
14199        // the discipline: the accessor's returned reference must
14200        // borrow from `&self` (the returned reference's lifetime is
14201        // tied to `&self`), and calling the accessor twice on the
14202        // same [`Caixa`] must yield references that are pointer-equal
14203        // (the underlying byte-buffer is the storage `Entrada`'s
14204        // allocation, not a fresh copy) as well as value-equal
14205        // (idempotent, no side effects on `&self`).
14206        //
14207        // Pins against a future silent detour that returned an owned
14208        // `Entrada` (which would type-check via the `Clone` impl but
14209        // silently clone on every call), a `&Entrada` panic-return on
14210        // the `None` arm (which would collapse the load-bearing
14211        // `Option` presence-bit into a runtime panic), or a one-arm-
14212        // only accessor that returned a saturating composite on some
14213        // sentinel input.
14214        use crate::aplicacao::Entrada;
14215        for entrada in [
14216            Some(Entrada {
14217                host: "checkout.quero.cloud".into(),
14218                para: "gateway".into(),
14219                paths: Vec::new(),
14220                port: crate::DEFAULT_SERVICO_PORT,
14221            }),
14222            Some(Entrada {
14223                host: "api.pleme.io".into(),
14224                para: "public-api".into(),
14225                paths: vec!["/v1".into(), "/v2".into()],
14226                port: 8080,
14227            }),
14228        ] {
14229            let c = caixa_aplicacao_with_entrada(entrada.clone());
14230            let first = c.entrada().unwrap();
14231            let second = c.entrada().unwrap();
14232            assert_eq!(
14233                first, second,
14234                "Caixa::entrada must be idempotent — two successive \
14235                 calls on the same &self must return the same &Entrada",
14236            );
14237            assert!(
14238                std::ptr::eq(first, second),
14239                "Caixa::entrada must borrow the underlying \
14240                 Option<Entrada> storage — two successive calls must \
14241                 return references with the same backing pointer (a \
14242                 fresh Entrada clone would change the pointer on every \
14243                 call)",
14244            );
14245            assert_eq!(
14246                Some(first),
14247                entrada.as_ref(),
14248                "Caixa::entrada must return :entrada verbatim by \
14249                 borrow — got {first:?}, expected {:?}",
14250                entrada.as_ref(),
14251            );
14252        }
14253        let c = caixa_aplicacao_with_entrada(None);
14254        assert!(
14255            c.entrada().is_none(),
14256            "Caixa::entrada must return None when :entrada is absent \
14257             — the author-omitted arm must project through the \
14258             accessor's Option::None unchanged",
14259        );
14260    }
14261
14262    // ── Caixa::estrategia — outer top-level Option<RestartStrategy> flat-spread supervisor-tree accessor ──
14263
14264    fn caixa_with_estrategia(estrategia: Option<crate::supervisor::RestartStrategy>) -> Caixa {
14265        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
14266        c.estrategia = estrategia;
14267        c
14268    }
14269
14270    #[test]
14271    fn estrategia_returns_estrategia_option_verbatim_across_permutations() {
14272        // The canonical per-`Caixa` `:estrategia` M2 supervisor-tree-slot
14273        // flat-spread `Option<RestartStrategy>`-return `Copy`-composite-
14274        // enum-arm scalar shape pin: [`Caixa::estrategia`] must return
14275        // the `:estrategia` typed `Option<crate::supervisor::RestartStrategy>`
14276        // verbatim as an `Option<RestartStrategy>` `Copy`-projected value
14277        // over the same discriminant the raw `self.estrategia` field
14278        // access carries, byte-equal across every representative fixture
14279        // in the accept-set — the author-omitted `None` shape (the
14280        // "defer to [`RestartStrategy::default`] through the
14281        // [`Self::supervisor_view`] `unwrap_or_default()` fold" partition
14282        // every non-`Supervisor`-kind `defcaixa` carries by
14283        // `#[serde(default)]`), and each of the four closed-set variants
14284        // [`RestartStrategy::OneForOne`] / [`RestartStrategy::OneForAll`]
14285        // / [`RestartStrategy::RestForOne`] /
14286        // [`RestartStrategy::SimpleOneForOne`] the author-declared arm
14287        // partitions on.
14288        //
14289        // Pins against a future silent detour that re-derived the
14290        // strategy from a peer axis (an accidental fallback to
14291        // `if children.is_empty() { SimpleOneForOne } else { OneForOne }`
14292        // collapse that read the outer `:children` list-length axis into
14293        // the strategy discriminator at the accessor boundary), a
14294        // stale-derive detour that substituted [`RestartStrategy::default`]
14295        // when the outer `Option` held `None` (which would silently
14296        // collapse the load-bearing "author explicitly declared
14297        // `:estrategia OneForOne`" vs "author omitted the slot and
14298        // inherited the default" partition the [`Self::declared_supervisor_slots`]
14299        // presence-probe reads — the enumerator gate would still push
14300        // `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA` on the omitted arm, silently
14301        // splitting the paired [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
14302        // kind-coherence gate's traversal head from the
14303        // [`Self::supervisor_view`] `unwrap_or_default()` fold's
14304        // composition head), a reference to an operator-resolved overlay
14305        // (the future per-cluster `:estrategia-overrides` slot — its
14306        // resolution must land at exactly this accessor body, not
14307        // silently divert the raw slot away from a second consumer), or
14308        // an axis-remap projection (a future detour that mapped
14309        // `OneForAll` through the accessor onto `OneForOne` would
14310        // silently split every downstream sibling-restart-strategy
14311        // consumer's per-arm fan-out).
14312        //
14313        // First outer top-level [`Caixa`] `Option<Copy>`-return
14314        // supervisor-tree-slot flat-spread accessor pin on the substrate
14315        // primitive — opens the outer-`Caixa` `Option<Copy>` flat-spread
14316        // projection pattern the sibling per-`Caixa` `:max-restarts` /
14317        // `:restart-window` future outer-scalar pins fold on. Peer of
14318        // the inner-altitude
14319        // `supervisor_spec_estrategia_returns_estrategia_verbatim_across_permutations`
14320        // (eafb619) pin on the post-composition [`SupervisorSpec`]
14321        // altitude — same "the substrate-primitive accessor must byte-
14322        // equal the raw field access verbatim across every author-
14323        // declared value" discipline extended onto the pre-composition
14324        // outer author-surface [`Caixa`] altitude. Peer of the closed
14325        // outer-`Caixa` `Option<&Composite>` composite-reference family
14326        // the sibling `limits` / `behavior` / `politicas` / `placement` /
14327        // `entrada`
14328        // `..._returns_..._option_ref_verbatim_across_permutations` pins
14329        // already carry on the outer `Option<&Composite>` altitude.
14330        use crate::supervisor::RestartStrategy;
14331        let fixtures: Vec<Option<RestartStrategy>> = vec![
14332            None,
14333            Some(RestartStrategy::OneForOne),
14334            Some(RestartStrategy::OneForAll),
14335            Some(RestartStrategy::RestForOne),
14336            Some(RestartStrategy::SimpleOneForOne),
14337        ];
14338        for estrategia in fixtures {
14339            let c = caixa_with_estrategia(estrategia);
14340            assert_eq!(
14341                c.estrategia(),
14342                estrategia,
14343                "Caixa::estrategia must return :estrategia verbatim (got \
14344                 {:?}, expected {:?})",
14345                c.estrategia(),
14346                estrategia,
14347            );
14348            assert_eq!(
14349                c.estrategia(),
14350                c.estrategia,
14351                "Caixa::estrategia accessor and self.estrategia field \
14352                 access must byte-equal — the accessor is the substrate-\
14353                 primitive typed dispatch every downstream supervisor-\
14354                 tree flat-spread consumer must route through, and a \
14355                 discriminant split would silently break every consumer \
14356                 that relied on the accessor sharing the field's own \
14357                 Option<Copy> shape",
14358            );
14359            assert_eq!(
14360                c.estrategia().is_some(),
14361                c.estrategia.is_some(),
14362                "Caixa::estrategia().is_some() must byte-equal \
14363                 self.estrategia.is_some() — a presence-bit drift would \
14364                 silently split the paired Caixa::declared_supervisor_slots \
14365                 presence-probe arm from the Caixa::supervisor_view \
14366                 unwrap_or_default() fold's composition input",
14367            );
14368        }
14369    }
14370
14371    #[test]
14372    fn declared_supervisor_slots_estrategia_arm_routes_through_accessor() {
14373        // Composition pin: [`Caixa::declared_supervisor_slots`]'s
14374        // `:estrategia` presence-probe arm must key off
14375        // [`Caixa::estrategia`], not the raw `self.estrategia.is_some()`
14376        // field-probe. Structurally: every `Caixa { estrategia:
14377        // Some(RestartStrategy::_), .. }` variant must push
14378        // `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA` onto the declared-slot list
14379        // (the presence bit is `Some` for every closed-set variant, so
14380        // the M2 supervisor-tree kind-coherence gate must surface the
14381        // slot as "declared" regardless of which variant the author
14382        // picked), and a `Caixa { estrategia: None, .. }` must NOT push
14383        // the label (the "author omitted the slot entirely, deferring
14384        // to [`RestartStrategy::default`] through the supervisor_view
14385        // fold" partition). The pair jointly pins the accessor +
14386        // declared-slot enumerator composition: any future silent detour
14387        // that had the accessor collapse `Some(RestartStrategy::default())`
14388        // to `None` (a `.filter(|e| *e != RestartStrategy::default())`
14389        // projection) would silently absorb the "declared but default-
14390        // valued" arm at the accessor boundary and the
14391        // [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
14392        // coherence gate would silently accept a struct-literal `Caixa`
14393        // carrying the drift.
14394        //
14395        // Peer of the sibling per-`Caixa`
14396        // `declared_servico_slots_limits_arm_routes_through_accessor`
14397        // (b2bd9d7) accessor-composition pin on the sibling outer-`Caixa`
14398        // `Option<&LimitsSpec>` composition axis — same "the enumerator
14399        // gate must route through the substrate-primitive typed
14400        // dispatch" discipline extended onto the flat-spread M2
14401        // supervisor-tree `Option<RestartStrategy>`-composition surface,
14402        // opening the outer-`Caixa` supervisor-tree-slot arm of the
14403        // composition-pin family.
14404        use crate::supervisor::RestartStrategy;
14405        for estrategia in [
14406            RestartStrategy::OneForOne,
14407            RestartStrategy::OneForAll,
14408            RestartStrategy::RestForOne,
14409            RestartStrategy::SimpleOneForOne,
14410        ] {
14411            let c = caixa_with_estrategia(Some(estrategia));
14412            let slots = c.declared_supervisor_slots();
14413            assert!(
14414                slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA),
14415                "declared_supervisor_slots must push \
14416                 SUPERVISOR_AUTHOR_KEY_ESTRATEGIA when `:estrategia` is \
14417                 Some({estrategia:?}) — the accessor and the enumerator \
14418                 gate must route through the same substrate-primitive \
14419                 typed dispatch on the outer :estrategia presence bit \
14420                 (got slots={slots:?})",
14421            );
14422        }
14423        let c = caixa_with_estrategia(None);
14424        let slots = c.declared_supervisor_slots();
14425        assert!(
14426            !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA),
14427            "declared_supervisor_slots must NOT push \
14428             SUPERVISOR_AUTHOR_KEY_ESTRATEGIA when `:estrategia` is None \
14429             — the author-omitted arm must route through the accessor's \
14430             None-return unchanged (got slots={slots:?})",
14431        );
14432    }
14433
14434    #[test]
14435    fn supervisor_view_estrategia_arm_routes_through_accessor() {
14436        // Composition pin: [`Caixa::supervisor_view`]'s per-`:estrategia`
14437        // [`SupervisorSpec`] construction arm must key off
14438        // [`Caixa::estrategia`]'s `unwrap_or_default()` fold, not the raw
14439        // `self.estrategia.unwrap_or_default()` field-fold. Structurally:
14440        // for every `:kind Supervisor` `Caixa` carrying an author-
14441        // declared `Some(RestartStrategy::_)` variant, the composed
14442        // [`SupervisorSpec`]'s `.estrategia` field must byte-equal the
14443        // outer accessor's declared variant unchanged; and for a
14444        // `:kind Supervisor` `Caixa` carrying `None`, the composed
14445        // [`SupervisorSpec`]'s `.estrategia` field must byte-equal
14446        // [`RestartStrategy::default`] (the [`RestartStrategy::OneForOne`]
14447        // arm the flat-spread `unwrap_or_default()` fold projects to on
14448        // the author-omitted arm — this is the *composition* between the
14449        // outer `Option<RestartStrategy>` accessor's presence-bit
14450        // surface and the inner post-composition non-`Option`
14451        // [`SupervisorSpec::estrategia`] altitude). The pair jointly
14452        // pins the accessor + supervisor_view composition: any future
14453        // silent detour that had the accessor promote `None` to
14454        // `Some(RestartStrategy::default())` (a `.or_else(|| Some(RestartStrategy::default()))`
14455        // projection) would silently collapse the two arms into one at
14456        // the accessor boundary and the [`Self::declared_supervisor_slots`]
14457        // presence probe would silently drift from the composition site.
14458        //
14459        // Peer of the sibling M2 supervisor-slot post-composition
14460        // `validate_reads_through_lifted_estrategia_accessor` (eafb619)
14461        // pin on the [`SupervisorSpec::validate`] altitude — this pin
14462        // extends that inner-altitude accessor-routing discipline onto
14463        // the pre-composition outer author-surface [`Caixa`] altitude,
14464        // pinning the composition edge between the flat-spread outer
14465        // `Option<RestartStrategy>` and the composed [`SupervisorSpec`]
14466        // `RestartStrategy` axes.
14467        use crate::CaixaKind;
14468        use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
14469        for estrategia in [
14470            RestartStrategy::OneForOne,
14471            RestartStrategy::OneForAll,
14472            RestartStrategy::RestForOne,
14473            RestartStrategy::SimpleOneForOne,
14474        ] {
14475            let mut c = caixa_with_estrategia(Some(estrategia));
14476            c.kind = CaixaKind::Supervisor;
14477            // Route the `SimpleOneForOne ↔ non-SimpleOneForOne` fixture-
14478            // shape partition through the [`gen_platform::IsVariant`]
14479            // derive-generated
14480            // [`RestartStrategy::is_simple_one_for_one`] predicate rather
14481            // than the raw `matches!(estrategia, RestartStrategy::
14482            // SimpleOneForOne)` open-coded pattern-match — same closed-
14483            // set-typed-enum arm-discriminator dispatch discipline the
14484            // sibling [`crate::upgrade::UpgradeInstruction::is_restart`]
14485            // convergence (915a934) extended onto its two paired positive
14486            // / negated `matches!` sites and the peer
14487            // [`crate::aplicacao::PlacementStrategy`] `IsVariant`-derived
14488            // predicate convergence (766ec63) extended onto the M3 mesh-
14489            // slot per-`:placement` distribution-strategy discriminator
14490            // axis. See the sibling `supervisor::tests::
14491            // round_trip_all_strategies` and
14492            // `supervisor::tests::supervisor_spec_estrategia_returns_estrategia_verbatim_across_permutations`
14493            // fixtures — the three sites (all test-only,
14494            // acknowledged in 915a934's Prior-commits footnote as the
14495            // outstanding follow-up) now consult one typed dispatch on
14496            // the substrate primitive.
14497            c.children = if estrategia.is_simple_one_for_one() {
14498                Vec::new()
14499            } else {
14500                vec![ChildSpec {
14501                    caixa: "worker".into(),
14502                    versao: "^0.1".into(),
14503                    restart: RestartPolicy::Permanent,
14504                }]
14505            };
14506            let view = c.supervisor_view().expect(
14507                "supervisor_view must materialize a SupervisorSpec for a \
14508                 :kind Supervisor Caixa carrying a Some(:estrategia) slot",
14509            );
14510            assert_eq!(
14511                view.estrategia(),
14512                c.estrategia().unwrap(),
14513                "supervisor_view must carry the outer Caixa::estrategia() \
14514                 declared variant onto the composed SupervisorSpec.estrategia \
14515                 field verbatim on the Some arm (got {:?}, expected {:?})",
14516                view.estrategia(),
14517                c.estrategia().unwrap(),
14518            );
14519        }
14520        // The author-omitted arm: outer `None` → composed
14521        // `RestartStrategy::default()` through the flat-spread
14522        // `unwrap_or_default()` fold.
14523        let mut c = caixa_with_estrategia(None);
14524        c.kind = CaixaKind::Supervisor;
14525        // Populate children so the sibling supervisor slots are coherent
14526        // for the [`Self::supervisor_view`] projection; the `:estrategia`
14527        // arm still defers to [`RestartStrategy::default`] on the
14528        // author-omitted arm even when the sibling slots carry values.
14529        c.children = vec![ChildSpec {
14530            caixa: "worker".into(),
14531            versao: "^0.1".into(),
14532            restart: RestartPolicy::Permanent,
14533        }];
14534        let view = c.supervisor_view().expect(
14535            "supervisor_view must materialize a SupervisorSpec for a \
14536             :kind Supervisor Caixa carrying a None `:estrategia` slot",
14537        );
14538        assert_eq!(
14539            view.estrategia(),
14540            RestartStrategy::default(),
14541            "supervisor_view must project the outer Caixa::estrategia() \
14542             None arm onto RestartStrategy::default() through the flat-\
14543             spread unwrap_or_default() fold (got {:?}, expected {:?})",
14544            view.estrategia(),
14545            RestartStrategy::default(),
14546        );
14547        assert!(
14548            c.estrategia().is_none(),
14549            "Caixa::estrategia() must remain None on the author-omitted \
14550             arm — the supervisor_view fold must not mutate the outer \
14551             flat-spread presence bit",
14552        );
14553    }
14554
14555    #[test]
14556    fn estrategia_projects_option_by_copy() {
14557        // The by-`Copy` pin: [`Caixa::estrategia`] returns
14558        // `Option<RestartStrategy>` by value (`RestartStrategy: Copy`) —
14559        // the accessor does not borrow `&self` past the call (no
14560        // lifetime on the return type), and calling the accessor twice
14561        // on the same [`Caixa`] must yield discriminant-equal values
14562        // (idempotent, no side effects on `&self`). Peer of the sibling
14563        // outer-`Caixa` `Option<&Composite>` by-borrow
14564        // `limits_projects_option_ref_by_borrow` (b2bd9d7) /
14565        // `behavior_projects_option_ref_by_borrow` (35d8b52) /
14566        // `politicas_projects_option_ref_by_borrow` (5d23d29) /
14567        // `placement_projects_option_ref_by_borrow` (4fb8074) /
14568        // `entrada_projects_option_ref_by_borrow` (e4128e4) by-borrow
14569        // pins on the outer-`Caixa` `Option<&Composite>`-return axes —
14570        // extended here to the outer-`Caixa` `Option<Copy>`-return
14571        // flat-spread axis. The `Copy` discipline replaces the pointer-
14572        // equality claim the by-borrow siblings pin (a fresh `Copy` of a
14573        // `Copy` discriminant is definitionally the same discriminant, so
14574        // the axis reduces to discriminant equality).
14575        //
14576        // Pins against a future silent detour that returned a fresh
14577        // `Option<&RestartStrategy>` (which would type-check but silently
14578        // introduce a borrow of `&self` past the call, collapsing the
14579        // load-bearing "no lifetime on the return type" `Copy` projection
14580        // the flat-spread axis's `Option<Copy>` shape carries), a stale-
14581        // read side effect that flipped the outer discriminant on
14582        // successive calls, or an axis-remap projection that returned a
14583        // different variant than the field storage.
14584        use crate::supervisor::RestartStrategy;
14585        for estrategia in [
14586            Some(RestartStrategy::OneForOne),
14587            Some(RestartStrategy::OneForAll),
14588            Some(RestartStrategy::RestForOne),
14589            Some(RestartStrategy::SimpleOneForOne),
14590        ] {
14591            let c = caixa_with_estrategia(estrategia);
14592            let first = c.estrategia();
14593            let second = c.estrategia();
14594            assert_eq!(
14595                first, second,
14596                "Caixa::estrategia must be idempotent — two successive \
14597                 calls on the same &self must return the same \
14598                 Option<RestartStrategy>",
14599            );
14600            assert_eq!(
14601                first, estrategia,
14602                "Caixa::estrategia must return :estrategia verbatim by \
14603                 Copy — got {first:?}, expected {estrategia:?}",
14604            );
14605        }
14606        let c = caixa_with_estrategia(None);
14607        assert!(
14608            c.estrategia().is_none(),
14609            "Caixa::estrategia must return None when :estrategia is \
14610             absent — the author-omitted arm must project through the \
14611             accessor's Option::None unchanged",
14612        );
14613    }
14614
14615    // ── Caixa::max_restarts / Caixa::restart_window —
14616    //    outer top-level M2 supervisor-tree-slot flat-spread accessors
14617    //    (Option<u32> / Option<&str>) folding on the ed04d3c
14618    //    Caixa::estrategia Option<Copy> sub-family ─────────────────────
14619
14620    fn caixa_with_max_restarts(max_restarts: Option<u32>) -> Caixa {
14621        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
14622        c.max_restarts = max_restarts;
14623        c
14624    }
14625
14626    fn caixa_supervisor_with_max_restarts_and_window(
14627        max_restarts: Option<u32>,
14628        restart_window: Option<&str>,
14629    ) -> Caixa {
14630        use crate::CaixaKind;
14631        use crate::supervisor::{ChildSpec, RestartPolicy};
14632        let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
14633        c.kind = CaixaKind::Supervisor;
14634        c.max_restarts = max_restarts;
14635        c.restart_window = restart_window.map(str::to_string);
14636        c.children = vec![ChildSpec {
14637            caixa: "worker".into(),
14638            versao: "^0.1".into(),
14639            restart: RestartPolicy::Permanent,
14640        }];
14641        c
14642    }
14643
14644    #[test]
14645    fn max_restarts_returns_max_restarts_option_verbatim_across_permutations() {
14646        // Value-shape pin: [`Caixa::max_restarts`] returns the
14647        // `:max-restarts` typed `Option<u32>` verbatim, `Copy`-projected
14648        // from the typed slot's own storage, byte-equal across the
14649        // author-omitted `None` arm (the "defer to the
14650        // [`Self::supervisor_view`] `unwrap_or(5)` OTP-canonical
14651        // `{intensity, 5, 60}` default" partition every
14652        // non-`Supervisor`-kind caixa carries by `#[serde(default)]`)
14653        // and each of the representative fixtures in the accept-set —
14654        // `0` (the zero-floor arm the peer
14655        // [`crate::supervisor::SupervisorSpec::validate`]
14656        // [`crate::SupervisorError::ZeroMaxRestarts`] gate refuses on
14657        // the post-composition altitude — the accessor must ship the
14658        // raw slot verbatim so struct-literal fixtures continue to
14659        // expose the zero at the accessor boundary), the OTP-canonical
14660        // `5` default (`{intensity, 5, 60}` worker-supervisor from
14661        // Learn You Some Erlang), `1000` (the
14662        // [`SUPERVISOR_MAX_RESTARTS_MAX`] cap the peer post-composition
14663        // upper-bound gate accepts on the boundary), `u32::MAX` (a
14664        // past-the-cap sentinel that the substrate-primitive accessor
14665        // must still ship verbatim). Second outer top-level
14666        // [`Caixa`] `Option<Copy>`-return supervisor-tree flat-spread
14667        // pin — folds on the sibling
14668        // `estrategia_returns_estrategia_option_verbatim_across_permutations`
14669        // (ed04d3c) pin's `Option<Copy>` shape, extending the sub-family
14670        // onto the sibling `Option<u32>` restart-budget-count arm.
14671        let fixtures: Vec<Option<u32>> = vec![None, Some(0), Some(5), Some(1000), Some(u32::MAX)];
14672        for max_restarts in fixtures {
14673            let c = caixa_with_max_restarts(max_restarts);
14674            assert_eq!(
14675                c.max_restarts(),
14676                max_restarts,
14677                "Caixa::max_restarts must return :max-restarts verbatim \
14678                 (got {:?}, expected {max_restarts:?})",
14679                c.max_restarts(),
14680            );
14681            assert_eq!(
14682                c.max_restarts(),
14683                c.max_restarts,
14684                "Caixa::max_restarts accessor and self.max_restarts \
14685                 field access must byte-equal — a presence-bit or count \
14686                 drift would silently split the paired \
14687                 Caixa::declared_supervisor_slots presence-probe arm \
14688                 from the Caixa::supervisor_view unwrap_or(5) fold's \
14689                 composition input",
14690            );
14691        }
14692    }
14693
14694    #[test]
14695    fn max_restarts_projects_option_by_copy() {
14696        // The by-`Copy` pin: [`Caixa::max_restarts`] returns
14697        // `Option<u32>` by value (`u32: Copy`) — the accessor does not
14698        // borrow `&self` past the call (no lifetime on the return type),
14699        // and calling the accessor twice on the same [`Caixa`] must
14700        // yield equal values (idempotent, no side effects). Peer of the
14701        // sibling `estrategia_projects_option_by_copy` (ed04d3c) pin on
14702        // the outer-`Caixa` `Option<Copy>`-return flat-spread axis.
14703        for max_restarts in [Some(0u32), Some(5), Some(1000), Some(u32::MAX), None] {
14704            let c = caixa_with_max_restarts(max_restarts);
14705            let first = c.max_restarts();
14706            let second = c.max_restarts();
14707            assert_eq!(
14708                first, second,
14709                "Caixa::max_restarts must be idempotent — two successive \
14710                 calls on the same &self must return the same Option<u32>",
14711            );
14712            assert_eq!(
14713                first, max_restarts,
14714                "Caixa::max_restarts must return :max-restarts verbatim \
14715                 by Copy — got {first:?}, expected {max_restarts:?}",
14716            );
14717        }
14718    }
14719
14720    #[test]
14721    fn declared_supervisor_slots_max_restarts_arm_routes_through_accessor() {
14722        // Composition pin: [`Caixa::declared_supervisor_slots`]'s
14723        // `:max-restarts` presence-probe arm must key off
14724        // [`Caixa::max_restarts`], not the raw
14725        // `self.max_restarts.is_some()` field-probe. Structurally: every
14726        // `Caixa { max_restarts: Some(_), .. }` variant must push
14727        // `SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS` onto the declared-slot
14728        // list (the presence bit is `Some` for every representative
14729        // count, so the M2 kind-coherence gate must surface the slot as
14730        // "declared"), and a `Caixa { max_restarts: None, .. }` must
14731        // NOT push the label. Peer of the sibling
14732        // `declared_supervisor_slots_estrategia_arm_routes_through_accessor`
14733        // (ed04d3c) composition pin — same routing-through-accessor
14734        // discipline extended onto the sibling flat-spread `Option<u32>`
14735        // arm.
14736        for max_restarts in [0u32, 5, 1000, u32::MAX] {
14737            let c = caixa_with_max_restarts(Some(max_restarts));
14738            let slots = c.declared_supervisor_slots();
14739            assert!(
14740                slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS),
14741                "declared_supervisor_slots must push \
14742                 SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS when `:max-restarts` \
14743                 is Some({max_restarts}) — the accessor and the \
14744                 enumerator gate must route through the same \
14745                 substrate-primitive typed dispatch on the outer \
14746                 :max-restarts presence bit (got slots={slots:?})",
14747            );
14748        }
14749        let c = caixa_with_max_restarts(None);
14750        let slots = c.declared_supervisor_slots();
14751        assert!(
14752            !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS),
14753            "declared_supervisor_slots must NOT push \
14754             SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS when `:max-restarts` is \
14755             None — the author-omitted arm must route through the \
14756             accessor's None-return unchanged (got slots={slots:?})",
14757        );
14758    }
14759
14760    #[test]
14761    fn supervisor_view_max_restarts_arm_routes_through_accessor() {
14762        // Composition pin: [`Caixa::supervisor_view`]'s per-`:max-restarts`
14763        // [`SupervisorSpec`] construction arm must key off
14764        // [`Caixa::max_restarts`]'s `unwrap_or(5)` fold, not the raw
14765        // `self.max_restarts.unwrap_or(5)` field-fold. Structurally: for
14766        // every `:kind Supervisor` `Caixa` carrying an author-declared
14767        // `Some(n)`, the composed [`SupervisorSpec`]'s `.max_restarts()`
14768        // must byte-equal `n`; and for a `:kind Supervisor` `Caixa`
14769        // carrying `None`, the composed [`SupervisorSpec`]'s
14770        // `.max_restarts()` must byte-equal the OTP-canonical `5`. Peer
14771        // of the sibling
14772        // `supervisor_view_estrategia_arm_routes_through_accessor`
14773        // (ed04d3c) composition pin.
14774        for max_restarts in [1u32, 5, 1000] {
14775            let c = caixa_supervisor_with_max_restarts_and_window(Some(max_restarts), None);
14776            let view = c.supervisor_view().expect(
14777                "supervisor_view must materialize a SupervisorSpec for a \
14778                 :kind Supervisor Caixa carrying a Some(:max-restarts)",
14779            );
14780            assert_eq!(
14781                view.max_restarts(),
14782                max_restarts,
14783                "supervisor_view must carry the outer \
14784                 Caixa::max_restarts() Some arm onto the composed \
14785                 SupervisorSpec.max_restarts field verbatim (got {}, \
14786                 expected {max_restarts})",
14787                view.max_restarts(),
14788            );
14789        }
14790        let c = caixa_supervisor_with_max_restarts_and_window(None, None);
14791        let view = c.supervisor_view().expect(
14792            "supervisor_view must materialize a SupervisorSpec for a \
14793             :kind Supervisor Caixa carrying a None :max-restarts",
14794        );
14795        assert_eq!(
14796            view.max_restarts(),
14797            5,
14798            "supervisor_view must project the outer \
14799             Caixa::max_restarts() None arm onto the OTP-canonical \
14800             {{intensity, 5, 60}} default (5) through the flat-spread \
14801             unwrap_or(5) fold (got {})",
14802            view.max_restarts(),
14803        );
14804        assert!(
14805            c.max_restarts().is_none(),
14806            "Caixa::max_restarts() must remain None on the author-\
14807             omitted arm — the supervisor_view fold must not mutate \
14808             the outer flat-spread presence bit",
14809        );
14810    }
14811
14812    #[test]
14813    fn restart_window_returns_restart_window_option_verbatim_across_permutations() {
14814        // Value-shape pin: [`Caixa::restart_window`] returns the
14815        // `:restart-window` typed `Option<String>` verbatim as an
14816        // `Option<&str>`, borrowed from the typed slot's own storage,
14817        // byte-equal across the author-omitted `None` arm and each of
14818        // the representative fixtures in the accept-set — the canonical
14819        // `"60s"` from `{intensity, 5, 60}`, the sibling
14820        // canonical-magnitude forms (`"5m"` / `"1h"` / `"500ms"` / `"30"`
14821        // / `"0s"`) the shared codec's positive-set sweep pin covers,
14822        // plus a past-the-guard sentinel (`"1.5s"` — the fractional-
14823        // seconds drift the sibling [`Self::validate_restart_window`]
14824        // gate refuses; the accessor must ship the raw slot verbatim
14825        // so struct-literal fixtures continue to expose the drift at
14826        // the accessor boundary). Third outer top-level [`Caixa`]
14827        // supervisor-tree flat-spread pin — extends the sub-family onto
14828        // the sibling `Option<&str>` raw-duration-string arm.
14829        for window in [
14830            None,
14831            Some("60s"),
14832            Some("5m"),
14833            Some("1h"),
14834            Some("500ms"),
14835            Some("1.5s"),
14836            Some(""),
14837        ] {
14838            let c = caixa_with_restart_window(window);
14839            assert_eq!(
14840                c.restart_window(),
14841                window,
14842                "Caixa::restart_window must return :restart-window \
14843                 verbatim as Option<&str> (got {:?}, expected {window:?})",
14844                c.restart_window(),
14845            );
14846            assert_eq!(
14847                c.restart_window(),
14848                c.restart_window.as_deref(),
14849                "Caixa::restart_window accessor and \
14850                 self.restart_window.as_deref() field access must \
14851                 byte-equal — a byte-level drift would silently split \
14852                 the paired Caixa::declared_supervisor_slots \
14853                 presence-probe arm from the \
14854                 Caixa::validate_restart_window shared-codec gate and \
14855                 the Caixa::supervisor_view soft-swallowing fold",
14856            );
14857        }
14858    }
14859
14860    #[test]
14861    fn restart_window_projects_slice_by_borrow() {
14862        // The by-borrow pin: [`Caixa::restart_window`] returns
14863        // `Option<&str>` by borrow — the returned string slice borrows
14864        // the underlying `Option<String>` storage of the `:restart-window`
14865        // slot and the accessor must not clone on every call. Peer of
14866        // the sibling outer top-level [`Caixa`] `Option<&str>`-return
14867        // by-borrow pins on the universal-axis scalar family
14868        // (`licenca_projects_option_ref_by_borrow` /
14869        // `descricao_projects_option_ref_by_borrow` and siblings) —
14870        // extended onto the M2 supervisor-tree flat-spread
14871        // `Option<&str>` raw-duration-string axis.
14872        for window in [None, Some("60s"), Some("5m"), Some("")] {
14873            let c = caixa_with_restart_window(window);
14874            let first = c.restart_window();
14875            let second = c.restart_window();
14876            assert_eq!(
14877                first, second,
14878                "Caixa::restart_window must be idempotent — two \
14879                 successive calls on the same &self must return the \
14880                 same Option<&str>",
14881            );
14882            if let (Some(a), Some(b)) = (first, second) {
14883                assert_eq!(
14884                    a.as_ptr(),
14885                    b.as_ptr(),
14886                    "Caixa::restart_window must borrow the underlying \
14887                     String storage — two successive Some-arm calls must \
14888                     return slices with the same backing pointer (a fresh \
14889                     String clone would change the pointer on every call)",
14890                );
14891            }
14892            assert_eq!(
14893                first, window,
14894                "Caixa::restart_window must return :restart-window \
14895                 verbatim by borrow — got {first:?}, expected {window:?}",
14896            );
14897        }
14898    }
14899
14900    #[test]
14901    fn declared_supervisor_slots_restart_window_arm_routes_through_accessor() {
14902        // Composition pin: [`Caixa::declared_supervisor_slots`]'s
14903        // `:restart-window` presence-probe arm must key off
14904        // [`Caixa::restart_window`], not the raw
14905        // `self.restart_window.is_some()` field-probe. Structurally:
14906        // every `Caixa { restart_window: Some(_), .. }` must push
14907        // `SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW` onto the declared-slot
14908        // list, and a `Caixa { restart_window: None, .. }` must NOT
14909        // push the label. Peer of the sibling
14910        // `declared_supervisor_slots_max_restarts_arm_routes_through_accessor`
14911        // routing pin.
14912        for window in ["60s", "5m", "1h", "500ms", "1.5s", ""] {
14913            let c = caixa_with_restart_window(Some(window));
14914            let slots = c.declared_supervisor_slots();
14915            assert!(
14916                slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW),
14917                "declared_supervisor_slots must push \
14918                 SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW when \
14919                 `:restart-window` is Some({window:?}) — the accessor \
14920                 and the enumerator gate must route through the same \
14921                 substrate-primitive typed dispatch on the outer \
14922                 :restart-window presence bit (got slots={slots:?})",
14923            );
14924        }
14925        let c = caixa_with_restart_window(None);
14926        let slots = c.declared_supervisor_slots();
14927        assert!(
14928            !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW),
14929            "declared_supervisor_slots must NOT push \
14930             SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW when `:restart-window` \
14931             is None — the author-omitted arm must route through the \
14932             accessor's None-return unchanged (got slots={slots:?})",
14933        );
14934    }
14935
14936    #[test]
14937    fn validate_restart_window_arm_routes_through_accessor() {
14938        // Composition pin: [`Caixa::validate_restart_window`]'s
14939        // shared-codec fold arm must key off [`Caixa::restart_window`],
14940        // not the raw `self.restart_window.as_deref()` field-projection.
14941        // Structurally: (1) `None` → `Ok(())` (the "omit the slot to
14942        // express no reset" canonical shape); (2) a canonical `Some`
14943        // arm (`"60s"`) → `Ok(())`; (3) a codec-rejected `Some` arm
14944        // (`"1.5s"`) → `Err(RestartWindowMalformed { restart_window,
14945        // .. })` carrying the offending raw string verbatim. The three
14946        // arms jointly pin that the validator's raw-string binding is
14947        // the accessor's return, not a peer projection — any future
14948        // silent detour that had the accessor collapse `Some("")` to
14949        // `None` would silently absorb the empty-after-trim refusal
14950        // case at the accessor boundary.
14951        caixa_with_restart_window(None)
14952            .validate_restart_window()
14953            .expect("None :restart-window must validate through the accessor");
14954        caixa_with_restart_window(Some("60s"))
14955            .validate_restart_window()
14956            .expect("canonical :restart-window \"60s\" must validate through the accessor");
14957        let err = caixa_with_restart_window(Some("1.5s"))
14958            .validate_restart_window()
14959            .expect_err("fractional-seconds :restart-window must fail through the accessor");
14960        assert!(
14961            matches!(
14962                err,
14963                ManifestError::RestartWindowMalformed { ref restart_window, .. }
14964                    if restart_window == "1.5s"
14965            ),
14966            "validator must carry the offending raw string verbatim \
14967             from the accessor's borrowed &str (got {err:?})",
14968        );
14969    }
14970
14971    #[test]
14972    fn supervisor_view_restart_window_arm_routes_through_accessor() {
14973        // Composition pin: [`Caixa::supervisor_view`]'s
14974        // per-`:restart-window` [`SupervisorSpec`] construction arm
14975        // must key off [`Caixa::restart_window`]'s soft-swallowing
14976        // `.and_then(|s| duration_codec::parse(s).ok())` fold, not the
14977        // raw `self.restart_window.as_deref().and_then(…)` field-fold.
14978        // Structurally: (1) `None` → `SupervisorSpec.restart_window ==
14979        // None` (the "never reset" sentinel); (2) canonical `Some("60s")`
14980        // → `SupervisorSpec.restart_window == Some(Duration::from_secs(60))`
14981        // (the shared codec's canonical parse); (3) codec-rejected
14982        // `Some("1.5s")` → `SupervisorSpec.restart_window == None`
14983        // (the soft-swallow preserving the view's best-effort shape).
14984        let c = caixa_supervisor_with_max_restarts_and_window(None, None);
14985        let view = c.supervisor_view().expect("Supervisor kind has a view");
14986        assert_eq!(
14987            view.restart_window(),
14988            None,
14989            "supervisor_view must project outer None :restart-window \
14990             onto None on the composed SupervisorSpec (never-reset \
14991             sentinel) through the accessor's None-return unchanged",
14992        );
14993
14994        let c = caixa_supervisor_with_max_restarts_and_window(None, Some("60s"));
14995        let view = c.supervisor_view().expect("Supervisor kind has a view");
14996        assert_eq!(
14997            view.restart_window(),
14998            Some(std::time::Duration::from_secs(60)),
14999            "supervisor_view must fold outer Some(\"60s\") through the \
15000             shared duration_codec into Duration::from_secs(60) on the \
15001             composed SupervisorSpec (accessor's Some(&str) → codec \
15002             parse → Some(Duration))",
15003        );
15004
15005        let c = caixa_supervisor_with_max_restarts_and_window(None, Some("1.5s"));
15006        let view = c.supervisor_view().expect("Supervisor kind has a view");
15007        assert_eq!(
15008            view.restart_window(),
15009            None,
15010            "supervisor_view must soft-swallow the shared-codec parse \
15011             failure to None (the view's best-effort shape the sibling \
15012             manifest-level validate_restart_window surfaces as \
15013             RestartWindowMalformed); the accessor's raw-string return \
15014             is the single input every downstream consumer keys off",
15015        );
15016    }
15017
15018    // ── Caixa::upgrade_from — outer top-level &[UpgradeFromEntry] composite-slice accessor ──
15019
15020    fn caixa_with_upgrade_from(upgrade_from: Vec<crate::upgrade::UpgradeFromEntry>) -> Caixa {
15021        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15022        c.upgrade_from = upgrade_from;
15023        c
15024    }
15025
15026    #[test]
15027    fn upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations() {
15028        // The canonical per-`Caixa` `:upgrade-from` M2 typed-slot
15029        // outer-composite `&[UpgradeFromEntry]`-return slice-shape
15030        // pin: [`Caixa::upgrade_from`] must return the `:upgrade-from`
15031        // typed `Vec<UpgradeFromEntry>` verbatim as a
15032        // `&[UpgradeFromEntry]` slice-view over the same backing
15033        // buffer the raw `self.upgrade_from.as_slice()` field access
15034        // borrows from, element-equal across every representative
15035        // fixture in the accept-set — `[]` (the "no hot-upgrade path
15036        // declared" arm every `defcaixa` without an `:upgrade-from`
15037        // block carries; `#[serde(default)]` folds an omitted slot
15038        // onto `Vec::new()`), a canonical single-entry `Restart`
15039        // fixture (the shape most Servicos carry — a single prior
15040        // version with the fallback strategy), a canonical multi-
15041        // entry list carrying every typed instruction variant
15042        // (`LoadModule` / `StateChange` / `SoftPurge` / `Purge` /
15043        // `Restart`), and a past-the-guard sentinel — a duplicate-
15044        // `:from` `[(0.1.0, Restart), (0.1.0, Restart)]` entry pair
15045        // ([`crate::upgrade::validate_upgrade_from`] rejects through
15046        // `DuplicateFrom { from: "0.1.0" }` but the accessor must
15047        // ship the raw slot verbatim so struct-literal fixtures
15048        // continue to expose the duplicate at the accessor boundary).
15049        //
15050        // Pins against a future silent detour that returned an owned
15051        // `Vec<UpgradeFromEntry>` (which would type-check but silently
15052        // clone on every accessor call, breaking the zero-cost
15053        // projection every peer sibling slice accessor carries), a
15054        // `[dup, dup] → [dup]` dedup collapse (which would silently
15055        // absorb the `DuplicateFrom` refusal case at the accessor
15056        // boundary and the [`crate::StandardLayout::verify`] cross-
15057        // entry gate would silently accept a struct-literal `Caixa`
15058        // carrying the drift), a reference to an operator-resolved
15059        // overlay (the future per-cluster `:upgrade-overrides` slot
15060        // — its resolution must land at exactly this accessor body,
15061        // not silently divert the raw slot away from a second
15062        // consumer), or an axis-shuffled projection (a future detour
15063        // that reordered entries through the accessor would silently
15064        // split the paired [`crate::StandardLayout::verify`] per-
15065        // `:upgrade-from` shape gate's traversal input from the peer
15066        // [`crate::render::servico_m2_overlay`] emitter's projection
15067        // input, since the operator's hot-upgrade dispatch matches
15068        // per-`:from` and axis reordering would silently split the
15069        // per-entry script-path existence probe's iteration order
15070        // from the M2 overlay emitter's serialized-entry order).
15071        //
15072        // First outer top-level [`Caixa`] `&[Composite]`-return
15073        // slice accessor pin on the substrate primitive for M2 / M3
15074        // typed-slot vec-carry axes — opens the outer-`Caixa`
15075        // `&[Composite]` composite-slice projection pattern the
15076        // sibling `:children` [`crate::supervisor::ChildSpec`] /
15077        // `:membros` [`crate::aplicacao::Membro`] / `:contratos`
15078        // [`crate::aplicacao::WitContract`] future outer-composite-
15079        // slice pins fold on. Peer of the closed outer-`Caixa`
15080        // scalar `Option<&Composite>` composite-reference family the
15081        // sibling `limits` / `behavior` / `politicas` / `placement`
15082        // / `entrada` `..._returns_..._option_ref_verbatim_across_
15083        // permutations` pins closed (b2bd9d7 → e4128e4) — extends
15084        // the "byte-equal, borrow-shared" outer-accessor discipline
15085        // onto the outer-`Caixa` `&[Composite]` vec-carry altitude.
15086        use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
15087        let fixtures: Vec<Vec<UpgradeFromEntry>> = vec![
15088            vec![],
15089            vec![UpgradeFromEntry {
15090                from: "0.0.1".into(),
15091                instructions: vec![UpgradeInstruction::Restart],
15092            }],
15093            vec![
15094                UpgradeFromEntry {
15095                    from: "0.0.1".into(),
15096                    instructions: vec![
15097                        UpgradeInstruction::LoadModule {
15098                            module: "demo".into(),
15099                        },
15100                        UpgradeInstruction::SoftPurge {
15101                            module: "demo".into(),
15102                        },
15103                    ],
15104                },
15105                UpgradeFromEntry {
15106                    from: "0.0.2".into(),
15107                    instructions: vec![
15108                        UpgradeInstruction::StateChange {
15109                            script: "servicos/upgrade.lisp".into(),
15110                        },
15111                        UpgradeInstruction::Purge {
15112                            module: "demo".into(),
15113                        },
15114                        UpgradeInstruction::Restart,
15115                    ],
15116                },
15117            ],
15118            vec![
15119                UpgradeFromEntry {
15120                    from: "0.1.0".into(),
15121                    instructions: vec![UpgradeInstruction::Restart],
15122                },
15123                UpgradeFromEntry {
15124                    from: "0.1.0".into(),
15125                    instructions: vec![UpgradeInstruction::Restart],
15126                },
15127            ],
15128        ];
15129        for upgrade_from in fixtures {
15130            let c = caixa_with_upgrade_from(upgrade_from.clone());
15131            assert_eq!(
15132                c.upgrade_from(),
15133                upgrade_from.as_slice(),
15134                "Caixa::upgrade_from must return :upgrade-from \
15135                 verbatim (got {:?}, expected {upgrade_from:?})",
15136                c.upgrade_from(),
15137            );
15138            assert_eq!(
15139                c.upgrade_from(),
15140                c.upgrade_from.as_slice(),
15141                "Caixa::upgrade_from must element-equal the raw \
15142                 `self.upgrade_from.as_slice()` field access across \
15143                 every value in the Vec<UpgradeFromEntry> accept-set",
15144            );
15145            assert_eq!(
15146                c.upgrade_from().is_empty(),
15147                c.upgrade_from.is_empty(),
15148                "Caixa::upgrade_from().is_empty() must byte-equal \
15149                 self.upgrade_from.is_empty() — a presence-bit drift \
15150                 would silently split the paired \
15151                 Caixa::declared_servico_slots M2 declared-slot \
15152                 enumerator's presence probe from the peer \
15153                 crate::render::servico_m2_overlay M2 overlay \
15154                 emitter's presence gate",
15155            );
15156        }
15157    }
15158
15159    #[test]
15160    fn declared_servico_slots_upgrade_from_arm_routes_through_accessor() {
15161        // Composition pin: [`Caixa::declared_servico_slots`]'s
15162        // `:upgrade-from` presence-probe arm must key off
15163        // [`Caixa::upgrade_from`], not the raw
15164        // `self.upgrade_from.is_empty()` field-probe. Structurally: a
15165        // `Caixa { upgrade_from: vec![UpgradeFromEntry { from: "0.0.1",
15166        // instructions: vec![Restart] }], .. }` must push
15167        // `M2_AUTHOR_KEY_UPGRADE_FROM` onto the declared-slot list
15168        // (the presence bit is non-empty, so the M2 kind-coherence
15169        // gate must surface the slot as "declared"), and a `Caixa {
15170        // upgrade_from: vec![], .. }` must NOT push the label (the
15171        // "author omitted the slot entirely" arm — the empty-slice
15172        // partition the serde-default folds onto). The pair jointly
15173        // pins the accessor + declared-slot enumerator composition:
15174        // any future silent detour that had the accessor collapse
15175        // `[Restart]` to `[]` (a `.filter(|e| !e.instructions.
15176        // is_empty())` projection) would silently absorb the
15177        // "declared but degenerate" arm at the accessor boundary and
15178        // the [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-
15179        // coherence gate would silently accept a struct-literal
15180        // `Caixa` carrying the drift.
15181        //
15182        // Peer of the sibling
15183        // `declared_servico_slots_limits_arm_routes_through_accessor`
15184        // (b2bd9d7) and
15185        // `declared_servico_slots_behavior_arm_routes_through_accessor`
15186        // (35d8b52) composition pins on the sibling `:limits` /
15187        // `:behavior` outer-`Option<&Composite>` arms — same "the
15188        // enumerator gate must route through the substrate-primitive
15189        // typed dispatch" discipline extended onto the third M2
15190        // Servico-runtime slot axis, closing the enumerator's routing
15191        // invariant on every M2 arm.
15192        use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
15193        let c = caixa_with_upgrade_from(vec![UpgradeFromEntry {
15194            from: "0.0.1".into(),
15195            instructions: vec![UpgradeInstruction::Restart],
15196        }]);
15197        let slots = c.declared_servico_slots();
15198        assert!(
15199            slots.contains(&crate::render::M2_AUTHOR_KEY_UPGRADE_FROM),
15200            "declared_servico_slots must push \
15201             M2_AUTHOR_KEY_UPGRADE_FROM when `:upgrade-from` is \
15202             non-empty — the accessor and the enumerator gate must \
15203             route through the same substrate-primitive typed \
15204             dispatch on the outer :upgrade-from presence bit (got \
15205             slots={slots:?})",
15206        );
15207        let c = caixa_with_upgrade_from(vec![]);
15208        let slots = c.declared_servico_slots();
15209        assert!(
15210            !slots.contains(&crate::render::M2_AUTHOR_KEY_UPGRADE_FROM),
15211            "declared_servico_slots must NOT push \
15212             M2_AUTHOR_KEY_UPGRADE_FROM when `:upgrade-from` is \
15213             empty — the author-omitted arm must route through the \
15214             accessor's empty-slice return unchanged (got \
15215             slots={slots:?})",
15216        );
15217    }
15218
15219    #[test]
15220    fn servico_m2_overlay_upgrade_from_arm_routes_through_accessor() {
15221        // Composition pin: [`crate::render::servico_m2_overlay`]'s
15222        // per-`:upgrade-from` M2 overlay emit arm must key off
15223        // [`Caixa::upgrade_from`], not the raw
15224        // `!caixa.upgrade_from.is_empty()` presence gate + the
15225        // `serde_yaml::to_value(&caixa.upgrade_from)` projection.
15226        // Structurally: a `Caixa { upgrade_from: vec![UpgradeFromEntry
15227        // { from: "0.0.1", instructions: vec![Restart] }], .. }` must
15228        // surface the `M2_KEY_UPGRADE_FROM` key with a per-entry
15229        // sequence in the overlay (the emitter fans onto the serde
15230        // slice-serialization), and a `Caixa { upgrade_from: vec![],
15231        // .. }` must omit the key entirely (the empty-slice
15232        // partition — the `!.is_empty()` outer gate elides the key
15233        // when the author omitted the slot). The pair jointly pins
15234        // the accessor + M2 overlay emitter composition: any future
15235        // silent detour that had the accessor return a fresh-cloned
15236        // `Vec<UpgradeFromEntry>` copy would silently break the
15237        // reference-identity pin the peer per-entry
15238        // `serde_yaml::to_value(caixa.upgrade_from())` projection
15239        // reads from — the projection would clone once per accessor
15240        // call instead of borrowing the storage buffer verbatim.
15241        //
15242        // Peer of the sibling
15243        // `servico_m2_overlay_limits_arm_routes_through_accessor`
15244        // (b2bd9d7) and
15245        // `servico_m2_overlay_behavior_arm_routes_through_accessor`
15246        // (35d8b52) composition pins on the sibling `:limits` /
15247        // `:behavior` outer-`Option<&Composite>` arms — same "the
15248        // M2 overlay emitter must route through the substrate-
15249        // primitive typed dispatch" discipline extended onto the
15250        // third M2 Servico-runtime slot axis, closing the overlay
15251        // emitter's routing invariant on every M2 arm.
15252        use crate::render::{M2_KEY_UPGRADE_FROM, servico_m2_overlay};
15253        use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
15254        let c = caixa_with_upgrade_from(vec![UpgradeFromEntry {
15255            from: "0.0.1".into(),
15256            instructions: vec![UpgradeInstruction::Restart],
15257        }]);
15258        let overlay = servico_m2_overlay(&c).unwrap();
15259        assert!(
15260            overlay.contains_key(M2_KEY_UPGRADE_FROM),
15261            "servico_m2_overlay must surface M2_KEY_UPGRADE_FROM when \
15262             `:upgrade-from` is non-empty — the accessor and the M2 \
15263             overlay emitter must route through the same substrate- \
15264             primitive typed dispatch on the outer :upgrade-from \
15265             slice (got overlay={overlay:?})",
15266        );
15267        let c = caixa_with_upgrade_from(vec![]);
15268        let overlay = servico_m2_overlay(&c).unwrap();
15269        assert!(
15270            !overlay.contains_key(M2_KEY_UPGRADE_FROM),
15271            "servico_m2_overlay must omit M2_KEY_UPGRADE_FROM when \
15272             `:upgrade-from` is empty — the empty-slice partition \
15273             must route through the accessor's empty-slice return \
15274             unchanged (got overlay={overlay:?})",
15275        );
15276    }
15277
15278    #[test]
15279    fn upgrade_from_projects_slice_by_borrow() {
15280        // The by-borrow pin: [`Caixa::upgrade_from`] returns
15281        // `&[UpgradeFromEntry]` by borrow — the returned slice
15282        // borrows the underlying `Vec<UpgradeFromEntry>` storage of
15283        // the `:upgrade-from` slot and the accessor must not clone
15284        // the backing `Vec` on every call. Peer of the sibling
15285        // outer top-level [`Caixa`] `&[T]`-return by-borrow pins
15286        // (`autores_projects_slice_by_borrow` b5d813f,
15287        // `etiquetas_projects_slice_by_borrow` 78c7d3c,
15288        // `bibliotecas_projects_slice_by_borrow` 8a36c23,
15289        // `exe_projects_slice_by_borrow` 65d9527,
15290        // `servicos_projects_slice_by_borrow` 611f78b,
15291        // `deps_projects_slice_by_borrow` ad34b4e,
15292        // `deps_dev_projects_slice_by_borrow` f7fd81e) on the
15293        // sibling outer top-level [`Caixa`] scalar-element `&[T]`
15294        // axes — extended here to the first outer-`Caixa`
15295        // composite-element `&[Composite]` axis: the accessor's
15296        // returned slice must borrow from `&self` (the returned
15297        // reference's lifetime is tied to `&self`), and calling the
15298        // accessor twice on the same [`Caixa`] must yield slices
15299        // that are pointer-equal (the underlying byte-buffer is the
15300        // storage `Vec`'s allocation, not a fresh copy) as well as
15301        // value-equal (idempotent, no side effects on `&self`).
15302        //
15303        // Pins against a future silent detour that returned an owned
15304        // `Vec<UpgradeFromEntry>` (which would type-check but
15305        // silently clone on every call), a `&Vec<UpgradeFromEntry>`
15306        // return (which would leak the backing `Vec`'s
15307        // grow/push/reserve surface no downstream consumer reaches
15308        // for), or a one-arm-only accessor that returned a
15309        // saturating value on some sentinel input.
15310        use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
15311        for upgrade_from in [
15312            vec![],
15313            vec![UpgradeFromEntry {
15314                from: "0.0.1".into(),
15315                instructions: vec![UpgradeInstruction::Restart],
15316            }],
15317            vec![
15318                UpgradeFromEntry {
15319                    from: "0.0.1".into(),
15320                    instructions: vec![UpgradeInstruction::Restart],
15321                },
15322                UpgradeFromEntry {
15323                    from: "0.0.2".into(),
15324                    instructions: vec![UpgradeInstruction::SoftPurge {
15325                        module: "demo".into(),
15326                    }],
15327                },
15328            ],
15329        ] {
15330            let c = caixa_with_upgrade_from(upgrade_from.clone());
15331            let first = c.upgrade_from();
15332            let second = c.upgrade_from();
15333            assert_eq!(
15334                first, second,
15335                "Caixa::upgrade_from must be idempotent — two \
15336                 successive calls on the same &self must return the \
15337                 same &[UpgradeFromEntry]",
15338            );
15339            assert_eq!(
15340                first.as_ptr(),
15341                second.as_ptr(),
15342                "Caixa::upgrade_from must borrow the underlying \
15343                 Vec<UpgradeFromEntry> storage — two successive calls \
15344                 must return slices with the same backing pointer (a \
15345                 fresh Vec<UpgradeFromEntry> clone would change the \
15346                 pointer on every call)",
15347            );
15348            assert_eq!(
15349                first,
15350                upgrade_from.as_slice(),
15351                "Caixa::upgrade_from must return :upgrade-from \
15352                 verbatim by borrow — got {first:?}, expected \
15353                 {upgrade_from:?}",
15354            );
15355        }
15356    }
15357
15358    // ── Caixa::children — outer top-level &[ChildSpec] composite-slice accessor ──
15359
15360    fn caixa_with_children(children: Vec<crate::supervisor::ChildSpec>) -> Caixa {
15361        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15362        c.children = children;
15363        c
15364    }
15365
15366    #[test]
15367    fn children_returns_children_slice_verbatim_across_permutations() {
15368        // The canonical per-`Caixa` `:children` M2 supervisor-tree-slot
15369        // outer-composite `&[ChildSpec]`-return slice-shape pin:
15370        // [`Caixa::children`] must return the `:children` typed
15371        // `Vec<ChildSpec>` verbatim as a `&[ChildSpec]` slice-view over
15372        // the same backing buffer the raw `self.children.as_slice()`
15373        // field access borrows from, element-equal across every
15374        // representative fixture in the accept-set — `[]` (the "no
15375        // static children declared" arm every non-`Supervisor`-kind
15376        // `defcaixa` carries by `#[serde(default)]` and every
15377        // `SimpleOneForOne` supervisor carries by cross-slot refusal),
15378        // a canonical single-child `Permanent` fixture (the shape
15379        // most `OneForOne` supervisors carry — a single long-running
15380        // worker child), a canonical multi-child list carrying every
15381        // typed restart-policy variant (`Permanent` / `Transient` /
15382        // `Temporary`), and a past-the-guard sentinel — a duplicate
15383        // `:caixa` `[("w", ...), ("w", ...)]` entry pair
15384        // ([`crate::SupervisorSpec::validate`] rejects through
15385        // `DuplicateChildNome { nome: "w" }` but the accessor must
15386        // ship the raw slot verbatim so struct-literal fixtures
15387        // continue to expose the duplicate at the accessor boundary).
15388        //
15389        // Pins against a future silent detour that returned an owned
15390        // `Vec<ChildSpec>` (which would type-check but silently clone
15391        // on every accessor call, breaking the zero-cost projection
15392        // every peer sibling slice accessor carries), a `[dup, dup] →
15393        // [dup]` dedup collapse (which would silently absorb the
15394        // `DuplicateChildNome` refusal case at the accessor boundary
15395        // and the [`crate::StandardLayout::verify`] cross-child gate
15396        // would silently accept a struct-literal `Caixa` carrying the
15397        // drift), a reference to an operator-resolved overlay (the
15398        // future per-cluster `:children-overrides` slot — its
15399        // resolution must land at exactly this accessor body, not
15400        // silently divert the raw slot away from a second consumer),
15401        // or an axis-shuffled projection (a future detour that
15402        // reordered children through the accessor would silently
15403        // split the paired [`crate::StandardLayout::verify`] per-
15404        // supervisor gate's traversal input from the peer
15405        // [`Self::supervisor_view`] fold-in path's clone-order input,
15406        // since the OTP `RestForOne` restart strategy dispatches on
15407        // declared child order and axis reordering would silently
15408        // split the operator's per-cluster restart-fan-out order
15409        // from the caixa.lisp source-order).
15410        //
15411        // Second outer top-level [`Caixa`] `&[Composite]`-return slice
15412        // accessor pin on the substrate primitive for M2 / M3 typed-
15413        // slot vec-carry axes — folds on the outer-`Caixa`
15414        // `&[Composite]` composite-slice sub-family the sibling
15415        // `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
15416        // (2a1f907) pin opened, peer at the outer altitude of the
15417        // closed inner-`SupervisorSpec` `SupervisorSpec::children`
15418        // (bc92bce) accessor on the same OTP-supervisor static-child-
15419        // list axis.
15420        use crate::supervisor::{ChildSpec, RestartPolicy};
15421        let fixtures: Vec<Vec<ChildSpec>> = vec![
15422            vec![],
15423            vec![ChildSpec {
15424                caixa: "worker".into(),
15425                versao: "^0.1".into(),
15426                restart: RestartPolicy::Permanent,
15427            }],
15428            vec![
15429                ChildSpec {
15430                    caixa: "worker-a".into(),
15431                    versao: "^0.1".into(),
15432                    restart: RestartPolicy::Permanent,
15433                },
15434                ChildSpec {
15435                    caixa: "worker-b".into(),
15436                    versao: "^0.1".into(),
15437                    restart: RestartPolicy::Transient,
15438                },
15439                ChildSpec {
15440                    caixa: "worker-c".into(),
15441                    versao: "^0.1".into(),
15442                    restart: RestartPolicy::Temporary,
15443                },
15444            ],
15445            vec![
15446                ChildSpec {
15447                    caixa: "w".into(),
15448                    versao: "^0.1".into(),
15449                    restart: RestartPolicy::Permanent,
15450                },
15451                ChildSpec {
15452                    caixa: "w".into(),
15453                    versao: "^0.1".into(),
15454                    restart: RestartPolicy::Permanent,
15455                },
15456            ],
15457        ];
15458        for children in fixtures {
15459            let c = caixa_with_children(children.clone());
15460            assert_eq!(
15461                c.children(),
15462                children.as_slice(),
15463                "Caixa::children must return :children verbatim \
15464                 (got {:?}, expected {children:?})",
15465                c.children(),
15466            );
15467            assert_eq!(
15468                c.children(),
15469                c.children.as_slice(),
15470                "Caixa::children must element-equal the raw \
15471                 `self.children.as_slice()` field access across \
15472                 every value in the Vec<ChildSpec> accept-set",
15473            );
15474            assert_eq!(
15475                c.children().is_empty(),
15476                c.children.is_empty(),
15477                "Caixa::children().is_empty() must byte-equal \
15478                 self.children.is_empty() — a presence-bit drift \
15479                 would silently split the paired \
15480                 Caixa::declared_supervisor_slots supervisor-tree \
15481                 declared-slot enumerator's presence probe from the \
15482                 peer Caixa::supervisor_view typed-view composer's \
15483                 fold-in path",
15484            );
15485        }
15486    }
15487
15488    #[test]
15489    fn declared_supervisor_slots_children_arm_routes_through_accessor() {
15490        // Composition pin: [`Caixa::declared_supervisor_slots`]'s
15491        // `:children` presence-probe arm must key off
15492        // [`Caixa::children`], not the raw
15493        // `!self.children.is_empty()` field-probe. Structurally: a
15494        // `Caixa { children: vec![ChildSpec { caixa: "w", versao:
15495        // "^0.1", restart: Permanent }], .. }` must push
15496        // `SUPERVISOR_AUTHOR_KEY_CHILDREN` onto the declared-slot list
15497        // (the presence bit is non-empty, so the supervisor-tree
15498        // kind-coherence gate must surface the slot as "declared"),
15499        // and a `Caixa { children: vec![], .. }` must NOT push the
15500        // label (the "author omitted the slot entirely" arm — the
15501        // empty-slice partition the serde-default folds onto). The
15502        // pair jointly pins the accessor + declared-slot enumerator
15503        // composition: any future silent detour that had the accessor
15504        // collapse `[Permanent]` to `[]` (a `.filter(|c| c.nome() !=
15505        // "__reserved__")` projection) would silently absorb the
15506        // "declared but degenerate" arm at the accessor boundary and
15507        // the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
15508        // kind-coherence gate would silently accept a struct-literal
15509        // `Caixa` carrying the drift.
15510        //
15511        // Peer of the sibling
15512        // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
15513        // (2a1f907) on the M2 `:upgrade-from` composite-slice arm —
15514        // same "the enumerator gate must route through the substrate-
15515        // primitive typed dispatch" discipline extended onto the
15516        // supervisor-tree `:children` composite-slice arm.
15517        use crate::supervisor::{ChildSpec, RestartPolicy};
15518        let c = caixa_with_children(vec![ChildSpec {
15519            caixa: "w".into(),
15520            versao: "^0.1".into(),
15521            restart: RestartPolicy::Permanent,
15522        }]);
15523        let slots = c.declared_supervisor_slots();
15524        assert!(
15525            slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN),
15526            "declared_supervisor_slots must push \
15527             SUPERVISOR_AUTHOR_KEY_CHILDREN when `:children` is \
15528             non-empty — the accessor and the enumerator gate must \
15529             route through the same substrate-primitive typed \
15530             dispatch on the outer :children presence bit (got \
15531             slots={slots:?})",
15532        );
15533        let c = caixa_with_children(vec![]);
15534        let slots = c.declared_supervisor_slots();
15535        assert!(
15536            !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN),
15537            "declared_supervisor_slots must NOT push \
15538             SUPERVISOR_AUTHOR_KEY_CHILDREN when `:children` is \
15539             empty — the author-omitted arm must route through the \
15540             accessor's empty-slice return unchanged (got \
15541             slots={slots:?})",
15542        );
15543    }
15544
15545    #[test]
15546    fn supervisor_view_children_arm_routes_through_accessor() {
15547        // Composition pin: [`Caixa::supervisor_view`]'s per-`:children`
15548        // fold-in arm must key off [`Caixa::children`], not the raw
15549        // `self.children.clone()` field-clone. Structurally: a `Caixa {
15550        // kind: Supervisor, estrategia: Some(OneForOne), children:
15551        // vec![ChildSpec { caixa: "w", .. }], .. }` must fold the
15552        // per-child list through the accessor into the typed
15553        // [`SupervisorSpec`] view's `children` field verbatim — every
15554        // entry the accessor surfaces must land in the view's
15555        // `children` slot in the same order. The pair jointly pins the
15556        // accessor + view-composer composition: any future silent
15557        // detour that had the accessor return a fresh-cloned
15558        // `Vec<ChildSpec>` copy would silently break the reference-
15559        // identity pin the peer `supervisor_view` fold-in path reads
15560        // from — the fold would clone once more per accessor call
15561        // instead of borrowing the storage buffer verbatim once.
15562        //
15563        // Peer of the sibling
15564        // `supervisor_view_kind_gate_routes_through_accessor` (35d8b52-
15565        // family) composition pin on the peer kind-gate arm — same
15566        // "the view composer must route through the substrate-
15567        // primitive typed dispatch" discipline extended onto the
15568        // per-`:children` fold-in arm, closing the supervisor-view
15569        // composer's routing invariant on the composite-slice input.
15570        use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
15571        let mut c = caixa_with_children(vec![
15572            ChildSpec {
15573                caixa: "worker-a".into(),
15574                versao: "^0.1".into(),
15575                restart: RestartPolicy::Permanent,
15576            },
15577            ChildSpec {
15578                caixa: "worker-b".into(),
15579                versao: "^0.1".into(),
15580                restart: RestartPolicy::Transient,
15581            },
15582        ]);
15583        c.kind = crate::CaixaKind::Supervisor;
15584        c.estrategia = Some(RestartStrategy::OneForOne);
15585        let view = c
15586            .supervisor_view()
15587            .expect("Supervisor kind must produce a supervisor_view");
15588        assert_eq!(
15589            view.children(),
15590            c.children(),
15591            "supervisor_view must fold Caixa::children verbatim into \
15592             SupervisorSpec::children — the accessor and the view \
15593             composer must route through the same substrate-primitive \
15594             typed dispatch on the outer :children slice (got view \
15595             children={:?}, expected {:?})",
15596            view.children(),
15597            c.children(),
15598        );
15599    }
15600
15601    #[test]
15602    fn children_projects_slice_by_borrow() {
15603        // The by-borrow pin: [`Caixa::children`] returns
15604        // `&[ChildSpec]` by borrow — the returned slice borrows the
15605        // underlying `Vec<ChildSpec>` storage of the `:children` slot
15606        // and the accessor must not clone the backing `Vec` on every
15607        // call. Peer of the sibling outer top-level [`Caixa`]
15608        // `&[T]`-return by-borrow pins (`autores_projects_slice_by_borrow`
15609        // b5d813f, `etiquetas_projects_slice_by_borrow` 78c7d3c,
15610        // `bibliotecas_projects_slice_by_borrow` 8a36c23,
15611        // `exe_projects_slice_by_borrow` 65d9527,
15612        // `servicos_projects_slice_by_borrow` 611f78b,
15613        // `deps_projects_slice_by_borrow` ad34b4e,
15614        // `deps_dev_projects_slice_by_borrow` f7fd81e,
15615        // `upgrade_from_projects_slice_by_borrow` 2a1f907) on the
15616        // sibling outer top-level [`Caixa`] scalar-element and
15617        // composite-element `&[T]` axes — folds on the outer-`Caixa`
15618        // composite-element `&[Composite]` axis: the accessor's
15619        // returned slice must borrow from `&self` (the returned
15620        // reference's lifetime is tied to `&self`), and calling the
15621        // accessor twice on the same [`Caixa`] must yield slices
15622        // that are pointer-equal (the underlying byte-buffer is the
15623        // storage `Vec`'s allocation, not a fresh copy) as well as
15624        // value-equal (idempotent, no side effects on `&self`).
15625        //
15626        // Pins against a future silent detour that returned an owned
15627        // `Vec<ChildSpec>` (which would type-check but silently clone
15628        // on every call), a `&Vec<ChildSpec>` return (which would leak
15629        // the backing `Vec`'s grow/push/reserve surface no downstream
15630        // consumer reaches for), or a one-arm-only accessor that
15631        // returned a saturating value on some sentinel input.
15632        use crate::supervisor::{ChildSpec, RestartPolicy};
15633        for children in [
15634            vec![],
15635            vec![ChildSpec {
15636                caixa: "w".into(),
15637                versao: "^0.1".into(),
15638                restart: RestartPolicy::Permanent,
15639            }],
15640            vec![
15641                ChildSpec {
15642                    caixa: "worker-a".into(),
15643                    versao: "^0.1".into(),
15644                    restart: RestartPolicy::Permanent,
15645                },
15646                ChildSpec {
15647                    caixa: "worker-b".into(),
15648                    versao: "^0.1".into(),
15649                    restart: RestartPolicy::Transient,
15650                },
15651            ],
15652        ] {
15653            let c = caixa_with_children(children.clone());
15654            let first = c.children();
15655            let second = c.children();
15656            assert_eq!(
15657                first, second,
15658                "Caixa::children must be idempotent — two successive \
15659                 calls on the same &self must return the same \
15660                 &[ChildSpec]",
15661            );
15662            assert_eq!(
15663                first.as_ptr(),
15664                second.as_ptr(),
15665                "Caixa::children must borrow the underlying \
15666                 Vec<ChildSpec> storage — two successive calls must \
15667                 return slices with the same backing pointer (a fresh \
15668                 Vec<ChildSpec> clone would change the pointer on \
15669                 every call)",
15670            );
15671            assert_eq!(
15672                first,
15673                children.as_slice(),
15674                "Caixa::children must return :children verbatim by \
15675                 borrow — got {first:?}, expected {children:?}",
15676            );
15677        }
15678    }
15679
15680    // ── Caixa::membros — outer top-level &[Membro] composite-slice accessor ──
15681
15682    fn caixa_aplicacao_with_membros(membros: Vec<crate::aplicacao::Membro>) -> Caixa {
15683        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15684        c.kind = CaixaKind::Aplicacao;
15685        c.membros = membros;
15686        c
15687    }
15688
15689    #[test]
15690    fn membros_returns_membros_slice_verbatim_across_permutations() {
15691        // The canonical per-`Caixa` `:membros` M3 mesh-slot outer-
15692        // composite `&[Membro]`-return slice-shape pin:
15693        // [`Caixa::membros`] must return the `:membros` typed
15694        // `Vec<Membro>` verbatim as a `&[Membro]` slice-view over the
15695        // same backing buffer the raw `self.membros.as_slice()` field
15696        // access borrows from, element-equal across every
15697        // representative fixture in the accept-set — `[]` (the "no
15698        // members declared" arm every non-`Aplicacao`-kind `defcaixa`
15699        // carries by `#[serde(default)]` and every partially-authored
15700        // Aplicacao carries before the
15701        // [`crate::AplicacaoError::MembrosEmpty`] gate fires), a
15702        // canonical single-member fixture (the shape a minimal
15703        // Aplicacao carries — one Servico wrapping one contained
15704        // computation), a canonical multi-member list carrying three
15705        // distinct entries (the canonical checkout-shape Aplicacao —
15706        // cart / pricing / auth — every canonical example carries), and
15707        // a past-the-guard sentinel — a duplicate `:caixa`
15708        // `[("cart", ...), ("cart", ...)]` entry pair
15709        // ([`crate::AplicacaoSpec::validate`] rejects through
15710        // `DuplicateMembro { nome: "cart" }` but the accessor must ship
15711        // the raw slot verbatim so struct-literal fixtures continue to
15712        // expose the duplicate at the accessor boundary).
15713        //
15714        // Pins against a future silent detour that returned an owned
15715        // `Vec<Membro>` (which would type-check but silently clone on
15716        // every accessor call, breaking the zero-cost projection every
15717        // peer sibling slice accessor carries), a `[dup, dup] → [dup]`
15718        // dedup collapse (which would silently absorb the
15719        // `DuplicateMembro` refusal case at the accessor boundary and
15720        // the [`crate::StandardLayout::verify`] cross-member gate would
15721        // silently accept a struct-literal `Caixa` carrying the drift),
15722        // a reference to an operator-resolved overlay (the future per-
15723        // cluster `:membros-overrides` slot — its resolution must land
15724        // at exactly this accessor body, not silently divert the raw
15725        // slot away from a second consumer), or an axis-shuffled
15726        // projection (a future detour that reordered members through
15727        // the accessor would silently split the paired
15728        // [`crate::StandardLayout::verify`] per-Aplicacao gate's
15729        // traversal input from the peer [`Self::aplicacao_view`] fold-
15730        // in path's clone-order input, since the canonical `:contratos`
15731        // `:de`/`:para` and `:entrada :para` cross-slot refusal probes
15732        // read the member set through the same slice).
15733        //
15734        // Third outer top-level [`Caixa`] `&[Composite]`-return slice
15735        // accessor pin on the substrate primitive for M2 / M3 typed-
15736        // slot vec-carry axes — opens the outer-`Caixa` M3 mesh-slot
15737        // arm of the `&[Composite]` composite-slice sub-family the
15738        // sibling M2 `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
15739        // (2a1f907) and
15740        // `children_returns_children_slice_verbatim_across_permutations`
15741        // (c17b51e) pins opened, peer at the outer altitude of the
15742        // closed inner-[`crate::AplicacaoSpec::membros`] (6c77e36)
15743        // accessor on the same MESH-COMPOSITION per-Aplicacao member-
15744        // list axis.
15745        use crate::aplicacao::Membro;
15746        let fixtures: Vec<Vec<Membro>> = vec![
15747            vec![],
15748            vec![Membro {
15749                caixa: "cart".into(),
15750                versao: "^0.1".into(),
15751            }],
15752            vec![
15753                Membro {
15754                    caixa: "cart".into(),
15755                    versao: "^0.1".into(),
15756                },
15757                Membro {
15758                    caixa: "pricing".into(),
15759                    versao: "^0.2".into(),
15760                },
15761                Membro {
15762                    caixa: "auth".into(),
15763                    versao: "^1.0".into(),
15764                },
15765            ],
15766            vec![
15767                Membro {
15768                    caixa: "cart".into(),
15769                    versao: "^0.1".into(),
15770                },
15771                Membro {
15772                    caixa: "cart".into(),
15773                    versao: "^0.1".into(),
15774                },
15775            ],
15776        ];
15777        for membros in fixtures {
15778            let c = caixa_aplicacao_with_membros(membros.clone());
15779            assert_eq!(
15780                c.membros(),
15781                membros.as_slice(),
15782                "Caixa::membros must return :membros verbatim \
15783                 (got {:?}, expected {membros:?})",
15784                c.membros(),
15785            );
15786            assert_eq!(
15787                c.membros(),
15788                c.membros.as_slice(),
15789                "Caixa::membros must element-equal the raw \
15790                 `self.membros.as_slice()` field access across every \
15791                 value in the Vec<Membro> accept-set",
15792            );
15793            assert_eq!(
15794                c.membros().is_empty(),
15795                c.membros.is_empty(),
15796                "Caixa::membros().is_empty() must byte-equal \
15797                 self.membros.is_empty() — a presence-bit drift would \
15798                 silently split the paired Caixa::declared_mesh_slots \
15799                 mesh declared-slot enumerator's presence probe from \
15800                 the peer Caixa::aplicacao_view typed-view composer's \
15801                 fold-in path",
15802            );
15803        }
15804    }
15805
15806    #[test]
15807    fn declared_mesh_slots_membros_arm_routes_through_accessor() {
15808        // Composition pin: [`Caixa::declared_mesh_slots`]'s `:membros`
15809        // presence-probe arm must key off [`Caixa::membros`], not the
15810        // raw `!self.membros.is_empty()` field-probe. Structurally: a
15811        // `Caixa { membros: vec![Membro { caixa: "cart", versao:
15812        // "^0.1" }], .. }` must push `M3_AUTHOR_KEY_MEMBROS` onto the
15813        // declared-slot list (the presence bit is non-empty, so the
15814        // mesh kind-coherence gate must surface the slot as
15815        // "declared"), and a `Caixa { membros: vec![], .. }` must NOT
15816        // push the label (the "author omitted the slot entirely" arm
15817        // — the empty-slice partition the serde-default folds onto).
15818        // The pair jointly pins the accessor + declared-slot
15819        // enumerator composition: any future silent detour that had
15820        // the accessor collapse `[Membro { .. }]` to `[]` (a
15821        // `.filter(|m| m.nome() != "__reserved__")` projection) would
15822        // silently absorb the "declared but degenerate" arm at the
15823        // accessor boundary and the
15824        // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
15825        // coherence gate would silently accept a struct-literal
15826        // `Caixa` carrying the drift.
15827        //
15828        // Peer of the sibling
15829        // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
15830        // (2a1f907) and
15831        // `declared_supervisor_slots_children_arm_routes_through_accessor`
15832        // (c17b51e) composition pins on the M2 `:upgrade-from` /
15833        // `:children` composite-slice arms — same "the enumerator gate
15834        // must route through the substrate-primitive typed dispatch"
15835        // discipline extended onto the M3 `:membros` composite-slice
15836        // arm, opening the M3 arm of the declared-slot enumerator's
15837        // routing invariant.
15838        use crate::aplicacao::Membro;
15839        let c = caixa_aplicacao_with_membros(vec![Membro {
15840            caixa: "cart".into(),
15841            versao: "^0.1".into(),
15842        }]);
15843        let slots = c.declared_mesh_slots();
15844        assert!(
15845            slots.contains(&crate::render::M3_AUTHOR_KEY_MEMBROS),
15846            "declared_mesh_slots must push M3_AUTHOR_KEY_MEMBROS when \
15847             `:membros` is non-empty — the accessor and the enumerator \
15848             gate must route through the same substrate-primitive \
15849             typed dispatch on the outer :membros presence bit (got \
15850             slots={slots:?})",
15851        );
15852        let c = caixa_aplicacao_with_membros(vec![]);
15853        let slots = c.declared_mesh_slots();
15854        assert!(
15855            !slots.contains(&crate::render::M3_AUTHOR_KEY_MEMBROS),
15856            "declared_mesh_slots must NOT push M3_AUTHOR_KEY_MEMBROS \
15857             when `:membros` is empty — the author-omitted arm must \
15858             route through the accessor's empty-slice return unchanged \
15859             (got slots={slots:?})",
15860        );
15861    }
15862
15863    #[test]
15864    fn aplicacao_view_membros_arm_routes_through_accessor() {
15865        // Composition pin: [`Caixa::aplicacao_view`]'s per-`:membros`
15866        // fold-in arm must key off [`Caixa::membros`], not the raw
15867        // `self.membros.clone()` field-clone. Structurally: a `Caixa {
15868        // kind: Aplicacao, membros: vec![Membro { caixa: "cart", .. },
15869        // Membro { caixa: "pricing", .. }], .. }` must fold the per-
15870        // member list through the accessor into the typed
15871        // [`crate::AplicacaoSpec`] view's `membros` slot verbatim —
15872        // every entry the accessor surfaces must land in the view's
15873        // `membros` slot in the same order. The pair jointly pins the
15874        // accessor + view-composer composition: any future silent
15875        // detour that had the accessor return a fresh-cloned
15876        // `Vec<Membro>` copy would silently break the reference-
15877        // identity pin the peer `aplicacao_view` fold-in path reads
15878        // from — the fold would clone once more per accessor call
15879        // instead of borrowing the storage buffer verbatim once.
15880        //
15881        // Peer of the sibling
15882        // `aplicacao_view_politicas_arm_folds_through_accessor`
15883        // (5d23d29) /
15884        // `aplicacao_view_placement_arm_folds_through_accessor`
15885        // (4fb8074) /
15886        // `aplicacao_view_entrada_arm_folds_through_accessor` (e4128e4)
15887        // composition pins on the M3 `:politicas` / `:placement` /
15888        // `:entrada` outer-`Option<&Composite>` arms — extended here to
15889        // the M3 `:membros` outer-`&[Composite]` composite-slice arm,
15890        // closing the aplicacao-view composer's routing invariant on
15891        // the composite-slice input.
15892        use crate::aplicacao::Membro;
15893        let c = caixa_aplicacao_with_membros(vec![
15894            Membro {
15895                caixa: "cart".into(),
15896                versao: "^0.1".into(),
15897            },
15898            Membro {
15899                caixa: "pricing".into(),
15900                versao: "^0.2".into(),
15901            },
15902        ]);
15903        let view = c
15904            .aplicacao_view()
15905            .expect("Aplicacao kind must produce an aplicacao_view");
15906        assert_eq!(
15907            view.membros(),
15908            c.membros(),
15909            "aplicacao_view must fold Caixa::membros verbatim into \
15910             AplicacaoSpec::membros — the accessor and the view \
15911             composer must route through the same substrate-primitive \
15912             typed dispatch on the outer :membros slice (got view \
15913             membros={:?}, expected {:?})",
15914            view.membros(),
15915            c.membros(),
15916        );
15917    }
15918
15919    #[test]
15920    fn membros_projects_slice_by_borrow() {
15921        // The by-borrow pin: [`Caixa::membros`] returns `&[Membro]` by
15922        // borrow — the returned slice borrows the underlying
15923        // `Vec<Membro>` storage of the `:membros` slot and the
15924        // accessor must not clone the backing `Vec` on every call.
15925        // Peer of the sibling outer top-level [`Caixa`] `&[T]`-return
15926        // by-borrow pins (`autores_projects_slice_by_borrow` b5d813f,
15927        // `etiquetas_projects_slice_by_borrow` 78c7d3c,
15928        // `bibliotecas_projects_slice_by_borrow` 8a36c23,
15929        // `exe_projects_slice_by_borrow` 65d9527,
15930        // `servicos_projects_slice_by_borrow` 611f78b,
15931        // `deps_projects_slice_by_borrow` ad34b4e,
15932        // `deps_dev_projects_slice_by_borrow` f7fd81e,
15933        // `upgrade_from_projects_slice_by_borrow` 2a1f907,
15934        // `children_projects_slice_by_borrow` c17b51e) on the sibling
15935        // outer top-level [`Caixa`] scalar-element and composite-
15936        // element `&[T]` axes — folds on the outer-`Caixa` M3 mesh-
15937        // slot composite-element `&[Composite]` axis: the accessor's
15938        // returned slice must borrow from `&self` (the returned
15939        // reference's lifetime is tied to `&self`), and calling the
15940        // accessor twice on the same [`Caixa`] must yield slices that
15941        // are pointer-equal (the underlying byte-buffer is the storage
15942        // `Vec`'s allocation, not a fresh copy) as well as value-equal
15943        // (idempotent, no side effects on `&self`).
15944        //
15945        // Pins against a future silent detour that returned an owned
15946        // `Vec<Membro>` (which would type-check but silently clone on
15947        // every call), a `&Vec<Membro>` return (which would leak the
15948        // backing `Vec`'s grow/push/reserve surface no downstream
15949        // consumer reaches for), or a one-arm-only accessor that
15950        // returned a saturating value on some sentinel input.
15951        use crate::aplicacao::Membro;
15952        for membros in [
15953            vec![],
15954            vec![Membro {
15955                caixa: "cart".into(),
15956                versao: "^0.1".into(),
15957            }],
15958            vec![
15959                Membro {
15960                    caixa: "cart".into(),
15961                    versao: "^0.1".into(),
15962                },
15963                Membro {
15964                    caixa: "pricing".into(),
15965                    versao: "^0.2".into(),
15966                },
15967            ],
15968        ] {
15969            let c = caixa_aplicacao_with_membros(membros.clone());
15970            let first = c.membros();
15971            let second = c.membros();
15972            assert_eq!(
15973                first, second,
15974                "Caixa::membros must be idempotent — two successive \
15975                 calls on the same &self must return the same &[Membro]",
15976            );
15977            assert_eq!(
15978                first.as_ptr(),
15979                second.as_ptr(),
15980                "Caixa::membros must borrow the underlying Vec<Membro> \
15981                 storage — two successive calls must return slices with \
15982                 the same backing pointer (a fresh Vec<Membro> clone \
15983                 would change the pointer on every call)",
15984            );
15985            assert_eq!(
15986                first,
15987                membros.as_slice(),
15988                "Caixa::membros must return :membros verbatim by borrow \
15989                 — got {first:?}, expected {membros:?}",
15990            );
15991        }
15992    }
15993
15994    // ── Caixa::contratos — outer top-level &[WitContract] composite-slice accessor ──
15995
15996    fn caixa_aplicacao_with_contratos(contratos: Vec<crate::aplicacao::WitContract>) -> Caixa {
15997        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15998        c.kind = CaixaKind::Aplicacao;
15999        c.contratos = contratos;
16000        c
16001    }
16002
16003    fn contrato_http_for_test(
16004        de: &str,
16005        para: &str,
16006        endpoint: &str,
16007    ) -> crate::aplicacao::WitContract {
16008        crate::aplicacao::WitContract {
16009            de: de.into(),
16010            para: para.into(),
16011            wit: "wasi:http/proxy".into(),
16012            endpoint: Some(endpoint.into()),
16013            subject: None,
16014            slot: None,
16015        }
16016    }
16017
16018    #[test]
16019    fn contratos_returns_contratos_slice_verbatim_across_permutations() {
16020        // The canonical per-`Caixa` `:contratos` M3 mesh-slot outer-
16021        // composite `&[WitContract]`-return slice-shape pin:
16022        // [`Caixa::contratos`] must return the `:contratos` typed
16023        // `Vec<WitContract>` verbatim as a `&[WitContract]` slice-view
16024        // over the same backing buffer the raw
16025        // `self.contratos.as_slice()` field access borrows from,
16026        // element-equal across every representative fixture in the
16027        // accept-set — `[]` (the "no contracts declared" arm every
16028        // non-`Aplicacao`-kind `defcaixa` carries by
16029        // `#[serde(default)]` and every leaf-Aplicacao with a single
16030        // member carries), a canonical single-edge fixture (the
16031        // minimal directed-graph shape: one HTTP-shape `(cart → catalog)`
16032        // edge), and a canonical multi-edge fixture with three distinct
16033        // edges (the checkout-shape Aplicacao's HTTP-fan pattern:
16034        // `(cart → catalog)`, `(cart → pricing)`, `(cart → auth)`).
16035        //
16036        // Pins against a future silent detour that returned an owned
16037        // `Vec<WitContract>` (which would type-check but silently clone
16038        // on every accessor call, breaking the zero-cost projection
16039        // every peer sibling slice accessor carries), an axis-shuffled
16040        // projection (a future detour that reordered edges through the
16041        // accessor would silently split the paired
16042        // [`crate::StandardLayout::verify`] per-Aplicacao gate's
16043        // traversal input from the peer [`Self::aplicacao_view`] fold-
16044        // in path's clone-order input, since every canonical
16045        // `caixa-mesh` renderer's per-`(:de, :para)` adjacency-list
16046        // seed dispatch reads the edge set through the same slice),
16047        // or a reference to an operator-resolved overlay (the future
16048        // per-cluster `:contratos-overrides` slot — its resolution
16049        // must land at exactly this accessor body, not silently divert
16050        // the raw slot away from a second consumer).
16051        //
16052        // Fourth outer top-level [`Caixa`] `&[Composite]`-return slice
16053        // accessor pin on the substrate primitive for M2 / M3 typed-
16054        // slot vec-carry axes — closes the outer-`Caixa`
16055        // `&[Composite]` composite-slice sub-family the sibling M2
16056        // `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
16057        // (2a1f907) and
16058        // `children_returns_children_slice_verbatim_across_permutations`
16059        // (c17b51e) pins opened and the M3
16060        // `membros_returns_membros_slice_verbatim_across_permutations`
16061        // (0f26987) pin folded on, closing the outer-`Caixa` M3 mesh-
16062        // slot arm of the composite-slice sub-family. Peer at the outer
16063        // altitude of the closed inner-
16064        // [`crate::AplicacaoSpec::contratos`] (0dcc926) accessor on the
16065        // same MESH-COMPOSITION per-Aplicacao contract-list axis.
16066        let fixtures: Vec<Vec<crate::aplicacao::WitContract>> = vec![
16067            vec![],
16068            vec![contrato_http_for_test("cart", "catalog", "/items")],
16069            vec![
16070                contrato_http_for_test("cart", "catalog", "/items"),
16071                contrato_http_for_test("cart", "pricing", "/price"),
16072                contrato_http_for_test("cart", "auth", "/whoami"),
16073            ],
16074        ];
16075        for contratos in fixtures {
16076            let c = caixa_aplicacao_with_contratos(contratos.clone());
16077            assert_eq!(
16078                c.contratos(),
16079                contratos.as_slice(),
16080                "Caixa::contratos must return :contratos verbatim \
16081                 (got {:?}, expected {contratos:?})",
16082                c.contratos(),
16083            );
16084            assert_eq!(
16085                c.contratos(),
16086                c.contratos.as_slice(),
16087                "Caixa::contratos must element-equal the raw \
16088                 `self.contratos.as_slice()` field access across every \
16089                 value in the Vec<WitContract> accept-set",
16090            );
16091            assert_eq!(
16092                c.contratos().is_empty(),
16093                c.contratos.is_empty(),
16094                "Caixa::contratos().is_empty() must byte-equal \
16095                 self.contratos.is_empty() — a presence-bit drift would \
16096                 silently split the paired Caixa::declared_mesh_slots \
16097                 mesh declared-slot enumerator's presence probe from \
16098                 the peer Caixa::aplicacao_view typed-view composer's \
16099                 fold-in path",
16100            );
16101        }
16102    }
16103
16104    #[test]
16105    fn declared_mesh_slots_contratos_arm_routes_through_accessor() {
16106        // Composition pin: [`Caixa::declared_mesh_slots`]'s `:contratos`
16107        // presence-probe arm must key off [`Caixa::contratos`], not the
16108        // raw `!self.contratos.is_empty()` field-probe. Structurally: a
16109        // `Caixa { contratos: vec![WitContract { .. }], .. }` must push
16110        // `M3_AUTHOR_KEY_CONTRATOS` onto the declared-slot list (the
16111        // presence bit is non-empty, so the mesh kind-coherence gate
16112        // must surface the slot as "declared"), and a `Caixa {
16113        // contratos: vec![], .. }` must NOT push the label (the "author
16114        // omitted the slot entirely" arm — the empty-slice partition
16115        // the serde-default folds onto). The pair jointly pins the
16116        // accessor + declared-slot enumerator composition: any future
16117        // silent detour that had the accessor collapse
16118        // `[WitContract { .. }]` to `[]` (a `.filter(|c| c.de() !=
16119        // "__reserved__")` projection) would silently absorb the
16120        // "declared but degenerate" arm at the accessor boundary and
16121        // the [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
16122        // coherence gate would silently accept a struct-literal
16123        // `Caixa` carrying the drift.
16124        //
16125        // Peer of the sibling
16126        // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
16127        // (2a1f907),
16128        // `declared_supervisor_slots_children_arm_routes_through_accessor`
16129        // (c17b51e), and
16130        // `declared_mesh_slots_membros_arm_routes_through_accessor`
16131        // (0f26987) composition pins on the M2 `:upgrade-from` /
16132        // `:children` / M3 `:membros` composite-slice arms — same "the
16133        // enumerator gate must route through the substrate-primitive
16134        // typed dispatch" discipline extended onto the M3 `:contratos`
16135        // composite-slice arm, closing the M3 mesh-slot arm of the
16136        // declared-slot enumerator's routing invariant on the
16137        // composite-slice inputs.
16138        let c = caixa_aplicacao_with_contratos(vec![contrato_http_for_test(
16139            "cart", "catalog", "/items",
16140        )]);
16141        let slots = c.declared_mesh_slots();
16142        assert!(
16143            slots.contains(&crate::render::M3_AUTHOR_KEY_CONTRATOS),
16144            "declared_mesh_slots must push M3_AUTHOR_KEY_CONTRATOS when \
16145             `:contratos` is non-empty — the accessor and the enumerator \
16146             gate must route through the same substrate-primitive \
16147             typed dispatch on the outer :contratos presence bit (got \
16148             slots={slots:?})",
16149        );
16150        let c = caixa_aplicacao_with_contratos(vec![]);
16151        let slots = c.declared_mesh_slots();
16152        assert!(
16153            !slots.contains(&crate::render::M3_AUTHOR_KEY_CONTRATOS),
16154            "declared_mesh_slots must NOT push M3_AUTHOR_KEY_CONTRATOS \
16155             when `:contratos` is empty — the author-omitted arm must \
16156             route through the accessor's empty-slice return unchanged \
16157             (got slots={slots:?})",
16158        );
16159    }
16160
16161    #[test]
16162    fn aplicacao_view_contratos_arm_routes_through_accessor() {
16163        // Composition pin: [`Caixa::aplicacao_view`]'s per-`:contratos`
16164        // fold-in arm must key off [`Caixa::contratos`], not the raw
16165        // `self.contratos.clone()` field-clone. Structurally: a `Caixa
16166        // { kind: Aplicacao, contratos: vec![WitContract { de: "cart",
16167        // .. }, WitContract { de: "pricing", .. }], .. }` must fold the
16168        // per-edge list through the accessor into the typed
16169        // [`crate::AplicacaoSpec`] view's `contratos` slot verbatim —
16170        // every entry the accessor surfaces must land in the view's
16171        // `contratos` slot in the same order. The pair jointly pins
16172        // the accessor + view-composer composition: a future silent
16173        // detour that had the accessor shuffle or drop an edge would
16174        // silently split the paired declared-slot enumerator's
16175        // presence bit from the typed-view composer's edge-list, a
16176        // two-consumer split at the enumerator and the view composer
16177        // far from the source `caixa.lisp`.
16178        //
16179        // Peer of the sibling
16180        // `aplicacao_view_membros_arm_routes_through_accessor`
16181        // (0f26987) composition pin on the M3 `:membros` outer-
16182        // `&[Composite]` composite-slice arm, closing the aplicacao-
16183        // view composer's routing invariant on the composite-slice
16184        // inputs at the outer altitude.
16185        let c = caixa_aplicacao_with_contratos(vec![
16186            contrato_http_for_test("cart", "catalog", "/items"),
16187            contrato_http_for_test("cart", "pricing", "/price"),
16188        ]);
16189        let view = c
16190            .aplicacao_view()
16191            .expect("Aplicacao kind must produce an aplicacao_view");
16192        assert_eq!(
16193            view.contratos(),
16194            c.contratos(),
16195            "aplicacao_view must fold Caixa::contratos verbatim into \
16196             AplicacaoSpec::contratos — the accessor and the view \
16197             composer must route through the same substrate-primitive \
16198             typed dispatch on the outer :contratos slice (got view \
16199             contratos={:?}, expected {:?})",
16200            view.contratos(),
16201            c.contratos(),
16202        );
16203    }
16204
16205    #[test]
16206    fn contratos_projects_slice_by_borrow() {
16207        // The by-borrow pin: [`Caixa::contratos`] returns `&[WitContract]`
16208        // by borrow — the returned slice borrows the underlying
16209        // `Vec<WitContract>` storage of the `:contratos` slot and the
16210        // accessor must not clone the backing `Vec` on every call.
16211        // Peer of the sibling outer top-level [`Caixa`] `&[T]`-return
16212        // by-borrow pins (`autores_projects_slice_by_borrow` b5d813f,
16213        // `etiquetas_projects_slice_by_borrow` 78c7d3c,
16214        // `bibliotecas_projects_slice_by_borrow` 8a36c23,
16215        // `exe_projects_slice_by_borrow` 65d9527,
16216        // `servicos_projects_slice_by_borrow` 611f78b,
16217        // `deps_projects_slice_by_borrow` ad34b4e,
16218        // `deps_dev_projects_slice_by_borrow` f7fd81e,
16219        // `upgrade_from_projects_slice_by_borrow` 2a1f907,
16220        // `children_projects_slice_by_borrow` c17b51e,
16221        // `membros_projects_slice_by_borrow` 0f26987) on the sibling
16222        // outer top-level [`Caixa`] scalar-element and composite-
16223        // element `&[T]` axes — closes the outer-`Caixa` M3 mesh-slot
16224        // composite-element `&[Composite]` axis on the by-borrow pin:
16225        // the accessor's returned slice must borrow from `&self` (the
16226        // returned reference's lifetime is tied to `&self`), and
16227        // calling the accessor twice on the same [`Caixa`] must yield
16228        // slices that are pointer-equal (the underlying byte-buffer is
16229        // the storage `Vec`'s allocation, not a fresh copy) as well as
16230        // value-equal (idempotent, no side effects on `&self`).
16231        //
16232        // Pins against a future silent detour that returned an owned
16233        // `Vec<WitContract>` (which would type-check but silently clone
16234        // on every call), a `&Vec<WitContract>` return (which would
16235        // leak the backing `Vec`'s grow/push/reserve surface no
16236        // downstream consumer reaches for), or a one-arm-only accessor
16237        // that returned a saturating value on some sentinel input.
16238        for contratos in [
16239            vec![],
16240            vec![contrato_http_for_test("cart", "catalog", "/items")],
16241            vec![
16242                contrato_http_for_test("cart", "catalog", "/items"),
16243                contrato_http_for_test("cart", "pricing", "/price"),
16244            ],
16245        ] {
16246            let c = caixa_aplicacao_with_contratos(contratos.clone());
16247            let first = c.contratos();
16248            let second = c.contratos();
16249            assert_eq!(
16250                first, second,
16251                "Caixa::contratos must be idempotent — two successive \
16252                 calls on the same &self must return the same \
16253                 &[WitContract]",
16254            );
16255            assert_eq!(
16256                first.as_ptr(),
16257                second.as_ptr(),
16258                "Caixa::contratos must borrow the underlying \
16259                 Vec<WitContract> storage — two successive calls must \
16260                 return slices with the same backing pointer (a fresh \
16261                 Vec<WitContract> clone would change the pointer on \
16262                 every call)",
16263            );
16264            assert_eq!(
16265                first,
16266                contratos.as_slice(),
16267                "Caixa::contratos must return :contratos verbatim by \
16268                 borrow — got {first:?}, expected {contratos:?}",
16269            );
16270        }
16271    }
16272
16273    // ── drift-detection: Caixa top-level multi-word serde-derive-to-const identity ──
16274
16275    #[test]
16276    fn caixa_multi_word_serde_keys_match_lifted_top_level_key_consts() {
16277        // Load-bearing invariant: every multi-word top-level [`Caixa`]
16278        // serde-derived JSON key routes through a lifted `&'static str`
16279        // const. The Rust field names are `snake_case`
16280        // (`deps_dev` / `upgrade_from` / `max_restarts` /
16281        // `restart_window`); [`Caixa`]'s `#[serde(rename_all =
16282        // "camelCase")]` derive attribute maps each to the camelCase
16283        // byte-string the [`Caixa::to_lisp`] round-trip's
16284        // `serde_json::to_value(self)` step lands under before
16285        // `tatara_lisp::domain::json_to_sexp` re-projects the JSON keys
16286        // to the kebab-case `:deps-dev` / `:upgrade-from` /
16287        // `:max-restarts` / `:restart-window` author surface. Serialize
16288        // a fully-populated [`Caixa`] and pin that each canonical
16289        // byte-sequence appears verbatim in the JSON — a future
16290        // accidental `rename_all = "snake_case"` / `"kebab-case"` /
16291        // verbatim-field-name flip at the derive attribute (any of
16292        // which would silently break every [`Caixa::to_lisp`]
16293        // round-trip and the future M4 operator-side manifest ingest's
16294        // `Value::get(<key>)` navigation) surfaces here as a build-time
16295        // test failure at `manifest.rs`, not as an apply-time
16296        // `.get(<stale-canonical-const>)` returning `None` far from the
16297        // derive-attr drift's commit. Same discipline the sibling
16298        // `supervisor_spec_serde_keys_match_lifted_supervisor_key_consts`
16299        // (40cc4e5), `membro_serde_keys_match_lifted_membro_key_consts`
16300        // (ce80ca0), and `upgrade_from_entry_serde_keys_match_lifted_
16301        // m2_upgrade_from_key_consts` (36ffe65) pins established on the
16302        // sibling M2 supervision-tree, M3 [`Membro`] per-entry, and M2
16303        // [`UpgradeFromEntry`] per-entry axes — extended here to the
16304        // enclosing M0 [`Caixa`] top-level axis so the last of the four
16305        // multi-word top-level [`Caixa`] serde-derived JSON keys
16306        // (`depsDev`) joins the substrate's "one canonical byte-string
16307        // per typed serialized-key axis" discipline.
16308        use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
16309        use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
16310        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16311        c.deps_dev = vec![Dep::simple("tatara-check", "^0.1")];
16312        c.upgrade_from = vec![UpgradeFromEntry {
16313            from: "0.0.1".into(),
16314            instructions: vec![UpgradeInstruction::Restart],
16315        }];
16316        c.estrategia = Some(RestartStrategy::OneForOne);
16317        c.max_restarts = Some(3);
16318        c.restart_window = Some("60s".into());
16319        c.children = vec![ChildSpec {
16320            caixa: "child".into(),
16321            versao: "^0.1".into(),
16322            restart: RestartPolicy::Permanent,
16323        }];
16324        let json = serde_json::to_string(&c).unwrap();
16325        for key in [
16326            crate::render::CAIXA_KEY_DEPS_DEV,
16327            crate::render::M2_KEY_UPGRADE_FROM,
16328            crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
16329            crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
16330        ] {
16331            let quoted = format!("\"{key}\"");
16332            assert!(
16333                json.contains(&quoted),
16334                "serialized Caixa must carry the lifted top-level \
16335                 multi-word byte-sequence {quoted} verbatim in the JSON \
16336                 emission (got: {json})",
16337            );
16338        }
16339    }
16340
16341    #[test]
16342    fn caixa_top_level_multi_word_key_consts_are_pairwise_distinct() {
16343        // Cross-axis drift-detection pin: a future collapse of the four
16344        // canonical [`Caixa`] top-level multi-word byte-strings onto the
16345        // same value (e.g. an accidental copy-paste flip of
16346        // [`crate::render::CAIXA_KEY_DEPS_DEV`] to also read
16347        // `"upgradeFrom"`) would silently reroute every downstream
16348        // `Value::get(<key>)` probe on one axis onto the sibling axis's
16349        // top-level entry and pass every propagation-probe test that
16350        // expected only the stale axis's value. Peer of the sibling
16351        // four-way distinct pin on the `SUPERVISOR_KEY_*` tetrad
16352        // (40cc4e5) and the two-way pin on `MEMBRO_KEY_*` (ce80ca0).
16353        let all = [
16354            crate::render::CAIXA_KEY_DEPS_DEV,
16355            crate::render::M2_KEY_UPGRADE_FROM,
16356            crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
16357            crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
16358        ];
16359        for (i, a) in all.iter().enumerate() {
16360            for b in all.iter().skip(i + 1) {
16361                assert_ne!(
16362                    a, b,
16363                    "Caixa top-level multi-word key consts must be \
16364                     pairwise-distinct canonical byte-sequences — got \
16365                     `{a}` == `{b}`",
16366                );
16367            }
16368        }
16369    }
16370
16371    #[test]
16372    fn caixa_top_level_multi_word_key_consts_are_lower_camel_case_shape() {
16373        // Shape-pin: every [`Caixa`] top-level multi-word key const must
16374        // be a lowerCamelCase byte-sequence (no `snake_case`
16375        // underscores, no `kebab-case` hyphens, no leading colon, no
16376        // `PascalCase` leading capital, no whitespace / dots) — the
16377        // canonical shape the `#[serde(rename_all = "camelCase")]`
16378        // derive produces on [`Caixa`]. A future flip to a
16379        // non-camelCase attribute at the derive surfaces both here
16380        // (this test fails on the stale-constant shape) and at
16381        // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
16382        // (that test fails on the mismatch between const and derive).
16383        // Peer with `membro_key_consts_are_lower_camel_case_shape`
16384        // (ce80ca0) and `supervisor_key_consts_are_lower_camel_case_shape`
16385        // (40cc4e5) on the sibling per-entry / supervisor-tree axes.
16386        for key in [
16387            crate::render::CAIXA_KEY_DEPS_DEV,
16388            crate::render::M2_KEY_UPGRADE_FROM,
16389            crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
16390            crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
16391        ] {
16392            assert!(
16393                !key.is_empty(),
16394                "Caixa top-level multi-word key const must be non-empty \
16395                 (got {key:?})"
16396            );
16397            let first = key.chars().next().unwrap();
16398            assert!(
16399                first.is_ascii_lowercase(),
16400                "Caixa top-level multi-word key const must lead with an \
16401                 ASCII-lowercase byte (got {key:?}, leads with {first:?})",
16402            );
16403            assert!(
16404                key.chars().all(|c| c.is_ascii_alphanumeric()),
16405                "Caixa top-level multi-word key const must be \
16406                 ASCII-alphanumeric only — no `_` / `-` / `:` / `.` / \
16407                 whitespace (got {key:?})",
16408            );
16409        }
16410    }
16411
16412    #[test]
16413    fn caixa_key_deps_dev_pins_canonical_camel_case_byte_string() {
16414        // Scalar-value pin: the byte-string the
16415        // [`crate::render::CAIXA_KEY_DEPS_DEV`] const resolves to,
16416        // asserted verbatim. A future rebrand (`depsDev` → `devDeps`
16417        // matching Cargo's verbatim `dev-dependencies` axis, `depsDev`
16418        // → `depsTest` matching a hypothetical per-test-target
16419        // vocabulary flip) lands as an edit to exactly one const AND
16420        // one derive attribute — the sibling
16421        // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
16422        // pin already ties the const to the derive attribute, so a
16423        // rebrand that touches only one side of the pair fails at
16424        // caixa-core build time. Same "scalar-value pin per const"
16425        // discipline the sibling
16426        // `m2_top_level_author_key_consts_pin_canonical_kebab_case_labels`
16427        // (f49c8b0) and `contrato_key_consts_pin_canonical_camel_case_labels`
16428        // (ca463a4) pins carry on the peer M2 / M3 top-level slot axes.
16429        assert_eq!(crate::render::CAIXA_KEY_DEPS_DEV, "depsDev");
16430    }
16431
16432    #[test]
16433    fn caixa_key_deps_pins_canonical_byte_string() {
16434        // Scalar-value pin: the byte-string the
16435        // [`crate::render::CAIXA_KEY_DEPS`] const resolves to, asserted
16436        // verbatim. Peer of `caixa_key_deps_dev_pins_canonical_camel_case_byte_string`
16437        // on the two-list dep-graph serialized-key axis — the sibling
16438        // pin covers the multi-word `deps_dev → depsDev` camelCase
16439        // arm, this pin covers the single-word `deps → deps` no-op arm
16440        // (the [`crate::Caixa::deps`] field name carries no `_`, so the
16441        // `#[serde(rename_all = "camelCase")]` derive is a no-op on this
16442        // axis and the emitted JSON key equals the source-side field
16443        // name byte-for-byte). A future [`crate::Caixa::deps`] field
16444        // rename (`deps` → `dependencies` matching Cargo's verbatim
16445        // `[dependencies]` axis, `deps` → `runtime_deps` matching a
16446        // hypothetical per-runtime-target vocabulary flip) OR an added
16447        // `#[serde(rename = "…")]` explicit override lands as an edit
16448        // to exactly one const AND one derive-attr / field name — the
16449        // sibling `caixa_deps_serde_key_matches_lifted_caixa_key_deps`
16450        // pin ties the const to the emitted JSON key, so a rebrand
16451        // that touches only one side of the pair fails at caixa-core
16452        // build time.
16453        assert_eq!(crate::render::CAIXA_KEY_DEPS, "deps");
16454    }
16455
16456    #[test]
16457    fn caixa_deps_serde_key_matches_lifted_caixa_key_deps() {
16458        // Load-bearing invariant on the single-word `deps` top-level
16459        // axis: the byte-string [`crate::render::CAIXA_KEY_DEPS`] pins
16460        // must appear verbatim in the JSON [`Caixa::to_lisp`]'s
16461        // `serde_json::to_value(self)` step emits. Serialize a
16462        // populated [`Caixa`] whose `:deps` slot carries at least one
16463        // entry (the `#[serde(default)]` attribute on the field emits
16464        // an empty `[]` even without members, but a non-empty vec
16465        // additionally covers the codec's per-`Dep`-entry emission
16466        // path) and pin that `"deps"` appears verbatim in the JSON
16467        // emission — a future accidental `rename_all = "snake_case"` /
16468        // `"kebab-case"` flip at the derive attribute (or an added
16469        // `#[serde(rename = "…")]` explicit override on the field, or
16470        // a Rust field rename) would break every [`Caixa::to_lisp`]
16471        // round-trip and the future M4 operator-side manifest ingest's
16472        // `Value::get(CAIXA_KEY_DEPS)` navigation — surfaces here as a
16473        // build-time test failure at `manifest.rs`, not as an
16474        // apply-time `.get(<stale-canonical-const>)` returning `None`
16475        // far from the drift's commit. Peer of the sibling
16476        // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
16477        // multi-word pin on the same M0 [`Caixa`] top-level
16478        // serialized-key axis, extended here to the single-word arm
16479        // the multi-word test's `rename_all = "camelCase"` sweep can't
16480        // reach (single-word `deps → deps` is a no-op the multi-word
16481        // pin's `\"depsDev\"` / `\"upgradeFrom\"` / `\"maxRestarts\"` /
16482        // `\"restartWindow\"` byte-scan can never observe).
16483        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16484        c.deps = vec![Dep::simple("caixa-core", "^0.1")];
16485        let json = serde_json::to_string(&c).unwrap();
16486        let quoted = format!("\"{}\"", crate::render::CAIXA_KEY_DEPS);
16487        assert!(
16488            json.contains(&quoted),
16489            "serialized Caixa must carry the lifted top-level `deps` \
16490             byte-sequence {quoted} verbatim in the JSON emission (got: \
16491             {json})",
16492        );
16493    }
16494
16495    #[test]
16496    fn caixa_dep_graph_two_list_key_consts_are_pairwise_distinct() {
16497        // Cross-axis drift-detection pin on the two-list dep-graph
16498        // renderer-side wire-key axis: a future collapse of the
16499        // canonical [`crate::render::CAIXA_KEY_DEPS`] /
16500        // [`crate::render::CAIXA_KEY_DEPS_DEV`] byte-strings onto the
16501        // same value (e.g. an accidental copy-paste flip of
16502        // `CAIXA_KEY_DEPS_DEV` to also read `"deps"`) would silently
16503        // reroute every downstream `Value::get(<key>)` probe on one
16504        // axis onto the sibling axis's dep-list and pass every
16505        // propagation-probe test that expected only the stale axis's
16506        // value — a dev-only dep would land in the runtime closure at
16507        // publish time, or a runtime dep would be excluded from the
16508        // published lacre. Peer of the sibling four-way distinct pin
16509        // on the top-level multi-word tetrad
16510        // (`caixa_top_level_multi_word_key_consts_are_pairwise_distinct`)
16511        // and the two-way pin on the sibling
16512        // [`DEP_AUTHOR_KEY_DEPS`] / [`DEP_AUTHOR_KEY_DEPS_DEV`]
16513        // author-facing arm (4da6fba's test), extended here to the
16514        // renderer-side wire-key arm of the same two-list dep-graph
16515        // axis so both halves of the "one canonical byte-string per
16516        // typed axis per (author, wire)" grid carry the same
16517        // distinct-ness discipline.
16518        assert_ne!(
16519            crate::render::CAIXA_KEY_DEPS,
16520            crate::render::CAIXA_KEY_DEPS_DEV,
16521            "CAIXA_KEY_DEPS and CAIXA_KEY_DEPS_DEV must be distinct \
16522             canonical byte-sequences on the two-list dep-graph \
16523             renderer-side wire-key axis"
16524        );
16525    }
16526
16527    // ── DepList / Caixa::push_dep pin ────────────────────────────────
16528    //
16529    // The compounding pin: the two-arm closed-set typed enum
16530    // [`crate::dep::DepList`] carries the runtime-closure `:deps`
16531    // (`Prod`) vs dev-only-closure `:deps-dev` (`Dev`) dispatch every
16532    // consumer of the top-level manifest's dep-mutation surface reads
16533    // through, and the typed dispatch [`Caixa::push_dep`] on the
16534    // substrate primitive folds the "select list → check within-list
16535    // dup → push" cascade onto one method call. Prior to this landing
16536    // the two axes lived across two `&'static str` constants
16537    // (`DEP_AUTHOR_KEY_DEPS`, `DEP_AUTHOR_KEY_DEPS_DEV`) with no closed-
16538    // set type carrying the pair; the `feira add` mutation site's
16539    // inline `if self.dev { &mut caixa.deps_dev } else { &mut
16540    // caixa.deps }` dispatch expressed no compile-time link back to
16541    // the substrate primitive, and a future third dep-list axis would
16542    // have silently split at every open-coded mutation site.
16543
16544    #[test]
16545    fn dep_list_as_str_routes_through_lifted_author_key_constants() {
16546        // Every arm returns the same `&'static str` the substrate's
16547        // canonical `DEP_AUTHOR_KEY_DEPS` / `DEP_AUTHOR_KEY_DEPS_DEV`
16548        // constants carry. A future rebrand on either constant reaches
16549        // the enum through one edit; a regression to inline literals
16550        // (e.g. `Prod => ":deps"`) would silently split the diagnostic
16551        // quotes from the wire-format constants every consumer routes
16552        // through and this pin flags it at build time.
16553        assert_eq!(
16554            crate::dep::DepList::Prod.as_str(),
16555            crate::render::DEP_AUTHOR_KEY_DEPS
16556        );
16557        assert_eq!(
16558            crate::dep::DepList::Dev.as_str(),
16559            crate::render::DEP_AUTHOR_KEY_DEPS_DEV
16560        );
16561    }
16562
16563    #[test]
16564    fn dep_list_display_routes_through_as_str() {
16565        // Same as-str-through-Display convergence discipline the
16566        // sibling closed-set typed enums carry — a `format!("{list}")`
16567        // call must land byte-for-byte on the accessor's return so a
16568        // future consumer that formats the enum for a diagnostic line
16569        // reaches the same wire-format constant the wire-format
16570        // producers do.
16571        assert_eq!(
16572            format!("{}", crate::dep::DepList::Prod),
16573            crate::dep::DepList::Prod.as_str()
16574        );
16575        assert_eq!(
16576            format!("{}", crate::dep::DepList::Dev),
16577            crate::dep::DepList::Dev.as_str()
16578        );
16579    }
16580
16581    #[test]
16582    fn dep_list_all_enumerates_every_variant_once() {
16583        // Exhaustive-iteration pin — every arm appears exactly once in
16584        // `ALL`, matching the closed set the compiler enforces on the
16585        // sibling `match self` arms. A future variant addition that
16586        // extends only one method's match without extending `ALL`
16587        // would silently drop the new arm from every consumer that
16588        // iterates the slice.
16589        let variants: &[crate::dep::DepList] = crate::dep::DepList::ALL;
16590        assert!(variants.contains(&crate::dep::DepList::Prod));
16591        assert!(variants.contains(&crate::dep::DepList::Dev));
16592        assert_eq!(variants.len(), 2);
16593    }
16594
16595    #[test]
16596    fn push_dep_routes_to_deps_slot_on_prod_arm() {
16597        // The `Prod` arm dispatches to the runtime-closure `:deps`
16598        // slot every downstream lacre-pipeline consumer resolves at
16599        // build time. A future arm that regressed to inline `&mut
16600        // self.deps_dev` on the `Prod` path would silently reroute
16601        // every runtime dep into the dev-only closure at publish time
16602        // — this pin refuses that regression.
16603        let src = Caixa::template("host");
16604        let mut caixa = Caixa::from_lisp(&src).expect("template parses");
16605        let before_deps = caixa.deps().len();
16606        let before_deps_dev = caixa.deps_dev().len();
16607        let dep = Dep {
16608            nome: "caixa-teia".to_string(),
16609            versao: "^0.1".to_string(),
16610            fonte: None,
16611            opcional: false,
16612            caracteristicas: Vec::new(),
16613        };
16614        caixa
16615            .push_dep(crate::dep::DepList::Prod, dep)
16616            .expect("first push into :deps succeeds");
16617        assert_eq!(caixa.deps().len(), before_deps + 1);
16618        assert_eq!(caixa.deps_dev().len(), before_deps_dev);
16619        assert_eq!(caixa.deps().last().unwrap().nome(), "caixa-teia");
16620    }
16621
16622    #[test]
16623    fn push_dep_routes_to_deps_dev_slot_on_dev_arm() {
16624        // Peer of the sibling `Prod`-arm dispatch pin — the `Dev` arm
16625        // must dispatch to the dev-only-closure `:deps-dev` slot every
16626        // downstream test-facing artifact resolver reads. A future
16627        // regression that inverted the two arms would silently route
16628        // every dev-only dep into the runtime closure at publish time
16629        // and this pin catches it before the drift ships.
16630        let src = Caixa::template("host");
16631        let mut caixa = Caixa::from_lisp(&src).expect("template parses");
16632        let dep = Dep {
16633            nome: "tatara-check".to_string(),
16634            versao: "*".to_string(),
16635            fonte: None,
16636            opcional: false,
16637            caracteristicas: Vec::new(),
16638        };
16639        caixa
16640            .push_dep(crate::dep::DepList::Dev, dep)
16641            .expect("first push into :deps-dev succeeds");
16642        assert!(caixa.deps().is_empty());
16643        assert_eq!(caixa.deps_dev().len(), 1);
16644        assert_eq!(caixa.deps_dev().last().unwrap().nome(), "tatara-check");
16645    }
16646
16647    #[test]
16648    fn push_dep_refuses_within_list_duplicate_nome_with_typed_error() {
16649        // Within-list dup check routes through the canonical
16650        // [`DepError::DuplicateNome`] carrier — the substrate's typed
16651        // diagnostic for the same axis [`Caixa::validate_deps`]'s
16652        // parse-time [`crate::render::insert_first_seen`] walk raises
16653        // on. Prior to the lift the mutation site's inline
16654        // `bail!("dep '{}' already declared", …)` string-diagnostic
16655        // path expressed no through-line back to the typed error;
16656        // routing every dep-list refusal through one carrier means an
16657        // author reading a `feira add` refusal and a `feira build`
16658        // refusal reaches for the same corrective surface without
16659        // switching diagnostic idioms.
16660        let src = Caixa::template("host");
16661        let mut caixa = Caixa::from_lisp(&src).expect("template parses");
16662        let dep = Dep {
16663            nome: "caixa-teia".to_string(),
16664            versao: "^0.1".to_string(),
16665            fonte: None,
16666            opcional: false,
16667            caracteristicas: Vec::new(),
16668        };
16669        caixa
16670            .push_dep(crate::dep::DepList::Prod, dep.clone())
16671            .expect("first push succeeds");
16672        let dup = Dep {
16673            nome: "caixa-teia".to_string(),
16674            versao: "^0.2".to_string(),
16675            fonte: None,
16676            opcional: false,
16677            caracteristicas: Vec::new(),
16678        };
16679        let err = caixa
16680            .push_dep(crate::dep::DepList::Prod, dup)
16681            .expect_err("second push with same :nome refuses");
16682        assert_eq!(
16683            err,
16684            DepError::DuplicateNome {
16685                nome: "caixa-teia".to_string(),
16686                list: crate::render::DEP_AUTHOR_KEY_DEPS,
16687            }
16688        );
16689        // The refused mutation must not corrupt the target list —
16690        // exactly one entry lives past the refusal, matching the
16691        // canonical single-source-of-truth invariant `Caixa::deps()`
16692        // carries.
16693        assert_eq!(caixa.deps().len(), 1);
16694    }
16695
16696    #[test]
16697    fn push_dep_refuses_dup_on_dev_list_arm_names_deps_dev_key() {
16698        // Peer of the sibling `Prod`-arm dup-refusal pin — the `Dev`
16699        // arm's refusal must carry `DEP_AUTHOR_KEY_DEPS_DEV` in the
16700        // `list` payload so a future author reading the refusal grep's
16701        // for the correct `:deps-dev` block in their `caixa.lisp`,
16702        // not the sibling `:deps` block the runtime closure resolves.
16703        let src = Caixa::template("host");
16704        let mut caixa = Caixa::from_lisp(&src).expect("template parses");
16705        let dep = Dep {
16706            nome: "tatara-check".to_string(),
16707            versao: "*".to_string(),
16708            fonte: None,
16709            opcional: false,
16710            caracteristicas: Vec::new(),
16711        };
16712        caixa
16713            .push_dep(crate::dep::DepList::Dev, dep.clone())
16714            .expect("first push succeeds");
16715        let err = caixa
16716            .push_dep(crate::dep::DepList::Dev, dep)
16717            .expect_err("second push with same :nome refuses");
16718        assert!(matches!(
16719            err,
16720            DepError::DuplicateNome {
16721                ref nome,
16722                list,
16723            } if nome == "tatara-check"
16724                && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
16725        ));
16726    }
16727
16728    #[test]
16729    fn push_dep_allows_same_nome_across_prod_and_dev_lists() {
16730        // The within-list dup check is scoped to the target arm — a
16731        // caixa may legitimately carry the same `:nome` under both
16732        // `:deps` and `:deps-dev` (though the substrate's peer
16733        // [`crate::Caixa::validate_deps`] walk still refuses the
16734        // shape at parse time; the mutation-site refusal is scoped to
16735        // the mutation-site's list to match the peer parse-time
16736        // per-list [`crate::render::insert_first_seen`] discipline).
16737        // The two arms hold independent seen-sets.
16738        let src = Caixa::template("host");
16739        let mut caixa = Caixa::from_lisp(&src).expect("template parses");
16740        let dep_prod = Dep {
16741            nome: "shared".to_string(),
16742            versao: "^0.1".to_string(),
16743            fonte: None,
16744            opcional: false,
16745            caracteristicas: Vec::new(),
16746        };
16747        let dep_dev = Dep {
16748            nome: "shared".to_string(),
16749            versao: "*".to_string(),
16750            fonte: None,
16751            opcional: false,
16752            caracteristicas: Vec::new(),
16753        };
16754        caixa
16755            .push_dep(crate::dep::DepList::Prod, dep_prod)
16756            .expect("push into :deps succeeds");
16757        caixa
16758            .push_dep(crate::dep::DepList::Dev, dep_dev)
16759            .expect("push same :nome into :deps-dev succeeds");
16760        assert_eq!(caixa.deps().len(), 1);
16761        assert_eq!(caixa.deps_dev().len(), 1);
16762    }
16763}