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` `:limits` M2 typed-slot outer-
1741    /// composite Lunatic-per-process wasm32-sandboxing-composite optional-
1742    /// composite-reference accessor every consumer of the top-level
1743    /// manifest's per-Servico [`LimitsSpec`] outer-composite reader keys
1744    /// off — returns the author-declared `:limits` typed composite
1745    /// verbatim as an `Option<&LimitsSpec>` reference over the same
1746    /// backing storage the raw `self.limits.as_ref()` field access
1747    /// borrows from, with `None` naming the "no `:limits` block
1748    /// authored — every per-axis Lunatic-sandbox cap defers to the
1749    /// wasm-engine-default arm named on the per-axis
1750    /// [`LimitsSpec::memory`] / [`LimitsSpec::fuel`] /
1751    /// [`LimitsSpec::wall_clock`] / [`LimitsSpec::cpu`] scalar-accessor
1752    /// docstrings" partition every downstream Servico-M2-overlay
1753    /// emitter treats as "emit nothing" and the sibling
1754    /// [`crate::StandardLayout::verify`] per-`:limits` shape gate
1755    /// treats as "skip the per-axis
1756    /// [`crate::LimitsError::MemoryZero`] / `MemoryBelowWasm32Page` /
1757    /// `FuelZero` / `WallClockZero` / `CpuZero` refusal cascade".
1758    ///
1759    /// The outer `:limits` slot carries the M2 Servico-runtime typed
1760    /// composite — the load-bearing container of every Lunatic-shaped
1761    /// per-process wasm32-sandbox cap axis every long-running wasm
1762    /// component's runtime dispatches on (INSPIRATIONS §III.1 —
1763    /// Lunatic per-process linear-memory / fuel / wall-clock /
1764    /// millicore cap primitives translated onto pleme-io's typed
1765    /// `:limits :memory` / `:limits :fuel` / `:limits :wall-clock` /
1766    /// `:limits :cpu` sub-slot axes; CAIXA-SDLC §II — the typed-M2
1767    /// slot algebra the wasm-engine + `pleme-computeunit` Helm-library
1768    /// chart both fan on). Every per-`:limits` axis threads through a
1769    /// lifted per-slot accessor on the [`LimitsSpec`] type: the
1770    /// [`LimitsSpec::memory`] wasm32 linear-memory byte-cap scalar
1771    /// accessor, the [`LimitsSpec::fuel`] wasmtime fuel-cap scalar
1772    /// accessor, the [`LimitsSpec::wall_clock`] per-call wall-clock
1773    /// deadline scalar accessor, and the [`LimitsSpec::cpu`]
1774    /// K8s-millicore soft-CPU-share scalar accessor. Every downstream
1775    /// consumer that reaches for a limits axis first passes through
1776    /// this outer accessor onto the composite and then dispatches
1777    /// onto the per-axis accessor — the two-level dispatch means
1778    /// every per-`:limits` reader now routes through a typed dispatch
1779    /// on the substrate primitive at both altitudes.
1780    ///
1781    /// Prior to this lift the `.limits` `Option<LimitsSpec>` composite
1782    /// was accessed inline at three production sites — the
1783    /// [`crate::StandardLayout::verify`] per-`:limits` shape gate's
1784    /// `if let Some(l) = &caixa.limits { … }` traversal head
1785    /// (caixa-core/src/layout.rs:882, which drives the per-axis
1786    /// refusal cascade on the composite: the `LimitsError::MemoryZero`
1787    /// / `MemoryBelowWasm32Page` / `MemoryExceedsWasm32Max` /
1788    /// `FuelZero` / `FuelExceedsMax` / `WallClockZero` /
1789    /// `WallClockExceedsMax` / `CpuZero` / `CpuExceedsMax` refusals
1790    /// [`LimitsSpec::validate`] fans onto), the
1791    /// [`crate::render::servico_m2_overlay`] per-Servico M2 overlay
1792    /// emitter's `if let Some(limits) = &caixa.limits { … }` traversal
1793    /// head (caixa-core/src/render.rs:18504, which drives the
1794    /// `M2_KEY_LIMITS`-keyed `limits.is_empty()`-gated `serde_yaml`
1795    /// projection every `caixa-helm` / `caixa-flux` Servico values-
1796    /// block emitter fans on), and the
1797    /// [`Self::declared_servico_slots`] per-Servico M2 declared-slot-
1798    /// set enumerator's `self.limits.is_some()` presence probe
1799    /// (caixa-core/src/manifest.rs:1788, which drives the
1800    /// `M2_AUTHOR_KEY_LIMITS` kebab-case author-label push every
1801    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
1802    /// gate reads) — three open-coded outer-field accesses that
1803    /// expressed no compile-time link back to the typed slot at the
1804    /// [`Caixa`] altitude. A future extension of the `:limits` outer
1805    /// axis to a richer author surface (a multi-`:limits` list the M4
1806    /// CR materializer resolves per-CR at admission time so a Servico
1807    /// can expose a compute-heavy + IO-heavy limits pair, a per-
1808    /// cluster `:limits-overrides` slot the operator pins so a
1809    /// cluster-specific policy can tighten a caixa-declared cap
1810    /// without re-authoring the `caixa.lisp`, a promotion of the
1811    /// plain `Option<LimitsSpec>` to a richer
1812    /// `{static, dynamic}` partition once the wasm-engine's runtime-
1813    /// resolved dynamic-cap surface lands) would have had to be
1814    /// threaded through all three open-coded copies in lockstep or
1815    /// one consumer would silently disagree with the peers on which
1816    /// limits composite a given Caixa resolves to — the layout gate's
1817    /// per-axis bracket-dispatch seed reading the raw slot while the
1818    /// peer `servico_m2_overlay` emitter read an operator-resolved
1819    /// slot would silently split the build-time sandbox-shape gate
1820    /// from the runtime `ComputeUnit` CR emission gate, a three-
1821    /// consumer split at the layout gate, the M2 overlay emitter, and
1822    /// the declared-slot enumerator far from the source `caixa.lisp`
1823    /// with no field naming the limits-drift root cause. Lifting the
1824    /// resolution rule to a typed method on the substrate primitive
1825    /// means every downstream consumer of the caixa's per-`Caixa`
1826    /// Lunatic-sandboxing outer-composite surface reaches for exactly
1827    /// one typed dispatch — the resolver's accept-set migrates as a
1828    /// unit on any future axis addition.
1829    ///
1830    /// First outer top-level [`Caixa`] `Option<&Composite>`-return
1831    /// composite-reference accessor — opens the outer-`Caixa`
1832    /// `Option<&Composite>` composite-reference projection pattern the
1833    /// sibling per-`Caixa` `:behavior` [`crate::BehaviorSpec`] /
1834    /// `:politicas` [`crate::aplicacao::MeshPolicy`] / `:placement`
1835    /// [`crate::aplicacao::Placement`] / `:entrada`
1836    /// [`crate::aplicacao::Entrada`] future outer-composite lifts
1837    /// fold on. Peer of the M3 mesh-slot outer-composite family the
1838    /// sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
1839    /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
1840    /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
1841    /// accessors already close on the outer [`crate::AplicacaoSpec`]
1842    /// altitude — extends that "one typed dispatch on the substrate
1843    /// primitive, thin projections at each consumer" discipline onto
1844    /// the outer top-level [`Caixa`] altitude, opening the M2 Servico-
1845    /// runtime slot family's outer-composite axis. Returns
1846    /// `Option<&LimitsSpec>` (not the owning composite by copy or
1847    /// clone) because every downstream consumer of the limits
1848    /// composite treats it as a read-only per-axis dispatch source —
1849    /// the reference-view is the narrowest borrow that supports every
1850    /// present + roadmapped consumer (per-axis accessor dispatch,
1851    /// `.is_empty()`-gated overlay projection, presence-probe early
1852    /// return on the "author-omitted `:limits` ⇒ engine-default
1853    /// applies" partition) without cloning the composite through
1854    /// every consumer's fast path. The `Option` half of the return-
1855    /// type preserves the load-bearing "author-omitted `:limits` ⇒
1856    /// engine-default applies" partition (not a default composite the
1857    /// downstream must reject on emptiness) — the accessor projects
1858    /// the raw `Option<LimitsSpec>` slot's presence bit through the
1859    /// reference-return unchanged. Named `limits()` to match the
1860    /// storage field's name verbatim and the tatara-lisp author-
1861    /// surface term (`:limits`) the field's own docstring already
1862    /// carries.
1863    #[must_use]
1864    pub fn limits(&self) -> Option<&LimitsSpec> {
1865        self.limits.as_ref()
1866    }
1867
1868    /// Substrate-canonical per-`Caixa` `:behavior` M2 typed-slot outer-
1869    /// composite OTP-`gen_server`-shaped callback-table optional-
1870    /// composite-reference accessor every consumer of the top-level
1871    /// manifest's per-Servico [`BehaviorSpec`] outer-composite reader
1872    /// keys off — returns the author-declared `:behavior` typed
1873    /// composite verbatim as an `Option<&BehaviorSpec>` reference over
1874    /// the same backing storage the raw `self.behavior.as_ref()` field
1875    /// access borrows from, with `None` naming the "no `:behavior`
1876    /// block authored — every per-callback OTP-shaped hook defers to
1877    /// the wasm-engine's runtime default arm named on the per-axis
1878    /// [`BehaviorSpec::on_init`] / [`BehaviorSpec::on_call`] /
1879    /// [`BehaviorSpec::on_cast`] / [`BehaviorSpec::on_info`] /
1880    /// [`BehaviorSpec::on_state_change`] /
1881    /// [`BehaviorSpec::on_terminate`] scalar-accessor docstrings"
1882    /// partition every downstream Servico-M2-overlay emitter treats as
1883    /// "emit nothing" and the sibling [`crate::StandardLayout::verify`]
1884    /// per-`:behavior` shape gate treats as "skip the per-arm
1885    /// [`crate::behavior::BehaviorError`] refusal cascade + the
1886    /// per-callback on-disk `MissingEntry` existence check".
1887    ///
1888    /// The outer `:behavior` slot carries the M2 Servico-runtime typed
1889    /// composite — the load-bearing container of every OTP-shaped
1890    /// per-Servico lifecycle-callback path axis every long-running wasm
1891    /// component's runtime dispatches on (INSPIRATIONS §II.3 — Erlang/
1892    /// OTP `gen_server:init/1` / `handle_call/3` / `handle_cast/2` /
1893    /// `handle_info/2` / `code_change/3` / `terminate/2` primitives
1894    /// translated onto pleme-io's typed `:behavior :on-init` /
1895    /// `:on-call` / `:on-cast` / `:on-info` / `:on-state-change` /
1896    /// `:on-terminate` sub-slot axes; CAIXA-SDLC §II — the typed-M2
1897    /// slot algebra the wasm-engine + `pleme-computeunit` Helm-library
1898    /// chart both fan on). Every per-`:behavior` axis threads through a
1899    /// lifted per-callback accessor on the [`BehaviorSpec`] type
1900    /// (9b4ecde / d66c702 / 156ddbe / 99616ac / 4846cef / 701add7).
1901    /// Every downstream consumer that reaches for a behavior axis
1902    /// first passes through this outer accessor onto the composite
1903    /// and then dispatches onto the per-callback accessor — the
1904    /// two-level dispatch means every per-`:behavior` reader now
1905    /// routes through a typed dispatch on the substrate primitive at
1906    /// both altitudes.
1907    ///
1908    /// Composes cross-slot with the M2 `:upgrade-from` gate: the
1909    /// [`crate::upgrade::validate_upgrade_from_against_behavior`]
1910    /// cross-slot composition gate at [`crate::StandardLayout::verify`]
1911    /// keys the "per-version `:state-change` instruction must have a
1912    /// `:on-state-change` callback" precondition off this accessor's
1913    /// composite (the callback-side counterpart to the
1914    /// `:upgrade-from :instructions :state-change :script` refusal at
1915    /// the appup-side). Threading that gate's traversal input through
1916    /// this accessor closes the cross-slot invariant on the substrate
1917    /// primitive, not on the raw field.
1918    ///
1919    /// Prior to this lift the `.behavior` `Option<BehaviorSpec>`
1920    /// composite was accessed inline at four production sites — the
1921    /// [`crate::StandardLayout::verify`] per-`:behavior` shape gate's
1922    /// `if let Some(b) = &caixa.behavior { … }` traversal head
1923    /// (caixa-core/src/layout.rs:896, which drives the per-arm
1924    /// `BehaviorError` refusal cascade + the per-callback on-disk
1925    /// [`crate::LayoutError::MissingEntry`] existence check under
1926    /// [`crate::render::LAYOUT_MISSING_ENTRY_KIND_BEHAVIOR_CALLBACK`]),
1927    /// the [`crate::upgrade::validate_upgrade_from_against_behavior`]
1928    /// cross-slot composition gate's `caixa.behavior.as_ref()`
1929    /// traversal-input feed (caixa-core/src/layout.rs:1008, which
1930    /// drives the `:state-change` ↔ `:on-state-change` precondition
1931    /// refusal), the [`crate::render::servico_m2_overlay`] per-Servico
1932    /// M2 overlay emitter's `if let Some(behavior) = &caixa.behavior
1933    /// { … }` traversal head (caixa-core/src/render.rs:18513, which
1934    /// drives the `M2_KEY_BEHAVIOR`-keyed `behavior.is_empty()`-gated
1935    /// `serde_yaml` projection every `caixa-helm` / `caixa-flux`
1936    /// Servico values-block emitter fans on), and the
1937    /// [`Self::declared_servico_slots`] per-Servico M2 declared-slot-
1938    /// set enumerator's `self.behavior.is_some()` presence probe
1939    /// (caixa-core/src/manifest.rs:1919, which drives the
1940    /// `M2_AUTHOR_KEY_BEHAVIOR` kebab-case author-label push every
1941    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
1942    /// gate reads) — four open-coded outer-field accesses that
1943    /// expressed no compile-time link back to the typed slot at the
1944    /// [`Caixa`] altitude. A future extension of the `:behavior`
1945    /// outer axis to a richer author surface (a per-callback overlay
1946    /// resolver the operator materializes at admission time so a
1947    /// cluster-specific policy can inject a per-callback tracing
1948    /// interceptor without re-authoring the `caixa.lisp`, a promotion
1949    /// of the plain `Option<BehaviorSpec>` to a richer `{static,
1950    /// dynamic}` partition once a runtime-resolved behavior-swap
1951    /// surface lands, the M4 per-callback middleware chain the
1952    /// caixa-operator's per-Servico admission webhook keys off) would
1953    /// have had to be threaded through all four open-coded copies in
1954    /// lockstep or one consumer would silently disagree with the
1955    /// peers on which behavior composite a given Caixa resolves to —
1956    /// the layout gate's per-callback existence-check seed reading
1957    /// the raw slot while the peer `servico_m2_overlay` emitter read
1958    /// an operator-resolved slot would silently split the build-time
1959    /// callback-shape gate from the runtime `ComputeUnit` CR emission
1960    /// gate from the cross-slot `:state-change` composition gate from
1961    /// the M2 declared-slot enumerator, a four-consumer split far
1962    /// from the source `caixa.lisp` with no field naming the
1963    /// behavior-drift root cause. Lifting the resolution rule to a
1964    /// typed method on the substrate primitive means every downstream
1965    /// consumer of the caixa's per-`Caixa` OTP-callback-table outer-
1966    /// composite surface reaches for exactly one typed dispatch — the
1967    /// resolver's accept-set migrates as a unit on any future axis
1968    /// addition.
1969    ///
1970    /// Second outer top-level [`Caixa`] `Option<&Composite>`-return
1971    /// composite-reference accessor — sibling to the opening
1972    /// [`Self::limits`] (b2bd9d7) accessor on the outer-`Caixa`
1973    /// `Option<&Composite>` composite-reference sub-family, extends
1974    /// the "one typed dispatch on the substrate primitive, thin
1975    /// projections at each consumer" discipline onto the second of
1976    /// the three M2 Servico-runtime slots. The remaining
1977    /// `Option<&Composite>` axes at the outer top-level [`Caixa`]
1978    /// altitude — the M3 mesh-slot family (`:politicas`,
1979    /// `:placement`, `:entrada` — already closed on the inner
1980    /// [`crate::AplicacaoSpec`] altitude via 534dc21 / 9abb8f0 /
1981    /// d32111c) — remain the future sibling lifts on the outer
1982    /// top-level projection. Returns `Option<&BehaviorSpec>` (not
1983    /// the owning composite by copy or clone) because every
1984    /// downstream consumer of the behavior composite treats it as a
1985    /// read-only per-callback dispatch source — the reference-view is
1986    /// the narrowest borrow that supports every present + roadmapped
1987    /// consumer (per-callback accessor dispatch, `.is_empty()`-gated
1988    /// overlay projection, presence-probe early return on the
1989    /// "author-omitted `:behavior` ⇒ runtime-default applies"
1990    /// partition, cross-slot `:state-change` composition input)
1991    /// without cloning the composite through every consumer's fast
1992    /// path. The `Option` half of the return-type preserves the
1993    /// load-bearing "author-omitted `:behavior` ⇒ runtime-default
1994    /// applies" partition (not a default composite the downstream
1995    /// must reject on emptiness) — the accessor projects the raw
1996    /// `Option<BehaviorSpec>` slot's presence bit through the
1997    /// reference-return unchanged. Named `behavior()` to match the
1998    /// storage field's name verbatim and the tatara-lisp author-
1999    /// surface term (`:behavior`) the field's own docstring already
2000    /// carries.
2001    #[must_use]
2002    pub fn behavior(&self) -> Option<&crate::BehaviorSpec> {
2003        self.behavior.as_ref()
2004    }
2005
2006    /// Substrate-canonical per-`Caixa` `:politicas` M3 mesh-slot outer-
2007    /// composite MESH-COMPOSITION-shaped mesh-policy optional-composite-
2008    /// reference accessor every consumer of the top-level manifest's
2009    /// per-Aplicacao [`crate::aplicacao::MeshPolicy`] outer-composite
2010    /// reader keys off — returns the author-declared `:politicas` typed
2011    /// composite verbatim as an `Option<&MeshPolicy>` reference over the
2012    /// same backing storage the raw `self.politicas.as_ref()` field
2013    /// access borrows from, with `None` naming the "no `:politicas`
2014    /// block authored — every per-axis mesh-policy scalar defers to the
2015    /// cluster-default arm named on the per-axis
2016    /// [`crate::aplicacao::MeshPolicy::timeout`] /
2017    /// [`crate::aplicacao::MeshPolicy::retries`] /
2018    /// [`crate::aplicacao::MeshPolicy::circuit_breaker`] /
2019    /// [`crate::aplicacao::MeshPolicy::mtls_required`] /
2020    /// [`crate::aplicacao::MeshPolicy::rate_limit`] scalar-accessor
2021    /// docstrings" partition every downstream caixa-mesh /
2022    /// caixa-flux / caixa-helm Aplicacao-artifact emitter treats as
2023    /// "emit no per-`:politicas` overlay" and the sibling
2024    /// [`Self::aplicacao_view`] Aplicacao-composition seed folds through
2025    /// the [`crate::aplicacao::MeshPolicy::default`] cluster-default
2026    /// arm.
2027    ///
2028    /// The outer `:politicas` slot carries the M3 mesh-slot per-
2029    /// Aplicacao typed composite — the load-bearing container of every
2030    /// mesh-level policy axis every Cilium NetworkPolicy / Gateway API
2031    /// v1.x HTTPRoute / future M4 per-edge policy overlay emitter fans
2032    /// on (MESH-COMPOSITION §III.2 — the Aplicacao's typed mesh-policy
2033    /// composite; §V — the "no infinite blocking" per-call deadline +
2034    /// "sandboxing-by-default" mTLS-enforcement CSE invariants; §III.3
2035    /// — the typed inter-Servico contrato-edge overlay the per-`(:de,
2036    /// :para)` mesh renderer keys off). Every per-`:politicas` axis
2037    /// threads through a lifted per-slot accessor on the
2038    /// [`crate::aplicacao::MeshPolicy`] type: the
2039    /// [`crate::aplicacao::MeshPolicy::mtls_required`] (c0110f1) Cilium
2040    /// mTLS-enforcement toggle, the
2041    /// [`crate::aplicacao::MeshPolicy::retries`] (bdfb399) transient-
2042    /// failure retry budget, the [`crate::aplicacao::MeshPolicy::timeout`]
2043    /// (7073d0f) Gateway-API per-call deadline, the
2044    /// [`crate::aplicacao::MeshPolicy::circuit_breaker`] (b0e741a)
2045    /// Envoy-outlier-detection composite. Every downstream consumer
2046    /// that reaches for a mesh-policy axis first passes through this
2047    /// outer accessor onto the composite and then dispatches onto the
2048    /// per-axis accessor — the two-level dispatch means every per-
2049    /// `:politicas` reader now routes through a typed dispatch on the
2050    /// substrate primitive at both altitudes.
2051    ///
2052    /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2053    /// seed: the Aplicacao-view builder folds the outer `Option`'s
2054    /// author-omitted arm onto the [`crate::aplicacao::MeshPolicy::default`]
2055    /// cluster-default, so the peer inner [`crate::AplicacaoSpec::politicas`]
2056    /// (534dc21) `&MeshPolicy`-return accessor observes a typed
2057    /// composite whether or not the author declared the outer slot.
2058    /// The outer accessor preserves the "author-omitted vs authored-
2059    /// empty" partition the inner accessor's `is_empty()`-gated
2060    /// renderer overlay collapses — routing the presence bit through
2061    /// this accessor keeps the [`Self::declared_mesh_slots`] M3 kind-
2062    /// coherence enumerator's `M3_AUTHOR_KEY_POLITICAS` push separate
2063    /// from the inner `MeshPolicy::is_empty()`-gated overlay elision.
2064    ///
2065    /// Prior to this lift the `.politicas` `Option<MeshPolicy>`
2066    /// composite was accessed inline at two production sites — the
2067    /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2068    /// `self.politicas.clone().unwrap_or_default()` traversal head
2069    /// (caixa-core/src/manifest.rs:1899, which drives the fold onto
2070    /// the [`crate::aplicacao::MeshPolicy::default`] cluster-default
2071    /// arm the inner [`crate::AplicacaoSpec::politicas`] accessor
2072    /// then observes), and the [`Self::declared_mesh_slots`] M3
2073    /// declared-slot-set enumerator's `self.politicas.is_some()`
2074    /// presence probe (caixa-core/src/manifest.rs:1961, which drives
2075    /// the `M3_AUTHOR_KEY_POLITICAS` kebab-case author-label push
2076    /// every [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
2077    /// coherence gate reads) — two open-coded outer-field accesses
2078    /// that expressed no compile-time link back to the typed slot at
2079    /// the [`Caixa`] altitude. A future extension of the `:politicas`
2080    /// outer axis to a richer author surface (a per-cluster
2081    /// `:politicas-overrides` slot the operator materializes at
2082    /// admission time so a cluster-specific policy can tighten the
2083    /// caixa-declared bound without re-authoring the `caixa.lisp`, a
2084    /// promotion of the plain `Option<MeshPolicy>` to a richer
2085    /// `{static, dynamic}` partition once the M4 per-edge
2086    /// contrato-scoped policy-override surface lands, the M5 traffic-
2087    /// shaping composition the caixa-operator's per-Aplicacao mesh
2088    /// admission webhook keys off) would have had to be threaded
2089    /// through both open-coded copies in lockstep or the Aplicacao-
2090    /// composition seed's default-fold arm would silently disagree
2091    /// with the M3 declared-slot enumerator on which policy composite
2092    /// a given Caixa resolves to — the seed reading an operator-
2093    /// resolved slot while the enumerator's presence probe read the
2094    /// raw slot would silently split the build-time mesh-artifact
2095    /// emission gate from the M3 declared-slot enumerator's kind-
2096    /// coherence gate, a two-consumer split far from the source
2097    /// `caixa.lisp` with no field naming the policy-drift root cause.
2098    /// Lifting the resolution rule to a typed method on the substrate
2099    /// primitive means every downstream consumer of the caixa's per-
2100    /// `Caixa` MESH-COMPOSITION mesh-policy outer-composite surface
2101    /// reaches for exactly one typed dispatch — the resolver's
2102    /// accept-set migrates as a unit on any future axis addition.
2103    ///
2104    /// Third outer top-level [`Caixa`] `Option<&Composite>`-return
2105    /// composite-reference accessor — sibling to the opening
2106    /// [`Self::limits`] (b2bd9d7) and [`Self::behavior`] (35d8b52)
2107    /// accessors on the outer-`Caixa` `Option<&Composite>` composite-
2108    /// reference sub-family, extends the "one typed dispatch on the
2109    /// substrate primitive, thin projections at each consumer"
2110    /// discipline onto the first of the three M3 mesh-slot axes.
2111    /// Peer of the closed inner mesh-slot outer-composite family the
2112    /// sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
2113    /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2114    /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2115    /// accessor pins already close on the inner [`crate::AplicacaoSpec`]
2116    /// altitude — opens the outer top-level [`Caixa`] altitude's M3
2117    /// mesh-slot arm of the composite-reference family the remaining
2118    /// two axes (`:placement`, `:entrada`) fold onto in future
2119    /// sibling lifts. Returns `Option<&MeshPolicy>` (not the owning
2120    /// composite by copy or clone) because every downstream consumer
2121    /// of the mesh-policy composite treats it as a read-only per-axis
2122    /// dispatch source — the reference-view is the narrowest borrow
2123    /// that supports every present + roadmapped consumer (per-axis
2124    /// accessor dispatch, `.is_empty()`-gated overlay projection,
2125    /// presence-probe early return on the "author-omitted `:politicas`
2126    /// ⇒ cluster-default applies" partition, `Aplicacao`-composition
2127    /// seed's default-fold arm) without cloning the composite through
2128    /// every consumer's fast path. The `Option` half of the return-
2129    /// type preserves the load-bearing "author-omitted `:politicas` ⇒
2130    /// cluster-default applies" partition (not a default composite
2131    /// the downstream must reject on emptiness) — the accessor
2132    /// projects the raw `Option<MeshPolicy>` slot's presence bit
2133    /// through the reference-return unchanged. Named `politicas()` to
2134    /// match the storage field's name verbatim and the tatara-lisp
2135    /// author-surface term (`:politicas`) the field's own docstring
2136    /// already carries.
2137    #[must_use]
2138    pub fn politicas(&self) -> Option<&crate::aplicacao::MeshPolicy> {
2139        self.politicas.as_ref()
2140    }
2141
2142    /// Substrate-canonical per-`Caixa` `:placement` M3 mesh-slot outer-
2143    /// composite MESH-COMPOSITION-shaped distribution optional-composite-
2144    /// reference accessor every consumer of the top-level manifest's
2145    /// per-Aplicacao [`crate::aplicacao::Placement`] outer-composite
2146    /// reader keys off — returns the author-declared `:placement` typed
2147    /// composite verbatim as an `Option<&Placement>` reference over the
2148    /// same backing storage the raw `self.placement.as_ref()` field
2149    /// access borrows from, with `None` naming the "no `:placement`
2150    /// block authored — every per-axis placement scalar defers to the
2151    /// cluster-default arm named on the per-axis
2152    /// [`crate::aplicacao::Placement::estrategia`] /
2153    /// [`crate::aplicacao::Placement::clusters`] /
2154    /// [`crate::aplicacao::Placement::affinity`] /
2155    /// [`crate::aplicacao::Placement::shard_key`] scalar-accessor
2156    /// docstrings" partition every downstream caixa-mesh /
2157    /// caixa-flux / caixa-helm Aplicacao-artifact emitter treats as
2158    /// "emit no per-`:placement` overlay" and the sibling
2159    /// [`Self::aplicacao_view`] Aplicacao-composition seed folds through
2160    /// the [`crate::aplicacao::Placement::default`] cluster-default arm.
2161    ///
2162    /// The outer `:placement` slot carries the M3 mesh-slot per-
2163    /// Aplicacao typed distribution composite — the load-bearing
2164    /// container of every where-does-this-Aplicacao-run axis every
2165    /// caixa-mesh programs.yaml per-cluster distribution overlay /
2166    /// caixa-flux per-Aplicacao GitRepository/HelmRelease fan-out /
2167    /// future M4 per-Aplicacao Akka-style cluster-sharding entity-id
2168    /// resolver emitter fans on (MESH-COMPOSITION §II.4 — the
2169    /// Aplicacao's typed distribution composite; §V CSE invariants —
2170    /// "distribution is a first-class typed composite, not a runtime
2171    /// scheduler hint" the per-axis scalars enforce; §III.3 — the
2172    /// typed inter-Servico contrato-edge overlay the per-cluster
2173    /// mesh renderer keys off). Every per-`:placement` axis threads
2174    /// through a lifted per-slot accessor on the
2175    /// [`crate::aplicacao::Placement`] type: the
2176    /// [`crate::aplicacao::Placement::estrategia`] (921fe1b)
2177    /// MESH-COMPOSITION distribution-strategy scalar, the
2178    /// [`crate::aplicacao::Placement::clusters`] (a6e18d7) per-cluster
2179    /// distribution-target slice, the [`crate::aplicacao::Placement::affinity`]
2180    /// M3-Adaptive-compression-hint optional-scalar, and the
2181    /// [`crate::aplicacao::Placement::shard_key`] (7cd2a28) Akka-cluster-
2182    /// sharding extractor-expression optional-scalar. Every downstream
2183    /// consumer that reaches for a placement axis first passes through
2184    /// this outer accessor onto the composite and then dispatches onto
2185    /// the per-axis accessor — the two-level dispatch means every per-
2186    /// `:placement` reader now routes through a typed dispatch on the
2187    /// substrate primitive at both altitudes.
2188    ///
2189    /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2190    /// seed: the Aplicacao-view builder folds the outer `Option`'s
2191    /// author-omitted arm onto the [`crate::aplicacao::Placement::default`]
2192    /// cluster-default, so the peer inner [`crate::AplicacaoSpec::placement`]
2193    /// (9abb8f0) `&Placement`-return accessor observes a typed composite
2194    /// whether or not the author declared the outer slot. The outer
2195    /// accessor preserves the "author-omitted vs authored-empty" partition
2196    /// the inner accessor collapses at the cluster-default fold —
2197    /// routing the presence bit through this accessor keeps the
2198    /// [`Self::declared_mesh_slots`] M3 kind-coherence enumerator's
2199    /// `M3_AUTHOR_KEY_PLACEMENT` push separate from the inner
2200    /// [`crate::AplicacaoSpec::validate_placement`]-gated overlay
2201    /// dispatch.
2202    ///
2203    /// Prior to this lift the `.placement` `Option<Placement>`
2204    /// composite was accessed inline at two production sites — the
2205    /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2206    /// `self.placement.clone().unwrap_or_default()` traversal head
2207    /// (caixa-core/src/manifest.rs:2036, which drives the fold onto
2208    /// the [`crate::aplicacao::Placement::default`] cluster-default
2209    /// arm the inner [`crate::AplicacaoSpec::placement`] accessor
2210    /// then observes), and the [`Self::declared_mesh_slots`] M3
2211    /// declared-slot-set enumerator's `self.placement.is_some()`
2212    /// presence probe (caixa-core/src/manifest.rs:2100, which drives
2213    /// the `M3_AUTHOR_KEY_PLACEMENT` kebab-case author-label push
2214    /// every [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
2215    /// coherence gate reads) — two open-coded outer-field accesses
2216    /// that expressed no compile-time link back to the typed slot at
2217    /// the [`Caixa`] altitude. A future extension of the `:placement`
2218    /// outer axis to a richer author surface (a per-cluster
2219    /// `:placement-overrides` slot the operator materializes at
2220    /// admission time so a cluster-specific placement can tighten the
2221    /// caixa-declared bound without re-authoring the `caixa.lisp`, a
2222    /// per-tenant placement-alias table the M4
2223    /// `mesh.pleme.io/v1alpha1/Aplicacao` CR materializer resolves
2224    /// per-CR at admission time, a promotion of the plain
2225    /// `Option<Placement>` to a richer `{static, dynamic}` partition
2226    /// once Orleans-style virtual-actor dynamic placement comes into
2227    /// typed scope) would have had to be threaded through both open-
2228    /// coded copies in lockstep or the Aplicacao-composition seed's
2229    /// default-fold arm would silently disagree with the M3 declared-
2230    /// slot enumerator on which distribution composite a given Caixa
2231    /// resolves to — the seed reading an operator-resolved slot while
2232    /// the enumerator's presence probe read the raw slot would
2233    /// silently split the build-time distribution-artifact emission
2234    /// gate from the M3 declared-slot enumerator's kind-coherence
2235    /// gate, a two-consumer split far from the source `caixa.lisp`
2236    /// with no field naming the distribution-drift root cause.
2237    /// Lifting the resolution rule to a typed method on the substrate
2238    /// primitive means every downstream consumer of the caixa's per-
2239    /// `Caixa` MESH-COMPOSITION distribution outer-composite surface
2240    /// reaches for exactly one typed dispatch — the resolver's
2241    /// accept-set migrates as a unit on any future axis addition.
2242    ///
2243    /// Fourth outer top-level [`Caixa`] `Option<&Composite>`-return
2244    /// composite-reference accessor — sibling to the opening
2245    /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) M2-
2246    /// Servico-runtime pair and the peer [`Self::politicas`] (5d23d29)
2247    /// M3-mesh-slot arm on the outer-`Caixa` `Option<&Composite>`
2248    /// composite-reference sub-family, folds on the "one typed
2249    /// dispatch on the substrate primitive, thin projections at each
2250    /// consumer" discipline extended onto the second of the three M3
2251    /// mesh-slot axes. Peer of the closed inner mesh-slot outer-
2252    /// composite family the sibling
2253    /// [`crate::AplicacaoSpec::politicas`] (534dc21) /
2254    /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2255    /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2256    /// accessor pins already close on the inner
2257    /// [`crate::AplicacaoSpec`] altitude — folds on the outer top-
2258    /// level [`Caixa`] altitude's M3 mesh-slot arm the sibling
2259    /// [`Self::politicas`] opened, extending the discipline onto the
2260    /// second of the three M3 mesh-slot axes. The remaining M3
2261    /// mesh-slot axis (`:entrada`) folds onto this accessor's
2262    /// discipline in the final sibling lift, closing the outer top-
2263    /// level [`Caixa`] `Option<&Composite>` M3 mesh-slot sub-family.
2264    /// Returns `Option<&Placement>` (not the owning composite by copy
2265    /// or clone) because every downstream consumer of the placement
2266    /// composite treats it as a read-only per-axis dispatch source —
2267    /// the reference-view is the narrowest borrow that supports every
2268    /// present + roadmapped consumer (per-axis accessor dispatch,
2269    /// serde composite-serialization on the programs.yaml overlay,
2270    /// presence-probe early return on the "author-omitted `:placement`
2271    /// ⇒ cluster-default applies" partition, `Aplicacao`-composition
2272    /// seed's default-fold arm) without cloning the composite through
2273    /// every consumer's fast path. The `Option` half of the return-
2274    /// type preserves the load-bearing "author-omitted `:placement` ⇒
2275    /// cluster-default applies" partition (not a default composite
2276    /// the downstream must reject on emptiness) — the accessor
2277    /// projects the raw `Option<Placement>` slot's presence bit
2278    /// through the reference-return unchanged. Named `placement()` to
2279    /// match the storage field's name verbatim and the tatara-lisp
2280    /// author-surface term (`:placement`) the field's own docstring
2281    /// already carries.
2282    #[must_use]
2283    pub fn placement(&self) -> Option<&crate::aplicacao::Placement> {
2284        self.placement.as_ref()
2285    }
2286
2287    /// Substrate-canonical per-`Caixa` `:entrada` M3 mesh-slot outer-
2288    /// composite MESH-COMPOSITION-shaped external-gateway optional-
2289    /// composite-reference accessor every consumer of the top-level
2290    /// manifest's per-Aplicacao [`crate::aplicacao::Entrada`] outer-
2291    /// composite reader keys off — returns the author-declared
2292    /// `:entrada` typed composite verbatim as an `Option<&Entrada>`
2293    /// reference over the same backing storage the raw
2294    /// `self.entrada.as_ref()` field access borrows from, with `None`
2295    /// naming the "no `:entrada` block authored — this Aplicacao is
2296    /// cluster-internal, no `Gateway`/`HTTPRoute` fan-out emitted"
2297    /// partition every downstream caixa-mesh Gateway-API artifact
2298    /// emitter treats as "emit no gateway-listener + no `HTTPRoute`
2299    /// backend for this Aplicacao" and the sibling
2300    /// [`Self::aplicacao_view`] Aplicacao-composition seed forwards
2301    /// verbatim (unlike the peer `:politicas` / `:placement` arms,
2302    /// `:entrada` has no cluster-default fold — an omitted `:entrada`
2303    /// stays `None` on the projected [`crate::AplicacaoSpec`] and the
2304    /// peer inner [`crate::AplicacaoSpec::entrada`] accessor observes
2305    /// the same `Option<&Entrada>` presence bit unchanged).
2306    ///
2307    /// The outer `:entrada` slot carries the M3 mesh-slot per-
2308    /// Aplicacao typed external-gateway composite — the load-bearing
2309    /// container of every how-does-the-outside-world-reach-this-
2310    /// Aplicacao axis every caixa-mesh `Gateway`/`HTTPRoute` fan-out
2311    /// emitter fans on (MESH-COMPOSITION §II.5 — the Aplicacao's typed
2312    /// external-entry composite; §V CSE invariants — "the external
2313    /// gateway is a first-class typed composite, not a per-Servico
2314    /// ingress annotation" the per-axis scalars enforce; §III.4 — the
2315    /// typed hostname + backend-Servico pair the per-cluster Gateway-
2316    /// API renderer keys off). Every per-`:entrada` axis threads
2317    /// through a lifted per-slot accessor on the
2318    /// [`crate::aplicacao::Entrada`] type: the
2319    /// [`crate::aplicacao::Entrada::host`] Gateway-API `Listener.hostname`
2320    /// scalar, the [`crate::aplicacao::Entrada::para`] backend-Servico
2321    /// caixa-name scalar, the [`crate::aplicacao::Entrada::paths`]
2322    /// per-rule `HTTPPathMatch` list, the [`crate::aplicacao::Entrada::port`]
2323    /// backend `trigger.service.port` scalar, and the
2324    /// [`crate::aplicacao::Entrada::resolved_paths`] URL-path fallback
2325    /// resolver every HTTPRoute-aware renderer consumes. Every
2326    /// downstream consumer that reaches for an entry axis first passes
2327    /// through this outer accessor onto the composite and then
2328    /// dispatches onto the per-axis accessor — the two-level dispatch
2329    /// means every per-`:entrada` reader now routes through a typed
2330    /// dispatch on the substrate primitive at both altitudes.
2331    ///
2332    /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2333    /// seed: the Aplicacao-view builder forwards the outer `Option`
2334    /// arm verbatim (no default fold — `:entrada` is inherently
2335    /// optional; a cluster-internal Aplicacao has no external gateway
2336    /// at all, not "an external gateway that defaults to nothing"), so
2337    /// the peer inner [`crate::AplicacaoSpec::entrada`] (d32111c)
2338    /// `Option<&Entrada>`-return accessor observes the same presence
2339    /// bit whether or not the author declared the outer slot. Routing
2340    /// the presence bit through this accessor keeps the
2341    /// [`Self::declared_mesh_slots`] M3 kind-coherence enumerator's
2342    /// `M3_AUTHOR_KEY_ENTRADA` push separate from the inner
2343    /// [`crate::AplicacaoSpec::validate_entrada`]-gated
2344    /// hostname/backend/path emission dispatch.
2345    ///
2346    /// Prior to this lift the `.entrada` `Option<Entrada>` composite
2347    /// was accessed inline at two production sites — the
2348    /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2349    /// `self.entrada.clone()` traversal head (caixa-core/src/manifest.rs:2182,
2350    /// which drives the forward onto the peer inner
2351    /// [`crate::AplicacaoSpec::entrada`] accessor the caixa-mesh
2352    /// Gateway-API fan-out then observes), and the
2353    /// [`Self::declared_mesh_slots`] M3 declared-slot-set enumerator's
2354    /// `self.entrada.is_some()` presence probe (caixa-core/src/manifest.rs:2248,
2355    /// which drives the `M3_AUTHOR_KEY_ENTRADA` kebab-case author-
2356    /// label push every [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
2357    /// kind-coherence gate reads) — two open-coded outer-field
2358    /// accesses that expressed no compile-time link back to the typed
2359    /// slot at the [`Caixa`] altitude. A future extension of the
2360    /// `:entrada` outer axis to a richer author surface (a per-cluster
2361    /// `:entrada-overrides` slot the operator materializes at admission
2362    /// time so a cluster-specific hostname can pin the caixa-declared
2363    /// bound without re-authoring the `caixa.lisp`, a per-tenant
2364    /// gateway-alias table the M4 `mesh.pleme.io/v1alpha1/Aplicacao`
2365    /// CR materializer resolves per-CR at admission time, a promotion
2366    /// of the plain `Option<Entrada>` to a richer
2367    /// `{public, private, internal}` partition once Cilium-identity-
2368    /// scoped internal gateways come into typed scope) would have had
2369    /// to be threaded through both open-coded copies in lockstep or the
2370    /// Aplicacao-composition seed's forward arm would silently
2371    /// disagree with the M3 declared-slot enumerator on which external-
2372    /// gateway composite a given Caixa resolves to — the seed reading
2373    /// an operator-resolved slot while the enumerator's presence probe
2374    /// read the raw slot would silently split the build-time gateway-
2375    /// artifact emission gate from the M3 declared-slot enumerator's
2376    /// kind-coherence gate, a two-consumer split far from the source
2377    /// `caixa.lisp` with no field naming the entry-drift root cause.
2378    /// Lifting the resolution rule to a typed method on the substrate
2379    /// primitive means every downstream consumer of the caixa's per-
2380    /// `Caixa` MESH-COMPOSITION external-gateway outer-composite
2381    /// surface reaches for exactly one typed dispatch — the resolver's
2382    /// accept-set migrates as a unit on any future axis addition.
2383    ///
2384    /// Fifth and final outer top-level [`Caixa`] `Option<&Composite>`-
2385    /// return composite-reference accessor — closes the outer-`Caixa`
2386    /// `Option<&Composite>` composite-reference sub-family opened by
2387    /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) on the
2388    /// M2 Servico-runtime arm and extended onto the M3 mesh-slot arm
2389    /// by [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074),
2390    /// folds on the "one typed dispatch on the substrate primitive,
2391    /// thin projections at each consumer" discipline extended onto the
2392    /// third and final M3 mesh-slot axis. Peer of the closed inner
2393    /// mesh-slot outer-composite family the sibling
2394    /// [`crate::AplicacaoSpec::politicas`] (534dc21) /
2395    /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2396    /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2397    /// accessor pins already close on the inner
2398    /// [`crate::AplicacaoSpec`] altitude — this lift closes the mirror
2399    /// sub-family on the outer top-level [`Caixa`] altitude, so both
2400    /// altitudes of the outer-composite reference-return discipline
2401    /// (per-`Caixa` outer-slot presence + per-`AplicacaoSpec` inner-
2402    /// slot presence) now carry the full five-arm accept-set behind a
2403    /// typed dispatch on the substrate primitive. Returns
2404    /// `Option<&Entrada>` (not the owning composite by copy or clone)
2405    /// because every downstream consumer of the entrada composite
2406    /// treats it as a read-only per-axis dispatch source — the
2407    /// reference-view is the narrowest borrow that supports every
2408    /// present + roadmapped consumer (per-axis accessor dispatch,
2409    /// serde composite-serialization on the programs.yaml overlay,
2410    /// presence-probe early return on the "author-omitted `:entrada`
2411    /// ⇒ cluster-internal Aplicacao" partition, `Aplicacao`-composition
2412    /// seed's forward arm) without cloning the composite through every
2413    /// consumer's fast path. The `Option` half of the return-type
2414    /// preserves the load-bearing "author-omitted `:entrada` ⇒
2415    /// cluster-internal Aplicacao" partition (not a default composite
2416    /// the downstream must reject on emptiness — a cluster-internal
2417    /// Aplicacao has no external gateway at all, not "a default gateway
2418    /// that emits nothing"); the accessor projects the raw
2419    /// `Option<Entrada>` slot's presence bit through the reference-
2420    /// return unchanged. Named `entrada()` to match the storage field's
2421    /// name verbatim and the tatara-lisp author-surface term
2422    /// (`:entrada`) the field's own docstring already carries.
2423    #[must_use]
2424    pub fn entrada(&self) -> Option<&crate::aplicacao::Entrada> {
2425        self.entrada.as_ref()
2426    }
2427
2428    /// Substrate-canonical per-`Caixa` `:ci` slot accessor — returns the
2429    /// author-declared typed CI run (`canteiro_types::CiRun`) verbatim as
2430    /// an `Option<&CiRun>`, borrowed from the typed slot's own
2431    /// `Option<CiRun>` storage. `None` when the slot is absent (every
2432    /// non-`Acao` kind, and an `Acao` caixa that hasn't declared `:ci`
2433    /// yet — the latter is caught by [`crate::LayoutError::MissingCi`],
2434    /// not silently accepted).
2435    ///
2436    /// Named `ci()` to match the storage field's name and the
2437    /// tatara-lisp author surface (`:ci`); mirrors the sibling
2438    /// `Option<&Composite>` accessors on this same `Caixa` altitude
2439    /// ([`Self::limits`], [`Self::behavior`], [`Self::politicas`],
2440    /// [`Self::placement`], [`Self::entrada`]) — one typed dispatch on
2441    /// the substrate primitive rather than an open-coded `self.ci.as_ref()`
2442    /// at every consumer.
2443    #[must_use]
2444    pub fn ci(&self) -> Option<&canteiro_types::CiRun> {
2445        self.ci.as_ref()
2446    }
2447
2448    /// Substrate-canonical per-`Caixa` `:estrategia` M2 supervisor-tree-
2449    /// slot flat-spread OTP-shaped sibling-restart-strategy discriminant
2450    /// accessor every consumer of the top-level manifest's per-Supervisor
2451    /// restart-strategy axis keys off — returns the author-declared
2452    /// `:estrategia` variant verbatim as an `Option<RestartStrategy>`,
2453    /// `Copy`-projected from the typed slot's own
2454    /// `Option<crate::supervisor::RestartStrategy>` storage. Optional
2455    /// (`:estrategia` is a flat-spread supervisor-only slot every
2456    /// non-`Supervisor`-kind `defcaixa` carries as `None` by
2457    /// `#[serde(default)]`, and every `Supervisor`-kind `defcaixa` may
2458    /// still omit to defer to [`RestartStrategy::default`] —
2459    /// [`RestartStrategy::OneForOne`] — through the [`Self::supervisor_view`]
2460    /// `unwrap_or_default()` fold; a returned `None` degenerates to the
2461    /// [`SupervisorSpec::default`]-inherited strategy without any silent
2462    /// promotion to a fresh explicit variant at the accessor boundary).
2463    ///
2464    /// The `:estrategia` slot carries the M2 typed OTP-shaped sibling-
2465    /// restart-strategy discriminant every substrate-side per-Supervisor
2466    /// dispatch fans on (INSPIRATIONS §II.2 — OTP `supervisor:strategy`
2467    /// closed-set `one_for_one | one_for_all | rest_for_one |
2468    /// simple_one_for_one` algebra translated onto pleme-io's typed
2469    /// [`RestartStrategy`] enum; CAIXA-SDLC §II — the M2 supervisor-tree
2470    /// slot algebra the operator's hierarchical reconciliation scheduler
2471    /// fans on). The slot is *flat-spread* on the outer top-level `Caixa`
2472    /// (per the field-shape docstring at caixa-core/src/manifest.rs — "The
2473    /// supervisor slots are flat on Caixa (vs nested under a
2474    /// `SupervisorSpec` sub-form) to keep tatara-lisp authoring at one
2475    /// level of nesting"), so the accessor's altitude is the outer
2476    /// [`Caixa`] surface rather than the composed [`SupervisorSpec`]
2477    /// altitude the sibling [`crate::supervisor::SupervisorSpec::estrategia`]
2478    /// (eafb619) accessor keys off. The two typed axes — the outer
2479    /// author-surface `Option<RestartStrategy>` on the [`Caixa`] altitude
2480    /// (author-omitted arm carried as `None`) and the inner post-
2481    /// composition `RestartStrategy` on the [`SupervisorSpec`] altitude
2482    /// (`Option` collapsed through the [`Self::supervisor_view`]
2483    /// `unwrap_or_default()` fold) — now share one accessor discipline for
2484    /// the shared substrate concept "the author-declared OTP-shaped
2485    /// sibling-restart-strategy variant that partitions the downstream
2486    /// per-Supervisor renderer's per-arm fan-out"; the outer-altitude
2487    /// `None` arm is the pre-composition presence bit every declared-slot
2488    /// enumerator ([`Self::declared_supervisor_slots`]) reads, and the
2489    /// inner-altitude non-`Option` `RestartStrategy` is the post-
2490    /// composition partition-dispatch input every strategy-arm consumer
2491    /// ([`SupervisorSpec::validate`], the future wasm-operator's per-
2492    /// Supervisor sibling-restart branch, the future M4
2493    /// `mesh.pleme.io/v1alpha1/Supervisor` CR materializer's admission
2494    /// webhook) fans on.
2495    ///
2496    /// Prior to this lift the `.estrategia` field was accessed inline at
2497    /// two production sites in `caixa-core/src/manifest.rs` — the
2498    /// [`Self::declared_supervisor_slots`] `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA`
2499    /// presence-probe arm at `if self.estrategia.is_some()` (which drives
2500    /// the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
2501    /// coherence gate's per-slot label push) and the [`Self::supervisor_view`]
2502    /// `SupervisorSpec` construction site at `estrategia:
2503    /// self.estrategia.unwrap_or_default()` (which composes the flat-
2504    /// spread outer author-surface `Option<RestartStrategy>` onto the
2505    /// inner post-composition [`SupervisorSpec`] `RestartStrategy` field
2506    /// the [`SupervisorSpec::estrategia`] accessor keys off) — two open-
2507    /// coded field-accesses that expressed no compile-time link back to
2508    /// the typed slot. A future extension of the outer `:estrategia` axis
2509    /// to a richer author surface (a per-cluster strategy override the
2510    /// operator pins through a future `:estrategia-overrides` overlay the
2511    /// MESH-COMPOSITION §III.2 supervision-canary roadmap acknowledges,
2512    /// a per-tenant strategy-alias table the M4 CR materializer resolves
2513    /// per-CR, a per-Supervisor dynamic strategy derivation the future
2514    /// adaptive-supervision engine computes from child-failure-history
2515    /// topology, a per-child-cohort strategy split the future
2516    /// `RestForCohort` extension the INSPIRATIONS.md §II.2 Erlang/OTP
2517    /// absorption roadmap acknowledges, a promotion of the plain
2518    /// `Option<RestartStrategy>` to a richer
2519    /// `AuthorDeclaredStrategy { declared, overlay }` newtype once the
2520    /// operator-resolved overlay lands) would have had to be threaded
2521    /// through both open-coded copies in lockstep or the enumerator's
2522    /// presence probe and the composition site's `unwrap_or_default()`
2523    /// fold would silently disagree on which strategy a given [`Caixa`]
2524    /// resolves to (an author's `:estrategia OneForAll` would satisfy
2525    /// the enumerator's presence probe while the composition site
2526    /// silently rendered a stale `OneForOne`, or vice versa). Lifting
2527    /// the resolution rule to a typed method on the substrate primitive
2528    /// means every downstream consumer of the caixa's per-`Caixa` outer-
2529    /// altitude sibling-restart-strategy surface reaches for exactly one
2530    /// typed dispatch — the resolver's accept-set migrates as a unit on
2531    /// any future axis addition.
2532    ///
2533    /// First outer top-level [`Caixa`] `Option<Copy>`-return supervisor-
2534    /// tree-slot flat-spread accessor for M2 supervisor-slot Copy-carry
2535    /// axes — opens the outer-`Caixa` `Option<Copy>` flat-spread
2536    /// projection pattern the sibling per-`Caixa` `:max-restarts`
2537    /// `Option<u32>` and (through the future duration-newtype landing)
2538    /// `:restart-window` `Option<Duration>` future outer-scalar lifts
2539    /// fold on. Peer of the inner-altitude [`crate::supervisor::SupervisorSpec::estrategia`]
2540    /// (eafb619) `Copy`-return sibling-restart-strategy scalar accessor on
2541    /// the post-composition [`SupervisorSpec`] altitude — same "one
2542    /// typed dispatch on the substrate primitive, thin projections at
2543    /// each consumer" discipline extended onto the pre-composition outer
2544    /// author-surface [`Caixa`] altitude for the same OTP-shaped
2545    /// sibling-restart-strategy axis. Peer of the closed outer-`Caixa`
2546    /// `Option<&Composite>` composite-reference family the sibling
2547    /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) /
2548    /// [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074) /
2549    /// [`Self::entrada`] (e4128e4) accessor pins already carry on the
2550    /// outer `Option<&Composite>` altitude — extends the outer-`Caixa`
2551    /// typed-slot accessor discipline onto the flat-spread M2 supervisor-
2552    /// tree `Option<Copy>`-discriminant sub-family the sibling M3
2553    /// [`crate::aplicacao::Placement::estrategia`] (921fe1b)
2554    /// `PlacementStrategy` `Copy`-composite-enum scalar accessor already
2555    /// pins on the inner-altitude per-`:placement` composite. Named
2556    /// `estrategia()` to match the storage field's name and the
2557    /// per-[`SupervisorSpec`] peer [`crate::supervisor::SupervisorSpec::estrategia`]
2558    /// / per-[`crate::aplicacao::Placement`] peer
2559    /// [`crate::aplicacao::Placement::estrategia`] method-name discipline
2560    /// verbatim; the accessor's identity name maps onto the canonical
2561    /// OTP-shape supervision vocabulary the [`RestartStrategy`] enum's
2562    /// docstring already carries.
2563    #[must_use]
2564    pub fn estrategia(&self) -> Option<crate::supervisor::RestartStrategy> {
2565        self.estrategia
2566    }
2567
2568    /// Substrate-canonical per-`Caixa` `:max-restarts` M2 supervisor-tree-
2569    /// slot flat-spread OTP-`MaxIntensity`-shaped restart-budget-count
2570    /// scalar accessor every consumer of the top-level manifest's per-
2571    /// Supervisor `:max-restarts` restart-budget-count axis keys off —
2572    /// returns the author-declared `:max-restarts` typed `Option<u32>`
2573    /// verbatim, `Copy`-projected from the typed slot's own `Option<u32>`
2574    /// storage (`u32` is `Copy`, so `Option<u32>` is `Copy` and the
2575    /// accessor returns by value; no borrow of `&self` past the call).
2576    /// Optional (`:max-restarts` is a flat-spread supervisor-only slot
2577    /// every non-`Supervisor`-kind `defcaixa` carries as `None` by
2578    /// `#[serde(default)]`, and every `Supervisor`-kind `defcaixa` may
2579    /// still omit to defer to the [`Self::supervisor_view`]
2580    /// `unwrap_or(5)` fold's OTP-canonical `{intensity, 5, 60}` default).
2581    ///
2582    /// The `:max-restarts` slot carries the M2 typed Erlang/OTP-shaped
2583    /// `MaxIntensity` restart-budget count that pairs with the sibling
2584    /// `:restart-window` `Period` to form the `MaxIntensity / Period`
2585    /// restart-intensity ratio the supervisor trips its own escalation on
2586    /// (INSPIRATIONS §II.2 — Erlang/OTP `supervisor` `{intensity, 5, 60}`
2587    /// worker-supervisor default; RUNTIME-PATTERNS §II.2; CAIXA-SDLC §II
2588    /// — the M2 supervisor-tree slot algebra the operator's hierarchical
2589    /// reconciliation scheduler fans on). The slot is *flat-spread* on
2590    /// the outer top-level `Caixa` (per the field-shape docstring at
2591    /// caixa-core/src/manifest.rs — "The supervisor slots are flat on
2592    /// Caixa (vs nested under a `SupervisorSpec` sub-form)"), so the
2593    /// accessor's altitude is the outer [`Caixa`] surface rather than the
2594    /// composed [`SupervisorSpec`] altitude the sibling
2595    /// [`crate::supervisor::SupervisorSpec::max_restarts`] accessor keys
2596    /// off. The two typed axes — the outer author-surface `Option<u32>`
2597    /// on the [`Caixa`] altitude (author-omitted arm carried as `None`)
2598    /// and the inner post-composition `u32` on the [`SupervisorSpec`]
2599    /// altitude (`Option` collapsed through the [`Self::supervisor_view`]
2600    /// `unwrap_or(5)` fold) — now share one accessor discipline for the
2601    /// shared substrate concept "the author-declared OTP-shaped
2602    /// restart-budget count every downstream per-Supervisor consumer's
2603    /// restart-intensity budget-vs-count comparator fans on".
2604    ///
2605    /// Prior to this lift the `.max_restarts` field was accessed inline
2606    /// at two production sites in `caixa-core/src/manifest.rs` — the
2607    /// [`Self::declared_supervisor_slots`] `SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS`
2608    /// presence-probe arm at `if self.max_restarts.is_some()` (which
2609    /// drives the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
2610    /// kind-coherence gate's per-slot label push) and the
2611    /// [`Self::supervisor_view`] `SupervisorSpec` construction site at
2612    /// `max_restarts: self.max_restarts.unwrap_or(5)` (which composes the
2613    /// flat-spread outer author-surface `Option<u32>` onto the inner
2614    /// post-composition [`SupervisorSpec`] `u32` field the
2615    /// [`SupervisorSpec::max_restarts`] accessor keys off) — two open-
2616    /// coded field-accesses that expressed no compile-time link back to
2617    /// the typed slot. A future extension of the outer `:max-restarts`
2618    /// axis to a richer author surface (a per-cluster restart-budget
2619    /// override the operator pins through a future `:max-restarts-overrides`
2620    /// overlay the MESH-COMPOSITION §III.2 supervision-canary roadmap
2621    /// acknowledges, a per-tenant restart-budget-alias table the M4 CR
2622    /// materializer resolves per-CR, a per-Supervisor dynamic restart-
2623    /// budget derivation the future adaptive-supervision engine computes
2624    /// from child-failure-history topology, a promotion of the plain
2625    /// `Option<u32>` count to a richer `{MaxR, MaxT}` per-child-cohort
2626    /// restart-budget-partition once the INSPIRATIONS §II.2 Erlang/OTP
2627    /// per-child-cohort roadmap lands) would have had to be threaded
2628    /// through both open-coded copies in lockstep or the enumerator's
2629    /// presence probe and the composition site's `unwrap_or(5)` fold
2630    /// would silently disagree on which restart-budget a given [`Caixa`]
2631    /// resolves to (an author's `:max-restarts 10` would satisfy the
2632    /// enumerator's presence probe while the composition site silently
2633    /// composed the OTP-canonical `5`, or vice versa). Lifting the
2634    /// resolution rule to a typed method on the substrate primitive means
2635    /// every downstream consumer of the caixa's per-`Caixa` outer-altitude
2636    /// restart-budget-count surface reaches for exactly one typed dispatch
2637    /// — the resolver's accept-set migrates as a unit on any future axis
2638    /// addition.
2639    ///
2640    /// Second outer top-level [`Caixa`] `Option<Copy>`-return supervisor-
2641    /// tree-slot flat-spread accessor for M2 supervisor-slot Copy-carry
2642    /// axes — folds on the outer-`Caixa` `Option<Copy>` flat-spread
2643    /// projection pattern the sibling per-`Caixa`
2644    /// [`Self::estrategia`] (ed04d3c) accessor opened, extends the
2645    /// sub-family onto the sibling `Option<u32>` restart-budget-count arm.
2646    /// Peer of the inner-altitude
2647    /// [`crate::supervisor::SupervisorSpec::max_restarts`] `u32` accessor
2648    /// on the post-composition [`SupervisorSpec`] altitude — same "one
2649    /// typed dispatch on the substrate primitive, thin projections at
2650    /// each consumer" discipline extended onto the pre-composition outer
2651    /// author-surface [`Caixa`] altitude for the same OTP-`MaxIntensity`-
2652    /// shaped restart-budget-count axis. Named `max_restarts()` to match
2653    /// the storage field's name and the per-[`SupervisorSpec`] peer
2654    /// [`crate::supervisor::SupervisorSpec::max_restarts`] method-name
2655    /// discipline verbatim; the accessor's identity maps onto the
2656    /// canonical OTP-shape supervision vocabulary the `:max-restarts`
2657    /// field's docstring already carries.
2658    #[must_use]
2659    pub const fn max_restarts(&self) -> Option<u32> {
2660        self.max_restarts
2661    }
2662
2663    /// Substrate-canonical per-`Caixa` `:restart-window` M2 supervisor-
2664    /// tree-slot flat-spread OTP-`Period`-shaped restart-intensity-
2665    /// denominator raw-duration-string scalar accessor every consumer of
2666    /// the top-level manifest's per-Supervisor `:restart-window` sliding-
2667    /// window axis keys off — returns the author-declared `:restart-window`
2668    /// typed `Option<String>` verbatim as an `Option<&str>`, borrowed
2669    /// from the typed slot's own `Option<String>` storage. `None` when
2670    /// the slot is absent (the canonical "never reset — every restart
2671    /// across the supervisor's lifetime counts against the sibling
2672    /// `:max-restarts` budget" sentinel every non-`Supervisor`-kind
2673    /// `defcaixa` carries by `#[serde(default)]` and every
2674    /// `Supervisor`-kind `defcaixa` may still omit to defer to the
2675    /// [`Self::supervisor_view`] `restart_window: None` composition
2676    /// through the [`crate::supervisor::duration_codec::parse`] soft-
2677    /// swallow `.and_then(|s| … .ok())` fold).
2678    ///
2679    /// The `:restart-window` slot carries the raw M2 typed Erlang/OTP-
2680    /// shaped `Period` sliding-observation-interval duration string that
2681    /// pairs with the sibling `:max-restarts` `MaxIntensity` restart-
2682    /// budget count to form the `MaxIntensity / Period` restart-intensity
2683    /// ratio the supervisor trips its own escalation on (INSPIRATIONS
2684    /// §II.2 — Erlang/OTP `supervisor` `{intensity, 5, 60}` worker-
2685    /// supervisor default; RUNTIME-PATTERNS §II.2). The outer-`Caixa`
2686    /// slot stores the raw duration string (`"60s"`, `"5m"`, `"500ms"`)
2687    /// authored under `:restart-window` — the typed [`SupervisorSpec`]
2688    /// holds an `Option<Duration>` routed through the shared
2689    /// [`crate::supervisor::duration_codec`] via `with = "duration_codec"`
2690    /// — so the outer altitude's accessor returns `Option<&str>` (raw
2691    /// authoring surface) while the inner altitude's
2692    /// [`crate::supervisor::SupervisorSpec::restart_window`] returns
2693    /// `Option<Duration>` (parsed typed surface). The parse-refusal arm
2694    /// is closed by the sibling [`Self::validate_restart_window`] gate
2695    /// that surfaces [`ManifestError::RestartWindowMalformed`] naming
2696    /// the offending value; the view-construction path
2697    /// [`Self::supervisor_view`] soft-swallows the same parse error to
2698    /// `None` to keep the view best-effort.
2699    ///
2700    /// Prior to this lift the `.restart_window` field was accessed inline
2701    /// at three production sites in `caixa-core/src/manifest.rs` — the
2702    /// [`Self::declared_supervisor_slots`]
2703    /// `SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW` presence-probe arm at
2704    /// `if self.restart_window.is_some()` (which drives the
2705    /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
2706    /// coherence gate's per-slot label push), the
2707    /// [`Self::validate_restart_window`] `let Some(s) =
2708    /// self.restart_window.as_deref()` empty-and-shape gate binding
2709    /// (which folds the raw string through the shared
2710    /// [`crate::supervisor::duration_codec::parse`] to surface
2711    /// [`ManifestError::RestartWindowMalformed`] naming the offending
2712    /// value), and the [`Self::supervisor_view`] `self.restart_window
2713    /// .as_deref().and_then(…)` view-construction fold (which composes
2714    /// the flat-spread outer author-surface `Option<String>` onto the
2715    /// inner post-composition [`SupervisorSpec`] `Option<Duration>`
2716    /// field the [`SupervisorSpec::restart_window`] accessor keys off) —
2717    /// three open-coded field-accesses that expressed no compile-time
2718    /// link back to the typed slot. A future extension of the outer
2719    /// `:restart-window` axis to a richer author surface (a per-cluster
2720    /// window override, a per-tenant window-alias table, a per-Supervisor
2721    /// dynamic window derivation the future adaptive-supervision engine
2722    /// computes from child-failure-history topology, a promotion of the
2723    /// plain `Option<String>` raw duration to a typed `Option<Duration>`
2724    /// once the future author-surface parser lands at the [`Caixa`]
2725    /// altitude and the raw-string form is retired) would have had to be
2726    /// threaded through every open-coded copy in lockstep or the three
2727    /// consumers would silently disagree on which raw string a given
2728    /// [`Caixa`] resolves to. Lifting the resolution rule to a typed
2729    /// method on the substrate primitive means every downstream consumer
2730    /// of the caixa's per-`Caixa` outer-altitude restart-window raw-
2731    /// string surface reaches for exactly one typed dispatch — the
2732    /// resolver's accept-set migrates as a unit on any future axis
2733    /// addition.
2734    ///
2735    /// Third outer top-level [`Caixa`] supervisor-tree-slot flat-spread
2736    /// accessor — folds on the outer-`Caixa` M2 supervisor-tree flat-
2737    /// spread projection pattern the sibling per-`Caixa`
2738    /// [`Self::estrategia`] (ed04d3c) `Option<Copy>` and
2739    /// [`Self::max_restarts`] `Option<Copy>` accessors opened, extends
2740    /// the sub-family onto the sibling `Option<&str>` raw-duration-
2741    /// string arm (the outer altitude's raw-string form; the inner
2742    /// altitude's parsed [`Duration`] form is the peer
2743    /// [`crate::supervisor::SupervisorSpec::restart_window`] accessor).
2744    /// Peer of the sibling per-`Caixa` `Option<&str>`-return scalar
2745    /// accessors ([`Self::licenca`] / [`Self::repositorio`] /
2746    /// [`Self::descricao`] / [`Self::edicao`]) on the universal-axis
2747    /// outer scalar-projection family the outer-`Caixa` `Option<&str>`
2748    /// sub-family already carries — same "one typed dispatch on the
2749    /// substrate primitive, thin projections at each consumer"
2750    /// discipline extended onto the M2 supervisor-tree flat-spread
2751    /// `Option<&str>` raw-duration-string arm. Named `restart_window()`
2752    /// to match the storage field's name and the per-[`SupervisorSpec`]
2753    /// peer [`crate::supervisor::SupervisorSpec::restart_window`]
2754    /// method-name discipline verbatim; the accessor's identity maps
2755    /// onto the canonical OTP-shape supervision vocabulary the
2756    /// `:restart-window` field's docstring already carries.
2757    #[must_use]
2758    pub fn restart_window(&self) -> Option<&str> {
2759        self.restart_window.as_deref()
2760    }
2761
2762    /// Substrate-canonical per-`Caixa` `:upgrade-from` M2 typed-slot
2763    /// outer-composite OTP-appup-shaped per-prior-version migration-
2764    /// entry-list slice accessor every consumer of the top-level
2765    /// manifest's per-Servico hot-upgrade-block `&[UpgradeFromEntry]`
2766    /// slice-view keys off — returns the author-declared `:upgrade-from`
2767    /// typed `Vec<UpgradeFromEntry>` verbatim as a
2768    /// `&[UpgradeFromEntry]` slice-view over the same backing buffer
2769    /// the raw `self.upgrade_from.as_slice()` field access borrows
2770    /// from. Empty-slice-carrying (the "no hot-upgrade path declared"
2771    /// arm every `defcaixa` without an `:upgrade-from` block carries;
2772    /// the [`Self::from_lisp`] derive folds an omitted `:upgrade-from`
2773    /// through `#[serde(default)]` to `Vec::new()`, so a `Caixa` past
2774    /// parse definitionally carries a `Vec<UpgradeFromEntry>` slot —
2775    /// possibly empty — and the returned `&[UpgradeFromEntry]`
2776    /// degenerates to an empty slice on that arm without any silent
2777    /// `None` collapse).
2778    ///
2779    /// The outer `:upgrade-from` slot carries the M2 typed OTP-appup
2780    /// migration block — the load-bearing container of every per-
2781    /// prior-`:versao` migration-instruction list the wasm-operator
2782    /// dispatches on at hot-upgrade time (INSPIRATIONS §II.4 — OTP
2783    /// `.appup` per-prior-version `LoadModule | StateChange |
2784    /// SoftPurge | Purge | Restart` instruction algebra translated
2785    /// onto pleme-io's typed `:upgrade-from :from` + `:instructions`
2786    /// entry list; CAIXA-SDLC §II — the typed-M2 slot algebra the
2787    /// operator's hot-upgrade dispatch fans on). Every per-entry axis
2788    /// threads through a lifted per-entry accessor on the
2789    /// [`UpgradeFromEntry`] type: the
2790    /// [`UpgradeFromEntry::prior_versao`] SemVer-shaped previous-
2791    /// version scalar accessor and the
2792    /// [`UpgradeFromEntry::instructions`] `&[UpgradeInstruction]`-
2793    /// return per-entry instruction-list accessor (0137e5a). Every
2794    /// downstream consumer of the hot-upgrade path first passes
2795    /// through this outer accessor onto the slice and then dispatches
2796    /// per-entry through the inner accessors — the two-level dispatch
2797    /// means every per-`:upgrade-from` reader now routes through a
2798    /// typed dispatch on the substrate primitive at both altitudes.
2799    ///
2800    /// Prior to this lift the `.upgrade_from` `Vec<UpgradeFromEntry>`
2801    /// slot was accessed inline at production sites across three
2802    /// files — the [`Self::declared_servico_slots`] M2 declared-slot
2803    /// enumerator's `self.upgrade_from.is_empty()` presence probe
2804    /// (caixa-core/src/manifest.rs, which drives the
2805    /// `M2_AUTHOR_KEY_UPGRADE_FROM` kebab-case author-label push every
2806    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
2807    /// gate reads), the [`crate::StandardLayout::verify`] per-
2808    /// `:upgrade-from` three-stage validation pass (caixa-core/src/
2809    /// layout.rs, which fans onto the
2810    /// [`crate::upgrade::validate_upgrade_from`] per-entry shape +
2811    /// cross-entry duplicate gate, the
2812    /// [`crate::upgrade::validate_upgrade_from_against_versao`]
2813    /// SemVer-precedence cross-slot gate, the
2814    /// [`crate::upgrade::validate_upgrade_from_against_behavior`]
2815    /// `:state-change` ↔ `:on-state-change` cross-slot composition
2816    /// gate, and the per-instruction script-path existence-probe walk
2817    /// that reads each entry's [`UpgradeFromEntry::instructions`] to
2818    /// resolve every declared migration script against the layout
2819    /// root), and the [`crate::render::servico_m2_overlay`] per-
2820    /// Servico M2 overlay emitter's `!caixa.upgrade_from.is_empty()`
2821    /// presence gate + `serde_yaml::to_value(&caixa.upgrade_from)`
2822    /// projection (caixa-core/src/render.rs, which drives the
2823    /// `M2_KEY_UPGRADE_FROM`-keyed `serde_yaml` projection every
2824    /// `caixa-helm` / `caixa-flux` Servico values-block emitter fans
2825    /// on and lands as the ComputeUnit CR's `spec.upgradeFrom` field).
2826    /// A future extension of the outer `:upgrade-from` axis (a per-
2827    /// cluster `:upgrade-overrides` overlay the wasm-engine operator
2828    /// resolves at admission time so a cluster-specific migration
2829    /// policy can tighten a caixa-declared step without re-authoring
2830    /// the `caixa.lisp`, promotion of the plain
2831    /// `Vec<UpgradeFromEntry>` to a richer `{static, dynamic}`
2832    /// partition once runtime-resolved hot-upgrade instructions land,
2833    /// per-entry priority annotation once multi-strategy fan-out
2834    /// lands) would have had to be threaded through all six open-
2835    /// coded copies in lockstep or one consumer would silently
2836    /// disagree with the peers on which upgrade slice a given Caixa
2837    /// resolves to — a six-consumer split at the enumerator, the
2838    /// three-stage validate pass, the script-path probe walk, and the
2839    /// M2 overlay emitter, far from the source `caixa.lisp` with no
2840    /// field naming the upgrade-drift root cause. Lifting the
2841    /// resolution rule to a typed method on the substrate primitive
2842    /// means every downstream consumer of the caixa's per-`Caixa`
2843    /// OTP-appup outer-slice surface reaches for exactly one typed
2844    /// dispatch — the resolver's accept-set migrates as a unit on any
2845    /// future axis addition.
2846    ///
2847    /// First outer top-level [`Caixa`] `&[Composite]`-return slice
2848    /// accessor for M2 / M3 typed-slot vec-carry axes — opens the
2849    /// outer-`Caixa` `&[Composite]` composite-slice projection
2850    /// pattern the sibling `:children`
2851    /// [`crate::supervisor::ChildSpec`] / `:membros`
2852    /// [`crate::aplicacao::Membro`] / `:contratos`
2853    /// [`crate::aplicacao::WitContract`] future outer-composite-slice
2854    /// lifts fold on. Peer of the closed outer-`Caixa` scalar
2855    /// `Option<&Composite>` composite-reference family the sibling
2856    /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) /
2857    /// [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074) /
2858    /// [`Self::entrada`] (e4128e4) accessors closed on the outer
2859    /// `Option<&Composite>` altitude, extended here to the outer-
2860    /// `Caixa` `&[Composite]` vec-carry altitude. Peer at the inner
2861    /// altitude of [`crate::upgrade::UpgradeFromEntry::instructions`]
2862    /// (0137e5a) — same "one typed dispatch on the substrate
2863    /// primitive, thin projections at each consumer" discipline
2864    /// folded onto the outer top-level [`Caixa`] altitude, opening the
2865    /// M2 vec-carry slot family's outer-composite-slice axis. Sibling
2866    /// in shape to the peer outer-`Caixa` `&[Dep]`-return
2867    /// [`Self::deps`] (ad34b4e) / [`Self::deps_dev`] (f7fd81e) and
2868    /// `&[String]`-return [`Self::autores`] (b5d813f) /
2869    /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`]
2870    /// (8a36c23) / [`Self::exe`] (65d9527) / [`Self::servicos`]
2871    /// (611f78b) slice-accessors on the sibling outer-`Caixa` scalar-
2872    /// element vec-carry axes — folds the "outer [`Caixa`] `&[T]`
2873    /// slice" projection pattern onto the sibling M2 typed-composite-
2874    /// element axis (`UpgradeFromEntry` composite, matching the
2875    /// per-inner [`UpgradeFromEntry::instructions`] element type at a
2876    /// different altitude).
2877    ///
2878    /// Returns `&[UpgradeFromEntry]` (not `&Vec<UpgradeFromEntry>`)
2879    /// because every downstream consumer of the hot-upgrade list
2880    /// treats it as a read-only sequence — the slice-view is the
2881    /// narrowest borrow that supports every present + roadmapped
2882    /// consumer (`.iter()`, `.len()`, `.is_empty()`, `serde` slice-
2883    /// serialization through
2884    /// `serde_yaml::to_value(&[UpgradeFromEntry])`) without leaking
2885    /// the backing `Vec`'s grow/push/reserve surface no consumer of
2886    /// the typed view reaches for (the storage-side `Vec` remains
2887    /// reachable through the `pub upgrade_from` field for the
2888    /// mutation-carrying serde round-trip and per-test fixture-
2889    /// mutation paths). Named `upgrade_from()` to match the storage
2890    /// field's `snake_case` name; the kebab-case author-surface tag
2891    /// `:upgrade-from` is the same axis after tatara-lisp's
2892    /// kebab↔snake fold and the accessor's identity maps onto the
2893    /// canonical CAIXA-SDLC §II vocabulary the slot's docstring
2894    /// already carries.
2895    #[must_use]
2896    pub fn upgrade_from(&self) -> &[UpgradeFromEntry] {
2897        self.upgrade_from.as_slice()
2898    }
2899
2900    /// Substrate-canonical per-`Caixa` `:children` M2 supervisor-tree-
2901    /// slot outer-composite OTP-shaped per-supervisor static-child-list
2902    /// slice accessor every consumer of the top-level manifest's per-
2903    /// Supervisor `&[ChildSpec]` slice-view keys off — returns the
2904    /// author-declared `:children` typed `Vec<crate::supervisor::ChildSpec>`
2905    /// verbatim as a `&[crate::supervisor::ChildSpec]` slice-view over
2906    /// the same backing buffer the raw `self.children.as_slice()` field
2907    /// access borrows from. Empty-slice-carrying (the "no static children
2908    /// declared" arm every non-`Supervisor`-kind `defcaixa` carries by
2909    /// #[serde(default)] and every `SimpleOneForOne` supervisor carries
2910    /// by [`crate::supervisor::SupervisorError::SimpleOneForOneWithStaticChildren`]
2911    /// gate; the returned `&[ChildSpec]` degenerates to an empty slice
2912    /// on those arms without any silent `None` collapse).
2913    ///
2914    /// The outer `:children` slot carries the M2 typed OTP-supervisor
2915    /// static-child list — the load-bearing container of every per-
2916    /// child `{caixa, versao, restart}` triple the wasm-operator's
2917    /// hierarchical reconciler dispatches on at supervisor-tree
2918    /// materialization time (INSPIRATIONS §II.2 — OTP `supervisor:init/1`
2919    /// static-child list translated onto pleme-io's typed
2920    /// [`crate::supervisor::ChildSpec`] entry list; CAIXA-SDLC §II —
2921    /// the typed-M2 slot algebra the operator's per-supervisor fan-out
2922    /// dispatch fans on). Every per-child axis threads through a lifted
2923    /// per-entry accessor on the [`crate::supervisor::ChildSpec`] type:
2924    /// the [`crate::supervisor::ChildSpec::nome`] DNS-1123-label
2925    /// child-caixa-identity scalar accessor, the peer versao SemVer-2
2926    /// version-requirement scalar accessor, and the
2927    /// [`crate::supervisor::ChildSpec::restart`] `Copy`-composite-enum
2928    /// per-child post-exit restart-decision-policy discriminant
2929    /// accessor (dfb4a81). Every downstream consumer of the supervisor-
2930    /// tree path first passes through this outer accessor onto the
2931    /// slice and then dispatches per-child through the inner accessors
2932    /// — the two-level dispatch means every per-`:children` reader now
2933    /// routes through a typed dispatch on the substrate primitive at
2934    /// both altitudes.
2935    ///
2936    /// Prior to this lift the `.children` `Vec<ChildSpec>` slot was
2937    /// accessed inline at three production sites across two files —
2938    /// the [`Self::declared_supervisor_slots`] supervisor-tree
2939    /// declared-slot enumerator's `!self.children.is_empty()` presence
2940    /// probe (caixa-core/src/manifest.rs, which drives the
2941    /// `SUPERVISOR_AUTHOR_KEY_CHILDREN` kebab-case author-label push
2942    /// every [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
2943    /// kind-coherence gate reads), the [`Self::supervisor_view`]
2944    /// per-supervisor typed-view composer's `self.children.clone()`
2945    /// per-child fold-in path (caixa-core/src/manifest.rs, which
2946    /// materializes the typed [`crate::supervisor::SupervisorSpec`]
2947    /// view every [`crate::StandardLayout::verify`] Supervisor-arm gate
2948    /// dispatches on), and the [`crate::StandardLayout::verify`] per-
2949    /// `:children :caixa` self-parent refusal probe's
2950    /// `&caixa.children`-borrowed
2951    /// [`crate::supervisor::validate_no_self_supervision`] input
2952    /// (caixa-core/src/layout.rs, which pins the "no child names the
2953    /// supervisor's own `:nome`" cross-slot coherence gate). A future
2954    /// extension of the outer `:children` axis (a per-cluster
2955    /// `:children-overrides` overlay the wasm-engine operator resolves
2956    /// at admission time so a cluster-specific child-set can tighten
2957    /// a caixa-declared list without re-authoring the `caixa.lisp`,
2958    /// promotion of the plain `Vec<ChildSpec>` to a richer
2959    /// `{static, dynamic}` partition once Erlang/OTP's
2960    /// `simple_one_for_one`-shaped dynamic-child slot lands as a typed
2961    /// axis, per-child priority annotation once multi-strategy fan-out
2962    /// lands) would have had to be threaded through all three open-
2963    /// coded copies in lockstep or one consumer would silently
2964    /// disagree with the peers on which child slice a given Caixa
2965    /// resolves to — the enumerator's presence probe reading the raw
2966    /// slot while the peer view-composer's fold-in path read an
2967    /// operator-resolved slot would silently split the paired
2968    /// declared-slot enumerator and typed-view composition, and the
2969    /// [`crate::supervisor::validate_no_self_supervision`] self-parent
2970    /// refusal probe reading a third borrow would silently drift the
2971    /// cross-slot coherence gate's traversal input from the two peers,
2972    /// a three-consumer split at the enumerator, the view composer,
2973    /// and the self-parent gate far from the source `caixa.lisp` with
2974    /// no field naming the child-set-drift root cause. Lifting the
2975    /// resolution rule to a typed method on the substrate primitive
2976    /// means every downstream consumer of the caixa's per-`Caixa`
2977    /// OTP-supervisor outer-slice surface reaches for exactly one
2978    /// typed dispatch — the resolver's accept-set migrates as a unit
2979    /// on any future axis addition.
2980    ///
2981    /// Second outer top-level [`Caixa`] `&[Composite]`-return slice
2982    /// accessor for M2 / M3 typed-slot vec-carry axes — folds on the
2983    /// outer-`Caixa` `&[Composite]` composite-slice sub-family the
2984    /// sibling [`Self::upgrade_from`] (2a1f907) accessor opened, peer
2985    /// at the outer altitude of the closed inner-`SupervisorSpec`
2986    /// [`crate::SupervisorSpec::children`] (bc92bce) accessor on the
2987    /// same OTP-supervisor static-child-list axis — same "byte-equal,
2988    /// borrow-shared" outer-accessor discipline extended onto the
2989    /// second outer-`Caixa` `&[Composite]` vec-carry axis. Sibling in
2990    /// shape to the peer outer-`Caixa` `&[Dep]`-return [`Self::deps`]
2991    /// (ad34b4e) / [`Self::deps_dev`] (f7fd81e) and `&[String]`-return
2992    /// [`Self::autores`] (b5d813f) / [`Self::etiquetas`] (78c7d3c) /
2993    /// [`Self::bibliotecas`] (8a36c23) / [`Self::exe`] (65d9527) /
2994    /// [`Self::servicos`] (611f78b) slice-accessors on the sibling
2995    /// outer-`Caixa` scalar-element vec-carry axes — folds the "outer
2996    /// [`Caixa`] `&[T]` slice" projection pattern onto the sibling
2997    /// M2 typed-composite-element axis
2998    /// ([`crate::supervisor::ChildSpec`] composite, matching the
2999    /// per-inner [`crate::SupervisorSpec::children`] element type at a
3000    /// different altitude).
3001    ///
3002    /// Returns `&[crate::supervisor::ChildSpec]` (not
3003    /// `&Vec<ChildSpec>`) because every downstream consumer of the
3004    /// child list treats it as a read-only sequence — the slice-view
3005    /// is the narrowest borrow that supports every present +
3006    /// roadmapped consumer (`.iter()`, `.len()`, `.is_empty()`, the
3007    /// [`crate::supervisor::validate_no_self_supervision`] `&[ChildSpec]`
3008    /// input, `serde` slice-serialization) without leaking the backing
3009    /// `Vec`'s grow/push/reserve surface no consumer of the typed view
3010    /// reaches for (the storage-side `Vec` remains reachable through
3011    /// the `pub children` field for the mutation-carrying serde round-
3012    /// trip and per-test fixture-mutation paths, including the
3013    /// [`Self::supervisor_view`] fold-in path that clones the slot
3014    /// into the typed view). Named `children()` to match the storage
3015    /// field's name verbatim and the tatara-lisp author-surface term
3016    /// (`:children`) the field's own docstring already carries; the
3017    /// accessor's identity maps onto the canonical OTP supervision
3018    /// vocabulary the [`Caixa::children`] field's docstring already
3019    /// reaches for ("Static children of a supervisor").
3020    #[must_use]
3021    pub fn children(&self) -> &[crate::supervisor::ChildSpec] {
3022        self.children.as_slice()
3023    }
3024
3025    /// Substrate-canonical per-`Caixa` `:membros` M3 mesh-slot outer-
3026    /// composite MESH-COMPOSITION-shaped per-Aplicacao member-list slice
3027    /// accessor every consumer of the top-level manifest's per-Aplicacao
3028    /// `&[crate::aplicacao::Membro]` slice-view keys off — returns the
3029    /// author-declared `:membros` typed `Vec<crate::aplicacao::Membro>`
3030    /// verbatim as a `&[crate::aplicacao::Membro]` slice-view over the
3031    /// same backing buffer the raw `self.membros.as_slice()` field access
3032    /// borrows from. Empty-slice-carrying (the "no members declared" arm
3033    /// every non-`Aplicacao`-kind `defcaixa` carries by `#[serde(default)]`
3034    /// and every partially-authored Aplicacao carries before the
3035    /// [`crate::AplicacaoError::MembrosEmpty`] gate fires; the returned
3036    /// `&[Membro]` degenerates to an empty slice on those arms without any
3037    /// silent `None` collapse).
3038    ///
3039    /// The outer `:membros` slot carries the M3 typed MESH-COMPOSITION
3040    /// per-Aplicacao member list — the load-bearing container of every
3041    /// per-member `{caixa, versao}` pair the caixa-mesh renderer's
3042    /// per-Aplicacao program-emission dispatch fans on at mesh-artifact
3043    /// materialization time (MESH-COMPOSITION §III.1 — the typed graph's
3044    /// vertex set the `:contratos` `:de`/`:para` edges resolve against and
3045    /// the `:entrada :para` external-gateway destination validates
3046    /// against; CAIXA-SDLC §II — the typed-M3 slot algebra the operator's
3047    /// per-Aplicacao fan-out dispatch fans on). Every per-member axis
3048    /// threads through a lifted per-entry accessor on the
3049    /// [`crate::aplicacao::Membro`] type: the
3050    /// [`crate::aplicacao::Membro::nome`] DNS-1123-label member-caixa-
3051    /// identity scalar accessor (4a32abf) and the peer
3052    /// [`crate::aplicacao::Membro::versao_requirement`] SemVer-2
3053    /// version-requirement scalar accessor (a40b0e3). Every downstream
3054    /// consumer of the mesh-graph path first passes through this outer
3055    /// accessor onto the slice and then dispatches per-member through
3056    /// the inner accessors — the two-level dispatch means every per-
3057    /// `:membros` reader now routes through a typed dispatch on the
3058    /// substrate primitive at both altitudes.
3059    ///
3060    /// Prior to this lift the `.membros` `Vec<Membro>` slot was accessed
3061    /// inline at three production sites across two files — the
3062    /// [`Self::declared_mesh_slots`] mesh-slot declared-slot
3063    /// enumerator's `!self.membros.is_empty()` presence probe
3064    /// (caixa-core/src/manifest.rs, which drives the
3065    /// `M3_AUTHOR_KEY_MEMBROS` kebab-case author-label push every
3066    /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-coherence
3067    /// gate reads), the [`Self::aplicacao_view`] per-Aplicacao typed-view
3068    /// composer's `self.membros.clone()` per-member fold-in path
3069    /// (caixa-core/src/manifest.rs, which materializes the typed
3070    /// [`crate::aplicacao::AplicacaoSpec`] view every
3071    /// [`crate::StandardLayout::verify`] Aplicacao-arm gate dispatches
3072    /// on), and the [`crate::StandardLayout::verify`] per-`:membros
3073    /// :caixa` self-membership refusal probe's `&caixa.membros`-borrowed
3074    /// [`crate::aplicacao::validate_no_self_membership`] input
3075    /// (caixa-core/src/layout.rs, which pins the "no member names the
3076    /// Aplicacao's own `:nome`" cross-slot coherence gate). A future
3077    /// extension of the outer `:membros` axis (a per-cluster
3078    /// `:membros-overrides` overlay the wasm-engine operator resolves at
3079    /// admission time so a cluster-specific member-set can tighten a
3080    /// caixa-declared list without re-authoring the `caixa.lisp`,
3081    /// promotion of the plain `Vec<Membro>` to a richer
3082    /// `{static, dynamic}` partition once runtime-resolved Aplicacao
3083    /// members land as a typed axis, per-member priority annotation once
3084    /// multi-strategy fan-out lands) would have had to be threaded
3085    /// through all three open-coded copies in lockstep or one consumer
3086    /// would silently disagree with the peers on which member slice a
3087    /// given Caixa resolves to — the enumerator's presence probe reading
3088    /// the raw slot while the peer view-composer's fold-in path read an
3089    /// operator-resolved slot would silently split the paired
3090    /// declared-slot enumerator and typed-view composition, and the
3091    /// [`crate::aplicacao::validate_no_self_membership`] self-membership
3092    /// refusal probe reading a third borrow would silently drift the
3093    /// cross-slot coherence gate's traversal input from the two peers, a
3094    /// three-consumer split at the enumerator, the view composer, and
3095    /// the self-membership gate far from the source `caixa.lisp` with no
3096    /// field naming the member-set-drift root cause. Lifting the
3097    /// resolution rule to a typed method on the substrate primitive
3098    /// means every downstream consumer of the caixa's per-`Caixa`
3099    /// MESH-COMPOSITION outer-slice surface reaches for exactly one
3100    /// typed dispatch — the resolver's accept-set migrates as a unit on
3101    /// any future axis addition.
3102    ///
3103    /// Third outer top-level [`Caixa`] `&[Composite]`-return slice
3104    /// accessor for M2 / M3 typed-slot vec-carry axes — opens the outer-
3105    /// `Caixa` M3 mesh-slot arm of the `&[Composite]` composite-slice
3106    /// sub-family the sibling M2 [`Self::upgrade_from`] (2a1f907) /
3107    /// [`Self::children`] (c17b51e) accessors opened for the M2 vec-carry
3108    /// altitude. Peer at the outer altitude of the closed inner-
3109    /// [`crate::AplicacaoSpec::membros`] (6c77e36) accessor on the same
3110    /// MESH-COMPOSITION per-Aplicacao member-list axis — the two
3111    /// altitudes now share the same "byte-equal, borrow-shared" outer-
3112    /// accessor discipline. Sibling in shape to the peer outer-`Caixa`
3113    /// `&[Dep]`-return [`Self::deps`] (ad34b4e) / [`Self::deps_dev`]
3114    /// (f7fd81e) and `&[String]`-return [`Self::autores`] (b5d813f) /
3115    /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`] (8a36c23) /
3116    /// [`Self::exe`] (65d9527) / [`Self::servicos`] (611f78b) slice-
3117    /// accessors on the sibling outer-`Caixa` scalar-element vec-carry
3118    /// axes — folds the "outer [`Caixa`] `&[T]` slice" projection
3119    /// pattern onto the sibling M3 typed-composite-element axis
3120    /// ([`crate::aplicacao::Membro`] composite, matching the per-inner
3121    /// [`crate::AplicacaoSpec::membros`] element type at a different
3122    /// altitude).
3123    ///
3124    /// Returns `&[crate::aplicacao::Membro]` (not `&Vec<Membro>`)
3125    /// because every downstream consumer of the member list treats it
3126    /// as a read-only sequence — the slice-view is the narrowest borrow
3127    /// that supports every present + roadmapped consumer (`.iter()`,
3128    /// `.len()`, `.is_empty()`, the
3129    /// [`crate::aplicacao::validate_no_self_membership`] `&[Membro]`
3130    /// input, `serde` slice-serialization) without leaking the backing
3131    /// `Vec`'s grow/push/reserve surface no consumer of the typed view
3132    /// reaches for (the storage-side `Vec` remains reachable through the
3133    /// `pub membros` field for the mutation-carrying serde round-trip
3134    /// and per-test fixture-mutation paths, including the
3135    /// [`Self::aplicacao_view`] fold-in path that clones the slot into
3136    /// the typed view). Named `membros()` to match the storage field's
3137    /// name verbatim and the tatara-lisp author-surface term
3138    /// (`:membros`) the field's own docstring already carries; the
3139    /// accessor's identity maps onto the canonical MESH-COMPOSITION
3140    /// vocabulary the [`Caixa::membros`] field's docstring already
3141    /// reaches for ("Member Servicos that make up this Aplicacao").
3142    #[must_use]
3143    pub fn membros(&self) -> &[crate::aplicacao::Membro] {
3144        self.membros.as_slice()
3145    }
3146
3147    /// Substrate-canonical per-`Caixa` `:contratos` M3 mesh-slot outer-
3148    /// composite MESH-COMPOSITION-shaped per-Aplicacao WIT-typed
3149    /// inter-Servico contract-list slice accessor every consumer of the
3150    /// top-level manifest's per-Aplicacao `&[crate::aplicacao::WitContract]`
3151    /// slice-view keys off — returns the author-declared `:contratos`
3152    /// typed `Vec<crate::aplicacao::WitContract>` verbatim as a
3153    /// `&[crate::aplicacao::WitContract]` slice-view over the same
3154    /// backing buffer the raw `self.contratos.as_slice()` field access
3155    /// borrows from. Empty-slice-carrying (the "no contracts declared"
3156    /// arm every non-`Aplicacao`-kind `defcaixa` carries by
3157    /// `#[serde(default)]` and every leaf Aplicacao carrying only a
3158    /// single member with no inter-Servico edge carries; the returned
3159    /// `&[WitContract]` degenerates to an empty slice on those arms
3160    /// without any silent `None` collapse).
3161    ///
3162    /// The outer `:contratos` slot carries the M3 typed MESH-COMPOSITION
3163    /// per-Aplicacao WIT-typed inter-Servico edge list — the load-bearing
3164    /// container of every per-edge `{de, para, wit, endpoint | subject |
3165    /// slot}` quadruple the caixa-mesh renderer's per-Aplicacao
3166    /// `CiliumNetworkPolicy` fan-out (one L7 policy per edge —
3167    /// MESH-COMPOSITION §III.2 point 2) and per-`(:de, :para)`
3168    /// adjacency-list seed dispatch on at mesh-artifact materialization
3169    /// time (MESH-COMPOSITION §III.1 — the typed graph's edge set the
3170    /// `:membros` vertex set resolves against, closed by the
3171    /// [`crate::AplicacaoError::ContractoUnknownMember`] / cycle-refusal
3172    /// gates in §III.3; CAIXA-SDLC §II — the typed-M3 slot algebra the
3173    /// operator's per-Aplicacao fan-out dispatch fans on). Every
3174    /// per-edge axis threads through a lifted per-entry accessor on the
3175    /// [`crate::aplicacao::WitContract`] type: the peer `de` / `para`
3176    /// DNS-1123-label member-caixa-name endpoint scalar accessors, the
3177    /// [`crate::aplicacao::WitContract::endpoint`] (7020470) HTTP-shape
3178    /// / [`crate::aplicacao::WitContract::subject`] (90de675)
3179    /// NATS-pub-sub-shape / [`crate::aplicacao::WitContract::slot`]
3180    /// (ed22b66) `wasi:keyvalue/store`-shape payload-carrier accessors,
3181    /// and the WIT-world discriminant. Every downstream consumer of the
3182    /// mesh-graph edge path first passes through this outer accessor
3183    /// onto the slice and then dispatches per-contract through the
3184    /// inner accessors — the two-level dispatch means every
3185    /// per-`:contratos` reader now routes through a typed dispatch on
3186    /// the substrate primitive at both altitudes.
3187    ///
3188    /// Prior to this lift the `.contratos` `Vec<WitContract>` slot was
3189    /// accessed inline at two production sites in
3190    /// caixa-core/src/manifest.rs — the [`Self::declared_mesh_slots`]
3191    /// mesh-slot declared-slot enumerator's
3192    /// `!self.contratos.is_empty()` presence probe (which drives the
3193    /// `M3_AUTHOR_KEY_CONTRATOS` kebab-case author-label push every
3194    /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-coherence
3195    /// gate reads) and the [`Self::aplicacao_view`] per-Aplicacao
3196    /// typed-view composer's `self.contratos.clone()` per-contract
3197    /// fold-in path (which materializes the typed
3198    /// [`crate::aplicacao::AplicacaoSpec`] view every
3199    /// [`crate::StandardLayout::verify`] Aplicacao-arm gate and every
3200    /// downstream `caixa-mesh` renderer dispatches on). A future
3201    /// extension of the outer `:contratos` axis (a per-cluster
3202    /// `:contratos-overrides` overlay the wasm-engine operator resolves
3203    /// at admission time so a cluster-specific edge-set can tighten a
3204    /// caixa-declared list without re-authoring the `caixa.lisp`,
3205    /// promotion of the plain `Vec<WitContract>` to a richer
3206    /// `{static, dynamic}` partition once runtime-resolved contract
3207    /// edges land, per-edge policy annotation once the M4 per-edge
3208    /// policy overlay axis lands) would have had to be threaded through
3209    /// both open-coded copies in lockstep or one consumer would
3210    /// silently disagree with the peer on which edge slice a given
3211    /// Caixa resolves to — the enumerator's presence probe reading the
3212    /// raw slot while the peer view-composer's fold-in path read an
3213    /// operator-resolved slot would silently split the paired
3214    /// declared-slot enumerator and typed-view composition, a
3215    /// two-consumer split at the enumerator and the view composer far
3216    /// from the source `caixa.lisp` with no field naming the edge-set-
3217    /// drift root cause. Lifting the resolution rule to a typed method
3218    /// on the substrate primitive means every downstream consumer of
3219    /// the caixa's per-`Caixa` MESH-COMPOSITION outer-slice surface
3220    /// reaches for exactly one typed dispatch — the resolver's
3221    /// accept-set migrates as a unit on any future axis addition.
3222    ///
3223    /// Fourth and final outer top-level [`Caixa`] `&[Composite]`-return
3224    /// slice accessor for M2 / M3 typed-slot vec-carry axes — closes
3225    /// the outer-`Caixa` `&[Composite]` composite-slice sub-family the
3226    /// sibling M2 [`Self::upgrade_from`] (2a1f907) / [`Self::children`]
3227    /// (c17b51e) accessors opened and the M3 [`Self::membros`]
3228    /// (0f26987) accessor folded on, and closes the outer-`Caixa` M3
3229    /// mesh-slot arm of the composite-slice sub-family the sibling
3230    /// [`Self::membros`] accessor opened for the M3 vec-carry altitude.
3231    /// Peer at the outer altitude of the closed inner-
3232    /// [`crate::AplicacaoSpec::contratos`] (0dcc926) accessor on the
3233    /// same MESH-COMPOSITION per-Aplicacao contract-list axis — the two
3234    /// altitudes now share the same "byte-equal, borrow-shared" outer-
3235    /// accessor discipline. Sibling in shape to the peer outer-`Caixa`
3236    /// `&[Dep]`-return [`Self::deps`] (ad34b4e) / [`Self::deps_dev`]
3237    /// (f7fd81e) and `&[String]`-return [`Self::autores`] (b5d813f) /
3238    /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`] (8a36c23) /
3239    /// [`Self::exe`] (65d9527) / [`Self::servicos`] (611f78b) slice-
3240    /// accessors on the sibling outer-`Caixa` scalar-element vec-carry
3241    /// axes — folds the "outer [`Caixa`] `&[T]` slice" projection
3242    /// pattern onto the sibling M3 typed-composite-element axis
3243    /// ([`crate::aplicacao::WitContract`] composite, matching the
3244    /// per-inner [`crate::AplicacaoSpec::contratos`] element type at a
3245    /// different altitude).
3246    ///
3247    /// Returns `&[crate::aplicacao::WitContract]` (not
3248    /// `&Vec<WitContract>`) because every downstream consumer of the
3249    /// contract list treats it as a read-only sequence — the slice-view
3250    /// is the narrowest borrow that supports every present + roadmapped
3251    /// consumer (`.iter()`, `.len()`, `.is_empty()`, per-edge WIT-world
3252    /// discriminant dispatch, `serde` slice-serialization) without
3253    /// leaking the backing `Vec`'s grow/push/reserve surface no
3254    /// consumer of the typed view reaches for (the storage-side `Vec`
3255    /// remains reachable through the `pub contratos` field for the
3256    /// mutation-carrying serde round-trip and per-test fixture-mutation
3257    /// paths, including the [`Self::aplicacao_view`] fold-in path that
3258    /// clones the slot into the typed view). Named `contratos()` to
3259    /// match the storage field's name verbatim and the tatara-lisp
3260    /// author-surface term (`:contratos`) the field's own docstring
3261    /// already carries; the accessor's identity maps onto the canonical
3262    /// MESH-COMPOSITION vocabulary the [`Caixa::contratos`] field's
3263    /// docstring already reaches for ("WIT-typed inter-Servico
3264    /// contracts").
3265    #[must_use]
3266    pub fn contratos(&self) -> &[crate::aplicacao::WitContract] {
3267        self.contratos.as_slice()
3268    }
3269
3270    /// Compose the Aplicacao-related flat slots into a single typed
3271    /// [`crate::aplicacao::AplicacaoSpec`] for validation +
3272    /// downstream renderer consumption. Returns `None` when the
3273    /// caixa isn't a `:kind Aplicacao`.
3274    #[must_use]
3275    pub fn aplicacao_view(&self) -> Option<crate::aplicacao::AplicacaoSpec> {
3276        if !self.kind().is_aplicacao() {
3277            return None;
3278        }
3279        Some(crate::aplicacao::AplicacaoSpec {
3280            membros: self.membros().to_vec(),
3281            contratos: self.contratos().to_vec(),
3282            politicas: self.politicas().cloned().unwrap_or_default(),
3283            placement: self.placement().cloned().unwrap_or_default(),
3284            entrada: self.entrada().cloned(),
3285        })
3286    }
3287
3288    /// The kebab-case `:slot` tags of every M3 mesh slot this caixa
3289    /// *declares* a value on, in canonical declaration order
3290    /// (`:membros` → `:contratos` → `:politicas` → `:placement` →
3291    /// `:entrada`). A slot counts as declared when its backing field
3292    /// carries a value — a non-empty `Vec`, or a `Some(...)`.
3293    ///
3294    /// The M3 mesh slots compose the typed graph of a `:kind Aplicacao`
3295    /// (MESH-COMPOSITION §III.1). [`Self::aplicacao_view`] only folds
3296    /// them into a validatable [`crate::aplicacao::AplicacaoSpec`] when
3297    /// the kind matches (returns `None` otherwise), and the caixa-mesh /
3298    /// caixa-flux / caixa-helm renderers only emit them for an
3299    /// Aplicacao. On any *other* kind a declared mesh slot is the
3300    /// manifest field's documented "ignored otherwise" (see the
3301    /// `:membros` … `:entrada` field docs): it silently passes
3302    /// [`Caixa::from_lisp`] and then vanishes — never validated, never
3303    /// rendered — far from the source caixa.lisp.
3304    /// [`crate::StandardLayout::verify`] consults this to reject that
3305    /// silent-drop at caixa-build time
3306    /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`]), mirroring the
3307    /// `SupervisorOwnsCode` / `AplicacaoOwnsCode` kind-coherence gates:
3308    /// a slot foreign to the kind is a build error, not a silent drop.
3309    ///
3310    /// Lifted as a typed method (rather than an inline disjunction at
3311    /// the verify call site) so the mesh-slot set lives in one place —
3312    /// a future M4 axis added to the Aplicacao surface (per-edge policy
3313    /// overlay, distributed-app takeover config) is one push here, and
3314    /// every consumer reaching for "which mesh slots are set" (the
3315    /// verify gate, a future `feira lint` kind-coherence advisory)
3316    /// inherits the canonical order without rolling its own.
3317    ///
3318    /// Each per-arm kebab-case label is routed through the peer
3319    /// [`crate::M3_AUTHOR_KEY_MEMBROS`] /
3320    /// [`crate::M3_AUTHOR_KEY_CONTRATOS`] /
3321    /// [`crate::M3_AUTHOR_KEY_POLITICAS`] /
3322    /// [`crate::M3_AUTHOR_KEY_PLACEMENT`] /
3323    /// [`crate::M3_AUTHOR_KEY_ENTRADA`] consts declared next to the
3324    /// [`crate::M3_KEY_PLACEMENT`] renderer-side wire-key peer, so both
3325    /// halves of every M3 top-level mesh slot's dual axis (author-facing
3326    /// kebab-case label + renderer-side artifact key) route through one
3327    /// canonical declaration per arm — same discipline the peer
3328    /// [`crate::M2_AUTHOR_KEY_LIMITS`] / [`crate::M2_AUTHOR_KEY_BEHAVIOR`]
3329    /// / [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] top-level M2 slot consts
3330    /// (f49c8b0) establish on the sibling per-Servico M2 top-level slot
3331    /// axis, extended here to close the M3 mesh-slot author-facing-label
3332    /// axis so both altitudes of the typed-slot algebra
3333    /// (per-Servico M2 + per-Aplicacao M3) share the same
3334    /// "one canonical byte-string per arm, next to the axis" discipline.
3335    #[must_use]
3336    pub fn declared_mesh_slots(&self) -> Vec<&'static str> {
3337        let mut slots = Vec::new();
3338        if !self.membros().is_empty() {
3339            slots.push(crate::render::M3_AUTHOR_KEY_MEMBROS);
3340        }
3341        if !self.contratos().is_empty() {
3342            slots.push(crate::render::M3_AUTHOR_KEY_CONTRATOS);
3343        }
3344        if self.politicas().is_some() {
3345            slots.push(crate::render::M3_AUTHOR_KEY_POLITICAS);
3346        }
3347        if self.placement().is_some() {
3348            slots.push(crate::render::M3_AUTHOR_KEY_PLACEMENT);
3349        }
3350        if self.entrada().is_some() {
3351            slots.push(crate::render::M3_AUTHOR_KEY_ENTRADA);
3352        }
3353        slots
3354    }
3355
3356    /// The kebab-case `:slot` tags of every supervisor-tree slot this
3357    /// caixa *declares* a value on, in canonical declaration order
3358    /// (`:estrategia` → `:max-restarts` → `:restart-window` →
3359    /// `:children`). A slot counts as declared when its backing field
3360    /// carries a value — a `Some(...)`, or a non-empty `Vec`.
3361    ///
3362    /// The supervisor-tree slots compose the typed OTP supervisor of a
3363    /// `:kind Supervisor` (INSPIRATIONS §II.2; the `:estrategia` +
3364    /// `:children` field docs above). [`Self::supervisor_view`] only
3365    /// folds them into a validatable [`SupervisorSpec`] when the kind
3366    /// matches (returns `None` otherwise), and the wasm-operator's
3367    /// hierarchical reconciler only consumes them for a Supervisor. On
3368    /// any *other* kind a declared supervisor slot is the manifest
3369    /// field's documented "ignored otherwise" (see the `:estrategia` …
3370    /// `:children` field docs): it silently passes [`Caixa::from_lisp`]
3371    /// and then vanishes — never validated, never reconciled — far from
3372    /// the source caixa.lisp. [`crate::StandardLayout::verify`] consults
3373    /// this to reject that silent-drop at caixa-build time
3374    /// ([`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]), the
3375    /// exact mirror of the [`Self::declared_mesh_slots`] /
3376    /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] gate on the
3377    /// Aplicacao-only slot set: a slot foreign to the kind is a build
3378    /// error, not a silent drop.
3379    #[must_use]
3380    pub fn declared_supervisor_slots(&self) -> Vec<&'static str> {
3381        let mut slots = Vec::new();
3382        if self.estrategia().is_some() {
3383            slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA);
3384        }
3385        if self.max_restarts().is_some() {
3386            slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS);
3387        }
3388        if self.restart_window().is_some() {
3389            slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW);
3390        }
3391        if !self.children().is_empty() {
3392            slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN);
3393        }
3394        slots
3395    }
3396
3397    /// The kebab-case `:slot` tags of every M2 Servico-runtime slot this
3398    /// caixa *declares* a value on, in canonical declaration order
3399    /// (`:limits` → `:behavior` → `:upgrade-from`). A slot counts as
3400    /// declared when its backing field carries a value — a `Some(...)`,
3401    /// or a non-empty `Vec`.
3402    ///
3403    /// The M2 slots configure the runtime of a long-running wasm
3404    /// component, i.e. a `:kind Servico`: `:limits` is Lunatic
3405    /// per-process sandboxing (INSPIRATIONS §III.1), `:behavior` is the
3406    /// OTP `gen_server` callback set (§II.3), `:upgrade-from` is the OTP
3407    /// appup hot-code-reload table (§II.4). The caixa-helm / caixa-flux
3408    /// renderers gate on [`crate::require_kind`]`(_, Servico)` and only
3409    /// emit these slots for a Servico; on any *other* kind a declared M2
3410    /// slot is the manifest field's documented "ignored otherwise": its
3411    /// well-formedness is checked by [`crate::StandardLayout::verify`]
3412    /// but the value is never rendered into a chart / programs.yaml entry
3413    /// — it silently passes [`Caixa::from_lisp`] + `feira build` and then
3414    /// vanishes, far from the source caixa.lisp.
3415    /// [`crate::StandardLayout::verify`] consults this to reject that
3416    /// silent-drop at caixa-build time
3417    /// ([`crate::LayoutError::ServicoSlotsOnNonServico`]), the exact
3418    /// mirror of the [`Self::declared_mesh_slots`] /
3419    /// [`Self::declared_supervisor_slots`] gates on the peer
3420    /// kind-exclusive slot sets: a slot foreign to the kind is a build
3421    /// error, not a silent drop.
3422    ///
3423    /// Each per-arm kebab-case label is routed through the peer
3424    /// [`crate::M2_AUTHOR_KEY_LIMITS`] / [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
3425    /// [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] consts declared next to the
3426    /// [`crate::M2_KEY_LIMITS`] / [`crate::M2_KEY_BEHAVIOR`] /
3427    /// [`crate::M2_KEY_UPGRADE_FROM`] renderer-side wire-key peers, so
3428    /// both halves of the M2 top-level slot's dual axis (author-facing
3429    /// kebab-case label + renderer-side camelCase overlay-container wire
3430    /// key) route through one canonical declaration per arm — same
3431    /// discipline the peer [`crate::M2_BEHAVIOR_AUTHOR_KEY_ON_*`] sub-slot
3432    /// author-label consts (889dc18) establish on the sibling
3433    /// per-callback axis inside the `:behavior` overlay block.
3434    #[must_use]
3435    pub fn declared_servico_slots(&self) -> Vec<&'static str> {
3436        let mut slots = Vec::new();
3437        if self.limits().is_some() {
3438            slots.push(crate::render::M2_AUTHOR_KEY_LIMITS);
3439        }
3440        if self.behavior().is_some() {
3441            slots.push(crate::render::M2_AUTHOR_KEY_BEHAVIOR);
3442        }
3443        if !self.upgrade_from().is_empty() {
3444            slots.push(crate::render::M2_AUTHOR_KEY_UPGRADE_FROM);
3445        }
3446        slots
3447    }
3448
3449    /// The kebab-case `:slot` tags of every code-surface slot this caixa
3450    /// declares a value on that its [`CaixaKind`] doesn't natively own,
3451    /// in canonical declaration order (`:exe` → `:servicos`). A
3452    /// code-surface slot is owned by exactly one kind: `:exe` by
3453    /// [`CaixaKind::Binario`] (the nix-built executable surface), and
3454    /// `:servicos` by [`CaixaKind::Servico`] (the wasm component +
3455    /// `ComputeUnit` daemon surface).
3456    ///
3457    /// Each is silently ignored when declared on the wrong kind: the
3458    /// caixa-helm / caixa-flux / caixa-flake renderers gate on
3459    /// [`crate::require_kind`]`(_, <owning-kind>)`, so on any *other*
3460    /// code-running kind a declared `:exe` / `:servicos` is the manifest
3461    /// field's documented "ignored otherwise" — its path is checked for
3462    /// existence by the layout's `bibliotecas`/`exe`/`servicos` loops
3463    /// (which run after [`Caixa::from_lisp`]), but the value is never
3464    /// rendered into a build target or programs.yaml entry. It silently
3465    /// passes [`Caixa::from_lisp`] + `feira build`, far from the source
3466    /// caixa.lisp, with no field naming which slot is foreign.
3467    ///
3468    /// [`crate::StandardLayout::verify`] consults this to reject that
3469    /// silent-drop at caixa-build time
3470    /// ([`crate::LayoutError::ForeignCodeSlot`]), beside the M2
3471    /// servico-runtime, supervisor-tree, and M3 mesh kind-coherence
3472    /// gates ([`Self::declared_servico_slots`] /
3473    /// [`Self::declared_supervisor_slots`] /
3474    /// [`Self::declared_mesh_slots`]): the fourth kind ↔ slot algebra
3475    /// axis to be closed on the typed surface. The Supervisor /
3476    /// Aplicacao "no code at all" cases ([`crate::LayoutError::SupervisorOwnsCode`]
3477    /// / [`crate::LayoutError::AplicacaoOwnsCode`]) keep their dedicated
3478    /// diagnostics — they fire ahead of this gate on the same `verify`
3479    /// pass, so for Supervisor / Aplicacao the `OwnCode` arm always wins
3480    /// and this method is moot. For Biblioteca / Binario / Servico, this
3481    /// gate fires when a code-running kind declares another code-running
3482    /// kind's exclusive code surface.
3483    ///
3484    /// `:bibliotecas` is deliberately excluded — a Binario or Servico
3485    /// may legitimately ship a `lib/` helper that the underlying
3486    /// substrate (the nix flake for Binario, the wasm component build
3487    /// for Servico) bundles into its build, so the slot's
3488    /// declared-on-wrong-kind cardinality isn't a structural error on
3489    /// either code-running kind. A Biblioteca declaring `:bibliotecas`
3490    /// is the native case (the slot's owning kind). Supervisor /
3491    /// Aplicacao declaring `:bibliotecas` is gated upstream by
3492    /// [`crate::LayoutError::SupervisorOwnsCode`] /
3493    /// [`crate::LayoutError::AplicacaoOwnsCode`].
3494    ///
3495    /// Lifted as a typed method (rather than an inline disjunction at
3496    /// the verify call site) so the foreign-code-slot set lives in one
3497    /// place — a future kind that gains its own code-surface slot is
3498    /// one push here, and every consumer reaching for "which code
3499    /// surfaces are foreign to this kind" (the verify gate, a future
3500    /// `feira lint` kind-coherence advisory, the future `app-operator`'s
3501    /// per-caixa build-target classifier) inherits the canonical order
3502    /// without rolling its own.
3503    #[must_use]
3504    pub fn declared_foreign_code_slots(&self) -> Vec<&'static str> {
3505        let mut slots = Vec::new();
3506        if !self.exe().is_empty() && !self.kind().requires_exe() {
3507            slots.push(":exe");
3508        }
3509        if !self.servicos().is_empty() && !self.kind().requires_servicos() {
3510            slots.push(":servicos");
3511        }
3512        slots
3513    }
3514
3515    /// Validate every entry of `:deps` and `:deps-dev` through
3516    /// [`Dep::validate`] — closing the parity loop with the per-axis
3517    /// `:versao` gates already wired into the typed-graph
3518    /// ([`crate::AplicacaoSpec::validate_membros`] for `:membros`,
3519    /// 9888b13) and typed supervisor tree
3520    /// ([`crate::SupervisorSpec::validate`] for `:children`, b38ff3a).
3521    ///
3522    /// Until this gate landed `:deps :versao` and `:deps-dev :versao`
3523    /// were the only `:versao` axes still untyped past
3524    /// [`Caixa::from_lisp`]: the derive macro stored the requirement
3525    /// as a String without parsing it, so a malformed-but-non-empty
3526    /// requirement (`"^bad-version"`, `"^^0.1"`, `"v0.1"`, `"not-a-req"`)
3527    /// silently passed parse and the `semver::Error` surfaced at
3528    /// lacre-resolve time, far from the source caixa.lisp, with no
3529    /// field naming which `:deps` entry carried the typo. Lifting the
3530    /// gate here makes the four `:versao` typed surfaces (`:deps`,
3531    /// `:deps-dev`, `:membros`, `:children`) structurally equivalent —
3532    /// every requirement string past `validate_deps` is round-trippable
3533    /// through [`crate::parse_requirement`] without re-checking at the
3534    /// resolver layer.
3535    ///
3536    /// Both lists run through the same per-entry validator so a typo
3537    /// in `:deps-dev` surfaces with the same diagnostic as one in
3538    /// `:deps` — neither axis is a second-class citizen of the typed
3539    /// surface.
3540    ///
3541    /// Within each list, [`DepError::DuplicateNome`] closes the
3542    /// set-not-multiset discipline on the `:nome` axis: two entries
3543    /// naming the same caixa carry two `:versao` / `:fonte` / feature
3544    /// triples that the caixa-resolver's lacre pipeline collapses to one
3545    /// via its `HashMap`-keyed-by-`:nome` consumption — the second entry
3546    /// silently overwrites the first at `concrete_versao`-resolve time
3547    /// (the same "second wins / one silently overwrites the other"
3548    /// shape the peer typed-graph duplicate gates already close on every
3549    /// other Vec-shaped authoring surface that keys by name). The
3550    /// duplicate check fires per-list and runs *after* each per-entry
3551    /// [`Dep::validate`] call so a malformed-and-duplicated entry
3552    /// surfaces its narrower per-entry diagnostic
3553    /// ([`DepError::NomeInvalid`], [`DepError::VersaoInvalid`],
3554    /// [`DepError::FonteRepoEmpty`], …) before the cross-entry duplicate
3555    /// diagnostic — the canonical "per-entry shape before cross-entry
3556    /// uniqueness" precedence the peer `:children :caixa`
3557    /// ([`crate::SupervisorSpec::validate`]), `:membros :caixa`
3558    /// ([`crate::AplicacaoSpec::validate_membros`]), `:contratos`
3559    /// ([`crate::AplicacaoSpec::validate`]), `:placement :clusters`
3560    /// ([`crate::AplicacaoSpec::validate_placement`]),
3561    /// `:entrada :paths` ([`crate::AplicacaoSpec::validate`]),
3562    /// `:upgrade-from :from` ([`crate::upgrade::validate_upgrade_from`]),
3563    /// and the within-`:upgrade-from`-entry per-instruction-class
3564    /// singularity gates ([`crate::UpgradeError::DuplicateLoadModule`],
3565    /// [`crate::UpgradeError::DuplicateStateChange`],
3566    /// [`crate::UpgradeError::DuplicateCleanup`]) all establish.
3567    ///
3568    /// Cross-list (`:deps` ↔ `:deps-dev`) coincidence is *not* gated
3569    /// here: Cargo's `[dependencies]` + `[dev-dependencies]` accept the
3570    /// same name in both tables (the dev table's pin overrides the
3571    /// runtime table's pin in test/dev contexts), and caixa's surface
3572    /// mirrors that convention until a deliberate choice retires the
3573    /// override pattern. Only within-list duplicates are structurally
3574    /// incoherent — those are what this gate closes.
3575    pub fn validate_deps(&self) -> Result<(), DepError> {
3576        let mut seen = std::collections::HashSet::new();
3577        for dep in self.deps() {
3578            dep.validate()?;
3579            crate::render::insert_first_seen(&mut seen, dep.nome(), || DepError::DuplicateNome {
3580                nome: dep.nome().to_string(),
3581                list: crate::render::DEP_AUTHOR_KEY_DEPS,
3582            })?;
3583        }
3584        let mut seen_dev = std::collections::HashSet::new();
3585        for dep in self.deps_dev() {
3586            dep.validate()?;
3587            crate::render::insert_first_seen(&mut seen_dev, dep.nome(), || {
3588                DepError::DuplicateNome {
3589                    nome: dep.nome().to_string(),
3590                    list: crate::render::DEP_AUTHOR_KEY_DEPS_DEV,
3591                }
3592            })?;
3593        }
3594        Ok(())
3595    }
3596
3597    /// Reject `:nome` values the K8s apiserver would refuse at admission
3598    /// time. The top-level Caixa identity flows directly into every
3599    /// substrate-side artifact's `metadata.name` axis: the
3600    /// `lareira-<nome>` Helm chart name ([`caixa-helm::lib::chart_name`]),
3601    /// the programs.yaml `name:` entry the `lareira-fleet-programs`
3602    /// aggregator keys ComputeUnit derivation off
3603    /// ([`caixa-flux::lib::programs_yaml_entry`]), the
3604    /// `LABEL_APLICACAO` label value carried on every Aplicacao-owned
3605    /// pod and the per-`:contratos` CiliumNetworkPolicy `metadata.name`
3606    /// (`<aplicacao>-<de>-to-<para>`) and the per-`:entrada`
3607    /// `<aplicacao>-<para>` HTTPRoute `metadata.name`
3608    /// ([`caixa-mesh::lib::cilium_network_policies`],
3609    /// [`caixa-mesh::lib::gateway_routes`]), and the default
3610    /// `lib/<nome>.lisp` / `exe/<nome>` layout paths
3611    /// ([`crate::StandardLayout::verify`]). Each K8s apiserver-side
3612    /// schema enforces the DNS-1123 label rule on admission; a
3613    /// structurally invalid `:nome` (`"MyApp"` — the canonical
3614    /// "I copied the display name verbatim" footgun, `"my_app"` — the
3615    /// Python-/Postgres-leak, `"team.app"` — `:nome` is a single label
3616    /// not a subdomain, `"-app"` / `"app-"` — DNS-1123 boundary
3617    /// violations, `"my app"` — the paste-from-doc footgun, `"café"` —
3618    /// IDN must be pre-encoded as Punycode, the 64-byte UUID-shaped
3619    /// over-cap slug) silently passed [`Caixa::from_lisp`] and the
3620    /// failure surfaced at `kubectl apply` time as a `metadata.name:
3621    /// Invalid value` rejection on whichever derived artifact admitted
3622    /// first, far from the source `caixa.lisp` and without any field
3623    /// naming the offending `:nome`.
3624    ///
3625    /// Thin wrapper around [`crate::render::is_dns_1123_label`] (the
3626    /// substrate-side predicate the per-axis name gates already share:
3627    /// `:membros :caixa` 3f9d7a0, `:placement :clusters` 6cbb900,
3628    /// `:children :caixa` 31bfa43) that maps the shared parser-shaped
3629    /// reason into the [`ManifestError::NomeInvalid`] variant, so the
3630    /// diagnostic is self-locating (the offending `:nome` is named
3631    /// verbatim) and the author can grep their `caixa.lisp` for
3632    /// `:nome "<value>"` and fix it in one edit. Same diagnostic shape
3633    /// every per-axis sibling gate already exposes
3634    /// ([`crate::AplicacaoError::MembroCaixaInvalid`],
3635    /// [`crate::AplicacaoError::PlacementClusterInvalid`],
3636    /// [`crate::SupervisorError::ChildCaixaInvalid`]).
3637    ///
3638    /// Empty `:nome` (which [`Caixa::from_lisp`] does not reject — the
3639    /// derive macro stores the raw String) is gated by the narrower
3640    /// [`ManifestError::NomeEmpty`] arm before the predicate is
3641    /// consulted, mirroring the empty-first cascade every per-axis
3642    /// name gate already uses (e.g. `MembroCaixaEmpty` before
3643    /// `MembroCaixaInvalid`, `EmptyChildName` before `ChildCaixaInvalid`).
3644    pub fn validate_nome(&self) -> Result<(), ManifestError> {
3645        // Routes through the shared
3646        // [`crate::render::require_valid_dns_1123_label`] gate the peer
3647        // name axes each land on so drift between the eight axes'
3648        // accepted DNS-1123-label sets is structurally impossible.
3649        let nome = self.nome();
3650        crate::render::require_valid_dns_1123_label(
3651            nome,
3652            || ManifestError::NomeEmpty,
3653            |reason| ManifestError::NomeInvalid {
3654                nome: nome.to_string(),
3655                reason,
3656            },
3657        )
3658    }
3659
3660    /// Reject `:nome` values whose joint length with the canonical
3661    /// [`crate::LAREIRA_CHART_NAME_PREFIX`] (`"lareira-"`) overflows
3662    /// the K8s DNS-1123 label cap [`crate::DNS_1123_LABEL_MAX_LEN`]
3663    /// (63 bytes). Every per-Servico / per-Aplicacao renderer the
3664    /// substrate carries materializes the caixa's `:nome` through the
3665    /// canonical [`crate::lareira_chart_name`] helper (f7320d7) into a
3666    /// `lareira-<nome>` artifact that lands as a K8s `metadata.name` /
3667    /// Helm chart name / `HelmRelease` `release_name`: `caixa-helm`'s
3668    /// `ChartDir.name` + `Chart.yaml::name`
3669    /// (caixa-helm/src/lib.rs:207), `caixa-flux`'s `cluster_bundle`
3670    /// `HelmRelease` `chart:` slot (caixa-flux/src/lib.rs:329),
3671    /// `caixa-tatara`'s `process_for_aplicacao` `release_name` +
3672    /// `oci://<registry>/lareira-<nome>` chart ref
3673    /// (caixa-tatara/src/lib.rs:124,178). Helm's own `Chart.yaml::name`
3674    /// admission rule strict-parses against DNS-1123-label, the Helm
3675    /// operator's tracking-secret name is derived from `release_name`
3676    /// and is itself DNS-1123-label-bounded, and the rendered chart's
3677    /// K8s object `metadata.name` axes embed the chart name as a
3678    /// prefix — every one fails admission on a > 63-byte chart name.
3679    ///
3680    /// The per-axis [`Self::validate_nome`] gate (6c992f8) already
3681    /// caps `:nome` itself at 63 bytes via [`is_dns_1123_label`], so a
3682    /// `:nome` of 56–63 bytes silently passed validate (the inner
3683    /// DNS-1123 check accepts the bare `:nome`) but produced a
3684    /// `lareira-<nome>` of 64–71 bytes that the apiserver / `helm lint`
3685    /// rejected at admission — far from the source `caixa.lisp`, with
3686    /// no field naming the overflow root cause. The
3687    /// [`lareira_chart_name`] helper's own doc comment
3688    /// (caixa-core/src/render.rs:3198) explicitly deferred the fix:
3689    /// "the M4 admission webhook will pin the joint-length invariant
3690    /// when it lands". This gate lands the invariant at the
3691    /// manifest-validate layer rather than waiting for the apiserver
3692    /// — the same fail-at-the-source posture every peer per-axis
3693    /// value-shape gate (DNS-1123 on `:nome`, SemVer-2 on `:versao`,
3694    /// SPDX-expression-shape on `:licenca`, 4-digit decimal year on
3695    /// `:edicao`, etc.) takes.
3696    ///
3697    /// Thin wrapper around
3698    /// [`crate::render::is_lareira_chart_name_shape`] (the
3699    /// substrate-side predicate that composes [`lareira_chart_name`] +
3700    /// [`is_dns_1123_label`] via the lifted
3701    /// [`crate::LAREIRA_CHART_NAME_NOME_MAX_LEN`] budget); maps the
3702    /// shared parser-shaped reason into the
3703    /// [`ManifestError::NomeChartNameBudgetExceeded`] variant so the
3704    /// diagnostic is self-locating (the offending `:nome` is named
3705    /// verbatim alongside the rendered chart name and the budget) and
3706    /// the author can shorten in one edit. The gate runs across every
3707    /// `:kind` — `:nome` is the substrate-wide identity axis any
3708    /// future renderer the substrate adds can derive a
3709    /// `lareira-<nome>` artifact from, and uniform enforcement closes
3710    /// the drift footgun where a future kind grows a chart-emitting
3711    /// render path while the validate cascade doesn't catch it.
3712    ///
3713    /// Runs *after* [`Self::validate_nome`] so the narrower
3714    /// `NomeEmpty` / `NomeInvalid` shape diagnostics fire first — a
3715    /// structurally-malformed `:nome` (empty, uppercase, underscore,
3716    /// dot, leading/trailing hyphen, Unicode, > 63 bytes) surfaces its
3717    /// specific shape error rather than the chart-name-budget error,
3718    /// preserving the legitimate "well-shaped `:nome` that happens to
3719    /// overflow the joint cap" arm for this gate.
3720    pub fn validate_nome_chart_name_budget(&self) -> Result<(), ManifestError> {
3721        let nome = self.nome();
3722        crate::render::is_lareira_chart_name_shape(nome).map_err(|reason| {
3723            ManifestError::NomeChartNameBudgetExceeded {
3724                nome: nome.to_string(),
3725                reason,
3726            }
3727        })
3728    }
3729
3730    /// Reject `:versao` values that don't parse as [`semver::Version`].
3731    /// The top-level Caixa version flows directly into every
3732    /// substrate-side artifact that carries a "this is which version of
3733    /// the caixa" axis: the `lareira-<nome>` Helm chart's `Chart.yaml`
3734    /// `version:` + `appVersion:` axes ([`caixa-helm::lib`] —
3735    /// SemVer-2-strict at `helm template` / `helm install` time per
3736    /// https://helm.sh/docs/topics/charts/#charts-and-versioning), the
3737    /// `feira publish` Zig-style `v<versao>` git tag
3738    /// ([`caixa-flux::lib::programs_yaml_entry`] / the
3739    /// `caixa-publish.yml` reusable workflow), the programs.yaml entry's
3740    /// `versao:` value the `lareira-fleet-programs` aggregator carries
3741    /// onto each rendered ComputeUnit, the OCI image's `:v<versao>` /
3742    /// `:latest` tags the substrate's `wasi-service-flake` builds with
3743    /// `skopeo push`, the lacre closure's pinned versions
3744    /// ([`caixa-resolver`] keys `concrete_versao`), and the
3745    /// `:upgrade-from :from` references peers in this exact `versao`
3746    /// shape (`semver::Version`, not `VersionReq`). Each consumer
3747    /// expects a strict three-part `MAJOR.MINOR.PATCH` (optionally
3748    /// `-prerelease` and/or `+build`); a structurally invalid `:versao`
3749    /// (`"0.1"` — missing patch, the canonical "I shortened it" footgun;
3750    /// `"v0.1.0"` — the git-tag-shape-leaking-into-versao typo;
3751    /// `"latest"` / `"main"` — the "I confused it with a docker tag"
3752    /// footgun; `"^0.1"` / `"~0.1.2"` — the requirement-shape leaking
3753    /// into the version field a peer `:deps :versao` accepts;
3754    /// `"0.1.0.0"` — the four-part Java/Microsoft convention DNS
3755    /// SemVer-2 forbids) silently passed [`Caixa::from_lisp`] (the
3756    /// derive macro stores the raw String) and the failure surfaced at
3757    /// the *first* downstream consumer that strict-parses it: at
3758    /// `helm install` time as a chart-version rejection, at
3759    /// `feira publish` time as a malformed git tag, at lacre-resolve
3760    /// time as a `semver::Error` not naming the offending caixa, at
3761    /// `feira upgrade --to <versao>` time as an unresolvable
3762    /// `:upgrade-from :from` match — far from the source `caixa.lisp`
3763    /// and without any field naming the offending `:versao`.
3764    ///
3765    /// Thin wrapper around [`semver::Version::parse`] — the same parser
3766    /// [`crate::CaixaVersion::parse`] (the typed `:versao` accessor)
3767    /// and [`crate::UpgradeFromEntry::validate`] (the peer
3768    /// `:upgrade-from :from` axis, 26da2c7) consume. Maps the
3769    /// `semver::Error` reason into the [`ManifestError::VersaoInvalid`]
3770    /// variant, carrying the offending `:versao` verbatim + a
3771    /// parser-shaped reason naming the specific violation, so the
3772    /// diagnostic is self-locating (the author can grep their
3773    /// `caixa.lisp` for `:versao "<value>"` and fix it in one edit).
3774    /// Same diagnostic shape as [`ManifestError::NomeInvalid`]
3775    /// (6c992f8) and [`crate::UpgradeError::FromInvalid`]
3776    /// (b0c8389) on the peer axes. With this gate, the typed `:versao`
3777    /// surfaces — top-level `:versao`, `:upgrade-from :from` — are
3778    /// now structurally equivalent (every value past validate is
3779    /// round-trippable through [`semver::Version::parse`] without
3780    /// re-checking at the renderer, resolver, or operator hot-upgrade
3781    /// layer), peer with the four `:versao` requirement axes (`:deps`,
3782    /// `:deps-dev`, `:membros`, `:children`) the prior commits
3783    /// (2420c44, 9888b13, b38ff3a) wired through `parse_requirement`.
3784    ///
3785    /// Empty `:versao` (which [`Caixa::from_lisp`] does not reject —
3786    /// the derive macro stores the raw String) is gated by the
3787    /// narrower [`ManifestError::VersaoEmpty`] arm before the parser is
3788    /// consulted, mirroring the empty-first cascade every per-axis
3789    /// version gate already uses (e.g. `MembroVersaoEmpty` before
3790    /// `MembroVersaoInvalid`, `EmptyChildVersion` before
3791    /// `ChildVersaoInvalid`, `NomeEmpty` before `NomeInvalid`).
3792    pub fn validate_versao(&self) -> Result<(), ManifestError> {
3793        let versao = self.versao();
3794        if versao.is_empty() {
3795            return Err(ManifestError::VersaoEmpty);
3796        }
3797        semver::Version::parse(versao).map_err(|e| ManifestError::VersaoInvalid {
3798            versao: versao.to_string(),
3799            reason: e.to_string(),
3800        })?;
3801        Ok(())
3802    }
3803
3804    /// Reject `:restart-window` values the shared
3805    /// [`crate::supervisor::duration_codec::parse`] refuses. The flat
3806    /// `restart_window: Option<String>` slot on [`Caixa`] is stored
3807    /// raw by the derive macro (the typed [`SupervisorSpec`] holds an
3808    /// `Option<Duration>` routed through the shared codec via `with =
3809    /// "duration_codec"`); the inline `Caixa → SupervisorSpec`
3810    /// view-construction path ([`Self::supervisor_view`]) folds the
3811    /// raw string through the same shared codec and soft-swallows the
3812    /// parse error as `None` to keep the view best-effort. Without
3813    /// this gate a malformed `:restart-window` (`"1.5s"` — the
3814    /// fractional-seconds drift class; `"1.0s"` — the decimal-shaped
3815    /// integer drift; `"0.5m"` — the unit-fraction drift; `"+30s"` /
3816    /// `"-30s"` — the leading-sign drift; `"30x"` — the unknown-unit
3817    /// footgun; `"abc"` — pure garbage; `""` — the empty-after-trim
3818    /// edge case) silently produced a `SupervisorSpec` with
3819    /// `restart_window: None`, indistinguishable from the canonical
3820    /// "omit the slot to express no reset" authoring shape — Erlang/OTP's
3821    /// `MaxIntensity / Period` invariant turns into a never-reset
3822    /// supervisor far from the source `caixa.lisp`, with no field
3823    /// naming the offending `:restart-window`. Lifting the gate to a
3824    /// Caixa-level validator mirrors the trajectory of the peer
3825    /// per-axis identity gates ([`Self::validate_nome`] 6c992f8,
3826    /// [`Self::validate_versao`] 1fdaa02, [`Self::validate_deps`]
3827    /// a7f0d8c) and the ABSORPTION-ROADMAP.md M2.2 test pin
3828    /// (line 196: "reject invalid `:restart-window` (non-duration)").
3829    ///
3830    /// Thin wrapper around [`crate::supervisor::duration_codec::parse`]
3831    /// (the shared codec backing `:supervisor :restart-window` as
3832    /// serde-routed on [`SupervisorSpec`], `:politicas :timeout`, and
3833    /// `:politicas :circuit-breaker :window` — all three covered by
3834    /// the integer-magnitude gate 1c55a2a). Maps the codec's parse
3835    /// error verbatim into the [`ManifestError::RestartWindowMalformed`]
3836    /// variant, carrying the offending raw string + a parser-shaped
3837    /// reason naming the canonical authoring form, so the diagnostic
3838    /// is self-locating (the author can grep their `caixa.lisp` for
3839    /// `:restart-window "<value>"` and fix it in one edit) and
3840    /// uniform with every other manifest-level validate diagnostic.
3841    /// With this gate the four `:restart-window`-shaped surfaces (the
3842    /// flat raw string on [`Caixa`], the typed `Option<Duration>` on
3843    /// [`SupervisorSpec`], the two `MeshPolicy` peer durations) are
3844    /// now structurally equivalent — every value past the codec is in
3845    /// one accepted set, by construction.
3846    ///
3847    /// `None` (the canonical "omit the slot to express no reset"
3848    /// shape) is accepted trivially — the gate is a no-op when the
3849    /// author didn't author a window. The empty string is rejected by
3850    /// the shared codec (its digit-only gate refuses an empty
3851    /// magnitude), surfacing the same `RestartWindowMalformed`
3852    /// diagnostic as every other rejected non-canonical shape.
3853    pub fn validate_restart_window(&self) -> Result<(), ManifestError> {
3854        let Some(s) = self.restart_window() else {
3855            return Ok(());
3856        };
3857        crate::supervisor::duration_codec::parse(s)
3858            .map(|_| ())
3859            .map_err(|reason| ManifestError::RestartWindowMalformed {
3860                restart_window: s.to_string(),
3861                reason,
3862            })
3863    }
3864
3865    /// Reject per-entry values on the three Caixa-level code-surface
3866    /// path lists (`:bibliotecas`, `:exe`, `:servicos`) that the
3867    /// layout checker's `root.join(p)` sandbox would silently subvert.
3868    /// Same three structural footguns the peer
3869    /// [`BehaviorSpec::validate`] (b0c8389) and
3870    /// [`crate::UpgradeInstruction::validate`] `StateChange` arm
3871    /// (26da2c7) already close on the M2 `:behavior :on-*` and
3872    /// `:upgrade-from :state-change :script` axes, here lifted onto
3873    /// the three top-level code-path axes through the shared
3874    /// [`is_sandboxed_relative_path`] predicate:
3875    ///
3876    ///   - empty entry (`(:bibliotecas (""))` / `(:exe (""))` /
3877    ///     `(:servicos (""))`): `PathBuf::new()` round-trips through
3878    ///     [`Path::join`] as the base itself — `root.join("")` ==
3879    ///     `root`, so the existence check (`self.exists(&root)`)
3880    ///     trivially passes (the project root exists), and the layout
3881    ///     silently treats the project root as a biblioteca / exe /
3882    ///     servico entry. The `:bibliotecas` loop then hands the root
3883    ///     to `tatara_lisp::read` at `feira build` time as if the root
3884    ///     directory itself were a Lisp source file — a parse error
3885    ///     far from the source `caixa.lisp` with no field naming the
3886    ///     offending entry.
3887    ///   - absolute path (`(:bibliotecas ("/etc/passwd"))`):
3888    ///     [`Path::join`] *replaces* the base when the right-hand side
3889    ///     is absolute, so `root.join("/etc/passwd")` resolves to
3890    ///     `"/etc/passwd"` and escapes the project sandbox entirely.
3891    ///     The existence check then silently consults whatever the
3892    ///     escaped path resolves to — for `:bibliotecas`, the layout
3893    ///     has no `starts_with`-fence (only `:exe` is fenced under
3894    ///     `exe/` and `:servicos` under `servicos/`), so an absolute
3895    ///     `:bibliotecas` entry that happens to resolve on disk
3896    ///     silently passes. For `:exe` / `:servicos` the fence catches
3897    ///     the absolute case downstream as `ExeOutsideDir` /
3898    ///     `ServicoOutsideDir` (or `MissingEntry` if the absolute path
3899    ///     doesn't exist), but with a downstream-shaped diagnostic
3900    ///     that names the resolved escape path rather than the
3901    ///     authoring footgun at the source.
3902    ///   - parent-escape (`(:bibliotecas ("../sibling/x.lisp"))` /
3903    ///     `(:exe ("exe/../../escape.lisp"))`): a [`PathBuf`] with any
3904    ///     [`std::path::Component::ParentDir`] anywhere round-trips
3905    ///     through [`Path::join`] as a traversal above the caixa root.
3906    ///     The `:exe` / `:servicos` `starts_with(<dir>)` fence is
3907    ///     *component-aware* (not canonical-path-aware), so
3908    ///     `root.join("exe/../../escape.lisp")` `starts_with(exe_dir)`
3909    ///     is **true** even though the canonical resolution
3910    ///     `{parent of root}/escape.lisp` lives outside the caixa root
3911    ///     — the fence silently lets the parent-escape through, and
3912    ///     the existence check passes if that escape-target happens
3913    ///     to exist. Caught regardless of where the `..` sits
3914    ///     (leading, mid-path, trailing) so the gate matches the peer
3915    ///     predicate's full coverage.
3916    ///
3917    /// Same `Empty` → `Absolute` → `ParentEscape` arm-ordering every peer
3918    /// `is_sandboxed_relative_path` consumer follows (b0c8389 / 26da2c7);
3919    /// same per-slot diagnostic shape every peer per-axis path-gate
3920    /// exposes (`*Empty { slot }` / `*Absolute { slot, path }` /
3921    /// `*ParentEscape { slot, path }`). Cross-slot precedence is
3922    /// `:bibliotecas` → `:exe` → `:servicos` — the same declaration
3923    /// order [`Caixa::declared_foreign_code_slots`] uses for its
3924    /// canonical foreign-code-slot diagnostic, so a manifest with
3925    /// multiple malformed slots surfaces the lexicographically-earliest
3926    /// slot's diagnostic deterministically.
3927    ///
3928    /// Lifted to the typed surface as a Caixa-level validator (peer
3929    /// of [`Self::validate_nome`] / [`Self::validate_versao`] /
3930    /// [`Self::validate_deps`] / [`Self::validate_restart_window`])
3931    /// and wired into [`crate::StandardLayout::verify`] before the
3932    /// existence-check loops so the diagnostic names the offending
3933    /// slot at the source caixa.lisp rather than reporting a
3934    /// downstream `MissingEntry` / `ExeOutsideDir` /
3935    /// `ServicoOutsideDir` against the resolved sandbox-escape path.
3936    /// The fourth typed code-path surface — every author-supplied
3937    /// path on the manifest — is now structurally accept-shaped
3938    /// past validate, peer with `:behavior :on-*` and
3939    /// `:upgrade-from :state-change :script`.
3940    pub fn validate_code_paths(&self) -> Result<(), ManifestError> {
3941        /// Per-slot file-type contract for the three Caixa-level
3942        /// code-path surfaces (`:bibliotecas`, `:exe`, `:servicos`).
3943        /// Each variant names the predicate the per-entry file-type
3944        /// gate consults; [`Self::None`] opts the slot out of any
3945        /// file-type contract. Lifted as a typed local enum so the
3946        /// per-slot dispatch is exhaustive at the `match` — adding a
3947        /// future axis to the typed-substrate `:` slot set (the
3948        /// future `:assets` resource axis the M5 roadmap names, the
3949        /// future `:nix-flake` derivation axis the caixa-flake
3950        /// emitter consults) lands as one variant + one `match` arm,
3951        /// not a coordinated rewrite of every per-slot bool flag.
3952        ///
3953        /// Peer of the typed-substrate per-slot variant disciplines
3954        /// already established on this surface
3955        /// ([`crate::supervisor::RestartStrategy`] +
3956        /// [`crate::supervisor::RestartPolicy`] on the OTP-shape
3957        /// supervision-tree axis,
3958        /// [`crate::aplicacao::PlacementStrategy`] on the §III.1
3959        /// placement axis, [`crate::aplicacao::WitTarget`] on the
3960        /// `:contratos` payload-target axis): the typed `enum` is
3961        /// the substrate's single source of truth for the per-axis
3962        /// dispatch, and every consumer (the per-arm body here, the
3963        /// future feira-lint per-slot diagnostic renderer, the M4
3964        /// per-axis admission webhook) reaches for the same typed
3965        /// surface rather than re-deriving the partition from inline
3966        /// flag combinations.
3967        enum CodePathFileType {
3968            /// `:exe` — nix-build derivation output, no terminating-
3969            /// extension contract (the canonical `"exe/<name>"`
3970            /// fixtures the layout's `ExeOutsideDir` error message
3971            /// documents carry no extension by convention).
3972            None,
3973            /// `:bibliotecas` — tatara-lisp source files the
3974            /// `feira build` loop reads through `tatara_lisp::read`
3975            /// at parse time. Routes to [`is_lisp_extension`].
3976            LispSource,
3977            /// `:servicos` — ComputeUnit-CR YAML files the
3978            /// caixa-helm / caixa-flux renderers consume through
3979            /// `serde_yaml::from_str`. Routes to
3980            /// [`is_computeunit_yaml_extension`].
3981            ComputeUnitYaml,
3982        }
3983
3984        // The per-slot [`CodePathFileType`] selects which axes carry the
3985        // lifted file-type predicate. `:bibliotecas` is the tatara-lisp
3986        // source axis (the `feira build` loop at
3987        // `caixa-feira/src/cmd/build.rs:33` reads each entry through
3988        // `tatara_lisp::read` at parse time) — the lifted
3989        // [`is_lisp_extension`] predicate gates the `.lisp` extension.
3990        // `:exe` is the nix-built executable surface (per the canonical
3991        // `"exe/<name>"`-shaped fixtures the layout's `ExeOutsideDir`
3992        // error message documents and every in-tree
3993        // `caixa_with_code_paths` positive control uses) — its file-type
3994        // contract is "nix-build derivation output", not a typed source
3995        // file, so [`CodePathFileType::None`] opts the slot out of any
3996        // file-type gate. `:servicos` is the `.computeunit.yaml`
3997        // ComputeUnit-CR axis (the peer caixa-helm / caixa-flux
3998        // renderers consume each entry through `serde_yaml::from_str` as
3999        // a typed `ComputeUnit` CR) — the lifted
4000        // [`is_computeunit_yaml_extension`] predicate gates the compound
4001        // `.computeunit.yaml` suffix. All three axes are surfaced through
4002        // the same iteration so the sandbox-shape + duplicate gates
4003        // apply uniformly; the typed file-type dispatch fires per-slot
4004        // exactly where the downstream consumer's accepted set demands
4005        // it. The third file-type variant ([`ComputeUnitYaml`]) is the
4006        // compounding lift on the peer 64772a9 `:bibliotecas`
4007        // `.lisp`-gate trajectory — the second of the three code-path
4008        // axes to land on a typed compound-suffix gate, with the same
4009        // self-locating per-slot diagnostic shape every peer per-axis
4010        // file-type lift uses (`*NonLispExtension { slot, path }` /
4011        // `*NonComputeUnitYamlExtension { slot, path }`).
4012        for (slot, list, file_type) in [
4013            (
4014                ":bibliotecas",
4015                &self.bibliotecas,
4016                CodePathFileType::LispSource,
4017            ),
4018            (":exe", &self.exe, CodePathFileType::None),
4019            (
4020                ":servicos",
4021                &self.servicos,
4022                CodePathFileType::ComputeUnitYaml,
4023            ),
4024        ] {
4025            // Per-slot set-not-multiset gate on the typed code-path axis.
4026            // Every peer Vec-shaped author-supplied list past validate is
4027            // a set, not a multiset: `:membros :caixa`
4028            // ([`crate::AplicacaoError::MembroDuplicate`]), `:placement
4029            // :clusters` ([`crate::AplicacaoError::PlacementClusterDuplicate`]),
4030            // `:entrada :paths` ([`crate::AplicacaoError::EntradaPathDuplicate`]),
4031            // `:contratos` ([`crate::AplicacaoError::ContratoDuplicate`]),
4032            // `:children :caixa` ([`crate::SupervisorError::DuplicateChild`]),
4033            // `:deps` / `:deps-dev` `:nome` ([`crate::DepError::DuplicateNome`]
4034            // per 359fba5), `:upgrade-from :from` ([`crate::UpgradeError::DuplicateFrom`]),
4035            // `:etiquetas` ([`ManifestError::EtiquetaDuplicate`] per 360a499),
4036            // `:autores` ([`ManifestError::AutorDuplicate`] per 86c769b) —
4037            // the three code-path lists are the last Vec-shaped author-
4038            // supplied slots on the typed Caixa surface still admitting a
4039            // duplicate entry silently. Scope is per-list (`:bibliotecas`
4040            // duplicates are flagged within `:bibliotecas`, not across
4041            // `:bibliotecas` ↔ `:exe`) — the same per-list scope `:deps`
4042            // ↔ `:deps-dev` use (a `:nome` present in both lists is a
4043            // legitimate dev-vs-runtime shape on the dep axis, fenced
4044            // separately by [`crate::dep::validate_no_self_dep`]). On the
4045            // code-path axis a cross-slot collision is structurally
4046            // impossible by the layout's `starts_with(<exe|servicos>_dir)`
4047            // fence — `:exe` and `:servicos` entries are confined to their
4048            // own directory trees, so the only way a string could appear
4049            // on two code-path lists is the (rare, structurally invalid)
4050            // case where `:bibliotecas` carries an `"exe/<x>"` or
4051            // `"servicos/<x>.yaml"`-shaped path.
4052            //
4053            // Without the gate three authoring footguns silently passed:
4054            //
4055            //   - `:bibliotecas ("lib/foo.lisp" "lib/foo.lisp")` — the
4056            //     canonical copy-paste-the-wrong-file footgun. `feira
4057            //     build` (`caixa-feira/src/cmd/build.rs:33`) walks the
4058            //     list and re-parses the same file twice, wasting work
4059            //     and silently masking the author's intent to declare a
4060            //     *second* biblioteca.
4061            //   - `:exe ("exe/cli" "exe/cli")` — the same footgun on the
4062            //     Binario surface. The future `caixa-flake` `nix flake`
4063            //     emitter that materializes each `:exe` entry as a flake
4064            //     `packages.<exe-name>` derivation would collide on the
4065            //     duplicate package name and surface a flake-eval error
4066            //     far from the source `caixa.lisp`.
4067            //   - `:servicos ("servicos/x.computeunit.yaml"
4068            //     "servicos/x.computeunit.yaml")` — the same footgun on
4069            //     the Servico surface. The peer `caixa-helm` / `caixa-flux`
4070            //     renderers already refuse `:servicos.len() != 1` with
4071            //     the narrower [`UnsupportedServicoCount`] diagnostic, but
4072            //     that diagnostic surfaces "too many servicos" without
4073            //     naming "duplicate entry" — the typed self-locating
4074            //     "which entry is the duplicate" framing only lands at
4075            //     this gate.
4076            //
4077            // Same `seen.insert(entry.as_str())` shape every peer per-list
4078            // duplicate gate uses (`:etiquetas` 360a499, `:autores`
4079            // 86c769b, `:deps` 359fba5) and the same "structural shape
4080            // checks fire before the duplicate check on the same entry"
4081            // ordering (a `(:bibliotecas ("" "lib/x.lisp" "lib/x.lisp"))`
4082            // shape surfaces the narrower [`Self::CodePathEmpty`] for the
4083            // empty entry first, not the duplicate on the later pair).
4084            let mut seen = std::collections::HashSet::new();
4085            for entry in list {
4086                let path = Path::new(entry);
4087                match is_sandboxed_relative_path(path) {
4088                    Ok(()) => {}
4089                    Err(PathShapeViolation::Empty) => {
4090                        return Err(ManifestError::CodePathEmpty { slot });
4091                    }
4092                    Err(PathShapeViolation::Absolute) => {
4093                        return Err(ManifestError::CodePathAbsolute {
4094                            slot,
4095                            path: path.to_path_buf(),
4096                        });
4097                    }
4098                    Err(PathShapeViolation::ParentEscape) => {
4099                        return Err(ManifestError::CodePathParentEscape {
4100                            slot,
4101                            path: path.to_path_buf(),
4102                        });
4103                    }
4104                }
4105                // The per-slot file-type gate dispatched through the
4106                // typed [`CodePathFileType`] selector above. Each variant
4107                // routes to the lifted predicate the downstream consumer
4108                // demands:
4109                //
4110                //   - [`LispSource`] → [`is_lisp_extension`] for
4111                //     `:bibliotecas` (the `feira build` loop's
4112                //     `tatara_lisp::read` consumer);
4113                //   - [`ComputeUnitYaml`] → [`is_computeunit_yaml_extension`]
4114                //     for `:servicos` (the caixa-helm / caixa-flux
4115                //     `serde_yaml::from_str` consumer's `ComputeUnit` CR
4116                //     accepted set);
4117                //   - [`None`] for `:exe` — the nix-build derivation-
4118                //     output axis has no terminating-extension contract.
4119                //
4120                // Fires after the sandbox-shape arms so a path that is
4121                // *both* sandbox-escaping and wrong-extension surfaces
4122                // the more fundamental sandbox-shape diagnostic first
4123                // (mirrors the peer `EmptyPath` → `AbsolutePath` →
4124                // `ParentEscape` → `NonLispExtension` arm-ordering on
4125                // `:behavior :on-*` c97815a, and `EmptyScript` →
4126                // `AbsoluteScript` → `ParentEscapeScript` →
4127                // `NonLispExtensionScript` on
4128                // `:upgrade-from :state-change :script` 33cc830), and
4129                // before the duplicate gate so the narrower per-entry
4130                // file-type shape dominates the cross-entry uniqueness
4131                // diagnostic (a
4132                // `("servicos/x.yaml" "servicos/x.yaml")` shape on
4133                // `:servicos` surfaces
4134                // `CodePathNonComputeUnitYamlExtension` on the first
4135                // entry rather than `CodePathDuplicate` on the pair —
4136                // peer with the 64772a9 `:bibliotecas`
4137                // `("lib/x.txt" "lib/x.txt")` ordering).
4138                match file_type {
4139                    CodePathFileType::None => {}
4140                    CodePathFileType::LispSource => {
4141                        if !is_lisp_extension(path) {
4142                            return Err(ManifestError::CodePathNonLispExtension {
4143                                slot,
4144                                path: path.to_path_buf(),
4145                            });
4146                        }
4147                    }
4148                    CodePathFileType::ComputeUnitYaml => {
4149                        if !is_computeunit_yaml_extension(path) {
4150                            return Err(ManifestError::CodePathNonComputeUnitYamlExtension {
4151                                slot,
4152                                path: path.to_path_buf(),
4153                            });
4154                        }
4155                    }
4156                }
4157                crate::render::insert_first_seen(&mut seen, entry.as_str(), || {
4158                    ManifestError::CodePathDuplicate {
4159                        slot,
4160                        path: path.to_path_buf(),
4161                    }
4162                })?;
4163            }
4164        }
4165        Ok(())
4166    }
4167
4168    /// Reject `:etiquetas` lists with an empty entry or with two entries
4169    /// agreeing on the same string. `:etiquetas` is the universal
4170    /// registry-search-tag axis on [`Caixa`] (every kind carries the
4171    /// `Vec<String>` slot) and lands verbatim as the Helm chart
4172    /// `Chart.yaml` `keywords:` array on every Servico (caixa-helm's
4173    /// `build_chart_yaml` at `caixa-helm/src/lib.rs:236` folds it through
4174    /// a [`std::collections::BTreeSet`] alongside the four substrate-
4175    /// fixed tags `lareira` / `wasm` / `tatara-lisp` / `caixa-servico`).
4176    /// Two authoring footguns silently passed validate without this gate:
4177    ///
4178    ///   - Empty entry (`(:etiquetas (""))` — the canonical paste-from-
4179    ///     blank-doc footgun) rendered as `keywords: ["", "caixa-servico",
4180    ///     "lareira", "tatara-lisp", "wasm"]` in `Chart.yaml`. Helm's
4181    ///     `chart.metadata.keywords` admits the value without a strict
4182    ///     parser-side gate, but the empty keyword has no operational
4183    ///     meaning — it indexes nothing in the future caixa-registry
4184    ///     search axis and clutters the rendered chart with a no-op tag.
4185    ///   - Duplicate entries (`(:etiquetas ("demo" "demo"))` — the
4186    ///     copy-paste-the-wrong-tag footgun) silently passed validate
4187    ///     and were silently dedup'd by caixa-helm's `BTreeSet` collect
4188    ///     at chart render — a "second wins / one silently disappears"
4189    ///     shape divergent from every peer typed-graph set gate
4190    ///     ([`crate::AplicacaoError::MembroDuplicate`] on `:membros`,
4191    ///     [`crate::AplicacaoError::PlacementClusterDuplicate`] on
4192    ///     `:placement :clusters`, [`crate::AplicacaoError::EntradaPathDuplicate`]
4193    ///     on `:entrada :paths`, [`crate::AplicacaoError::ContratoDuplicate`]
4194    ///     on `:contratos`, [`crate::DepError::DuplicateNome`] on
4195    ///     `:deps` / `:deps-dev` per 359fba5, [`crate::UpgradeError::DuplicateFrom`]
4196    ///     on `:upgrade-from`, the per-instruction-class singularity
4197    ///     gates [`crate::UpgradeError::DuplicateLoadModule`] /
4198    ///     [`crate::UpgradeError::DuplicateStateChange`] /
4199    ///     [`crate::UpgradeError::DuplicateCleanup`]). The typed-graph
4200    ///     discipline is uniform: every Vec-shaped author-supplied list
4201    ///     past validate is set-not-multiset, by construction.
4202    ///
4203    /// Past the empty arm the gate enforces the chart-keyword shape
4204    /// predicate via [`crate::render::is_chart_keyword_shape`]: Cargo's
4205    /// crates.io `[package] keywords` grammar — 1..=20 bytes, starts
4206    /// with an ASCII letter, ASCII alphanumeric / `_` / `-`
4207    /// continuation. Closes the canonical paste-from-doc footguns the
4208    /// bare empty + duplicate arms left open: paste-from-aligned-doc
4209    /// whitespace (`" mesh"`, `"mesh "`), paste-from-multiline-doc
4210    /// newline (`"mesh\nhttp"` — the author pasted a multi-tag block
4211    /// into one entry instead of splitting), paste-from-Windows-CRLF-doc
4212    /// carriage return, CSV-list-separator confusion (`"mesh,http,grpc"`
4213    /// — the author meant three separate list entries), path-separator
4214    /// confusion (`"caixa/servico"`), namespace-suffix (`"http.1"`),
4215    /// leading-digit (`"1foo"`), kebab-leak (`"-foo"`), snake-leak
4216    /// (`"_foo"`), non-ASCII (`"café"`), and paste-from-binary-blob
4217    /// control bytes that would silently land as malformed search tags
4218    /// in the rendered Chart.yaml `keywords:` array and break the
4219    /// Artifact Hub keyword index lookup far from the source caixa.lisp.
4220    /// Mirrors the [`Self::validate_autores`] shape-predicate cascade
4221    /// established on the sibling universal-axis `Vec<String>` surface
4222    /// — the second universal-axis Vec<String> surface to land the
4223    /// empty-first-then-shape-then-duplicate per-entry cascade.
4224    ///
4225    /// Same empty-first cascade discipline every peer per-axis gate
4226    /// uses: the per-entry empty arm fires before the per-entry shape
4227    /// arm fires before the cross-entry duplicate arm, so an
4228    /// `("" "mesh" "mesh")` authoring shape surfaces the narrower
4229    /// [`ManifestError::EtiquetaEmpty`] (the structural "this entry
4230    /// has no value" defect) before either the shape or the duplicate
4231    /// diagnostic. Walks the list in declaration order so the
4232    /// first-collision diagnostic surfaces the lexicographically-
4233    /// earliest offending position, peer with every other duplicate
4234    /// gate on this surface.
4235    ///
4236    /// Universal-axis (every kind carries `:etiquetas`), so wired at the
4237    /// caixa-build gate alongside the peer universal gates
4238    /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4239    /// [`Self::validate_deps`] / [`Self::validate_code_paths`] — before
4240    /// the kind-coherence gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`]
4241    /// / [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4242    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4243    /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-specific
4244    /// slot sets. The future caixa-registry search axis can reach for
4245    /// `caixa.etiquetas` knowing every entry is a non-empty distinct
4246    /// chart-keyword-shaped string without re-deriving the precondition.
4247    pub fn validate_etiquetas(&self) -> Result<(), ManifestError> {
4248        let mut seen = std::collections::HashSet::new();
4249        for etiqueta in self.etiquetas() {
4250            if etiqueta.is_empty() {
4251                return Err(ManifestError::EtiquetaEmpty);
4252            }
4253            crate::render::is_chart_keyword_shape(etiqueta).map_err(|reason| {
4254                ManifestError::EtiquetaInvalid {
4255                    etiqueta: etiqueta.clone(),
4256                    reason,
4257                }
4258            })?;
4259            crate::render::insert_first_seen(&mut seen, etiqueta.as_str(), || {
4260                ManifestError::EtiquetaDuplicate {
4261                    etiqueta: etiqueta.clone(),
4262                }
4263            })?;
4264        }
4265        Ok(())
4266    }
4267
4268    /// Reject `:autores` lists with an empty entry or with two entries
4269    /// agreeing on the same string. `:autores` is the universal
4270    /// maintainer-axis on [`Caixa`] (every kind carries the
4271    /// `Vec<String>` slot) and lands verbatim as the Helm chart
4272    /// `Chart.yaml` `maintainers:` array on every Servico (caixa-helm's
4273    /// `build_chart_yaml` at `caixa-helm/src/lib.rs:251` maps each entry
4274    /// to a `Maintainer { name, email: None }` without dedup). Two
4275    /// authoring footguns silently passed validate without this gate:
4276    ///
4277    ///   - Empty entry (`(:autores (""))` — the canonical paste-from-
4278    ///     blank-doc footgun) rendered as
4279    ///     `maintainers: [{name: "", email: null}]` in `Chart.yaml`. The
4280    ///     empty maintainer name has no operational meaning — it
4281    ///     identifies no one in the substrate's authorship index and
4282    ///     clutters the rendered chart with a no-op maintainer.
4283    ///   - Duplicate entries (`(:autores ("pleme-io" "pleme-io"))` —
4284    ///     the copy-paste-the-wrong-author footgun) silently passed
4285    ///     validate and rendered as two identical maintainer entries.
4286    ///     Unlike the [`Self::validate_etiquetas`] peer (caixa-helm's
4287    ///     `BTreeSet`-collect on `:etiquetas` silently dedups the
4288    ///     rendered `keywords:` array at chart-render time), the
4289    ///     `maintainers:` rendering has *no* dedup — duplicate `:autores`
4290    ///     entries stack verbatim in the chart, divergent from every
4291    ///     peer typed-graph set gate ([`crate::AplicacaoError::MembroDuplicate`]
4292    ///     on `:membros`, [`crate::AplicacaoError::PlacementClusterDuplicate`]
4293    ///     on `:placement :clusters`, [`crate::AplicacaoError::EntradaPathDuplicate`]
4294    ///     on `:entrada :paths`, [`crate::AplicacaoError::ContratoDuplicate`]
4295    ///     on `:contratos`, [`crate::DepError::DuplicateNome`] on
4296    ///     `:deps` / `:deps-dev`, [`crate::UpgradeError::DuplicateFrom`]
4297    ///     on `:upgrade-from`, [`ManifestError::EtiquetaDuplicate`] on
4298    ///     `:etiquetas`).
4299    ///
4300    /// Past the empty arm the gate enforces the chart-maintainer-name
4301    /// shape predicate via [`crate::render::is_chart_maintainer_name_shape`]:
4302    /// the structural single-line printable-UTF-8 floor every realistic
4303    /// Helm chart maintainer name carries — 1..=128 bytes, no leading
4304    /// or trailing whitespace, no ASCII control characters anywhere,
4305    /// Unicode bytes accepted. Closes the canonical paste-from-doc
4306    /// footguns the bare empty + duplicate arms left open:
4307    /// paste-from-aligned-doc whitespace (`" pleme-io"`, `"pleme-io "`),
4308    /// paste-from-multiline-doc newline (`"alice\nbob"` — the author
4309    /// pasted a multi-line block of author records into one `:autores`
4310    /// entry instead of splitting into one entry per author),
4311    /// paste-from-Windows-CRLF-doc carriage return, tab-from-aligned-doc,
4312    /// and the paste-from-binary-blob control bytes that would silently
4313    /// land as YAML-illegal byte sequences in the rendered Chart.yaml
4314    /// `maintainers:` array. Mirrors the shape-predicate cascade
4315    /// [`Self::validate_descricao`] / [`Self::validate_licenca`] /
4316    /// [`Self::validate_edicao`] / [`Self::validate_repositorio`]
4317    /// establish past their own empty arms on the sibling universal-axis
4318    /// `Option<String>` surfaces — the first universal-axis Vec<String>
4319    /// surface to land the empty-first-then-shape-then-duplicate per-entry
4320    /// cascade.
4321    ///
4322    /// Same empty-first cascade discipline every peer per-axis gate
4323    /// uses: the per-entry empty arm fires before the per-entry shape
4324    /// arm before the cross-entry duplicate arm. Walks the list in
4325    /// declaration order so the first-collision diagnostic surfaces the
4326    /// lexicographically-earliest offending position, peer with every
4327    /// other duplicate gate on this surface.
4328    ///
4329    /// Universal-axis (every kind carries `:autores`), so wired at the
4330    /// caixa-build gate alongside the peer universal gates
4331    /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4332    /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
4333    /// [`Self::validate_code_paths`] — before the kind-coherence gates
4334    /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
4335    /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4336    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4337    /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-specific
4338    /// slot sets.
4339    pub fn validate_autores(&self) -> Result<(), ManifestError> {
4340        let mut seen = std::collections::HashSet::new();
4341        for autor in self.autores() {
4342            if autor.is_empty() {
4343                return Err(ManifestError::AutorEmpty);
4344            }
4345            crate::render::is_chart_maintainer_name_shape(autor).map_err(|reason| {
4346                ManifestError::AutorInvalid {
4347                    autor: autor.clone(),
4348                    reason,
4349                }
4350            })?;
4351            crate::render::insert_first_seen(&mut seen, autor.as_str(), || {
4352                ManifestError::AutorDuplicate {
4353                    autor: autor.clone(),
4354                }
4355            })?;
4356        }
4357        Ok(())
4358    }
4359
4360    /// Reject `:repositorio` values whose shape the shared
4361    /// [`crate::render::is_git_repo_url`] predicate refuses. The flat
4362    /// `repositorio: Option<String>` slot on [`Caixa`] is the
4363    /// universal git-shaped homepage axis every kind carries — the
4364    /// substrate routes the same string through two load-bearing
4365    /// consumers:
4366    ///
4367    ///   - [`caixa-helm`] folds it verbatim into the rendered
4368    ///     `lareira-<nome>` Helm chart's `Chart.yaml` `home:` field
4369    ///     (`build_chart_yaml` at `caixa-helm/src/lib.rs:268`) and into
4370    ///     the chart `README.md` `repo = …` interpolation
4371    ///     (`caixa-helm/src/lib.rs:359`).
4372    ///   - [`caixa-flux`] folds it verbatim into the standalone
4373    ///     `ClusterBundleOpts::for_caixa` `git_url:` field
4374    ///     (`caixa-flux/src/lib.rs:293`), which becomes the `FluxCD`
4375    ///     `GitRepository.spec.url` the cluster's source-controller
4376    ///     polls — the load-bearing deploy-time axis.
4377    ///
4378    /// Both consumers use `Option::unwrap_or_else(|| <fallback>)` to
4379    /// substitute a placeholder when the slot is absent (`None` → the
4380    /// fallback fires); a `Some("")` *skips the fallback* and silently
4381    /// passes the empty string through to `Chart.yaml home: ""` /
4382    /// `GitRepository url: ""` — Helm's chart lint and `FluxCD`'s source
4383    /// controller both reject the empty URL far from the source
4384    /// `caixa.lisp`, with no field naming the offending `:repositorio`.
4385    /// Similarly a malformed `:repositorio` (whitespace, control char,
4386    /// missing `:` separator, leading `-`) silently lands in the
4387    /// rendered artifacts and breaks at `git clone` / `helm template`
4388    /// / `flux reconcile` time.
4389    ///
4390    /// Thin wrapper around [`crate::render::is_git_repo_url`] — the
4391    /// same shared predicate the peer [`crate::DepSource::validate`]
4392    /// routes the `:fonte (:tipo git :repo …)` axis through. With this
4393    /// gate the two `git URL`-shaped surfaces on the typed Caixa
4394    /// (`:repositorio` here, `:deps :fonte :repo` peer) are
4395    /// structurally equivalent: every value past validate is
4396    /// guaranteed-acceptable by the predicate's union of constraints
4397    /// (non-empty, length-bounded, no leading `-`, no whitespace, no
4398    /// control chars, ASCII only, no leading `:`, contains a `:`
4399    /// separator). The predicate accepts every documented authoring
4400    /// shape — `github:org/repo` shorthand, `https://host/path`,
4401    /// `ssh://[user@]host/path`, `git://host/path`, `git@host:path`
4402    /// scp-style SSH, `file:///path` — and refuses the canonical
4403    /// paste-from-blank-doc / paste-from-multiline-doc / CLI-arg-
4404    /// injection footguns at validate time. Maps the predicate's
4405    /// `String` reason verbatim into the
4406    /// [`ManifestError::RepositorioInvalid`] variant, carrying the
4407    /// offending value + parser-shaped reason so the diagnostic is
4408    /// self-locating (the author can grep their `caixa.lisp` for
4409    /// `:repositorio "<value>"` and fix it in one edit).
4410    ///
4411    /// `None` (the canonical "omit the slot to express no published
4412    /// homepage" shape) is accepted trivially — the gate is a no-op
4413    /// when the author didn't declare a value. `Some("")` is gated by
4414    /// the narrower [`ManifestError::RepositorioEmpty`] arm before the
4415    /// shape predicate is consulted, mirroring the empty-first cascade
4416    /// every peer per-axis identity gate uses
4417    /// ([`ManifestError::NomeEmpty`] → [`ManifestError::NomeInvalid`],
4418    /// [`ManifestError::VersaoEmpty`] → [`ManifestError::VersaoInvalid`],
4419    /// [`crate::DepError::FonteRepoEmpty`] →
4420    /// [`crate::DepError::FonteRepoInvalid`]).
4421    ///
4422    /// Universal-axis (every kind carries `:repositorio`), so wired at
4423    /// the caixa-build gate alongside the peer universal gates
4424    /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4425    /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
4426    /// [`Self::validate_autores`] / [`Self::validate_code_paths`] —
4427    /// before the kind-coherence gates
4428    /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
4429    /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4430    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4431    /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
4432    /// specific slot sets.
4433    pub fn validate_repositorio(&self) -> Result<(), ManifestError> {
4434        let Some(s) = self.repositorio() else {
4435            return Ok(());
4436        };
4437        if s.is_empty() {
4438            return Err(ManifestError::RepositorioEmpty);
4439        }
4440        is_git_repo_url(s).map_err(|reason| ManifestError::RepositorioInvalid {
4441            repositorio: s.to_string(),
4442            reason,
4443        })
4444    }
4445
4446    /// Reject `:descricao` values that are the empty string. The flat
4447    /// `descricao: Option<String>` slot on [`Caixa`] is the universal
4448    /// free-form-prose homepage axis every kind carries — the
4449    /// substrate routes the same string through two load-bearing
4450    /// consumers in the [`caixa-helm`] renderer:
4451    ///
4452    ///   - `build_chart_yaml` folds it verbatim into the rendered
4453    ///     `lareira-<nome>` Helm chart's `Chart.yaml` `description:`
4454    ///     field (`caixa-helm/src/lib.rs:232-235`).
4455    ///   - `build_readme` folds it verbatim into the rendered chart
4456    ///     `README.md` header (`caixa-helm/src/lib.rs:333-336`).
4457    ///
4458    /// Both consumers use `Option::unwrap_or_else(|| <fallback>)` to
4459    /// substitute a `caixa.nome`-derived placeholder when the slot is
4460    /// absent (`None` → the fallback fires); a `Some("")` *skips the
4461    /// fallback* and silently passes the empty string through to
4462    /// `Chart.yaml description: ""` / a blank chart `README.md`
4463    /// header. Helm's chart spec requires a non-empty `description:`
4464    /// field on `apiVersion: v2` charts (`helm lint` surfaces it as
4465    /// `WARNING [chart.metadata.description]: description is required`),
4466    /// so the empty `Some("")` silently lands in the rendered
4467    /// artifacts and breaks at `helm lint` / `helm install` time far
4468    /// from the source `caixa.lisp`, with no field naming the
4469    /// offending `:descricao`.
4470    ///
4471    /// `None` (the canonical "omit the slot to defer to the renderer's
4472    /// `caixa.nome`-derived fallback" shape) is accepted trivially —
4473    /// the gate is a no-op when the author didn't declare a value.
4474    /// `Some("")` is gated by the narrower
4475    /// [`ManifestError::DescricaoEmpty`] arm, mirroring the empty-arm
4476    /// shape every peer per-axis empty gate uses
4477    /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
4478    /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
4479    /// [`ManifestError::RepositorioEmpty`]).
4480    ///
4481    /// Universal-axis (every kind carries `:descricao`), so wired at
4482    /// the caixa-build gate alongside the peer universal gates
4483    /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4484    /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
4485    /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
4486    /// [`Self::validate_code_paths`] — before the kind-coherence
4487    /// gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
4488    /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4489    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4490    /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
4491    /// specific slot sets.
4492    ///
4493    /// Past the empty arm the gate enforces the chart-description
4494    /// shape predicate via [`crate::render::is_chart_description_shape`]:
4495    /// the structural single-line UTF-8 floor every realistic chart
4496    /// description in the wild matches — 1..=512 bytes, no leading
4497    /// or trailing whitespace, no ASCII control characters anywhere
4498    /// (`0x00..=0x1F` plus `0x7F` DEL — banning tab, newline,
4499    /// carriage return, and every other control byte), Unicode
4500    /// continuation bytes accepted (the canonical fixtures carry
4501    /// `→` and `—`). Closes the canonical paste-from-doc footguns
4502    /// the bare empty-arm gate left open: paste-from-aligned-doc
4503    /// leading / trailing whitespace (`" Checkout flow."`,
4504    /// `"Checkout flow. "`), paste-from-multiline-doc newline
4505    /// (`"Checkout\nflow."`), paste-from-Windows-CRLF-doc CR
4506    /// (`"Checkout\rflow."`), tab-from-aligned-doc
4507    /// (`"Checkout\tflow."`), and paste-from-binary-blob NUL / BEL /
4508    /// ESC / DEL bytes. Mirrors the shape-predicate cascade
4509    /// [`Self::validate_repositorio`] / [`Self::validate_licenca`] /
4510    /// [`Self::validate_edicao`] establish past their own empty arms
4511    /// on the sibling universal-axis `Option<String>` Caixa-level
4512    /// value-shape surfaces.
4513    ///
4514    /// The empty-first cascade discipline mirrors every peer per-axis
4515    /// identity gate: [`ManifestError::DescricaoEmpty`] runs before
4516    /// [`ManifestError::DescricaoInvalid`], so the narrower empty
4517    /// diagnostic surfaces on `Some("")` rather than the broader
4518    /// shape-predicate diagnostic — peer with how
4519    /// [`ManifestError::LicencaEmpty`] runs before
4520    /// [`ManifestError::LicencaInvalid`],
4521    /// [`ManifestError::EdicaoEmpty`] runs before
4522    /// [`ManifestError::EdicaoInvalid`],
4523    /// [`ManifestError::RepositorioEmpty`] runs before
4524    /// [`ManifestError::RepositorioInvalid`].
4525    pub fn validate_descricao(&self) -> Result<(), ManifestError> {
4526        let Some(s) = self.descricao() else {
4527            return Ok(());
4528        };
4529        if s.is_empty() {
4530            return Err(ManifestError::DescricaoEmpty);
4531        }
4532        crate::render::is_chart_description_shape(s).map_err(|reason| {
4533            ManifestError::DescricaoInvalid {
4534                descricao: s.to_string(),
4535                reason,
4536            }
4537        })?;
4538        Ok(())
4539    }
4540
4541    /// Reject `:licenca` values that are the empty string. The flat
4542    /// `licenca: Option<String>` slot on [`Caixa`] is the universal
4543    /// SPDX-shaped license-expression axis every kind carries — the
4544    /// substrate routes the same string through the [`caixa-helm`]
4545    /// renderer's `build_readme` which folds it verbatim into the
4546    /// rendered `lareira-<nome>` Helm chart's `README.md` `## License`
4547    /// section (`caixa-helm/src/lib.rs:361`) via
4548    /// `caixa.licenca.clone().unwrap_or_else(|| "MIT".into())`. The
4549    /// fallback only fires on `None`; a `Some("")` *skips the
4550    /// fallback* and silently passes the empty string through to a
4551    /// chart `README.md` whose `License` section renders as the bare
4552    /// trailing period (`.\n`) — peer footgun with the
4553    /// `Some("")`-skips-`unwrap_or_else` shape the
4554    /// [`Self::validate_descricao`] and [`Self::validate_repositorio`]
4555    /// gates close on the sibling free-form-prose and git-URL axes.
4556    ///
4557    /// `None` (the canonical "omit the slot to defer to the
4558    /// renderer's `MIT` fallback" shape every existing fixture
4559    /// carries) is accepted trivially — the gate is a no-op when the
4560    /// author didn't declare a value. `Some("")` is gated by the
4561    /// narrower [`ManifestError::LicencaEmpty`] arm, mirroring the
4562    /// empty-arm shape every peer per-axis empty gate uses
4563    /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
4564    /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
4565    /// [`ManifestError::RepositorioEmpty`],
4566    /// [`ManifestError::DescricaoEmpty`]).
4567    ///
4568    /// Universal-axis (every kind carries `:licenca`), so wired at
4569    /// the caixa-build gate alongside the peer universal gates
4570    /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4571    /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
4572    /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
4573    /// [`Self::validate_descricao`] / [`Self::validate_code_paths`]
4574    /// — before the kind-coherence gates
4575    /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
4576    /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4577    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4578    /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
4579    /// specific slot sets.
4580    ///
4581    /// Past the empty arm the gate enforces the SPDX-expression shape
4582    /// predicate via [`crate::render::is_spdx_expression_shape`]: the
4583    /// structural alphabet floor every realistic SPDX expression in
4584    /// the wild uses — ASCII alphanumeric plus `.`, `-`, `+`, `(`,
4585    /// `)`, `:` (the `DocumentRef-…:LicenseRef-…` separator), and a
4586    /// single ASCII space (token separator). Closes the canonical
4587    /// paste-from-doc footguns the bare empty-arm gate left open:
4588    /// paste-from-doc whitespace (`"MIT "`, `" MIT"`), paste-from-
4589    /// multiline-doc CRLF (`"MIT\n"`), tab-from-aligned-doc
4590    /// (`"MIT\tOR Apache-2.0"`), non-ASCII smart-quote paste,
4591    /// underscore-instead-of-hyphen typo (`"Apache_2.0"`),
4592    /// comma-instead-of-`OR`-keyword colloquial idiom (`"MIT,
4593    /// Apache-2.0"`), slash-dual-license colloquial idiom (`"MIT/
4594    /// Apache-2.0"`), and semicolon-list-separator confusion
4595    /// (`"MIT; Apache-2.0"`). Mirrors the shape-predicate cascade
4596    /// [`Self::validate_repositorio`] / [`Self::validate_edicao`]
4597    /// establish past their own empty arms.
4598    ///
4599    /// The empty-first cascade discipline mirrors every peer per-axis
4600    /// identity gate: [`ManifestError::LicencaEmpty`] runs before
4601    /// [`ManifestError::LicencaInvalid`], so the narrower empty
4602    /// diagnostic surfaces on `Some("")` rather than the broader
4603    /// shape-predicate diagnostic — peer with how
4604    /// [`ManifestError::EdicaoEmpty`] runs before
4605    /// [`ManifestError::EdicaoInvalid`],
4606    /// [`ManifestError::RepositorioEmpty`] runs before
4607    /// [`ManifestError::RepositorioInvalid`].
4608    ///
4609    /// A future tightening on this axis can extend the alphabet
4610    /// floor into a full SPDX expression parser + license-id
4611    /// allowlist (rejecting alphabet-valid values that don't name a
4612    /// real SPDX license identifier — e.g., `"NotAReal"` is
4613    /// alphabet-valid but no `NotAReal` license-id exists). That
4614    /// parser only becomes meaningful past a real SPDX-spec
4615    /// dependency; this gate establishes the structural floor by
4616    /// refusing every non-SPDX-alphabet value at validate time.
4617    pub fn validate_licenca(&self) -> Result<(), ManifestError> {
4618        let Some(s) = self.licenca() else {
4619            return Ok(());
4620        };
4621        if s.is_empty() {
4622            return Err(ManifestError::LicencaEmpty);
4623        }
4624        crate::render::is_spdx_expression_shape(s).map_err(|reason| {
4625            ManifestError::LicencaInvalid {
4626                licenca: s.to_string(),
4627                reason,
4628            }
4629        })?;
4630        Ok(())
4631    }
4632
4633    /// Reject `:edicao` values that are the empty string. The flat
4634    /// `edicao: Option<String>` slot on [`Caixa`] is the universal
4635    /// language-edition axis every kind carries — it determines the
4636    /// tatara-lisp macro surface + compatibility flags the substrate
4637    /// applies when building a caixa, and lands verbatim in the
4638    /// `Caixa::template` author-time scaffold (the canonical
4639    /// `:edicao "2026"` line every `feira init` emits via
4640    /// [`Caixa::template`] at `caixa-core/src/manifest.rs:1193`) and
4641    /// in every renderer-side fixture (`caixa-helm/src/lib.rs:375`,
4642    /// `caixa-flux/src/lib.rs:445`, `caixa-mesh/src/lib.rs:629`,
4643    /// `caixa-core/src/render.rs:2510`) via
4644    /// `edicao: Some("2026".into())`.
4645    ///
4646    /// `None` (the canonical "omit the slot to defer to the
4647    /// substrate's default edition" shape every existing
4648    /// [`caixa-resolver`] integration test fixture carries via
4649    /// `edicao: None` — see `caixa-resolver/tests/git_integration.rs`)
4650    /// is accepted trivially — the gate is a no-op when the author
4651    /// didn't declare a value. `Some("")` is gated by the narrower
4652    /// [`ManifestError::EdicaoEmpty`] arm, mirroring the empty-arm
4653    /// shape every peer per-axis empty gate uses
4654    /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
4655    /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
4656    /// [`ManifestError::RepositorioEmpty`],
4657    /// [`ManifestError::DescricaoEmpty`], [`ManifestError::LicencaEmpty`]).
4658    ///
4659    /// Universal-axis (every kind carries `:edicao`), so wired at
4660    /// the caixa-build gate alongside the peer universal gates
4661    /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4662    /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
4663    /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
4664    /// [`Self::validate_descricao`] / [`Self::validate_licenca`] /
4665    /// [`Self::validate_code_paths`] — before the kind-coherence
4666    /// gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
4667    /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4668    /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4669    /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
4670    /// specific slot sets.
4671    ///
4672    /// Past the empty arm the gate enforces the canonical year-shape
4673    /// predicate: every documented tatara-lisp edition is a 4-digit
4674    /// ASCII decimal year (`"2026"` is the only edition currently
4675    /// minted; future-introduced siblings will follow the same
4676    /// shape, peer with Cargo's `[package] edition` grammar which
4677    /// every value Cargo has ever accepted matches — `"2015"`,
4678    /// `"2018"`, `"2021"`, `"2024"`). Any value that's not exactly
4679    /// 4 ASCII decimal bytes is rejected with the narrower
4680    /// [`ManifestError::EdicaoInvalid`] arm, mirroring the
4681    /// shape-predicate cascade [`Self::validate_repositorio`]
4682    /// establishes past its own empty arm
4683    /// ([`ManifestError::RepositorioEmpty`] →
4684    /// [`ManifestError::RepositorioInvalid`]). Closes the canonical
4685    /// paste-from-doc footguns the bare empty-arm gate left open:
4686    ///
4687    ///   - leading / trailing whitespace from a paste-from-doc
4688    ///     (`"2026 "`, `" 2026"`)
4689    ///   - control characters / CRLF from a paste-from-multiline-doc
4690    ///     (`"2026\n"`)
4691    ///   - non-ASCII look-alikes from a fullwidth keyboard
4692    ///     (`"2026"`) which would silently land as a non-ASCII
4693    ///     string in the rendered caixa.lisp
4694    ///   - free-form non-year values (`"x"`, `"latest"`,
4695    ///     `"nightly"`) that have no operational meaning on the
4696    ///     substrate's build-time edition selector
4697    ///   - leading non-digit prefixes (`"v2026"`, `"e2026"`,
4698    ///     `"r2026"`) — common version-tag idioms that don't apply
4699    ///     to the year-shaped edition axis
4700    ///   - decimal-shaped values (`"2026.1"`, `"2026.0"`) — every
4701    ///     edition is a year, not a fractional version
4702    ///   - wrong-length numeric values (`"26"`, `"202"`, `"20260"`,
4703    ///     `"00026"`) that don't name a year
4704    ///
4705    /// `None` (the canonical "omit the slot to defer to the
4706    /// substrate's default edition" shape every existing
4707    /// [`caixa-resolver`] integration test fixture carries via
4708    /// `edicao: None` — see `caixa-resolver/tests/git_integration.rs`)
4709    /// is accepted trivially — the gate is a no-op when the author
4710    /// didn't declare a value. The empty-first cascade discipline
4711    /// mirrors every peer per-axis identity gate:
4712    /// [`ManifestError::EdicaoEmpty`] runs before
4713    /// [`ManifestError::EdicaoInvalid`], so the narrower empty
4714    /// diagnostic surfaces on `Some("")` rather than the broader
4715    /// shape-predicate diagnostic — peer with how
4716    /// [`ManifestError::NomeEmpty`] runs before
4717    /// [`ManifestError::NomeInvalid`],
4718    /// [`ManifestError::VersaoEmpty`] runs before
4719    /// [`ManifestError::VersaoInvalid`],
4720    /// [`ManifestError::RepositorioEmpty`] runs before
4721    /// [`ManifestError::RepositorioInvalid`].
4722    ///
4723    /// A future tightening on this axis can extend the shape
4724    /// predicate into a known-edition allowlist (rejecting
4725    /// year-shaped values that don't name a tatara-lisp edition
4726    /// the substrate actually understands — e.g., `"1999"` is
4727    /// year-shaped but no `1999` edition exists). That allowlist
4728    /// only becomes meaningful past the introduction of a sibling
4729    /// edition to `"2026"`; this gate establishes the structural
4730    /// floor by refusing every non-year-shaped value at validate
4731    /// time.
4732    pub fn validate_edicao(&self) -> Result<(), ManifestError> {
4733        let Some(s) = self.edicao() else {
4734            return Ok(());
4735        };
4736        if s.is_empty() {
4737            return Err(ManifestError::EdicaoEmpty);
4738        }
4739        if s.len() != 4 || !s.bytes().all(|b| b.is_ascii_digit()) {
4740            return Err(ManifestError::EdicaoInvalid {
4741                edicao: s.to_string(),
4742                reason: "must be a 4-digit ASCII decimal year (canonical \"2026\")".to_string(),
4743            });
4744        }
4745        Ok(())
4746    }
4747
4748    /// Compose the supervisor-related flat slots into a single
4749    /// [`SupervisorSpec`] for validation. Returns `None` when the
4750    /// caixa isn't a `:kind Supervisor`.
4751    ///
4752    /// The flat representation in [`Caixa`] keeps tatara-lisp authoring
4753    /// simple (one form, no nested `:supervisor (…)` block); this view
4754    /// is the "typed shape" the operator + supervisor reconciler
4755    /// consume.
4756    #[must_use]
4757    pub fn supervisor_view(&self) -> Option<SupervisorSpec> {
4758        if !self.kind().is_supervisor() {
4759            return None;
4760        }
4761        // Fold through the shared `supervisor::duration_codec::parse`
4762        // — the same parser the serde-routed `with = "duration_codec"`
4763        // on `SupervisorSpec::restart_window`, the `:politicas
4764        // :timeout` codec, and the `:politicas :circuit-breaker
4765        // :window` codec all consume. The prior inline f64-shaped
4766        // duplicate (`parse_window_inline`) admitted every magnitude
4767        // the integer-magnitude gate (1c55a2a) rejects on the three
4768        // serde-routed siblings — `"1.5s"`, `"1.0s"`, `"0.5m"`,
4769        // `"+30s"`, `"-30s"` — and silently dropped malformed input as
4770        // `None` (i.e. "no reset"), divergent from the shared codec's
4771        // integer-magnitude discipline by construction. The fold
4772        // closes the divergence: every value the typed
4773        // `SupervisorSpec` carries past `supervisor_view` is in the
4774        // shared codec's accepted set. The `.ok()` here preserves the
4775        // existing soft-swallow shape on this view-construction path;
4776        // the new [`Caixa::validate_restart_window`] (sibling of
4777        // [`Self::validate_nome`] / [`Self::validate_versao`]) names
4778        // the offending raw string at build time so authoring tools
4779        // (`feira lint`, the future layout-side wire-up) surface a
4780        // self-locating diagnostic instead of a silently dropped
4781        // window.
4782        let restart_window = self
4783            .restart_window()
4784            .and_then(|s| crate::supervisor::duration_codec::parse(s).ok());
4785        Some(SupervisorSpec {
4786            estrategia: self.estrategia().unwrap_or_default(),
4787            max_restarts: self.max_restarts().unwrap_or(5),
4788            restart_window,
4789            children: self.children().to_vec(),
4790        })
4791    }
4792
4793    /// A minimal starter manifest emitted by `feira init`.
4794    #[must_use]
4795    pub fn template(nome: &str) -> String {
4796        format!(
4797            "(defcaixa\n  \
4798               :nome        {nome:?}\n  \
4799               :versao      \"0.1.0\"\n  \
4800               :kind        Biblioteca\n  \
4801               :edicao      \"2026\"\n  \
4802               :descricao   \"FIXME — describe this caixa\"\n  \
4803               :autores     ()\n  \
4804               :etiquetas   ()\n  \
4805               :deps        ()\n  \
4806               :deps-dev    ()\n  \
4807               :bibliotecas (\"lib/{nome}.lisp\"))\n"
4808        )
4809    }
4810
4811    /// Serialize to a canonical `caixa.lisp` source — suitable for writing
4812    /// back after mutation (e.g. `feira add`).
4813    ///
4814    /// Goes through serde JSON → canonical Sexp → per-field pretty print.
4815    /// The derive-macro `compile_from_sexp` path is the inverse, so any
4816    /// `Caixa` round-trips through `to_lisp` + `from_lisp`.
4817    #[must_use]
4818    pub fn to_lisp(&self) -> String {
4819        let json = serde_json::to_value(self).expect("Caixa serialize");
4820        let sexp = tatara_lisp::domain::json_to_sexp(&json);
4821        let tatara_lisp::Sexp::List(items) = sexp else {
4822            return format!("(defcaixa {sexp})\n");
4823        };
4824        let mut out = String::from("(defcaixa");
4825        let mut i = 0;
4826        while i + 1 < items.len() {
4827            out.push_str("\n  ");
4828            out.push_str(&items[i].to_string());
4829            out.push(' ');
4830            out.push_str(&items[i + 1].to_string());
4831            i += 2;
4832        }
4833        out.push_str(")\n");
4834        out
4835    }
4836}
4837
4838/// Errors raised by top-level [`Caixa`] validators that don't fit
4839/// the per-axis [`DepError`] / [`crate::AplicacaoError`] /
4840/// [`crate::SupervisorError`] / [`crate::LayoutError`] families —
4841/// the Caixa's own identity axes (`:nome`, `:versao`) that flow
4842/// through every substrate-side artifact's `metadata.name` /
4843/// version derivation.
4844///
4845/// A future top-level sum (the M4 `CaixaError` the [`DepError`]
4846/// doc-comment anticipates) can hold one of each per-axis error
4847/// family without reshaping individual diagnostics; this enum is
4848/// the first such per-Caixa-identity family.
4849#[derive(Debug, Error, PartialEq, Eq)]
4850pub enum ManifestError {
4851    #[error(
4852        ":nome is empty (every caixa must name itself; the value flows \
4853         into every K8s artifact's `metadata.name` derivation and into \
4854         the default `lib/<nome>.lisp` / `exe/<nome>` layout paths)"
4855    )]
4856    NomeEmpty,
4857    #[error(
4858        ":nome {nome:?} is not a valid DNS-1123 label: {reason} (the K8s \
4859         apiserver enforces this rule on every `metadata.name` the \
4860         caixa's substrate-side renderers derive from `:nome` — the \
4861         `lareira-<nome>` Helm chart name, the programs.yaml entry \
4862         name, the `LABEL_APLICACAO` label value, the `<aplicacao>-<de>-to-<para>` \
4863         CiliumNetworkPolicy name, the `<aplicacao>-<para>` HTTPRoute \
4864         name; use a lowercase alphanumeric + hyphen identifier like \
4865         `\"checkout\"` or `\"cart-v2\"`)"
4866    )]
4867    NomeInvalid { nome: String, reason: String },
4868    #[error(
4869        ":nome {nome:?} overflows the joint-length budget on the canonical \
4870         `lareira-<nome>` chart-name shape: {reason} (every per-Servico / \
4871         per-Aplicacao renderer the substrate carries — `caixa-helm`'s \
4872         `Chart.yaml::name`, `caixa-flux`'s `cluster_bundle` HelmRelease \
4873         `chart:` slot, `caixa-tatara`'s `release_name` + \
4874         `oci://<registry>/lareira-<nome>` chart ref — derives the same \
4875         joint name through the canonical `lareira_chart_name` helper, and \
4876         Helm's `Chart.yaml::name` admission rule + the K8s apiserver's \
4877         DNS-1123 label cap on every chart-name-derived `metadata.name` \
4878         reject any joint name exceeding 63 bytes; the narrower \
4879         `:nome` shape (`NomeInvalid`) gates the bare-`:nome` budget, this \
4880         arm gates the chart-name budget downstream renderers inherit)"
4881    )]
4882    NomeChartNameBudgetExceeded { nome: String, reason: String },
4883    #[error(
4884        ":versao is empty (every caixa must pin its own version; the value flows \
4885         into the `lareira-<nome>` Helm chart's `Chart.yaml` version + appVersion, \
4886         the `feira publish` `v<versao>` git tag, the OCI image's `:v<versao>` / \
4887         `:latest` tags, the lacre closure's `concrete_versao`, and the \
4888         `:upgrade-from :from` peers — use a SemVer-2 literal like `\"0.1.0\"`)"
4889    )]
4890    VersaoEmpty,
4891    #[error(
4892        ":versao {versao:?} is not a valid SemVer-2 version: {reason} (the substrate \
4893         consumes this string as `semver::Version` — three-part `MAJOR.MINOR.PATCH` \
4894         with optional `-prerelease` and `+build` — across every artifact derived \
4895         from `:versao`: the `lareira-<nome>` Helm chart's `Chart.yaml` version + \
4896         appVersion (Helm SemVer-2-strict), the `feira publish` `v<versao>` git tag, \
4897         the OCI image's `:v<versao>` tag, the lacre closure's `concrete_versao`, \
4898         and the `:upgrade-from :from` peers that match against this exact shape; \
4899         use a literal like `\"0.1.0\"`, `\"0.2.0-rc.1\"`, or `\"1.0.0+build.42\"` — \
4900         not a git-tag-shape like `\"v0.1.0\"`, a docker-tag-shape like `\"latest\"`, \
4901         a requirement-shape like `\"^0.1\"`, or a four-part `\"0.1.0.0\"`)"
4902    )]
4903    VersaoInvalid { versao: String, reason: String },
4904    #[error(
4905        ":restart-window {restart_window:?} is not a valid duration: {reason} (the \
4906         substrate consumes this string through the shared \
4907         `supervisor::duration_codec` — the same parser routed via `with = \
4908         \"duration_codec\"` onto the typed `SupervisorSpec::restart_window`, \
4909         `:politicas :timeout`, and `:politicas :circuit-breaker :window` slots; \
4910         the canonical authoring form is `<integer><unit>` where the unit is one \
4911         of `ms` / `s` / `m` / `h` and the magnitude has no decimal point and no \
4912         leading `+` / `-` sign — e.g. `\"60s\"`, `\"5m\"`, `\"1h\"`, `\"500ms\"`. \
4913         Without this gate a malformed `:restart-window` silently produced a \
4914         supervisor with `restart_window: None` (\"never reset\"), turning OTP's \
4915         `MaxIntensity / Period` invariant into a never-reset supervisor far from \
4916         the source `caixa.lisp`; the gate moves the diagnostic to the manifest \
4917         layer with the offending value named verbatim. Omit the slot entirely to \
4918         express \"no reset\"; carry a positive integer duration to express the \
4919         sliding window)"
4920    )]
4921    RestartWindowMalformed {
4922        restart_window: String,
4923        reason: String,
4924    },
4925    #[error(
4926        "{slot} entry is an empty path string — every {slot} entry must name \
4927         a file relative to the caixa root; omit the entry to omit the file \
4928         (the layout checker's `root.join(\"\")` resolves to the caixa root \
4929         itself, so an empty entry silently aliases the project root as a \
4930         declared {slot} file, then fails downstream at parse / existence \
4931         time with a diagnostic that names the root rather than the offending \
4932         entry)"
4933    )]
4934    CodePathEmpty { slot: &'static str },
4935    #[error(
4936        "{slot} entry {} is an absolute path — entries must be relative to \
4937         the caixa root, since `Path::join` replaces the base with an absolute \
4938         right-hand side and `root.join(\"/abs/...\")` resolves to \"/abs/...\" \
4939         outside the caixa root sandbox; rewrite the entry as a relative path \
4940         under the caixa root (e.g. `\"lib/<name>.lisp\"`, `\"exe/<name>\"`, \
4941         `\"servicos/<name>.computeunit.yaml\"`)",
4942        path.display()
4943    )]
4944    CodePathAbsolute { slot: &'static str, path: PathBuf },
4945    #[error(
4946        "{slot} entry {} contains a `..` component — entries must not traverse \
4947         above the caixa root (the layout's `starts_with(<dir>)` fence on \
4948         `:exe` / `:servicos` is component-aware, not canonical-path-aware, \
4949         so a mid-path `..` silently traverses the sandbox; `:bibliotecas` \
4950         has no such fence, so a leading `..` escapes unconditionally if the \
4951         resolved target happens to exist)",
4952        path.display()
4953    )]
4954    CodePathParentEscape { slot: &'static str, path: PathBuf },
4955    #[error(
4956        "{slot} entry {} does not terminate in the `.lisp` extension — every \
4957         `:bibliotecas` entry is a tatara-lisp source file the `feira build` \
4958         loop reads through `tatara_lisp::read` at parse time, so any other \
4959         extension (`.rs`, `.txt`, `.lisp.bak`) or no-extension shape is \
4960         structurally a parser error far from the source caixa.lisp, with \
4961         no field naming the offending `:bibliotecas` entry. Pin a relative \
4962         path under the caixa root whose terminating extension is \
4963         lowercase-`.lisp` (e.g. `\"lib/<name>.lisp\"`, \
4964         `\"lib/handlers.lisp\"`) — the same file-type contract the peer \
4965         `:behavior :on-*` (c97815a) and `:upgrade-from :state-change :script` \
4966         (33cc830) axes already carry through the same lifted \
4967         `is_lisp_extension` predicate",
4968        path.display()
4969    )]
4970    CodePathNonLispExtension { slot: &'static str, path: PathBuf },
4971    #[error(
4972        "{slot} entry {} does not terminate in the `.computeunit.yaml` \
4973         compound suffix — every `:servicos` entry is a typed `ComputeUnit` \
4974         CR YAML file the peer caixa-helm / caixa-flux renderers consume \
4975         through `serde_yaml::from_str` at chart / FluxCD bundle render \
4976         time, so any other extension (`.yaml`, `.yml`, `.json`, the \
4977         off-by-one-segment `.computeunit-yaml`, the editor-backup \
4978         `.computeunit.yaml.bak`) or no-extension shape is structurally a \
4979         YAML-parser error / `ComputeUnit` schema-mismatch far from the \
4980         source caixa.lisp, with no field naming the offending `:servicos` \
4981         entry. Pin a relative path under the caixa root whose terminating \
4982         compound suffix is lowercase-`.computeunit.yaml` (e.g. \
4983         `\"servicos/<name>.computeunit.yaml\"`, \
4984         `\"servicos/hello-rio.computeunit.yaml\"`) — the same file-type \
4985         contract the sibling `:bibliotecas` axis (64772a9) already carries \
4986         on the tatara-lisp-source axis through the peer lifted \
4987         `is_lisp_extension` predicate, here on the compound-suffix axis \
4988         `Path::extension` can't express on its own through the lifted \
4989         `is_computeunit_yaml_extension` predicate",
4990        path.display()
4991    )]
4992    CodePathNonComputeUnitYamlExtension { slot: &'static str, path: PathBuf },
4993    #[error(
4994        "{slot} entry {} appears more than once (the code-path list is \
4995         a set, not a multiset; every peer Vec-shaped author-supplied \
4996         list past validate is set-not-multiset — `:membros :caixa`, \
4997         `:placement :clusters`, `:entrada :paths`, `:contratos`, \
4998         `:children :caixa`, `:deps` / `:deps-dev` `:nome`, \
4999         `:upgrade-from :from`, `:etiquetas`, `:autores` — and the three \
5000         code-path lists are the last Vec-shaped author-supplied slots on \
5001         the typed Caixa surface still admitting a duplicate entry. \
5002         `:bibliotecas` duplicates re-parse the same file at \
5003         `feira build` time and silently mask the author's intent to \
5004         declare a *second* biblioteca; `:exe` duplicates collide on the \
5005         flake `packages.<name>` derivation key at the future \
5006         `caixa-flake` materializer; `:servicos` duplicates surface as the \
5007         narrower [`caixa-helm`] / [`caixa-flux`] `UnsupportedServicoCount` \
5008         rejection far from the source `caixa.lisp`. Drop the duplicate \
5009         or rename it to the actual second file intended)",
5010        path.display()
5011    )]
5012    CodePathDuplicate { slot: &'static str, path: PathBuf },
5013    #[error(
5014        ":etiquetas entry is empty (every tag must carry a non-empty \
5015         registry-search identifier; the empty entry has no operational \
5016         meaning — it indexes nothing in the future caixa-registry search \
5017         axis and clutters the rendered Helm `Chart.yaml` `keywords:` array \
5018         with a no-op tag; omit the entry to express \"no tag on this \
5019         position\")"
5020    )]
5021    EtiquetaEmpty,
5022    #[error(
5023        ":etiquetas entry {etiqueta:?} appears more than once (the \
5024         registry-search tag set is a set, not a multiset; duplicate \
5025         entries are silently dedup'd by caixa-helm's `BTreeSet` collect \
5026         at chart render — a \"second wins / one silently disappears\" \
5027         shape divergent from every peer typed-graph set gate \
5028         (`:membros :caixa`, `:placement :clusters`, `:entrada :paths`, \
5029         `:contratos`, `:deps :nome`, `:upgrade-from :from`); drop the \
5030         duplicate or rename it to the actual tag intended)"
5031    )]
5032    EtiquetaDuplicate { etiqueta: String },
5033    #[error(
5034        ":etiquetas entry {etiqueta:?} is not a valid chart-keyword shape: \
5035         {reason} (the substrate consumes this string through the shared \
5036         `crate::render::is_chart_keyword_shape` predicate — the same \
5037         Cargo crates.io `[package] keywords` grammar entry shape: 1..=20 \
5038         bytes, starts with an ASCII letter, ASCII alphanumeric / `_` / `-` \
5039         continuation. The canonical authoring shapes are short kebab-case \
5040         identifiers like `\"mesh\"`, `\"wasm\"`, `\"tatara-lisp\"`, \
5041         `\"hello-world\"`, `\"caixa-servico\"`, `\"infrastructure\"`. \
5042         Without this gate a malformed `:etiquetas` entry (paste-from-doc \
5043         leading / trailing whitespace `\" mesh\"` / `\"mesh \"`; \
5044         paste-from-multiline-doc newline `\"mesh\\nhttp\"`; \
5045         paste-from-Windows-CRLF-doc CR; CSV-list-separator confusion \
5046         `\"mesh,http,grpc\"` — the author meant to author three separate \
5047         list entries; path-separator confusion `\"caixa/servico\"`; \
5048         namespace-suffix `\"http.1\"`; leading-digit `\"1foo\"`; \
5049         kebab-leak `\"-foo\"`; snake-leak `\"_foo\"`; non-ASCII \
5050         `\"café\"` — every legitimate search tag is strict ASCII; \
5051         paste-from-binary-blob NUL / BEL / ESC / DEL byte) silently \
5052         passed `from_lisp` + `validate_etiquetas` + \
5053         `StandardLayout::verify` and landed in the rendered \
5054         `lareira-<nome>` Helm chart's `Chart.yaml keywords:` array as a \
5055         malformed search tag — Artifact Hub's keyword index + the future \
5056         caixa-registry's keyword index would either silently drop the \
5057         tag or fail to index it far from the source caixa.lisp; the gate \
5058         moves the diagnostic to the manifest layer with the offending \
5059         value named verbatim)"
5060    )]
5061    EtiquetaInvalid { etiqueta: String, reason: String },
5062    #[error(
5063        ":autores entry is empty (every maintainer must carry a non-empty \
5064         identifier; the empty entry has no operational meaning — it \
5065         identifies no one in the substrate's authorship index and renders \
5066         as `maintainers: [{{name: \"\", email: null}}]` in the Helm chart's \
5067         `Chart.yaml`, a no-op maintainer the substrate cannot route to; \
5068         omit the entry to express \"no maintainer on this position\")"
5069    )]
5070    AutorEmpty,
5071    #[error(
5072        ":autores entry {autor:?} appears more than once (the maintainer \
5073         set is a set, not a multiset; unlike `:etiquetas`, caixa-helm's \
5074         `maintainers:` rendering does *no* dedup — duplicate entries \
5075         stack verbatim in `Chart.yaml` as two identical \
5076         `Maintainer {{ name, email: None }}` records, divergent from every \
5077         peer typed-graph set gate (`:etiquetas`, `:membros :caixa`, \
5078         `:placement :clusters`, `:entrada :paths`, `:contratos`, \
5079         `:deps :nome`, `:upgrade-from :from`); drop the duplicate or \
5080         rename it to the actual author intended)"
5081    )]
5082    AutorDuplicate { autor: String },
5083    #[error(
5084        ":autores entry {autor:?} is not a valid chart-maintainer-name shape: \
5085         {reason} (the substrate consumes this string through the shared \
5086         `crate::render::is_chart_maintainer_name_shape` predicate — the same \
5087         single-line-UTF-8 floor every realistic chart maintainer name carries: \
5088         1..=128 bytes, no leading or trailing whitespace, no ASCII control \
5089         characters anywhere, Unicode bytes accepted. The canonical authoring \
5090         shapes are short single-line identifiers like `\"pleme-io\"`, \
5091         `\"Pleme Contributors\"`, `\"alice <alice@example.com>\"`, \
5092         `\"François Dupont\"`. Without this gate a malformed `:autores` entry \
5093         (paste-from-aligned-doc leading whitespace `\" pleme-io\"` / trailing \
5094         whitespace `\"pleme-io \"`; paste-from-multiline-doc newline \
5095         `\"alice\\nbob\"` — the author pasted a multi-line block of author \
5096         records into one entry instead of splitting into one entry per author; \
5097         paste-from-Windows-CRLF-doc carriage return `\"alice\\rbob\"`; \
5098         tab-from-aligned-doc `\"Pleme\\tContributors\"`; paste-from-binary-blob \
5099         NUL / BEL / ESC / DEL byte) silently passed `from_lisp` + \
5100         `validate_autores` + `StandardLayout::verify` and landed in the \
5101         rendered `lareira-<nome>` Helm chart's `Chart.yaml maintainers:` array \
5102         as a YAML-illegal multi-line scalar or a silently-trimmed whitespace \
5103         round-trip — every chart-aware UI (`helm list`, `helm search`, \
5104         Artifact Hub maintainer index) would render the maintainer name in a \
5105         single-line column far from the source caixa.lisp; the gate moves the \
5106         diagnostic to the manifest layer with the offending value named \
5107         verbatim)"
5108    )]
5109    AutorInvalid { autor: String, reason: String },
5110    #[error(
5111        ":repositorio is the empty string (every published caixa names its \
5112         git source via a non-empty `:repositorio` locator — the value \
5113         flows verbatim into the rendered `lareira-<nome>` Helm chart's \
5114         `Chart.yaml` `home:` field via `caixa-helm` and into the FluxCD \
5115         `GitRepository.spec.url` via `caixa-flux`'s \
5116         `ClusterBundleOpts::for_caixa`; both consumers' \
5117         `Option::unwrap_or_else` fallbacks only fire when the slot is \
5118         `None`, so an empty `Some(\"\")` silently lands as `home: \"\"` / \
5119         `url: \"\"` in the rendered artifacts and breaks at `helm \
5120         template` / FluxCD source-controller reconcile time far from the \
5121         source caixa.lisp; omit the slot entirely to defer to the \
5122         renderer's `https://github.com/pleme-io/<nome>` / \
5123         `caixa.nome`-derived fallback, or carry a canonical authoring \
5124         shape like `\"github:org/repo\"`, `\"https://host/path\"`, \
5125         `\"ssh://[user@]host/path\"`, `\"git@host:path\"`, or \
5126         `\"file:///path\"`)"
5127    )]
5128    RepositorioEmpty,
5129    #[error(
5130        ":repositorio {repositorio:?} is not a valid git repo URL: {reason} \
5131         (the substrate consumes this string through the shared \
5132         `crate::render::is_git_repo_url` predicate — the same parser the \
5133         peer `:deps :fonte (:tipo git :repo …)` axis routes its `:repo` \
5134         value through via `DepSource::validate`; the canonical authoring \
5135         shapes are `\"github:org/repo\"` shorthand, `\"https://host/path\"` \
5136         / `\"ssh://[user@]host/path\"` / `\"git://host/path\"` / \
5137         `\"file:///path\"` URL schemes, or the `\"git@host:path\"` \
5138         scp-style SSH form. Without this gate a malformed `:repositorio` \
5139         (whitespace from a paste-from-doc; control characters / CRLF \
5140         from a paste-from-multiline-doc; a leading `-` from a \
5141         CLI-argument-injection footgun; a missing `:` separator from a \
5142         bare `org/repo` shape git treats as a relative filesystem path) \
5143         silently landed in the rendered `Chart.yaml home:` and the \
5144         FluxCD `GitRepository.spec.url` and broke at `git clone` / \
5145         FluxCD reconcile time far from the source caixa.lisp; the gate \
5146         moves the diagnostic to the manifest layer with the offending \
5147         value named verbatim)"
5148    )]
5149    RepositorioInvalid { repositorio: String, reason: String },
5150    #[error(
5151        ":descricao is the empty string (every published caixa names \
5152         its purpose via a non-empty `:descricao` summary — the value \
5153         flows verbatim into the rendered `lareira-<nome>` Helm \
5154         chart's `Chart.yaml` `description:` field via `caixa-helm`'s \
5155         `build_chart_yaml` and into the chart `README.md` header via \
5156         `build_readme`; both consumers' `Option::unwrap_or_else` \
5157         `caixa.nome`-derived fallbacks only fire when the slot is \
5158         `None`, so an empty `Some(\"\")` silently lands as \
5159         `description: \"\"` / a blank `README.md` header in the \
5160         rendered artifacts and breaks at `helm lint` time \
5161         (`WARNING [chart.metadata.description]: description is \
5162         required` on `apiVersion: v2` charts) far from the source \
5163         caixa.lisp; omit the slot entirely to defer to the \
5164         renderer's `\"Generated chart for caixa Servico <nome>\"` / \
5165         `\"caixa Servico <nome>\"` fallbacks, or carry a non-empty \
5166         summary like `\"Canonical Rust→wasm32-wasip2 caixa \
5167         Servico.\"`)"
5168    )]
5169    DescricaoEmpty,
5170    #[error(
5171        ":descricao {descricao:?} is not a valid chart-description shape: \
5172         {reason} (the substrate consumes this string through the shared \
5173         `crate::render::is_chart_description_shape` predicate — the same \
5174         single-line-UTF-8 floor every realistic chart description carries: \
5175         1..=512 bytes, no leading or trailing whitespace, no ASCII control \
5176         characters anywhere, Unicode prose bytes accepted. The canonical \
5177         authoring shapes are short single-line summaries like `\"Canonical \
5178         Rust→wasm32-wasip2 caixa Servico.\"`, `\"Checkout flow.\"`, \
5179         `\"AWS provider caixa for tatara-lisp\"`. Without this gate a \
5180         malformed `:descricao` (paste-from-aligned-doc leading whitespace \
5181         `\" Checkout flow.\"` / trailing whitespace `\"Checkout flow. \"`; \
5182         paste-from-multiline-doc newline `\"Checkout\\nflow.\"`; \
5183         paste-from-Windows-CRLF-doc carriage return `\"Checkout\\rflow.\"`; \
5184         tab-from-aligned-doc `\"Checkout\\tflow.\"`; paste-from-binary-blob \
5185         NUL / BEL / ESC / DEL byte) silently passed `from_lisp` + \
5186         `validate_descricao` + `StandardLayout::verify` and landed in the \
5187         rendered `lareira-<nome>` Helm chart's `Chart.yaml description:` \
5188         field + `README.md` header paragraph as a YAML-illegal multi-line \
5189         scalar or a silently-trimmed whitespace round-trip — every \
5190         chart-aware UI (`helm list`, `helm search`, Artifact Hub) would \
5191         render the description in a single-line column far from the source \
5192         caixa.lisp; the gate moves the diagnostic to the manifest layer \
5193         with the offending value named verbatim)"
5194    )]
5195    DescricaoInvalid { descricao: String, reason: String },
5196    #[error(
5197        ":licenca is the empty string (every published caixa names \
5198         its license via a non-empty `:licenca` SPDX expression — the \
5199         value flows verbatim into the rendered `lareira-<nome>` Helm \
5200         chart's `README.md` `## License` section via `caixa-helm`'s \
5201         `build_readme` at `caixa-helm/src/lib.rs:361`; the consumer's \
5202         `Option::unwrap_or_else(|| \"MIT\".into())` `MIT` fallback \
5203         only fires when the slot is `None`, so an empty `Some(\"\")` \
5204         silently lands as a bare trailing period in the rendered \
5205         chart `README.md` `License` section far from the source \
5206         caixa.lisp; omit the slot entirely to defer to the \
5207         renderer's `MIT` fallback, or carry a canonical SPDX \
5208         expression like `\"MIT\"`, `\"Apache-2.0\"`, \
5209         `\"Apache-2.0 OR MIT\"`)"
5210    )]
5211    LicencaEmpty,
5212    #[error(
5213        ":licenca {licenca:?} is not a valid SPDX expression shape: {reason} \
5214         (the substrate consumes this string through the shared \
5215         `crate::render::is_spdx_expression_shape` predicate — the same \
5216         alphabet-floor parser every peer per-axis value-shape gate routes \
5217         its value through; the canonical authoring shapes are single \
5218         license identifiers like `\"MIT\"`, `\"Apache-2.0\"`, `\"BSD-3-Clause\"`, \
5219         compound expressions like `\"Apache-2.0 OR MIT\"`, \
5220         `\"MIT AND BSD-3-Clause\"`, `\"(MIT OR Apache-2.0) AND ISC\"`, \
5221         license-with-exception forms like `\"Apache-2.0 WITH LLVM-exception\"`, \
5222         `+`-suffix variants like `\"GPL-2.0+\"`, and user-defined references \
5223         like `\"LicenseRef-MyLicense\"` / \
5224         `\"DocumentRef-doc:LicenseRef-MyLicense\"`. Without this gate a \
5225         malformed `:licenca` (paste-from-doc whitespace `\"MIT \"` / \
5226         `\" MIT\"`; paste-from-multiline-doc CRLF `\"MIT\\n\"`; \
5227         tab-from-aligned-doc `\"MIT\\tOR Apache-2.0\"`; non-ASCII byte from \
5228         a smart-quote paste; underscore-instead-of-hyphen typo \
5229         `\"Apache_2.0\"`; comma-instead-of-`OR`-keyword colloquial idiom \
5230         `\"MIT, Apache-2.0\"`; slash-dual-license colloquial idiom \
5231         `\"MIT/Apache-2.0\"`; semicolon-list-separator confusion \
5232         `\"MIT; Apache-2.0\"`) silently landed in the rendered chart \
5233         `README.md` `## License` section + a future SPDX-aware \
5234         `Chart.yaml license:` emitter would refuse the value at \
5235         `helm lint` time far from the source caixa.lisp; the gate moves \
5236         the diagnostic to the manifest layer with the offending value \
5237         named verbatim)"
5238    )]
5239    LicencaInvalid { licenca: String, reason: String },
5240    #[error(
5241        ":edicao is the empty string (every published caixa names \
5242         its language edition via a non-empty `:edicao` value — the \
5243         edition determines the tatara-lisp macro surface + \
5244         compatibility flags the substrate applies when building \
5245         the caixa; the canonical `Caixa::template` scaffold every \
5246         `feira init` emits carries `:edicao \"2026\"` verbatim and \
5247         every renderer-side fixture (`caixa-helm`, `caixa-flux`, \
5248         `caixa-mesh`) carries `edicao: Some(\"2026\".into())` by \
5249         construction, so an empty `Some(\"\")` silently lands as a \
5250         bare `(:edicao \"\")` line in the rendered `caixa.lisp` and \
5251         a future renderer-side consumer that folds it through \
5252         `Option::unwrap_or_else` will skip the fallback and pass the \
5253         empty edition through to the substrate's build-time edition \
5254         selector far from the source caixa.lisp; omit the slot \
5255         entirely to defer to the substrate's default edition, or \
5256         carry a canonical edition like `\"2026\"`)"
5257    )]
5258    EdicaoEmpty,
5259    #[error(
5260        ":edicao {edicao:?} is not a valid edition: {reason} (every \
5261         documented tatara-lisp edition is a 4-digit ASCII decimal \
5262         year — `\"2026\"` is the only edition currently minted; \
5263         future-introduced siblings will follow the same shape, peer \
5264         with Cargo's `[package] edition` grammar which every value \
5265         Cargo has ever accepted matches: `\"2015\"`, `\"2018\"`, \
5266         `\"2021\"`, `\"2024\"`. Without this gate the canonical \
5267         paste-from-doc footguns silently passed: a trailing space \
5268         (`\"2026 \"`) from a paste-from-doc, a CRLF (`\"2026\\n\"`) \
5269         from a paste-from-multiline-doc, a fullwidth-keyboard \
5270         look-alike (`\"2026\"`), a free-form non-year value \
5271         (`\"x\"`, `\"latest\"`, `\"nightly\"`), a leading non-digit \
5272         version-tag prefix (`\"v2026\"`, `\"e2026\"`), a \
5273         decimal-shaped pseudo-version (`\"2026.1\"`), or a \
5274         wrong-length numeric value (`\"26\"`, `\"202\"`, \
5275         `\"20260\"`) all landed as `(:edicao \"<garbage>\")` in the \
5276         rendered caixa.lisp and broke at the substrate's \
5277         build-time edition selector far from the source caixa.lisp; \
5278         omit the slot entirely to defer to the substrate's default \
5279         edition, or carry a canonical 4-digit ASCII decimal year \
5280         like `\"2026\"`)"
5281    )]
5282    EdicaoInvalid { edicao: String, reason: String },
5283}
5284
5285#[cfg(test)]
5286mod tests {
5287    use super::*;
5288
5289    #[test]
5290    fn template_round_trips() {
5291        let src = Caixa::template("demo");
5292        let c = Caixa::from_lisp(&src).expect("template must parse");
5293        assert_eq!(c.nome, "demo");
5294        assert_eq!(c.versao, "0.1.0");
5295        assert_eq!(c.kind, CaixaKind::Biblioteca);
5296        assert_eq!(c.bibliotecas, vec!["lib/demo.lisp".to_string()]);
5297        assert!(c.deps.is_empty());
5298        assert!(c.deps_dev.is_empty());
5299    }
5300
5301    #[test]
5302    fn register_populates_registry() {
5303        Caixa::register();
5304        let kws = tatara_lisp::domain::registered_keywords();
5305        assert!(kws.contains(&"defcaixa"));
5306    }
5307
5308    #[test]
5309    fn to_lisp_round_trips() {
5310        let src = Caixa::template("demo");
5311        let c1 = Caixa::from_lisp(&src).unwrap();
5312        let emitted = c1.to_lisp();
5313        let c2 = Caixa::from_lisp(&emitted).expect("emitted lisp parses back");
5314        assert_eq!(c1, c2);
5315    }
5316
5317    // ── DialetoEstrangeiro carries a single typed axis ────────────────────
5318    //
5319    // The compounding pin: the variant stores only the typed
5320    // [`crate::dialeto::CaixaDialeto`], and every user-facing byte-string
5321    // (canonical keyword, description, consumer) routes through the enum's
5322    // own accessors at Display time. Prior to that closure the variant
5323    // carried each accessor's return value as a stored `&'static str`
5324    // snapshot alongside `dialeto`; a caller could construct the variant
5325    // with a snapshot that drifted from what `dialeto`'s accessors would
5326    // return, and every downstream user-facing projection would silently
5327    // disagree with the classification. Storing only the axis makes the
5328    // drift structurally impossible.
5329
5330    #[test]
5331    fn dialeto_estrangeiro_variant_carries_only_the_typed_dialeto_axis() {
5332        // Single-field construction is the whole compounding shape — a
5333        // future re-introduction of a snapshot field (a `palavra_canonica:
5334        // &'static str`, a stored `descricao:`, a stored `consumidor:`)
5335        // would re-open the drift surface and this construction would fail
5336        // to compile with "missing field" until every snapshot was seeded
5337        // at the call site again. The compile-time guarantee is the
5338        // invariant; the assertion below only witnesses that the
5339        // construction is well-formed after the closure.
5340        let err = LeituraError::DialetoEstrangeiro {
5341            dialeto: crate::dialeto::CaixaDialeto::Molde,
5342        };
5343        assert!(matches!(
5344            err,
5345            LeituraError::DialetoEstrangeiro {
5346                dialeto: crate::dialeto::CaixaDialeto::Molde,
5347            }
5348        ));
5349    }
5350
5351    #[test]
5352    fn dialeto_estrangeiro_display_routes_through_typed_dialeto_accessors() {
5353        // For every foreign-dialect classification the variant surfaces —
5354        // [`crate::dialeto::CaixaDialeto::Molde`] and
5355        // [`crate::dialeto::CaixaDialeto::MoldePosicional`], the two
5356        // variants [`Caixa::from_lisp`] raises this error for — the
5357        // rendered [`std::fmt::Display`] byte-string must interpolate each
5358        // typed accessor's return verbatim. A future re-introduction of a
5359        // stored `&'static str` snapshot alongside `dialeto` that Display
5360        // read instead of the accessor would fail this pin as soon as the
5361        // two disagreed; a future accessor rebrand (a per-dialect
5362        // consumer rename, a canonical-keyword shift once the substrate
5363        // migration named in [`crate::dialeto`] completes) reaches every
5364        // consumer through one typed dispatch and this pin verifies the
5365        // display path is one of them.
5366        for d in [
5367            crate::dialeto::CaixaDialeto::Molde,
5368            crate::dialeto::CaixaDialeto::MoldePosicional,
5369        ] {
5370            let rendered = LeituraError::DialetoEstrangeiro { dialeto: d }.to_string();
5371            assert!(
5372                rendered.contains(d.palavra_canonica()),
5373                "Display must interpolate `dialeto.palavra_canonica()` \
5374                 verbatim — a stored snapshot would silently drift from \
5375                 the typed accessor. dialect: {d}, rendered: {rendered:?}"
5376            );
5377            assert!(
5378                rendered.contains(d.descricao()),
5379                "Display must interpolate `dialeto.descricao()` verbatim. \
5380                 dialect: {d}, rendered: {rendered:?}"
5381            );
5382            assert!(
5383                rendered.contains(d.consumidor()),
5384                "Display must interpolate `dialeto.consumidor()` verbatim. \
5385                 dialect: {d}, rendered: {rendered:?}"
5386            );
5387        }
5388    }
5389
5390    #[test]
5391    fn from_lisp_rejects_molde_dialect_via_typed_variant() {
5392        // The end-to-end pin the compounding closure defends: a
5393        // Molde-dialect source lands as [`LeituraError::DialetoEstrangeiro`]
5394        // carrying [`crate::dialeto::CaixaDialeto::Molde`], and the
5395        // rendered Display byte-string names the Molde accessors'
5396        // returns verbatim. Any future path that constructed the variant
5397        // with a mismatched snapshot (a stored `palavra_canonica:
5398        // "defcaixa"` on a `Molde` classification) would land Display
5399        // pointing at `defcaixa` while the typed axis said `Molde` — the
5400        // exact drift the closure removes.
5401        let src = r#"
5402          (defcaixa
5403            :name "x"
5404            :kind :Biblioteca
5405            :ecosystem :rust-single-crate
5406            :package {:name "x" :version "0.1.0"})
5407        "#;
5408        let err = Caixa::from_lisp(src).expect_err("Molde dialect must not parse as Pacote");
5409        match err {
5410            LeituraError::DialetoEstrangeiro { dialeto } => {
5411                assert_eq!(dialeto, crate::dialeto::CaixaDialeto::Molde);
5412                let rendered = LeituraError::DialetoEstrangeiro { dialeto }.to_string();
5413                assert!(rendered.contains(dialeto.palavra_canonica()));
5414                assert!(rendered.contains(dialeto.consumidor()));
5415                assert!(rendered.contains(dialeto.descricao()));
5416            }
5417            other => panic!("expected DialetoEstrangeiro, got {other:?}"),
5418        }
5419    }
5420
5421    // ── M2 typed-substrate slot tests (limits, behavior, upgrade-from, supervisor) ──
5422
5423    #[test]
5424    fn limits_round_trip_via_json() {
5425        use crate::LimitsSpec;
5426        use std::time::Duration;
5427        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5428        c.limits = Some(LimitsSpec {
5429            memory: Some(64 * 1024 * 1024),
5430            fuel: Some(1_000_000),
5431            wall_clock: Some(Duration::from_secs(30)),
5432            cpu: Some(500),
5433        });
5434        let json = serde_json::to_string(&c).unwrap();
5435        assert!(json.contains("\"limits\""));
5436        assert!(json.contains("\"64MiB\""));
5437        assert!(json.contains("\"30s\""));
5438        assert!(json.contains("\"500m\""));
5439        let back: Caixa = serde_json::from_str(&json).unwrap();
5440        assert_eq!(c.limits, back.limits);
5441    }
5442
5443    #[test]
5444    fn behavior_round_trip_via_json() {
5445        use crate::BehaviorSpec;
5446        use std::path::PathBuf;
5447        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5448        c.behavior = Some(BehaviorSpec {
5449            on_init: Some(PathBuf::from("lib/init.lisp")),
5450            on_call: Some(PathBuf::from("lib/handlers.lisp")),
5451            ..Default::default()
5452        });
5453        let json = serde_json::to_string(&c).unwrap();
5454        let back: Caixa = serde_json::from_str(&json).unwrap();
5455        assert_eq!(c.behavior, back.behavior);
5456    }
5457
5458    #[test]
5459    fn upgrade_from_round_trip_via_json() {
5460        use crate::{UpgradeFromEntry, UpgradeInstruction};
5461        use std::path::PathBuf;
5462        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5463        c.upgrade_from = vec![UpgradeFromEntry {
5464            from: "0.1.0".into(),
5465            instructions: vec![
5466                UpgradeInstruction::LoadModule {
5467                    module: "demo".into(),
5468                },
5469                UpgradeInstruction::StateChange {
5470                    script: PathBuf::from("lib/migrations/v01-to-v02.lisp"),
5471                },
5472                UpgradeInstruction::SoftPurge {
5473                    module: "demo-old".into(),
5474                },
5475            ],
5476        }];
5477        let json = serde_json::to_string(&c).unwrap();
5478        let back: Caixa = serde_json::from_str(&json).unwrap();
5479        assert_eq!(c.upgrade_from, back.upgrade_from);
5480    }
5481
5482    #[test]
5483    fn supervisor_view_returns_typed_shape() {
5484        use crate::{ChildSpec, RestartPolicy, RestartStrategy};
5485        let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
5486        c.kind = CaixaKind::Supervisor;
5487        c.bibliotecas.clear();
5488        c.estrategia = Some(RestartStrategy::OneForOne);
5489        c.max_restarts = Some(5);
5490        c.restart_window = Some("60s".into());
5491        c.children = vec![ChildSpec {
5492            caixa: "worker".into(),
5493            versao: "^0.1".into(),
5494            restart: RestartPolicy::Permanent,
5495        }];
5496        let view = c.supervisor_view().expect("Supervisor kind has a view");
5497        assert_eq!(view.estrategia, RestartStrategy::OneForOne);
5498        assert_eq!(view.max_restarts, 5);
5499        assert_eq!(
5500            view.restart_window,
5501            Some(std::time::Duration::from_secs(60))
5502        );
5503        assert_eq!(view.children.len(), 1);
5504        view.validate().unwrap();
5505    }
5506
5507    #[test]
5508    fn supervisor_view_none_for_non_supervisor_kinds() {
5509        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5510        assert!(c.supervisor_view().is_none());
5511    }
5512
5513    #[test]
5514    fn declared_mesh_slots_empty_for_bare_caixa() {
5515        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5516        assert!(c.declared_mesh_slots().is_empty());
5517    }
5518
5519    #[test]
5520    fn declared_mesh_slots_reports_only_set_slots_in_canonical_order() {
5521        use crate::{Entrada, Membro};
5522        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5523        // Set a non-adjacent pair (:membros + :entrada) to pin that the
5524        // canonical declaration order is preserved regardless of which
5525        // subset is populated.
5526        c.membros = vec![Membro {
5527            caixa: "a".into(),
5528            versao: "^0.1".into(),
5529        }];
5530        c.entrada = Some(Entrada {
5531            host: "x.example.com".into(),
5532            para: "a".into(),
5533            paths: vec![],
5534            port: 8080,
5535        });
5536        assert_eq!(
5537            c.declared_mesh_slots(),
5538            vec![
5539                crate::render::M3_AUTHOR_KEY_MEMBROS,
5540                crate::render::M3_AUTHOR_KEY_ENTRADA,
5541            ]
5542        );
5543    }
5544
5545    #[test]
5546    fn m3_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
5547        // Scalar-value pin: the five author-facing kebab-case labels the
5548        // `(defcaixa … :<slot> (…))` surface admits on the M3 top-level
5549        // mesh slot axis, one arm per typed slot. Mirrors the peer
5550        // scalar-value pin the sibling
5551        // [`crate::M2_AUTHOR_KEY_LIMITS`] /
5552        // [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
5553        // [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] M2 top-level slot consts
5554        // carry (f49c8b0), so both altitudes of the typed-slot algebra
5555        // (per-Servico M2 + per-Aplicacao M3) share the same
5556        // "one canonical byte-string per arm" discipline. A future
5557        // rebrand (`:membros` → `:members`, `:contratos` → `:contracts`,
5558        // `:politicas` → `:policies`, `:placement` → `:distribution`,
5559        // `:entrada` → `:ingress`) lands as an edit to exactly one const,
5560        // and every consumer that reaches for the label picks it up at
5561        // build time rather than at runtime as a downstream mismatch.
5562        assert_eq!(crate::render::M3_AUTHOR_KEY_MEMBROS, ":membros");
5563        assert_eq!(crate::render::M3_AUTHOR_KEY_CONTRATOS, ":contratos");
5564        assert_eq!(crate::render::M3_AUTHOR_KEY_POLITICAS, ":politicas");
5565        assert_eq!(crate::render::M3_AUTHOR_KEY_PLACEMENT, ":placement");
5566        assert_eq!(crate::render::M3_AUTHOR_KEY_ENTRADA, ":entrada");
5567    }
5568
5569    #[test]
5570    fn declared_mesh_slots_route_through_lifted_m3_author_key_consts() {
5571        // Production-through-const pin: the five per-arm labels the
5572        // [`Caixa::declared_mesh_slots`] tagger pushes onto its return
5573        // `Vec` route through the lifted
5574        // [`crate::M3_AUTHOR_KEY_MEMBROS`] /
5575        // [`crate::M3_AUTHOR_KEY_CONTRATOS`] /
5576        // [`crate::M3_AUTHOR_KEY_POLITICAS`] /
5577        // [`crate::M3_AUTHOR_KEY_PLACEMENT`] /
5578        // [`crate::M3_AUTHOR_KEY_ENTRADA`] consts, in canonical
5579        // declaration order. A future re-order or drift at the tagger
5580        // (a rename that reaches the tagger but not the const, or vice
5581        // versa) surfaces here at build time rather than at runtime as
5582        // a [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
5583        // `slots: <stale-kebab-case>` diagnostic far from the rename's
5584        // commit. Mirror of the peer
5585        // [`declared_servico_slots_route_through_lifted_m2_author_key_consts`]
5586        // pin (f49c8b0) on the sibling per-Servico M2 top-level slot
5587        // axis.
5588        use crate::{Entrada, Membro, MeshPolicy, Placement, PlacementStrategy, WitContract};
5589        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5590        c.membros = vec![Membro {
5591            caixa: "a".into(),
5592            versao: "^0.1".into(),
5593        }];
5594        c.contratos = vec![WitContract {
5595            de: "a".into(),
5596            para: "a".into(),
5597            wit: "wasi:http/proxy".into(),
5598            endpoint: Some("/x".into()),
5599            subject: None,
5600            slot: None,
5601        }];
5602        c.politicas = Some(MeshPolicy::default());
5603        c.placement = Some(Placement {
5604            estrategia: PlacementStrategy::Replicated,
5605            clusters: vec!["rio".into()],
5606            affinity: None,
5607            shard_key: None,
5608        });
5609        c.entrada = Some(Entrada {
5610            host: "x.example.com".into(),
5611            para: "a".into(),
5612            paths: vec![],
5613            port: 8080,
5614        });
5615        assert_eq!(
5616            c.declared_mesh_slots(),
5617            vec![
5618                crate::render::M3_AUTHOR_KEY_MEMBROS,
5619                crate::render::M3_AUTHOR_KEY_CONTRATOS,
5620                crate::render::M3_AUTHOR_KEY_POLITICAS,
5621                crate::render::M3_AUTHOR_KEY_PLACEMENT,
5622                crate::render::M3_AUTHOR_KEY_ENTRADA,
5623            ]
5624        );
5625    }
5626
5627    #[test]
5628    fn declared_supervisor_slots_empty_for_bare_caixa() {
5629        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5630        assert!(c.declared_supervisor_slots().is_empty());
5631    }
5632
5633    #[test]
5634    fn declared_supervisor_slots_reports_only_set_slots_in_canonical_order() {
5635        use crate::RestartStrategy;
5636        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5637        // Set a non-adjacent pair (:estrategia + :restart-window) to pin
5638        // that the canonical declaration order is preserved regardless
5639        // of which subset is populated.
5640        c.estrategia = Some(RestartStrategy::OneForOne);
5641        c.restart_window = Some("60s".into());
5642        assert_eq!(
5643            c.declared_supervisor_slots(),
5644            vec![
5645                crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
5646                crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
5647            ]
5648        );
5649    }
5650
5651    #[test]
5652    fn supervisor_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
5653        // Scalar-value pin: the four author-facing kebab-case labels the
5654        // `(defcaixa … :<slot> (…))` surface admits on the Supervisor
5655        // supervision-tree slot axis, one arm per typed slot. Mirrors the
5656        // peer scalar-value pins the sibling
5657        // [`crate::render::M2_AUTHOR_KEY_LIMITS`] /
5658        // [`crate::render::M2_AUTHOR_KEY_BEHAVIOR`] /
5659        // [`crate::render::M2_AUTHOR_KEY_UPGRADE_FROM`] top-level M2 slot
5660        // consts and [`crate::render::M3_AUTHOR_KEY_MEMBROS`] etc.
5661        // top-level M3 slot consts carry, so all three kind-scoped
5662        // typed-slot-family author-facing-label axes route through one
5663        // canonical per-arm declaration. A future rebrand
5664        // (`:estrategia` → `:strategy` for English uniformity,
5665        // `:max-restarts` → `:max-intensity` matching Erlang/OTP's
5666        // `MaxIntensity` name, `:restart-window` → `:period` matching
5667        // OTP's `Period` name, `:children` → `:workers` matching Elixir
5668        // idiom) lands as an edit to exactly one const, and every
5669        // consumer that reaches for the label picks it up at build time
5670        // rather than at runtime as a downstream mismatch.
5671        assert_eq!(
5672            crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
5673            ":estrategia"
5674        );
5675        assert_eq!(
5676            crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS,
5677            ":max-restarts"
5678        );
5679        assert_eq!(
5680            crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
5681            ":restart-window"
5682        );
5683        assert_eq!(crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN, ":children");
5684    }
5685
5686    #[test]
5687    fn declared_supervisor_slots_route_through_lifted_supervisor_author_key_consts() {
5688        // Production-through-const pin: the four per-arm labels the
5689        // [`Caixa::declared_supervisor_slots`] tagger pushes onto its
5690        // return `Vec` route through the lifted
5691        // [`crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA`] /
5692        // [`crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS`] /
5693        // [`crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW`] /
5694        // [`crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN`] consts, in
5695        // canonical declaration order. A future re-order or drift at the
5696        // tagger (a rename that reaches the tagger but not the const, or
5697        // vice versa) surfaces here at build time rather than at runtime
5698        // as a [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
5699        // `slots: <stale-kebab-case>` diagnostic far from the rename's
5700        // commit. Mirror of the peer
5701        // [`declared_servico_slots_route_through_lifted_m2_author_key_consts`]
5702        // (f49c8b0) and
5703        // [`declared_mesh_slots_route_through_lifted_m3_author_key_consts`]
5704        // (882f498) pins on the sibling M2 / M3 top-level slot axes.
5705        use crate::{ChildSpec, RestartPolicy, RestartStrategy};
5706        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5707        c.estrategia = Some(RestartStrategy::OneForOne);
5708        c.max_restarts = Some(5);
5709        c.restart_window = Some("60s".into());
5710        c.children = vec![ChildSpec {
5711            caixa: "worker".into(),
5712            versao: "^0.1".into(),
5713            restart: RestartPolicy::Permanent,
5714        }];
5715        assert_eq!(
5716            c.declared_supervisor_slots(),
5717            vec![
5718                crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
5719                crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS,
5720                crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
5721                crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN,
5722            ]
5723        );
5724    }
5725
5726    #[test]
5727    fn declared_servico_slots_empty_for_bare_caixa() {
5728        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5729        assert!(c.declared_servico_slots().is_empty());
5730    }
5731
5732    #[test]
5733    fn declared_servico_slots_reports_only_set_slots_in_canonical_order() {
5734        use crate::{UpgradeFromEntry, UpgradeInstruction};
5735        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5736        // Set a non-adjacent pair (:limits + :upgrade-from) to pin that
5737        // the canonical declaration order is preserved regardless of
5738        // which subset is populated.
5739        c.limits = Some(crate::LimitsSpec {
5740            fuel: Some(1_000_000),
5741            ..Default::default()
5742        });
5743        c.upgrade_from = vec![UpgradeFromEntry {
5744            from: "0.1.0".into(),
5745            instructions: vec![UpgradeInstruction::Restart],
5746        }];
5747        assert_eq!(
5748            c.declared_servico_slots(),
5749            vec![
5750                crate::render::M2_AUTHOR_KEY_LIMITS,
5751                crate::render::M2_AUTHOR_KEY_UPGRADE_FROM,
5752            ]
5753        );
5754    }
5755
5756    #[test]
5757    fn m2_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
5758        // Scalar-value pin: the three author-facing kebab-case labels
5759        // the `(defcaixa … :<slot> (…))` surface admits on the M2
5760        // top-level slot axis, one arm per typed slot. Mirrors the peer
5761        // scalar-value pin the sibling renderer-side
5762        // [`crate::M2_KEY_LIMITS`] / [`crate::M2_KEY_BEHAVIOR`] /
5763        // [`crate::M2_KEY_UPGRADE_FROM`] camelCase overlay-container
5764        // consts carry, so both halves of the M2 top-level slot dual
5765        // axis (author-facing kebab-case label + renderer-side
5766        // camelCase overlay-container wire key) route through one
5767        // canonical per-arm declaration. A future rebrand
5768        // (`:limits` → `:sandbox` matching Lunatic per-process
5769        // terminology INSPIRATIONS §III.1, `:behavior` → `:gen-server`
5770        // matching Erlang's verbatim name, `:upgrade-from` → `:appup`
5771        // matching Erlang's verbatim appup name) lands as an edit to
5772        // exactly one const, and every consumer that reaches for the
5773        // label picks it up at build time rather than at runtime as a
5774        // downstream mismatch.
5775        assert_eq!(crate::render::M2_AUTHOR_KEY_LIMITS, ":limits");
5776        assert_eq!(crate::render::M2_AUTHOR_KEY_BEHAVIOR, ":behavior");
5777        assert_eq!(crate::render::M2_AUTHOR_KEY_UPGRADE_FROM, ":upgrade-from");
5778    }
5779
5780    #[test]
5781    fn declared_servico_slots_route_through_lifted_m2_author_key_consts() {
5782        // Production-through-const pin: the three per-arm labels the
5783        // [`Caixa::declared_servico_slots`] tagger pushes onto its
5784        // return `Vec` route through the lifted
5785        // [`crate::M2_AUTHOR_KEY_LIMITS`] /
5786        // [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
5787        // [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] consts, in canonical
5788        // declaration order. A future re-order or drift at the tagger
5789        // (a rename that reaches the tagger but not the const, or vice
5790        // versa) surfaces here at build time rather than at runtime as
5791        // a [`crate::LayoutError::ServicoSlotsOnNonServico`]
5792        // `slots: <stale-kebab-case>` diagnostic far from the rename's
5793        // commit. Mirror of the peer
5794        // [`crate::behavior::BehaviorSpec::declared_slots`] production
5795        // tagger pin (889dc18) on the sibling per-callback axis.
5796        use crate::{BehaviorSpec, UpgradeFromEntry, UpgradeInstruction};
5797        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5798        c.limits = Some(crate::LimitsSpec {
5799            fuel: Some(1_000_000),
5800            ..Default::default()
5801        });
5802        c.behavior = Some(BehaviorSpec {
5803            on_init: Some(PathBuf::from("lib/init.lisp")),
5804            ..Default::default()
5805        });
5806        c.upgrade_from = vec![UpgradeFromEntry {
5807            from: "0.1.0".into(),
5808            instructions: vec![UpgradeInstruction::Restart],
5809        }];
5810        assert_eq!(
5811            c.declared_servico_slots(),
5812            vec![
5813                crate::render::M2_AUTHOR_KEY_LIMITS,
5814                crate::render::M2_AUTHOR_KEY_BEHAVIOR,
5815                crate::render::M2_AUTHOR_KEY_UPGRADE_FROM,
5816            ]
5817        );
5818    }
5819
5820    #[test]
5821    fn existing_manifests_unaffected_by_new_optional_slots() {
5822        // Regression test: a caixa.lisp authored before M2 typed slots
5823        // should still parse + serialize cleanly. The bare `defcaixa`
5824        // emitted by `Caixa::template` has none of the new fields.
5825        let src = Caixa::template("legacy");
5826        let c = Caixa::from_lisp(&src).unwrap();
5827        assert!(c.limits.is_none());
5828        assert!(c.behavior.is_none());
5829        assert!(c.upgrade_from.is_empty());
5830        assert!(c.estrategia.is_none());
5831        assert!(c.children.is_empty());
5832
5833        // And to_lisp emits a manifest with the new slots in the
5834        // empty/default state — round-trippable.
5835        let emitted = c.to_lisp();
5836        let back = Caixa::from_lisp(&emitted).unwrap();
5837        assert_eq!(c, back);
5838    }
5839
5840    #[test]
5841    fn validate_deps_accepts_canonical_caixa() {
5842        // Positive control: the bare template — zero deps, zero
5843        // deps_dev — passes the gate trivially. A future axis added to
5844        // `Dep::validate` mustn't regress an empty-deps caixa to a
5845        // build error.
5846        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5847        c.validate_deps().unwrap();
5848    }
5849
5850    #[test]
5851    fn validate_deps_rejects_invalid_versao_in_deps() {
5852        // Fail-before-pass-after pin: a malformed `:deps :versao`
5853        // surfaces at validate_deps() time, not at lacre-resolve time.
5854        // Mirrors `rejects_invalid_membro_versao_requirement` and
5855        // `validate_rejects_invalid_child_versao_requirement` on the
5856        // other two `:versao` axes.
5857        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5858        c.deps = vec![Dep::simple("caixa-teia", "^bad-version")];
5859        let err = c.validate_deps().unwrap_err();
5860        assert!(
5861            matches!(
5862                err,
5863                crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
5864                    if nome == "caixa-teia" && versao == "^bad-version"
5865            ),
5866            "got {err:?}"
5867        );
5868    }
5869
5870    #[test]
5871    fn validate_deps_rejects_invalid_versao_in_deps_dev() {
5872        // Parity pin: `:deps-dev` must run through the same per-entry
5873        // validator as `:deps` — a typo in either axis surfaces the
5874        // same diagnostic. Without this leg, `:deps-dev` would be a
5875        // second-class citizen of the typed surface and an author
5876        // could land a build that passes validate_deps but fails at
5877        // `feira lock`-time when the dev-dep is resolved for a test
5878        // build.
5879        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5880        c.deps_dev = vec![Dep::simple("tatara-check", "^^0.1")];
5881        let err = c.validate_deps().unwrap_err();
5882        assert!(
5883            matches!(
5884                err,
5885                crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
5886                    if nome == "tatara-check" && versao == "^^0.1"
5887            ),
5888            "got {err:?}"
5889        );
5890    }
5891
5892    #[test]
5893    fn validate_deps_runs_deps_before_deps_dev() {
5894        // Order pin: when both lists carry typos, the `:deps`
5895        // diagnostic surfaces first. The author's mental model is
5896        // "runtime deps are load-bearing; dev deps are scaffolding";
5897        // surfacing the runtime axis first matches that hierarchy.
5898        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5899        c.deps = vec![Dep::simple("runtime-dep", "^bad-runtime")];
5900        c.deps_dev = vec![Dep::simple("dev-dep", "^bad-dev")];
5901        let err = c.validate_deps().unwrap_err();
5902        assert!(
5903            matches!(
5904                err,
5905                crate::dep::DepError::VersaoInvalid { ref nome, .. }
5906                    if nome == "runtime-dep"
5907            ),
5908            "expected `:deps` typo to surface first, got {err:?}"
5909        );
5910    }
5911
5912    #[test]
5913    fn validate_deps_accepts_canonical_versao_forms_in_both_lists() {
5914        // Positive control sweep across both lists. Pin every
5915        // canonical Cargo-shaped form so a future tightening of the
5916        // accepted set surfaces here as a test failure (parity with
5917        // `accepts_canonical_membro_versao_forms` and
5918        // `validate_accepts_canonical_child_versao_forms`).
5919        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5920        c.deps = vec![
5921            Dep::simple("caret", "^0.1"),
5922            Dep::simple("tilde", "~0.1.2"),
5923            Dep::simple("exact", "0.1.0"),
5924            Dep::simple("wildcard", "*"),
5925            Dep::simple("multi-range", ">=0.1, <2"),
5926        ];
5927        c.deps_dev = vec![
5928            Dep::simple("dev-caret", "^0.1"),
5929            Dep::simple("dev-wildcard", "*"),
5930        ];
5931        c.validate_deps().unwrap();
5932    }
5933
5934    #[test]
5935    fn validate_deps_diagnostic_carries_offending_dep() {
5936        // Diagnostic-shape pin: the error names the offending entry's
5937        // `:nome` + `:versao` verbatim and carries a non-empty
5938        // `reason` from `semver::VersionReq::parse`, so a `feira lint`
5939        // run can render the diagnostic without re-parsing.
5940        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5941        c.deps = vec![Dep::simple("caixa-teia", "not-a-req")];
5942        let err = c.validate_deps().unwrap_err();
5943        let crate::dep::DepError::VersaoInvalid {
5944            nome,
5945            versao,
5946            reason,
5947        } = err
5948        else {
5949            panic!("expected VersaoInvalid, got other variant");
5950        };
5951        assert_eq!(nome, "caixa-teia");
5952        assert_eq!(versao, "not-a-req");
5953        assert!(
5954            !reason.is_empty(),
5955            "VersaoInvalid `reason` must carry the parser's wording verbatim"
5956        );
5957    }
5958
5959    #[test]
5960    fn validate_deps_rejects_ambiguous_fonte_in_deps_dev() {
5961        // Cross-axis pin: `validate_deps` walks both :deps and
5962        // :deps-dev through `Dep::validate`, and the new fonte gate
5963        // (`:tag` + `:branch` both set — the canonical "pin drift"
5964        // footgun) must surface from the :deps-dev arm with the
5965        // offending entry's :nome named. Pin the :deps-dev arm
5966        // explicitly so a future shortcut that only walks :deps
5967        // surfaces here as a regression.
5968        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5969        c.deps_dev = vec![Dep {
5970            nome: "dev-only".into(),
5971            versao: "^0.1".into(),
5972            fonte: Some(crate::DepSource::Git {
5973                repo: "github:p/x".into(),
5974                tag: Some("v1".into()),
5975                rev: None,
5976                branch: Some("main".into()),
5977            }),
5978            opcional: false,
5979            caracteristicas: vec![],
5980        }];
5981        let err = c.validate_deps().unwrap_err();
5982        let crate::dep::DepError::FontePinAmbiguous { nome, pins } = err else {
5983            panic!("expected FontePinAmbiguous from :deps-dev walk");
5984        };
5985        assert_eq!(nome, "dev-only");
5986        assert!(pins.contains(":tag") && pins.contains(":branch"));
5987    }
5988
5989    #[test]
5990    fn validate_deps_rejects_empty_repo_in_deps() {
5991        // Parity pin on the :deps arm: an empty :repo on the runtime
5992        // deps list surfaces the same FonteRepoEmpty diagnostic the
5993        // dep.rs per-entry tests pin, naming the offending entry.
5994        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
5995        c.deps = vec![Dep {
5996            nome: "runtime".into(),
5997            versao: "^0.1".into(),
5998            fonte: Some(crate::DepSource::Git {
5999                repo: String::new(),
6000                tag: Some("v1".into()),
6001                rev: None,
6002                branch: None,
6003            }),
6004            opcional: false,
6005            caracteristicas: vec![],
6006        }];
6007        let err = c.validate_deps().unwrap_err();
6008        assert!(
6009            matches!(
6010                err,
6011                crate::dep::DepError::FonteRepoEmpty { ref nome }
6012                    if nome == "runtime"
6013            ),
6014            "got {err:?}"
6015        );
6016    }
6017
6018    // ── validate_deps: within-list :nome set-not-multiset gate ─────────
6019
6020    #[test]
6021    fn validate_deps_rejects_duplicate_nome_in_deps() {
6022        // Fail-before-pass-after pin: two `:deps` entries naming the same
6023        // caixa carry two `:versao` / `:fonte` / feature triples that the
6024        // caixa-resolver's lacre pipeline collapses (the second silently
6025        // overwrites the first at `concrete_versao`-resolve time). The
6026        // gate surfaces the duplicate at validate-time, naming the
6027        // offending caixa + the list, before the resolver-side silent
6028        // drop. Mirrors the peer typed-graph duplicate gates
6029        // (`DuplicateChildCaixa`, `MembroDuplicate`, `DuplicateFrom`, …).
6030        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6031        c.deps = vec![
6032            Dep::simple("caixa-teia", "^0.1"),
6033            Dep::simple("caixa-teia", "^0.2"),
6034        ];
6035        let err = c.validate_deps().unwrap_err();
6036        assert!(
6037            matches!(
6038                err,
6039                crate::dep::DepError::DuplicateNome { ref nome, list }
6040                    if nome == "caixa-teia" && list == crate::render::DEP_AUTHOR_KEY_DEPS
6041            ),
6042            "got {err:?}"
6043        );
6044    }
6045
6046    #[test]
6047    fn validate_deps_rejects_duplicate_nome_in_deps_dev() {
6048        // Parity pin: `:deps-dev` runs through the same per-list
6049        // duplicate check as `:deps` — neither axis is a second-class
6050        // citizen of the set-not-multiset discipline.
6051        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6052        c.deps_dev = vec![
6053            Dep::simple("tatara-check", "*"),
6054            Dep::simple("tatara-check", "^0.1"),
6055        ];
6056        let err = c.validate_deps().unwrap_err();
6057        assert!(
6058            matches!(
6059                err,
6060                crate::dep::DepError::DuplicateNome { ref nome, list }
6061                    if nome == "tatara-check" && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
6062            ),
6063            "got {err:?}"
6064        );
6065    }
6066
6067    #[test]
6068    fn validate_deps_accepts_cross_list_same_nome() {
6069        // The Cargo `[dependencies]` + `[dev-dependencies]` override
6070        // convention is preserved: a name appearing in *both* lists is
6071        // valid (the dev-pin overrides at test/dev time). Only
6072        // within-list duplicates are structurally incoherent — pin the
6073        // permissive cross-list semantics so a future shortcut that
6074        // collapses the two seen-sets into one surfaces here as a test
6075        // failure.
6076        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6077        c.deps = vec![Dep::simple("caixa-teia", "^0.1")];
6078        c.deps_dev = vec![Dep::simple("caixa-teia", "^0.2")];
6079        c.validate_deps().unwrap();
6080    }
6081
6082    #[test]
6083    fn validate_deps_accepts_distinct_nome_in_both_lists() {
6084        // Positive control: distinct names within each list pass — the
6085        // gate's identity element on the canonical authoring shape.
6086        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6087        c.deps = vec![
6088            Dep::simple("caixa-teia", "^0.1"),
6089            Dep::simple("pleme-mesh", "*"),
6090        ];
6091        c.deps_dev = vec![
6092            Dep::simple("tatara-check", "*"),
6093            Dep::simple("dev-shim", "^0.1"),
6094        ];
6095        c.validate_deps().unwrap();
6096    }
6097
6098    #[test]
6099    fn validate_deps_per_entry_validate_fires_before_duplicate_in_deps() {
6100        // Diagnostic-precedence pin: a malformed `:versao` on the
6101        // duplicating entry surfaces its narrower `VersaoInvalid`
6102        // diagnostic first, before the cross-entry duplicate gate fires
6103        // — the canonical "per-entry shape before cross-entry uniqueness"
6104        // precedence every peer set-not-multiset gate establishes
6105        // (`*_invalid_fires_before_duplicate_check` pins on
6106        // `SupervisorSpec::validate`, `AplicacaoSpec::validate_membros`,
6107        // `validate_upgrade_from`).
6108        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6109        c.deps = vec![
6110            Dep::simple("caixa-teia", "^0.1"),
6111            Dep::simple("caixa-teia", "^bad-version"),
6112        ];
6113        let err = c.validate_deps().unwrap_err();
6114        assert!(
6115            matches!(
6116                err,
6117                crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
6118                    if nome == "caixa-teia" && versao == "^bad-version"
6119            ),
6120            "expected VersaoInvalid to surface before DuplicateNome, got {err:?}"
6121        );
6122    }
6123
6124    #[test]
6125    fn validate_deps_duplicate_diagnostic_names_first_collision() {
6126        // First-collision determinism pin: with three entries naming the
6127        // same caixa, the first colliding pair surfaces — not the last.
6128        // Mirrors the peer first-collision posture on every
6129        // duplicate-target gate
6130        // (`validate_upgrade_from_duplicate_diagnostic_names_second_collision`
6131        // — the second entry is the first collision; this gate uses the
6132        // same shape: the second entry's `:nome` lands in the diagnostic
6133        // because `seen.insert(first.nome)` already populated the set).
6134        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6135        c.deps = vec![
6136            Dep::simple("caixa-teia", "^0.1"),
6137            Dep::simple("caixa-teia", "^0.2"),
6138            Dep::simple("caixa-teia", "^0.3"),
6139        ];
6140        let err = c.validate_deps().unwrap_err();
6141        // The diagnostic carries the offending caixa name; the
6142        // implementation surfaces on the *second* entry (the first
6143        // collision), so the test pins the `:nome` value.
6144        assert!(
6145            matches!(
6146                err,
6147                crate::dep::DepError::DuplicateNome { ref nome, list }
6148                    if nome == "caixa-teia" && list == crate::render::DEP_AUTHOR_KEY_DEPS
6149            ),
6150            "got {err:?}"
6151        );
6152    }
6153
6154    #[test]
6155    fn validate_deps_duplicate_in_deps_fires_before_duplicate_in_deps_dev() {
6156        // Cross-list precedence pin: when both lists carry duplicates,
6157        // the `:deps` diagnostic surfaces first — same author-mental-
6158        // model ordering the `validate_deps_runs_deps_before_deps_dev`
6159        // pin establishes for malformed `:versao` (runtime axis before
6160        // dev axis).
6161        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6162        c.deps = vec![
6163            Dep::simple("runtime-dep", "^0.1"),
6164            Dep::simple("runtime-dep", "^0.2"),
6165        ];
6166        c.deps_dev = vec![Dep::simple("dev-dep", "*"), Dep::simple("dev-dep", "^0.1")];
6167        let err = c.validate_deps().unwrap_err();
6168        assert!(
6169            matches!(
6170                err,
6171                crate::dep::DepError::DuplicateNome { ref nome, list }
6172                    if nome == "runtime-dep" && list == crate::render::DEP_AUTHOR_KEY_DEPS
6173            ),
6174            "expected :deps duplicate to surface before :deps-dev duplicate, got {err:?}"
6175        );
6176    }
6177
6178    #[test]
6179    fn validate_deps_empty_lists_pass_duplicate_gate() {
6180        // Empty-set identity pin: the bare template (zero deps, zero
6181        // deps_dev) passes the duplicate gate as the gate's identity
6182        // element. A future tighten that conflates "empty" with
6183        // "missing" would regress this baseline.
6184        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6185        c.validate_deps().unwrap();
6186    }
6187
6188    #[test]
6189    fn validate_deps_duplicate_diagnostic_carries_list_tag() {
6190        // Diagnostic-shape pin: the `list:` field tags which list the
6191        // duplicate landed in (`:deps` vs `:deps-dev`) verbatim, so a
6192        // `feira lint` run can route the author to the right block in
6193        // their caixa.lisp without re-deriving the list from context.
6194        // Same self-locating shape every peer per-axis diagnostic
6195        // already exposes.
6196        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6197        c.deps_dev = vec![
6198            Dep::simple("dev-thing", "*"),
6199            Dep::simple("dev-thing", "^0.1"),
6200        ];
6201        let err = c.validate_deps().unwrap_err();
6202        let crate::dep::DepError::DuplicateNome { nome, list } = err else {
6203            panic!("expected DuplicateNome from :deps-dev walk");
6204        };
6205        assert_eq!(nome, "dev-thing");
6206        assert_eq!(list, crate::render::DEP_AUTHOR_KEY_DEPS_DEV);
6207    }
6208
6209    // ── validate_deps: per-entry :caracteristicas set-discipline gate ──
6210
6211    #[test]
6212    fn validate_deps_surfaces_caracteristicas_duplicate_in_deps_list() {
6213        // Thread-through pin on `:deps`: the per-entry
6214        // `Dep::validate_caracteristicas` gate fires inside
6215        // `Caixa::validate_deps`'s linear walk, so a malformed feature
6216        // list on any `:deps` entry surfaces as a `DepError` from
6217        // `validate_deps` — the same reachability shape every per-entry
6218        // `Dep::validate` arm threads through. Without this pin a future
6219        // shortcut that skips the per-entry `Dep::validate` call on the
6220        // cross-entry-uniqueness path would mask the within-entry
6221        // `:caracteristicas` gates.
6222        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6223        c.deps = vec![Dep {
6224            nome: "caixa-teia".into(),
6225            versao: "^0.1".into(),
6226            fonte: None,
6227            opcional: false,
6228            caracteristicas: vec!["http".into(), "http".into()],
6229        }];
6230        let err = c.validate_deps().unwrap_err();
6231        let crate::dep::DepError::CaracteristicaDuplicate {
6232            nome,
6233            caracteristica,
6234        } = err
6235        else {
6236            panic!("expected CaracteristicaDuplicate from :deps walk, got {err:?}");
6237        };
6238        assert_eq!(nome, "caixa-teia");
6239        assert_eq!(caracteristica, "http");
6240    }
6241
6242    #[test]
6243    fn validate_deps_surfaces_caracteristicas_empty_in_deps_dev_list() {
6244        // Peer thread-through pin on `:deps-dev`: same reachability as
6245        // the `:deps` arm above, on the dev-only authoring axis. Pins
6246        // that the `validate_deps` walk visits both lists' per-entry
6247        // gates uniformly. The empty-feature arm carries here so both
6248        // new `:caracteristicas` arms are surfaced via at least one
6249        // `validate_deps` thread-through.
6250        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6251        c.deps_dev = vec![Dep {
6252            nome: "caixa-teia".into(),
6253            versao: "^0.1".into(),
6254            fonte: None,
6255            opcional: false,
6256            caracteristicas: vec![String::new()],
6257        }];
6258        let err = c.validate_deps().unwrap_err();
6259        let crate::dep::DepError::CaracteristicaEmpty { nome } = err else {
6260            panic!("expected CaracteristicaEmpty from :deps-dev walk, got {err:?}");
6261        };
6262        assert_eq!(nome, "caixa-teia");
6263    }
6264
6265    #[test]
6266    fn validate_deps_surfaces_caracteristicas_invalid_in_deps_list() {
6267        // Thread-through pin on `:deps`: the per-entry
6268        // `Dep::validate_caracteristicas` value-shape gate (lifted via
6269        // `crate::render::is_cargo_feature_name`) fires inside
6270        // `Caixa::validate_deps`'s linear walk on the `:deps` list, so
6271        // a structurally invalid feature name on any `:deps` entry
6272        // surfaces as `DepError::CaracteristicaInvalid` from
6273        // `validate_deps` — the same reachability shape every per-entry
6274        // `Dep::validate` arm threads through. Without this pin a
6275        // future shortcut that skips the per-entry `Dep::validate` call
6276        // on the cross-entry-uniqueness path would mask the within-
6277        // entry `:caracteristicas` value-shape gate.
6278        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6279        c.deps = vec![Dep {
6280            nome: "caixa-teia".into(),
6281            versao: "^0.1".into(),
6282            fonte: None,
6283            opcional: false,
6284            caracteristicas: vec!["+http".into()],
6285        }];
6286        let err = c.validate_deps().unwrap_err();
6287        let crate::dep::DepError::CaracteristicaInvalid {
6288            nome,
6289            caracteristica,
6290            ..
6291        } = err
6292        else {
6293            panic!("expected CaracteristicaInvalid from :deps walk, got {err:?}");
6294        };
6295        assert_eq!(nome, "caixa-teia");
6296        assert_eq!(caracteristica, "+http");
6297    }
6298
6299    #[test]
6300    fn validate_deps_surfaces_caracteristicas_invalid_in_deps_dev_list() {
6301        // Peer thread-through pin on `:deps-dev`: same reachability as
6302        // the `:deps` arm above, on the dev-only authoring axis. The
6303        // `http/json` shape carries here so the segment-separator
6304        // diagnostic (the canonical Cargo `dep/feat` namespaced-dep
6305        // confusion footgun) is surfaced via the cross-entry walk too —
6306        // pinning that the `:deps-dev` list visits the same per-entry
6307        // value-shape gate as the `:deps` list.
6308        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6309        c.deps_dev = vec![Dep {
6310            nome: "caixa-teia".into(),
6311            versao: "^0.1".into(),
6312            fonte: None,
6313            opcional: false,
6314            caracteristicas: vec!["http/json".into()],
6315        }];
6316        let err = c.validate_deps().unwrap_err();
6317        let crate::dep::DepError::CaracteristicaInvalid {
6318            nome,
6319            caracteristica,
6320            ..
6321        } = err
6322        else {
6323            panic!("expected CaracteristicaInvalid from :deps-dev walk, got {err:?}");
6324        };
6325        assert_eq!(nome, "caixa-teia");
6326        assert_eq!(caracteristica, "http/json");
6327    }
6328
6329    #[test]
6330    fn to_lisp_preserves_deps() {
6331        let src = r#"
6332(defcaixa
6333  :nome "x"
6334  :versao "0.1.0"
6335  :kind Biblioteca
6336  :deps ((:nome "a" :versao "^0.1")
6337         (:nome "b" :versao "*" :fonte (:tipo git :repo "github:o/b" :tag "v1"))))
6338"#;
6339        let c1 = Caixa::from_lisp(src).unwrap();
6340        let emitted = c1.to_lisp();
6341        let c2 = Caixa::from_lisp(&emitted).expect("round trip");
6342        assert_eq!(c1.deps, c2.deps);
6343    }
6344
6345    // ── Caixa::validate_nome — top-level :nome value-shape gate ─────────
6346
6347    fn caixa_with_nome(nome: &str) -> Caixa {
6348        let mut c = Caixa::from_lisp(&Caixa::template("placeholder")).unwrap();
6349        c.nome = nome.to_string();
6350        c
6351    }
6352
6353    #[test]
6354    fn validate_nome_accepts_canonical_template() {
6355        // Positive control: the bare `feira init`-style template's
6356        // `:nome` ("demo") is a canonical DNS-1123 label; the gate must
6357        // not regress this baseline shape. A future tightening of the
6358        // accepted set surfaces here as a test failure first.
6359        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6360        c.validate_nome().unwrap();
6361    }
6362
6363    #[test]
6364    fn validate_nome_accepts_canonical_forms() {
6365        // Positive-set sweep: each realistic caixa-name shape the K8s
6366        // apiserver accepts as a `metadata.name` label must pass —
6367        // single-word, hyphen-joined, version-suffixed, single-char,
6368        // two-char, digit-start (DNS-1123 allows this; the stricter
6369        // DNS-1035 Service-name rule doesn't), version-suffix-bearing.
6370        // Mirrors `accepts_canonical_membro_caixa_forms` (3f9d7a0) on
6371        // the peer member-name axis.
6372        for nome in [
6373            "checkout",
6374            "cart-v2",
6375            "a",
6376            "db",
6377            "3rd-party-shim",
6378            "payment-retry",
6379            "0",
6380        ] {
6381            caixa_with_nome(nome)
6382                .validate_nome()
6383                .unwrap_or_else(|e| panic!("canonical :nome {nome:?} must validate, got {e:?}"));
6384        }
6385    }
6386
6387    #[test]
6388    fn validate_nome_rejects_empty() {
6389        // Fail-before-pass-after pin: `Caixa::from_lisp` does not refuse
6390        // an empty `:nome` (the derive macro stores the raw String);
6391        // the gate's empty arm names the offending axis with a narrower
6392        // diagnostic than the `NomeInvalid` parse arm would emit.
6393        let c = caixa_with_nome("");
6394        let err = c.validate_nome().unwrap_err();
6395        assert_eq!(err, ManifestError::NomeEmpty);
6396    }
6397
6398    #[test]
6399    fn validate_nome_rejects_uppercase() {
6400        // The canonical "I copied the TitleCase display name verbatim"
6401        // footgun. The K8s apiserver rejects `metadata.name: MyApp` at
6402        // admission on every derived artifact (Helm chart, ComputeUnit,
6403        // CNP, HTTPRoute, label values); the gate moves the diagnostic
6404        // to the source `caixa.lisp` and the reason suggests the
6405        // lowercased fix verbatim.
6406        let c = caixa_with_nome("MyApp");
6407        let err = c.validate_nome().unwrap_err();
6408        let ManifestError::NomeInvalid { nome, reason } = err else {
6409            panic!("expected NomeInvalid for uppercase :nome");
6410        };
6411        assert_eq!(nome, "MyApp");
6412        assert!(
6413            reason.contains("uppercase") && reason.contains("myapp"),
6414            "diagnostic must name the violation + the lowercased fix, got {reason:?}"
6415        );
6416    }
6417
6418    #[test]
6419    fn validate_nome_rejects_underscore() {
6420        // The Python-/Postgres-style `snake_case` leak. DNS-1123 forbids
6421        // `_`; the apiserver rejects on admission across every derived
6422        // artifact. Same fixture pinned for `:membros :caixa` (3f9d7a0)
6423        // and `:children :caixa` (31bfa43).
6424        let c = caixa_with_nome("my_app");
6425        let err = c.validate_nome().unwrap_err();
6426        assert!(
6427            matches!(
6428                err,
6429                ManifestError::NomeInvalid { ref nome, ref reason }
6430                    if nome == "my_app" && reason.contains('_')
6431            ),
6432            "got {err:?}"
6433        );
6434    }
6435
6436    #[test]
6437    fn validate_nome_rejects_dot() {
6438        // A `:nome` is a single DNS-1123 label, not a subdomain. The
6439        // "I want to namespace with `.`" footgun the gate redirects to
6440        // `-` via the shared predicate's reason wording.
6441        let c = caixa_with_nome("team.app");
6442        let err = c.validate_nome().unwrap_err();
6443        assert!(
6444            matches!(
6445                err,
6446                ManifestError::NomeInvalid { ref nome, ref reason }
6447                    if nome == "team.app" && reason.contains('.')
6448            ),
6449            "got {err:?}"
6450        );
6451    }
6452
6453    #[test]
6454    fn validate_nome_rejects_leading_hyphen() {
6455        // DNS-1123 boundary rule: the label must start with an ASCII
6456        // alphanumeric. Pin the leading-`-` arm explicitly.
6457        let c = caixa_with_nome("-app");
6458        let err = c.validate_nome().unwrap_err();
6459        assert!(
6460            matches!(
6461                err,
6462                ManifestError::NomeInvalid { ref nome, .. } if nome == "-app"
6463            ),
6464            "got {err:?}"
6465        );
6466    }
6467
6468    #[test]
6469    fn validate_nome_rejects_trailing_hyphen() {
6470        // Symmetric arm of the boundary rule, pinned separately so a
6471        // future relaxation that only checks the leading position
6472        // surfaces here. Mirrors `rejects_membro_caixa_with_trailing_hyphen`
6473        // and `_with_trailing_hyphen` on the supervisor / aplicacao
6474        // axes.
6475        let c = caixa_with_nome("app-");
6476        let err = c.validate_nome().unwrap_err();
6477        assert!(
6478            matches!(
6479                err,
6480                ManifestError::NomeInvalid { ref nome, .. } if nome == "app-"
6481            ),
6482            "got {err:?}"
6483        );
6484    }
6485
6486    #[test]
6487    fn validate_nome_rejects_unicode() {
6488        // IDN must be pre-encoded as Punycode (`xn--…`); raw Unicode
6489        // bytes are rejected by the K8s apiserver on every name axis.
6490        let c = caixa_with_nome("café");
6491        let err = c.validate_nome().unwrap_err();
6492        assert!(
6493            matches!(
6494                err,
6495                ManifestError::NomeInvalid { ref nome, .. } if nome == "café"
6496            ),
6497            "got {err:?}"
6498        );
6499    }
6500
6501    #[test]
6502    fn validate_nome_rejects_whitespace() {
6503        // The paste-from-sketch / paste-from-spec footgun. Internal
6504        // whitespace is rejected by every K8s name axis.
6505        let c = caixa_with_nome("my app");
6506        let err = c.validate_nome().unwrap_err();
6507        assert!(
6508            matches!(
6509                err,
6510                ManifestError::NomeInvalid { ref nome, .. } if nome == "my app"
6511            ),
6512            "got {err:?}"
6513        );
6514    }
6515
6516    #[test]
6517    fn validate_nome_rejects_too_long() {
6518        // 64-byte boundary pin: the K8s apiserver rejects any
6519        // `metadata.name` over 63 bytes at admission; the diagnostic
6520        // names both the 63-byte cap and the actual length so the
6521        // author can shorten in one edit. Mirrors `_too_long` on the
6522        // peer member-/cluster-/child-name axes.
6523        let over = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN + 1);
6524        let c = caixa_with_nome(&over);
6525        let err = c.validate_nome().unwrap_err();
6526        let ManifestError::NomeInvalid { nome, reason } = err else {
6527            panic!("expected NomeInvalid for over-cap :nome");
6528        };
6529        assert_eq!(nome.len(), crate::DNS_1123_LABEL_MAX_LEN + 1);
6530        assert!(
6531            reason.contains("63") && reason.contains("64"),
6532            "diagnostic must name the cap + actual length, got {reason:?}"
6533        );
6534    }
6535
6536    #[test]
6537    fn nome_max_length_validates() {
6538        // The 63-byte cap exactly — the boundary-accepting case pinned
6539        // alongside `validate_nome_rejects_too_long` so a future cap
6540        // shift surfaces both arms simultaneously. Mirrors
6541        // `membro_caixa_max_length_validates`,
6542        // `placement_cluster_max_length_validates`,
6543        // `child_caixa_max_length_validates`.
6544        let at_cap = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN);
6545        caixa_with_nome(&at_cap).validate_nome().unwrap();
6546    }
6547
6548    #[test]
6549    fn nome_empty_takes_precedence_over_invalid() {
6550        // Order pin: the empty arm fires before the predicate is
6551        // consulted. Empty < invalid in self-locating-ness — the
6552        // narrower `NomeEmpty` diagnostic doesn't carry a useless
6553        // `nome: ""` reference into the parser-shaped reason. Mirrors
6554        // `membro_caixa_empty_takes_precedence_over_invalid` on the
6555        // peer axis (3f9d7a0).
6556        let c = caixa_with_nome("");
6557        assert_eq!(c.validate_nome().unwrap_err(), ManifestError::NomeEmpty);
6558    }
6559
6560    #[test]
6561    fn nome_invalid_diagnostic_carries_offending_nome() {
6562        // Diagnostic-shape pin: the error names the offending `:nome`
6563        // verbatim with a non-empty parser-shaped reason, so a `feira
6564        // lint` run can render the diagnostic without re-parsing.
6565        // Mirrors `membro_caixa_invalid_diagnostic_carries_offending_caixa`.
6566        let c = caixa_with_nome("MyApp");
6567        let err = c.validate_nome().unwrap_err();
6568        let ManifestError::NomeInvalid { nome, reason } = err else {
6569            panic!("expected NomeInvalid variant");
6570        };
6571        assert_eq!(nome, "MyApp");
6572        assert!(
6573            !reason.is_empty(),
6574            "NomeInvalid `reason` must carry the predicate's wording verbatim"
6575        );
6576    }
6577
6578    // ── Caixa::validate_nome_chart_name_budget — joint-length on `:nome` ──
6579    //
6580    // The bare-`:nome` axis [`Caixa::validate_nome`] caps at 63 bytes
6581    // via DNS-1123; this second-axis gate caps the joint
6582    // `lareira-<nome>` chart name at the same 63-byte ceiling. The
6583    // canonical [`crate::lareira_chart_name`] helper's doc comment
6584    // (f7320d7, caixa-core/src/render.rs:3198) explicitly deferred:
6585    // "the M4 admission webhook will pin the joint-length invariant
6586    // when it lands". These tests pin it at the manifest-validate
6587    // layer instead, fail-before-pass-after on the 56-byte boundary.
6588
6589    #[test]
6590    fn validate_nome_chart_name_budget_accepts_canonical_template() {
6591        // Positive control: the bare `feira init`-style template's
6592        // `:nome` ("demo") sits far below the cap; the gate must not
6593        // regress this baseline. Same shape every peer
6594        // value-shape-gate baseline pin uses.
6595        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6596        c.validate_nome_chart_name_budget().unwrap();
6597    }
6598
6599    #[test]
6600    fn validate_nome_chart_name_budget_accepts_canonical_fixtures() {
6601        // Positive-set sweep across the canonical author surface every
6602        // in-tree fixture uses (`hello-rio`, `cart`, `checkout`,
6603        // `worker`, the `checkout-aplicacao` example members, the
6604        // `akeyless-attest` caixa-tatara fixture). Every value sits
6605        // far below the 55-byte per-`:nome` budget. Same shape every
6606        // peer per-axis baseline pin uses.
6607        for nome in [
6608            "hello-rio",
6609            "cart",
6610            "checkout",
6611            "worker",
6612            "akeyless-attest",
6613            "demo",
6614            "a",
6615        ] {
6616            caixa_with_nome(nome)
6617                .validate_nome_chart_name_budget()
6618                .unwrap_or_else(|e| {
6619                    panic!("canonical :nome {nome:?} must pass chart-name budget, got {e:?}")
6620                });
6621        }
6622    }
6623
6624    #[test]
6625    fn validate_nome_chart_name_budget_accepts_nome_at_cap() {
6626        // Boundary-accepting case at the 55-byte per-`:nome` budget —
6627        // the joint chart name is exactly 63 bytes, the DNS-1123 label
6628        // cap. Pinned alongside the rejecting-arm test so a future cap
6629        // shift surfaces both arms simultaneously. Mirrors
6630        // `nome_max_length_validates` on the peer bare-`:nome` axis.
6631        let at_cap = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN);
6632        caixa_with_nome(&at_cap)
6633            .validate_nome_chart_name_budget()
6634            .unwrap();
6635    }
6636
6637    #[test]
6638    fn validate_nome_chart_name_budget_rejects_nome_one_over_cap() {
6639        // Fail-before-pass-after pin on the 56-byte boundary: the
6640        // smallest `:nome` length that overflows the joint chart-name
6641        // cap. The inner [`is_dns_1123_label`] gate
6642        // (`Caixa::validate_nome`) accepts it (56 ≤ 63), so prior to
6643        // this gate it silently passed the manifest-validate cascade
6644        // and surfaced as a `helm lint` / apiserver rejection on the
6645        // rendered chart name far from the source `caixa.lisp`, with
6646        // no field naming the overflow. With this gate the diagnostic
6647        // names the offending `:nome` verbatim alongside the rendered
6648        // chart name and the budget, so the author can shorten in one
6649        // edit. Mirrors `validate_nome_rejects_too_long` on the peer
6650        // bare-`:nome` axis.
6651        let over = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
6652        let c = caixa_with_nome(&over);
6653        let err = c.validate_nome_chart_name_budget().unwrap_err();
6654        let ManifestError::NomeChartNameBudgetExceeded { nome, reason } = err else {
6655            panic!("expected NomeChartNameBudgetExceeded for over-budget :nome");
6656        };
6657        assert_eq!(nome.len(), crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
6658        assert_eq!(nome, over);
6659        assert!(
6660            reason.contains("63") && reason.contains("64") && reason.contains("55"),
6661            "diagnostic must name the DNS-1123 cap (63), the actual chart-name length (64), \
6662             and the per-`:nome` budget (55), got {reason:?}"
6663        );
6664    }
6665
6666    #[test]
6667    fn validate_nome_chart_name_budget_rejects_nome_at_bare_dns_cap() {
6668        // The 63-byte `:nome` boundary — passes the bare-`:nome`
6669        // [`is_dns_1123_label`] cap exactly, but produces a 71-byte
6670        // joint chart name that overflows the DNS-1123 label cap
6671        // structurally. The most stringent fail-before-pass-after
6672        // surface: every `:nome` in the 56..=63-byte range passed the
6673        // prior cascade and broke at admission.
6674        let bare_max = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN);
6675        let c = caixa_with_nome(&bare_max);
6676        // The bare-`:nome` gate accepts the 63-byte length.
6677        c.validate_nome().unwrap();
6678        // The new joint-length gate rejects it.
6679        let err = c.validate_nome_chart_name_budget().unwrap_err();
6680        assert!(
6681            matches!(
6682                err,
6683                ManifestError::NomeChartNameBudgetExceeded { ref nome, .. }
6684                    if nome.len() == crate::DNS_1123_LABEL_MAX_LEN
6685            ),
6686            "got {err:?}"
6687        );
6688    }
6689
6690    #[test]
6691    fn validate_nome_chart_name_budget_diagnostic_carries_offending_chart_name() {
6692        // Diagnostic-shape pin: the rendered `lareira-<nome>` chart
6693        // name appears verbatim in the diagnostic so the author sees
6694        // exactly the string the apiserver / `helm lint` would have
6695        // rejected — no re-derivation required to grep the source.
6696        // Peer with `nome_invalid_diagnostic_carries_offending_nome`
6697        // on the bare-`:nome` axis.
6698        let over = "x".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 5);
6699        let c = caixa_with_nome(&over);
6700        let err = c.validate_nome_chart_name_budget().unwrap_err();
6701        let ManifestError::NomeChartNameBudgetExceeded { nome, reason } = err else {
6702            panic!("expected NomeChartNameBudgetExceeded variant");
6703        };
6704        assert_eq!(nome, over);
6705        let expected_chart = crate::lareira_chart_name(&over);
6706        assert!(
6707            reason.contains(&expected_chart),
6708            "diagnostic must carry the rendered chart name {expected_chart:?} verbatim, \
6709             got {reason:?}"
6710        );
6711        assert!(
6712            reason.contains("lareira-"),
6713            "diagnostic must name the canonical chart-name prefix verbatim, got {reason:?}"
6714        );
6715    }
6716
6717    #[test]
6718    fn validate_nome_chart_name_budget_runs_after_nome_shape_via_layout_verify() {
6719        // Order pin on the layout cascade: the narrower
6720        // `NomeInvalid` (bare-DNS-1123 shape) fires before the
6721        // joint-length budget. A structurally-malformed `:nome` (here:
6722        // uppercase) surfaces its specific shape error rather than
6723        // the chart-name-budget error, even when the joint length
6724        // would also overflow — the narrower diagnostic is more
6725        // self-locating. Mirrors the cascade-precedence pins peer
6726        // gates already use (e.g. `EntradaParaEmpty` before
6727        // `EntradaParaInvalid`).
6728        let over = "A".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
6729        let c = caixa_with_nome(&over);
6730        // The bare-shape gate fires first.
6731        let err = c.validate_nome().unwrap_err();
6732        assert!(
6733            matches!(err, ManifestError::NomeInvalid { .. }),
6734            "bare-shape gate must fire before chart-name-budget gate; got {err:?}"
6735        );
6736        // And the layout verify cascade surfaces that diagnostic, not
6737        // the budget arm. Inject a path-exists oracle so the cascade
6738        // gets past the manifest-presence check and into the
6739        // value-shape gates.
6740        let layout = crate::StandardLayout::new().with_path_exists(|_| true);
6741        let err = crate::LayoutInvariants::verify(
6742            &layout,
6743            &c,
6744            std::path::Path::new("/tmp/caixa-test-fake-root"),
6745        )
6746        .unwrap_err();
6747        let issue = err.to_string();
6748        assert!(
6749            issue.contains("DNS-1123") || issue.contains("uppercase"),
6750            "layout cascade must surface the bare-DNS-1123 diagnostic on a \
6751             structurally-malformed :nome, not the chart-name-budget diagnostic; got {issue:?}"
6752        );
6753    }
6754
6755    #[test]
6756    fn layout_verify_routes_chart_name_budget_through_nome_violation() {
6757        // Cross-axis envelope pin: the layout cascade wraps both
6758        // bare-`:nome` and joint-length-`:nome` failures through the
6759        // same [`LayoutError::NomeViolation`] envelope, since both
6760        // arms are on the `:nome` axis. The user's diagnostic stays
6761        // self-locating ("which axis"), and a future consumer that
6762        // dispatches on the layout-error variant (e.g. a `feira lint`
6763        // exit-code mapping) sees a single per-axis envelope. The
6764        // wrapped `issue:` carries the full inner diagnostic.
6765        let over = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
6766        let c = caixa_with_nome(&over);
6767        // The bare-shape gate accepts.
6768        c.validate_nome().unwrap();
6769        let layout = crate::StandardLayout::new().with_path_exists(|_| true);
6770        let err = crate::LayoutInvariants::verify(
6771            &layout,
6772            &c,
6773            std::path::Path::new("/tmp/caixa-test-fake-root"),
6774        )
6775        .unwrap_err();
6776        let crate::LayoutError::NomeViolation { caixa, issue } = err else {
6777            panic!("expected LayoutError::NomeViolation, got {err:?}");
6778        };
6779        assert_eq!(caixa, over);
6780        assert!(
6781            issue.contains("lareira-") && issue.contains("63") && issue.contains("55"),
6782            "wrapped issue must carry the joint-length diagnostic verbatim, got {issue:?}"
6783        );
6784    }
6785
6786    // ── Caixa::validate_versao — top-level :versao value-shape gate ─────
6787
6788    fn caixa_with_versao(versao: &str) -> Caixa {
6789        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6790        c.versao = versao.to_string();
6791        c
6792    }
6793
6794    #[test]
6795    fn validate_versao_accepts_canonical_template() {
6796        // Positive control: the bare `feira init`-style template's
6797        // `:versao` ("0.1.0") is a canonical SemVer-2 literal; the gate
6798        // must not regress this baseline shape. A future tightening of
6799        // the accepted set surfaces here as a test failure first.
6800        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6801        c.validate_versao().unwrap();
6802    }
6803
6804    #[test]
6805    fn validate_versao_accepts_canonical_forms() {
6806        // Positive-set sweep: each realistic SemVer-2 shape the
6807        // substrate's downstream consumers accept must pass — bare
6808        // MAJOR.MINOR.PATCH, pre-release tags (`-rc.1`, `-alpha.0`),
6809        // build metadata (`+build.42`), the combined form, and the
6810        // `0.0.0` boundary case. Mirrors `accepts_canonical_forms` on
6811        // the peer `:nome` axis (6c992f8).
6812        for versao in [
6813            "0.1.0",
6814            "0.0.0",
6815            "1.0.0",
6816            "0.2.0-rc.1",
6817            "1.0.0-alpha.0",
6818            "1.0.0+build.42",
6819            "1.0.0-rc.1+build.42",
6820            "10.20.30",
6821        ] {
6822            caixa_with_versao(versao)
6823                .validate_versao()
6824                .unwrap_or_else(|e| {
6825                    panic!("canonical :versao {versao:?} must validate, got {e:?}")
6826                });
6827        }
6828    }
6829
6830    #[test]
6831    fn validate_versao_rejects_empty() {
6832        // Fail-before-pass-after pin: `Caixa::from_lisp` does not refuse
6833        // an empty `:versao` (the derive macro stores the raw String);
6834        // the gate's empty arm names the offending axis with a narrower
6835        // diagnostic than the `VersaoInvalid` parse arm would emit.
6836        // Mirrors `validate_nome_rejects_empty` (6c992f8).
6837        let c = caixa_with_versao("");
6838        let err = c.validate_versao().unwrap_err();
6839        assert_eq!(err, ManifestError::VersaoEmpty);
6840    }
6841
6842    #[test]
6843    fn validate_versao_rejects_git_tag_shape() {
6844        // The canonical "I copied the git tag verbatim" footgun —
6845        // `feira publish` *emits* `v<versao>` git tags, so a leaked
6846        // `v0.1.0` in `:versao` would render as `vv0.1.0` and silently
6847        // shift every downstream consumer's version axis. `semver`
6848        // rejects the leading `v` at parse time; the gate moves the
6849        // diagnostic to the source `caixa.lisp`.
6850        let c = caixa_with_versao("v0.1.0");
6851        let err = c.validate_versao().unwrap_err();
6852        let ManifestError::VersaoInvalid { versao, reason } = err else {
6853            panic!("expected VersaoInvalid for git-tag-shape :versao");
6854        };
6855        assert_eq!(versao, "v0.1.0");
6856        assert!(
6857            !reason.is_empty(),
6858            "VersaoInvalid `reason` must carry the parser's wording, got {reason:?}"
6859        );
6860    }
6861
6862    #[test]
6863    fn validate_versao_rejects_missing_patch() {
6864        // The canonical "I shortened it" footgun — SemVer-2 requires
6865        // three parts. Cargo's `version =` field accepts the shortened
6866        // form as a requirement, conflating the two leaks across the
6867        // typed `:deps :versao` vs top-level `:versao` axes; the gate
6868        // pins the top-level axis to the strict three-part shape.
6869        let c = caixa_with_versao("0.1");
6870        let err = c.validate_versao().unwrap_err();
6871        assert!(
6872            matches!(
6873                err,
6874                ManifestError::VersaoInvalid { ref versao, .. } if versao == "0.1"
6875            ),
6876            "got {err:?}"
6877        );
6878    }
6879
6880    #[test]
6881    fn validate_versao_rejects_requirement_shape() {
6882        // The canonical "I leaked a requirement into a version" footgun —
6883        // the typed `:deps :versao` / `:membros :versao` axes accept
6884        // `^0.1` (a `VersionReq`); the top-level `:versao` requires a
6885        // concrete `Version`. Without this gate the two typed surfaces
6886        // would silently overlap, and a top-level `^0.1` would surface
6887        // at `helm install` time as a Chart.yaml version rejection far
6888        // from the source `caixa.lisp`.
6889        let c = caixa_with_versao("^0.1");
6890        let err = c.validate_versao().unwrap_err();
6891        assert!(
6892            matches!(
6893                err,
6894                ManifestError::VersaoInvalid { ref versao, .. } if versao == "^0.1"
6895            ),
6896            "got {err:?}"
6897        );
6898    }
6899
6900    #[test]
6901    fn validate_versao_rejects_docker_tag_shape() {
6902        // The "I confused it with a docker tag" footgun — `latest`,
6903        // `main`, `stable` parse as identifiers, not SemVer-2 versions.
6904        // SemVer rejects at parse time; the gate moves the diagnostic
6905        // to the source `caixa.lisp`.
6906        for bad in ["latest", "main", "stable"] {
6907            let c = caixa_with_versao(bad);
6908            let err = c.validate_versao().unwrap_err();
6909            assert!(
6910                matches!(
6911                    err,
6912                    ManifestError::VersaoInvalid { ref versao, .. } if versao == bad
6913                ),
6914                "got {err:?} for {bad:?}"
6915            );
6916        }
6917    }
6918
6919    #[test]
6920    fn validate_versao_rejects_four_part_form() {
6921        // The Java/Microsoft "MAJOR.MINOR.PATCH.BUILD" convention
6922        // SemVer-2 forbids. A leak from a non-SemVer ecosystem; the
6923        // semver crate rejects the extra `.0` at parse time.
6924        let c = caixa_with_versao("0.1.0.0");
6925        let err = c.validate_versao().unwrap_err();
6926        assert!(
6927            matches!(
6928                err,
6929                ManifestError::VersaoInvalid { ref versao, .. } if versao == "0.1.0.0"
6930            ),
6931            "got {err:?}"
6932        );
6933    }
6934
6935    #[test]
6936    fn versao_empty_takes_precedence_over_invalid() {
6937        // Order pin: the empty arm fires before the parser is consulted.
6938        // Empty < invalid in self-locating-ness — the narrower
6939        // `VersaoEmpty` diagnostic doesn't carry a useless `versao: ""`
6940        // reference into the parser-shaped reason. Mirrors
6941        // `nome_empty_takes_precedence_over_invalid` (6c992f8) on the
6942        // peer axis.
6943        let c = caixa_with_versao("");
6944        assert_eq!(c.validate_versao().unwrap_err(), ManifestError::VersaoEmpty);
6945    }
6946
6947    #[test]
6948    fn versao_invalid_diagnostic_carries_offending_versao() {
6949        // Diagnostic-shape pin: the error names the offending `:versao`
6950        // verbatim with a non-empty parser-shaped reason, so a `feira
6951        // lint` run can render the diagnostic without re-parsing.
6952        // Mirrors `nome_invalid_diagnostic_carries_offending_nome`.
6953        let c = caixa_with_versao("v0.1.0");
6954        let err = c.validate_versao().unwrap_err();
6955        let ManifestError::VersaoInvalid { versao, reason } = err else {
6956            panic!("expected VersaoInvalid variant");
6957        };
6958        assert_eq!(versao, "v0.1.0");
6959        assert!(
6960            !reason.is_empty(),
6961            "VersaoInvalid `reason` must carry the parser's wording verbatim"
6962        );
6963    }
6964
6965    #[test]
6966    fn validate_versao_accepts_what_upgrade_from_from_accepts() {
6967        // Parity pin: every shape `UpgradeFromEntry::validate` accepts
6968        // for `:upgrade-from :from` must also pass `validate_versao` —
6969        // the two `:versao`-typed surfaces (top-level `:versao`,
6970        // `:upgrade-from :from`) consume the *same* `semver::Version`
6971        // parser, so they must agree on the accepted set. Without this
6972        // pin, a future tightening of one axis could silently diverge
6973        // from the other. Mirrors the `:versao` requirement-axis
6974        // parity (`:deps`/`:deps-dev`/`:membros`/`:children`) the prior
6975        // commits established.
6976        for versao in ["0.1.0", "0.2.0-rc.1", "1.0.0+build.42"] {
6977            // From the canonical UpgradeFromEntry round-trip fixture
6978            // (`upgrade::tests::round_trip_load_module` peers).
6979            let entry = crate::UpgradeFromEntry {
6980                from: versao.to_string(),
6981                instructions: Vec::new(),
6982            };
6983            entry
6984                .validate()
6985                .unwrap_or_else(|e| panic!(":from {versao:?} must validate, got {e:?}"));
6986            caixa_with_versao(versao)
6987                .validate_versao()
6988                .unwrap_or_else(|e| {
6989                    panic!(":versao {versao:?} must validate, got {e:?} — peer axis diverges")
6990                });
6991        }
6992    }
6993
6994    // ── Caixa::validate_restart_window — supervisor restart-window
6995    //    folds through the shared `supervisor::duration_codec` ────────
6996
6997    fn caixa_with_restart_window(window: Option<&str>) -> Caixa {
6998        let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
6999        c.kind = CaixaKind::Supervisor;
7000        c.restart_window = window.map(str::to_string);
7001        c
7002    }
7003
7004    #[test]
7005    fn validate_restart_window_accepts_none() {
7006        // The canonical "omit the slot to express no reset" shape — a
7007        // `None` raw string is the absence of the typed
7008        // `:restart-window` slot, which is exactly the SupervisorSpec
7009        // "never reset" semantics. The gate must be a no-op here; a
7010        // future tightening that rejected `None` would force every
7011        // supervisor caixa to authoring-time pin a window even when
7012        // the OTP semantics call for none.
7013        caixa_with_restart_window(None)
7014            .validate_restart_window()
7015            .unwrap();
7016    }
7017
7018    #[test]
7019    fn validate_restart_window_accepts_canonical_forms() {
7020        // Positive-set sweep across the canonical authoring units the
7021        // shared `supervisor::duration_codec::parse` accepts —
7022        // matches the codec-side `parse_accepts_integer_canonical_units`
7023        // pin in supervisor::tests so a future codec-side tightening
7024        // surfaces simultaneously on both axes.
7025        for window in ["60s", "5m", "1h", "500ms", "30", "0s"] {
7026            caixa_with_restart_window(Some(window))
7027                .validate_restart_window()
7028                .unwrap_or_else(|e| {
7029                    panic!("canonical :restart-window {window:?} must validate, got {e:?}")
7030                });
7031        }
7032    }
7033
7034    #[test]
7035    fn validate_restart_window_rejects_fractional_seconds() {
7036        // Fail-before-pass-after pin: the `"1.5s"` drift class (parses
7037        // as f64 to 1.5 → renders back as `"1500ms"` on first
7038        // serialize). Prior to the fold + this gate, the inline
7039        // `parse_window_inline` accepted f64 magnitudes and silently
7040        // produced a `Duration::from_secs_f64(1.5)`, divergent from
7041        // the shared codec's integer-magnitude discipline on the
7042        // serde-routed siblings. The gate now surfaces a self-locating
7043        // diagnostic at the manifest layer.
7044        let err = caixa_with_restart_window(Some("1.5s"))
7045            .validate_restart_window()
7046            .unwrap_err();
7047        let ManifestError::RestartWindowMalformed {
7048            restart_window,
7049            reason,
7050        } = err
7051        else {
7052            panic!("expected RestartWindowMalformed for fractional seconds");
7053        };
7054        assert_eq!(restart_window, "1.5s");
7055        assert!(
7056            reason.contains("\"1.5\"") && reason.contains("not a non-negative integer"),
7057            "diagnostic must carry shared-codec wording, got {reason:?}"
7058        );
7059    }
7060
7061    #[test]
7062    fn validate_restart_window_rejects_decimal_shaped_integer() {
7063        // The `"1.0s"` class — numerically `1s` exactly, but the
7064        // canonical form is `"1s"` not `"1.0s"`. Decimal-shape leak
7065        // gets the same canonical-form diagnostic.
7066        let err = caixa_with_restart_window(Some("1.0s"))
7067            .validate_restart_window()
7068            .unwrap_err();
7069        assert!(
7070            matches!(
7071                err,
7072                ManifestError::RestartWindowMalformed { ref restart_window, .. }
7073                    if restart_window == "1.0s"
7074            ),
7075            "got {err:?}"
7076        );
7077    }
7078
7079    #[test]
7080    fn validate_restart_window_rejects_half_unit_minute() {
7081        // `"0.5m"` is the unit-fraction footgun — author writes a
7082        // human-readable half-minute, the prior inline parser silently
7083        // produced `Duration::from_secs_f64(30.0)` and serde
7084        // re-emitted as `"30s"`, rewriting author intent. The gate
7085        // closes the loop at the manifest layer.
7086        let err = caixa_with_restart_window(Some("0.5m"))
7087            .validate_restart_window()
7088            .unwrap_err();
7089        let ManifestError::RestartWindowMalformed {
7090            restart_window,
7091            reason,
7092        } = err
7093        else {
7094            panic!("expected RestartWindowMalformed");
7095        };
7096        assert_eq!(restart_window, "0.5m");
7097        assert!(
7098            reason.contains("\"30s\""),
7099            "diagnostic must point at the canonical-form remediation, got {reason:?}"
7100        );
7101    }
7102
7103    #[test]
7104    fn validate_restart_window_rejects_leading_sign() {
7105        // `"+30s"` and `"-30s"` both round-tripped through f64 cleanly
7106        // on the prior parser (`+30` parses as `30.0`; `-30` parsed
7107        // and was caught by the `num < 0.0` arm which silently
7108        // returned `None`, dropping the author-supplied window). The
7109        // shared codec's digit-only gate rejects both with a unified
7110        // canonical-form diagnostic; the manifest-layer wrapper names
7111        // the offending value.
7112        for bad in ["+30s", "-30s"] {
7113            let err = caixa_with_restart_window(Some(bad))
7114                .validate_restart_window()
7115                .unwrap_err();
7116            assert!(
7117                matches!(
7118                    err,
7119                    ManifestError::RestartWindowMalformed { ref restart_window, .. }
7120                        if restart_window == bad
7121                ),
7122                "got {err:?} for {bad:?}"
7123            );
7124        }
7125    }
7126
7127    #[test]
7128    fn validate_restart_window_rejects_unknown_unit() {
7129        // `"30x"` — the typo / wrong-unit footgun. The shared codec's
7130        // unit dispatch surfaces an `unknown duration unit` reason;
7131        // the manifest-layer wrapper names the offending value.
7132        let err = caixa_with_restart_window(Some("30x"))
7133            .validate_restart_window()
7134            .unwrap_err();
7135        let ManifestError::RestartWindowMalformed {
7136            restart_window,
7137            reason,
7138        } = err
7139        else {
7140            panic!("expected RestartWindowMalformed for unknown unit");
7141        };
7142        assert_eq!(restart_window, "30x");
7143        assert!(
7144            reason.contains("unknown duration unit"),
7145            "diagnostic must carry shared-codec unit-rejection wording, got {reason:?}"
7146        );
7147    }
7148
7149    #[test]
7150    fn validate_restart_window_rejects_garbage() {
7151        // Pure non-numeric magnitude (`"abc"`) falls through to the
7152        // shared codec's narrower `"bad duration magnitude"` arm. Same
7153        // diagnostic shape as the codec-side
7154        // `parse_garbage_still_falls_through_to_bad_magnitude` pin.
7155        let err = caixa_with_restart_window(Some("abc"))
7156            .validate_restart_window()
7157            .unwrap_err();
7158        let ManifestError::RestartWindowMalformed {
7159            restart_window,
7160            reason,
7161        } = err
7162        else {
7163            panic!("expected RestartWindowMalformed for garbage");
7164        };
7165        assert_eq!(restart_window, "abc");
7166        assert!(
7167            reason.contains("bad duration magnitude"),
7168            "diagnostic must carry shared-codec garbage-rejection wording, got {reason:?}"
7169        );
7170    }
7171
7172    #[test]
7173    fn validate_restart_window_rejects_empty_string() {
7174        // The empty-after-trim edge case — distinct from the `None`
7175        // canonical "omit the slot" shape. The shared codec's
7176        // digit-only gate refuses an empty magnitude; the manifest
7177        // layer names the offending `""` so the author can grep for
7178        // the literal empty value in their `caixa.lisp` and either
7179        // remove the slot (the canonical "no reset" shape) or pin a
7180        // positive duration.
7181        let err = caixa_with_restart_window(Some(""))
7182            .validate_restart_window()
7183            .unwrap_err();
7184        assert!(
7185            matches!(
7186                err,
7187                ManifestError::RestartWindowMalformed { ref restart_window, .. }
7188                    if restart_window.is_empty()
7189            ),
7190            "got {err:?}"
7191        );
7192    }
7193
7194    #[test]
7195    fn validate_restart_window_diagnostic_carries_offending_value() {
7196        // Diagnostic-shape pin (peer with
7197        // `nome_invalid_diagnostic_carries_offending_nome` /
7198        // `versao_invalid_diagnostic_carries_offending_versao`): the
7199        // error names the offending raw `:restart-window` verbatim
7200        // with a non-empty shared-codec-shaped reason, so a `feira
7201        // lint` run can render the diagnostic without re-parsing.
7202        let err = caixa_with_restart_window(Some("1.5s"))
7203            .validate_restart_window()
7204            .unwrap_err();
7205        let ManifestError::RestartWindowMalformed {
7206            restart_window,
7207            reason,
7208        } = err
7209        else {
7210            panic!("expected RestartWindowMalformed variant");
7211        };
7212        assert_eq!(restart_window, "1.5s");
7213        assert!(
7214            !reason.is_empty(),
7215            "RestartWindowMalformed `reason` must carry the codec's wording verbatim"
7216        );
7217    }
7218
7219    #[test]
7220    fn supervisor_view_folds_through_shared_codec_on_canonical_form() {
7221        // Behavioral parity pin after the fold (`parse_window_inline`
7222        // deletion): the canonical `"60s"` still produces
7223        // `Duration::from_secs(60)` on the typed view — the fold is
7224        // semantically equivalent to the prior inline parser on the
7225        // accepted set. Mirrors the pre-fold `supervisor_view_returns_typed_shape`
7226        // pin, narrowed to the parser-side contract.
7227        let c = caixa_with_restart_window(Some("60s"));
7228        let view = c.supervisor_view().expect("Supervisor kind has a view");
7229        assert_eq!(
7230            view.restart_window,
7231            Some(std::time::Duration::from_secs(60))
7232        );
7233    }
7234
7235    #[test]
7236    fn supervisor_view_soft_swallows_what_validate_rejects() {
7237        // Parity pin between the view-construction path and the
7238        // manifest-level validator: the same `"1.5s"` that surfaces
7239        // `RestartWindowMalformed` at `validate_restart_window` time
7240        // becomes `restart_window: None` on the typed view (the fold
7241        // preserves the existing best-effort shape of `supervisor_view`).
7242        // The contract is: a layout-verifier / `feira lint` flow that
7243        // cares about the malformed-window axis MUST consult
7244        // `validate_restart_window` — relying solely on the view's
7245        // `None` swallows the diagnostic silently. This pin makes the
7246        // expectation a typed invariant.
7247        let c = caixa_with_restart_window(Some("1.5s"));
7248        let view = c.supervisor_view().expect("Supervisor kind has a view");
7249        assert_eq!(
7250            view.restart_window, None,
7251            "view-construction path soft-swallows the parse error to None"
7252        );
7253        // And the manifest-level validator does NOT soft-swallow:
7254        assert!(
7255            matches!(
7256                c.validate_restart_window().unwrap_err(),
7257                ManifestError::RestartWindowMalformed { ref restart_window, .. }
7258                    if restart_window == "1.5s"
7259            ),
7260            "validator must surface the offending value",
7261        );
7262    }
7263
7264    // ── validate_code_paths — per-entry shape on :bibliotecas / :exe / :servicos ──
7265
7266    fn caixa_with_code_paths(bibliotecas: Vec<&str>, exe: Vec<&str>, servicos: Vec<&str>) -> Caixa {
7267        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7268        c.bibliotecas = bibliotecas.into_iter().map(String::from).collect();
7269        c.exe = exe.into_iter().map(String::from).collect();
7270        c.servicos = servicos.into_iter().map(String::from).collect();
7271        c
7272    }
7273
7274    #[test]
7275    fn validate_code_paths_accepts_canonical_template() {
7276        // The bare `Caixa::template` shape is the gate's identity element
7277        // on the canonical authoring shape — `:bibliotecas
7278        // ("lib/demo.lisp")` + empty `:exe` + empty `:servicos`. Pins
7279        // that the gate is non-disruptive against every existing caixa.
7280        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7281        c.validate_code_paths().unwrap();
7282    }
7283
7284    #[test]
7285    fn validate_code_paths_accepts_explicit_relative_paths_on_every_slot() {
7286        // Positive control sweep: a canonical-shaped path on every slot
7287        // passes. Mirrors the peer
7288        // `behavior::validate_every_slot_relative_is_ok` pin.
7289        let c = caixa_with_code_paths(
7290            vec!["lib/demo.lisp", "lib/helpers.lisp"],
7291            vec!["exe/demo", "exe/tool"],
7292            vec!["servicos/demo.computeunit.yaml"],
7293        );
7294        c.validate_code_paths().unwrap();
7295    }
7296
7297    #[test]
7298    fn validate_code_paths_accepts_all_empty_lists() {
7299        // The empty-list identity element: every Caixa with no declared
7300        // code paths trivially passes (Supervisor / Aplicacao kinds rely
7301        // on this — the OwnCode gate already rejected them before the
7302        // path-shape gate runs in the layout, but the validator itself
7303        // must accept the empty shape).
7304        let c = caixa_with_code_paths(vec![], vec![], vec![]);
7305        c.validate_code_paths().unwrap();
7306    }
7307
7308    #[test]
7309    fn validate_code_paths_rejects_empty_bibliotecas_entry() {
7310        let c = caixa_with_code_paths(vec![""], vec![], vec![]);
7311        let err = c.validate_code_paths().unwrap_err();
7312        assert!(
7313            matches!(
7314                err,
7315                ManifestError::CodePathEmpty {
7316                    slot: ":bibliotecas"
7317                }
7318            ),
7319            "got {err:?}",
7320        );
7321    }
7322
7323    #[test]
7324    fn validate_code_paths_rejects_empty_exe_entry() {
7325        let c = caixa_with_code_paths(vec![], vec![""], vec![]);
7326        let err = c.validate_code_paths().unwrap_err();
7327        assert!(
7328            matches!(err, ManifestError::CodePathEmpty { slot: ":exe" }),
7329            "got {err:?}",
7330        );
7331    }
7332
7333    #[test]
7334    fn validate_code_paths_rejects_empty_servicos_entry() {
7335        let c = caixa_with_code_paths(vec![], vec![], vec![""]);
7336        let err = c.validate_code_paths().unwrap_err();
7337        assert!(
7338            matches!(err, ManifestError::CodePathEmpty { slot: ":servicos" }),
7339            "got {err:?}",
7340        );
7341    }
7342
7343    #[test]
7344    fn validate_code_paths_rejects_absolute_bibliotecas_entry() {
7345        // `:bibliotecas` has no `starts_with(<dir>)` fence downstream,
7346        // so an absolute path that resolves on disk silently passes the
7347        // layout's existence check — the canonical sandbox-escape on
7348        // the biblioteca axis.
7349        let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
7350        let err = c.validate_code_paths().unwrap_err();
7351        let ManifestError::CodePathAbsolute { slot, path } = err else {
7352            panic!("expected CodePathAbsolute, got {err:?}");
7353        };
7354        assert_eq!(slot, ":bibliotecas");
7355        assert_eq!(path, PathBuf::from("/etc/passwd"));
7356    }
7357
7358    #[test]
7359    fn validate_code_paths_rejects_absolute_exe_entry() {
7360        let c = caixa_with_code_paths(vec![], vec!["/usr/bin/env"], vec![]);
7361        let err = c.validate_code_paths().unwrap_err();
7362        let ManifestError::CodePathAbsolute { slot, path } = err else {
7363            panic!("expected CodePathAbsolute, got {err:?}");
7364        };
7365        assert_eq!(slot, ":exe");
7366        assert_eq!(path, PathBuf::from("/usr/bin/env"));
7367    }
7368
7369    #[test]
7370    fn validate_code_paths_rejects_absolute_servicos_entry() {
7371        let c = caixa_with_code_paths(vec![], vec![], vec!["/var/servicos/x.yaml"]);
7372        let err = c.validate_code_paths().unwrap_err();
7373        let ManifestError::CodePathAbsolute { slot, path } = err else {
7374            panic!("expected CodePathAbsolute, got {err:?}");
7375        };
7376        assert_eq!(slot, ":servicos");
7377        assert_eq!(path, PathBuf::from("/var/servicos/x.yaml"));
7378    }
7379
7380    #[test]
7381    fn validate_code_paths_rejects_parent_escape_bibliotecas_leading() {
7382        // Canonical "I want a lib from a sibling caixa" footgun on the
7383        // biblioteca axis. `:bibliotecas` has no `starts_with` fence
7384        // downstream, so a leading `..` traverses to the parent of the
7385        // caixa root with no diagnostic at layout time if the resolved
7386        // target exists.
7387        let c = caixa_with_code_paths(vec!["../sibling/x.lisp"], vec![], vec![]);
7388        let err = c.validate_code_paths().unwrap_err();
7389        let ManifestError::CodePathParentEscape { slot, path } = err else {
7390            panic!("expected CodePathParentEscape, got {err:?}");
7391        };
7392        assert_eq!(slot, ":bibliotecas");
7393        assert_eq!(path, PathBuf::from("../sibling/x.lisp"));
7394    }
7395
7396    #[test]
7397    fn validate_code_paths_rejects_parent_escape_exe_mid_path() {
7398        // Mid-path `..` defeats the layout's component-aware
7399        // `starts_with(exe_dir)` fence — `root.join("exe/../../escape")`
7400        // `starts_with(<root>/exe)` is true, but the canonical resolution
7401        // lives outside the caixa root. Caught regardless of where the
7402        // `..` sits — mirrors the peer
7403        // `behavior::validate_rejects_parent_escape_mid_path` pin.
7404        let c = caixa_with_code_paths(vec![], vec!["exe/../../escape"], vec![]);
7405        let err = c.validate_code_paths().unwrap_err();
7406        let ManifestError::CodePathParentEscape { slot, path } = err else {
7407            panic!("expected CodePathParentEscape, got {err:?}");
7408        };
7409        assert_eq!(slot, ":exe");
7410        assert_eq!(path, PathBuf::from("exe/../../escape"));
7411    }
7412
7413    #[test]
7414    fn validate_code_paths_rejects_parent_escape_servicos_trailing() {
7415        let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/foo/../../escape.yaml"]);
7416        let err = c.validate_code_paths().unwrap_err();
7417        let ManifestError::CodePathParentEscape { slot, path } = err else {
7418            panic!("expected CodePathParentEscape, got {err:?}");
7419        };
7420        assert_eq!(slot, ":servicos");
7421        assert_eq!(path, PathBuf::from("servicos/foo/../../escape.yaml"));
7422    }
7423
7424    #[test]
7425    fn validate_code_paths_cross_slot_precedence_bibliotecas_before_exe_before_servicos() {
7426        // Cross-slot precedence pin: `:bibliotecas` → `:exe` →
7427        // `:servicos`. A manifest with malformed entries on all three
7428        // surfaces surfaces the `:bibliotecas` defect first, mirroring
7429        // the canonical declaration order
7430        // `Caixa::declared_foreign_code_slots` already establishes for
7431        // the foreign-code-slot diagnostic.
7432        let c = caixa_with_code_paths(vec![""], vec![""], vec![""]);
7433        let err = c.validate_code_paths().unwrap_err();
7434        assert!(
7435            matches!(
7436                err,
7437                ManifestError::CodePathEmpty {
7438                    slot: ":bibliotecas"
7439                }
7440            ),
7441            "got {err:?}",
7442        );
7443    }
7444
7445    #[test]
7446    fn validate_code_paths_within_slot_precedence_empty_before_absolute_before_parent_escape() {
7447        // Within-slot precedence pin: empty → absolute → parent-escape,
7448        // matching the [`PathShapeViolation`] arm-ordering every peer
7449        // `is_sandboxed_relative_path` caller follows (b0c8389
7450        // BehaviorSpec, 26da2c7 UpgradeInstruction::StateChange). A
7451        // `:bibliotecas` list whose first entry is empty *and* whose
7452        // later entries are absolute/parent-escape surfaces the empty
7453        // arm first, on the lexicographically-earliest offending entry.
7454        let c = caixa_with_code_paths(vec!["", "/etc/passwd", "../escape.lisp"], vec![], vec![]);
7455        let err = c.validate_code_paths().unwrap_err();
7456        assert!(
7457            matches!(
7458                err,
7459                ManifestError::CodePathEmpty {
7460                    slot: ":bibliotecas"
7461                }
7462            ),
7463            "got {err:?}",
7464        );
7465    }
7466
7467    #[test]
7468    fn validate_code_paths_first_offender_per_slot_wins() {
7469        // Within a single slot, the first declaration-order offender
7470        // surfaces — pins that the gate is left-to-right deterministic
7471        // (peer of every `*_first_collision_*` pin on duplicate gates).
7472        let c = caixa_with_code_paths(
7473            vec!["lib/ok.lisp", "/etc/escape", "../also-escape"],
7474            vec![],
7475            vec![],
7476        );
7477        let err = c.validate_code_paths().unwrap_err();
7478        let ManifestError::CodePathAbsolute { slot, path } = err else {
7479            panic!("expected CodePathAbsolute, got {err:?}");
7480        };
7481        assert_eq!(slot, ":bibliotecas");
7482        assert_eq!(path, PathBuf::from("/etc/escape"));
7483    }
7484
7485    #[test]
7486    fn validate_code_paths_diagnostic_carries_offending_slot_and_path() {
7487        // Diagnostic-shape pin (peer with
7488        // `nome_invalid_diagnostic_carries_offending_nome` /
7489        // `versao_invalid_diagnostic_carries_offending_versao`): the
7490        // error's Display surfaces both the offending `:slot` tag and
7491        // the offending path verbatim, so a `feira lint` run can render
7492        // the diagnostic without re-parsing.
7493        let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
7494        let rendered = c.validate_code_paths().unwrap_err().to_string();
7495        assert!(
7496            rendered.contains(":bibliotecas"),
7497            "diagnostic must name the offending slot: {rendered}",
7498        );
7499        assert!(
7500            rendered.contains("/etc/passwd"),
7501            "diagnostic must quote the offending path: {rendered}",
7502        );
7503    }
7504
7505    #[test]
7506    fn validate_code_paths_rejects_duplicate_bibliotecas_entry() {
7507        // Canonical copy-paste-the-wrong-file footgun on the biblioteca
7508        // axis. Without the gate `feira build` re-parses the same lib
7509        // twice, wasting work and silently masking the author's intent
7510        // to declare a *second* biblioteca.
7511        let c = caixa_with_code_paths(vec!["lib/demo.lisp", "lib/demo.lisp"], vec![], vec![]);
7512        let err = c.validate_code_paths().unwrap_err();
7513        let ManifestError::CodePathDuplicate { slot, path } = err else {
7514            panic!("expected CodePathDuplicate, got {err:?}");
7515        };
7516        assert_eq!(slot, ":bibliotecas");
7517        assert_eq!(path, PathBuf::from("lib/demo.lisp"));
7518    }
7519
7520    #[test]
7521    fn validate_code_paths_rejects_duplicate_exe_entry() {
7522        // Same footgun on the Binario surface. The future `caixa-flake`
7523        // emitter that materializes each `:exe` entry as a flake
7524        // `packages.<name>` derivation would collide on the duplicate
7525        // package key — surfaced here at the typed-validate layer with a
7526        // self-locating diagnostic instead.
7527        let c = caixa_with_code_paths(vec![], vec!["exe/cli", "exe/cli"], vec![]);
7528        let err = c.validate_code_paths().unwrap_err();
7529        let ManifestError::CodePathDuplicate { slot, path } = err else {
7530            panic!("expected CodePathDuplicate, got {err:?}");
7531        };
7532        assert_eq!(slot, ":exe");
7533        assert_eq!(path, PathBuf::from("exe/cli"));
7534    }
7535
7536    #[test]
7537    fn validate_code_paths_rejects_duplicate_servicos_entry() {
7538        // Same footgun on the Servico surface. The peer caixa-helm /
7539        // caixa-flux renderers refuse `:servicos.len() != 1` with the
7540        // narrower `UnsupportedServicoCount` diagnostic, but that
7541        // diagnostic surfaces "too many servicos" without naming
7542        // "duplicate entry" — the typed self-locating framing only lands
7543        // at this gate.
7544        let c = caixa_with_code_paths(
7545            vec![],
7546            vec![],
7547            vec![
7548                "servicos/demo.computeunit.yaml",
7549                "servicos/demo.computeunit.yaml",
7550            ],
7551        );
7552        let err = c.validate_code_paths().unwrap_err();
7553        let ManifestError::CodePathDuplicate { slot, path } = err else {
7554            panic!("expected CodePathDuplicate, got {err:?}");
7555        };
7556        assert_eq!(slot, ":servicos");
7557        assert_eq!(path, PathBuf::from("servicos/demo.computeunit.yaml"));
7558    }
7559
7560    #[test]
7561    fn validate_code_paths_accepts_same_path_across_slots() {
7562        // Per-list scope pin: a `:bibliotecas` entry that happens to
7563        // collide with an `:exe` or `:servicos` entry as a *string* is
7564        // not a duplicate by this gate (each list gets its own HashSet),
7565        // mirroring the peer `:deps` ↔ `:deps-dev` per-list scope
7566        // (a `:nome` present in both lists is a legitimate dev-vs-runtime
7567        // shape on the dep axis). The structural `starts_with(<exe |
7568        // servicos>_dir)` fence at layout time prevents the realistic
7569        // cross-slot collision case from existing on disk, but the gate's
7570        // per-list scope is correct independent of that downstream fence.
7571        let c = caixa_with_code_paths(
7572            vec!["lib/x.lisp"],
7573            vec!["exe/x"],
7574            vec!["servicos/x.computeunit.yaml"],
7575        );
7576        c.validate_code_paths().unwrap();
7577    }
7578
7579    #[test]
7580    fn validate_code_paths_duplicate_fires_after_structural_checks_on_same_slot() {
7581        // Within-slot ordering pin: structural defects (empty / absolute
7582        // / parent-escape) fire before the duplicate gate on the same
7583        // slot. A `:bibliotecas ("" "lib/x.lisp" "lib/x.lisp")` shape
7584        // surfaces the narrower `CodePathEmpty` for the empty entry
7585        // first, not the duplicate on the later pair — same arm-ordering
7586        // every peer per-list duplicate gate uses (`:etiquetas` 360a499,
7587        // `:autores` 86c769b, `:deps` 359fba5).
7588        let c = caixa_with_code_paths(vec!["", "lib/x.lisp", "lib/x.lisp"], vec![], vec![]);
7589        let err = c.validate_code_paths().unwrap_err();
7590        assert!(
7591            matches!(
7592                err,
7593                ManifestError::CodePathEmpty {
7594                    slot: ":bibliotecas"
7595                }
7596            ),
7597            "got {err:?}",
7598        );
7599    }
7600
7601    #[test]
7602    fn validate_code_paths_duplicate_in_bibliotecas_fires_before_duplicate_in_exe() {
7603        // Cross-slot ordering pin on the duplicate arm: `:bibliotecas`
7604        // duplicates surface before `:exe` duplicates, matching the
7605        // canonical `:bibliotecas` → `:exe` → `:servicos` declaration
7606        // order every peer per-slot diagnostic on this surface follows.
7607        let c = caixa_with_code_paths(
7608            vec!["lib/x.lisp", "lib/x.lisp"],
7609            vec!["exe/y", "exe/y"],
7610            vec![],
7611        );
7612        let err = c.validate_code_paths().unwrap_err();
7613        let ManifestError::CodePathDuplicate { slot, path } = err else {
7614            panic!("expected CodePathDuplicate, got {err:?}");
7615        };
7616        assert_eq!(slot, ":bibliotecas");
7617        assert_eq!(path, PathBuf::from("lib/x.lisp"));
7618    }
7619
7620    #[test]
7621    fn validate_code_paths_duplicate_diagnostic_carries_offending_slot_and_path() {
7622        // Diagnostic-shape pin (peer with
7623        // `validate_code_paths_diagnostic_carries_offending_slot_and_path`
7624        // on the structural arm): the duplicate-arm Display surfaces both
7625        // the offending `:slot` tag and the offending path verbatim, so a
7626        // `feira lint` run can render the diagnostic without re-parsing.
7627        let c = caixa_with_code_paths(
7628            vec![],
7629            vec![],
7630            vec![
7631                "servicos/demo.computeunit.yaml",
7632                "servicos/demo.computeunit.yaml",
7633            ],
7634        );
7635        let rendered = c.validate_code_paths().unwrap_err().to_string();
7636        assert!(
7637            rendered.contains(":servicos"),
7638            "diagnostic must name the offending slot: {rendered}",
7639        );
7640        assert!(
7641            rendered.contains("servicos/demo.computeunit.yaml"),
7642            "diagnostic must quote the offending path: {rendered}",
7643        );
7644    }
7645
7646    // ── validate_code_paths — `.lisp` extension gate on :bibliotecas ──
7647    //
7648    // The lifted [`crate::render::is_lisp_extension`] predicate (33cc830)
7649    // now gates `:bibliotecas` entries on the tatara-lisp-source file-type
7650    // contract. The `feira build` loop (`caixa-feira/src/cmd/build.rs:33`)
7651    // reads every declared `:bibliotecas` entry through `tatara_lisp::read`
7652    // at parse time — the same downstream consumer the peer `:behavior
7653    // :on-*` (c97815a, [`crate::BehaviorError::NonLispExtension`]) and
7654    // `:upgrade-from :state-change :script` (33cc830,
7655    // [`crate::UpgradeError::NonLispExtensionScript`]) axes route through.
7656    // `:exe` and `:servicos` are deliberately excluded — `:exe` is the
7657    // nix-built executable surface (`"exe/<name>"` shape per the canonical
7658    // [`crate::LayoutError::ExeOutsideDir`] error message and every
7659    // in-tree `caixa_with_code_paths` positive control), and `:servicos`
7660    // is the `.computeunit.yaml` ComputeUnit-CR axis.
7661
7662    #[test]
7663    fn validate_code_paths_rejects_no_extension_bibliotecas_entry() {
7664        // Canonical "I dragged the wrong file from the workspace tree"
7665        // footgun on the biblioteca axis. Without the gate `feira build`
7666        // hands the extensionless path to `tatara_lisp::read` and fails
7667        // with a parser-shaped diagnostic far from the source caixa.lisp,
7668        // with no field naming the offending `:bibliotecas` entry.
7669        for relpath in ["lib/demo", "demo", "lib/handlers/inner"] {
7670            let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
7671            let err = c.validate_code_paths().unwrap_err();
7672            let ManifestError::CodePathNonLispExtension { slot, path } = err else {
7673                panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
7674            };
7675            assert_eq!(slot, ":bibliotecas");
7676            assert_eq!(path, PathBuf::from(relpath));
7677        }
7678    }
7679
7680    #[test]
7681    fn validate_code_paths_rejects_wrong_extension_bibliotecas_entry() {
7682        // Wrong-extension sweep across common authoring footguns. Same
7683        // sweep posture as the peer
7684        // `behavior::validate_rejects_wrong_extension` (c97815a) and
7685        // `upgrade::tests::state_change_rejects_wrong_extension_script`
7686        // (33cc830) cases.
7687        for relpath in [
7688            "lib/demo.rs",
7689            "lib/demo.txt",
7690            "lib/demo.md",
7691            "lib/demo.json",
7692            "lib/demo.yaml",
7693            "lib/demo.toml",
7694            "lib/demo.lisp.bak",
7695            "lib/demo.lispx",
7696            "lib/demo.lis",
7697        ] {
7698            let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
7699            let err = c.validate_code_paths().unwrap_err();
7700            let ManifestError::CodePathNonLispExtension { slot, path } = err else {
7701                panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
7702            };
7703            assert_eq!(slot, ":bibliotecas");
7704            assert_eq!(path, PathBuf::from(relpath));
7705        }
7706    }
7707
7708    #[test]
7709    fn validate_code_paths_rejects_case_folded_extension_bibliotecas_entry() {
7710        // Case-sensitivity sweep — pins the strict lowercase `.lisp`
7711        // contract. An uppercase `.LISP` shape that the layout's existence
7712        // check would (case-insensitively, on case-insensitive volumes)
7713        // match the on-disk file still mismatches the canonical form the
7714        // codec emits, breaking the THEORY.md §V.2.7 render-determinism
7715        // contract. Mirrors the peer
7716        // `behavior::validate_rejects_case_folded_extension` (c97815a) and
7717        // `upgrade::tests::state_change_rejects_case_folded_extension_script`
7718        // (33cc830) sweeps.
7719        for relpath in [
7720            "lib/demo.LISP",
7721            "lib/demo.Lisp",
7722            "lib/demo.LiSp",
7723            "lib/demo.lISP",
7724        ] {
7725            let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
7726            let err = c.validate_code_paths().unwrap_err();
7727            let ManifestError::CodePathNonLispExtension { slot, path } = err else {
7728                panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
7729            };
7730            assert_eq!(slot, ":bibliotecas");
7731            assert_eq!(path, PathBuf::from(relpath));
7732        }
7733    }
7734
7735    #[test]
7736    fn validate_code_paths_accepts_canonical_lisp_shapes() {
7737        // Positive-control sweep through every canonical authoring shape
7738        // every in-tree fixture and the `Caixa::template` scaffold use.
7739        // Mirrors the peer `behavior::validate_accepts_canonical_lisp_paths`
7740        // (c97815a) and the lifted predicate's own
7741        // `is_lisp_extension_accepts_canonical_shapes` sweep in render.rs
7742        // (33cc830).
7743        for relpath in [
7744            "lib/demo.lisp",
7745            "lib/handlers.lisp",
7746            "lib/migrations/v01-to-v02.lisp",
7747            "demo.lisp",
7748            "a.lisp",
7749            "./lib/demo.lisp",
7750            "lib/./handlers.lisp",
7751            "lib/migrations/v.0.1.lisp",
7752        ] {
7753            let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
7754            c.validate_code_paths()
7755                .unwrap_or_else(|e| panic!("canonical shape {relpath:?} must pass, got {e:?}"));
7756        }
7757    }
7758
7759    #[test]
7760    fn validate_code_paths_non_lisp_extension_does_not_fire_on_exe_or_servicos() {
7761        // The file-type gate is per-slot — only `:bibliotecas` carries the
7762        // tatara-lisp-source contract. An extensionless `:exe` entry
7763        // (`exe/demo`) and a `.computeunit.yaml` `:servicos` entry are the
7764        // canonical shapes every in-tree fixture uses, and must continue
7765        // to pass validate. Pins that a future tightening that broadens
7766        // the `.lisp` gate to either axis surfaces as a test failure
7767        // rather than as a silent breaking change to existing valid
7768        // manifests.
7769        let c = caixa_with_code_paths(
7770            vec![],
7771            vec!["exe/demo", "exe/tool"],
7772            vec!["servicos/demo.computeunit.yaml"],
7773        );
7774        c.validate_code_paths().unwrap();
7775    }
7776
7777    #[test]
7778    fn validate_code_paths_sandbox_shape_arms_precede_non_lisp_extension() {
7779        // Cross-arm precedence pin: a `:bibliotecas` entry that is *both*
7780        // sandbox-escaping and non-`.lisp` surfaces the more fundamental
7781        // sandbox-shape diagnostic first (the `.lisp` remediation would
7782        // be misleading when the offending path can never resolve under
7783        // the caixa root anyway). Mirrors the peer
7784        // `EmptyPath` → `AbsolutePath` → `ParentEscape` → `NonLispExtension`
7785        // ordering on `:behavior :on-*` (c97815a) and `EmptyScript` →
7786        // `AbsoluteScript` → `ParentEscapeScript` → `NonLispExtensionScript`
7787        // on `:upgrade-from :state-change :script` (33cc830).
7788        //
7789        // Empty wins (the strictly-smaller-scope structural arm).
7790        let c = caixa_with_code_paths(vec![""], vec![], vec![]);
7791        assert!(
7792            matches!(
7793                c.validate_code_paths().unwrap_err(),
7794                ManifestError::CodePathEmpty {
7795                    slot: ":bibliotecas"
7796                }
7797            ),
7798            "empty must win over non-lisp-extension",
7799        );
7800        // Absolute wins (the path can't resolve under the caixa root).
7801        let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
7802        let err = c.validate_code_paths().unwrap_err();
7803        let ManifestError::CodePathAbsolute { slot, .. } = err else {
7804            panic!("absolute must win over non-lisp-extension, got {err:?}");
7805        };
7806        assert_eq!(slot, ":bibliotecas");
7807        // ParentEscape wins (the path escapes the caixa root).
7808        let c = caixa_with_code_paths(vec!["../sibling/x.txt"], vec![], vec![]);
7809        let err = c.validate_code_paths().unwrap_err();
7810        let ManifestError::CodePathParentEscape { slot, .. } = err else {
7811            panic!("parent-escape must win over non-lisp-extension, got {err:?}");
7812        };
7813        assert_eq!(slot, ":bibliotecas");
7814    }
7815
7816    #[test]
7817    fn validate_code_paths_non_lisp_extension_precedes_duplicate() {
7818        // Within-slot precedence pin: the per-entry file-type shape gate
7819        // fires before the cross-entry duplicate gate, so the narrower
7820        // structural defect dominates the uniqueness diagnostic. A
7821        // `("lib/x.txt" "lib/x.txt")` shape surfaces
7822        // `CodePathNonLispExtension` on the first entry rather than
7823        // `CodePathDuplicate` on the pair — same posture every per-entry
7824        // shape-gate-precedes-duplicate cascade follows on this surface
7825        // (the empty / absolute / parent-escape arms already precede the
7826        // duplicate arm; the lifted file-type arm joins that set).
7827        let c = caixa_with_code_paths(vec!["lib/x.txt", "lib/x.txt"], vec![], vec![]);
7828        let err = c.validate_code_paths().unwrap_err();
7829        let ManifestError::CodePathNonLispExtension { slot, path } = err else {
7830            panic!("expected CodePathNonLispExtension, got {err:?}");
7831        };
7832        assert_eq!(slot, ":bibliotecas");
7833        assert_eq!(path, PathBuf::from("lib/x.txt"));
7834    }
7835
7836    #[test]
7837    fn validate_code_paths_non_lisp_extension_diagnostic_carries_offending_slot_and_path() {
7838        // Diagnostic-shape pin (peer with
7839        // `validate_code_paths_diagnostic_carries_offending_slot_and_path`
7840        // on the sandbox-shape arms and
7841        // `validate_code_paths_duplicate_diagnostic_carries_offending_slot_and_path`
7842        // on the duplicate arm): the file-type-arm Display surfaces both
7843        // the offending `:slot` tag, the offending path verbatim, and the
7844        // expected `.lisp` extension named in the remediation text, so a
7845        // `feira lint` run can render the diagnostic without re-parsing.
7846        let c = caixa_with_code_paths(vec!["lib/demo.rs"], vec![], vec![]);
7847        let rendered = c.validate_code_paths().unwrap_err().to_string();
7848        assert!(
7849            rendered.contains(":bibliotecas"),
7850            "diagnostic must name the offending slot: {rendered}",
7851        );
7852        assert!(
7853            rendered.contains("lib/demo.rs"),
7854            "diagnostic must quote the offending path: {rendered}",
7855        );
7856        assert!(
7857            rendered.contains(".lisp"),
7858            "diagnostic must name the expected extension: {rendered}",
7859        );
7860    }
7861
7862    // ── validate_code_paths — `.computeunit.yaml` compound-suffix gate on :servicos ──
7863    //
7864    // The lifted [`crate::render::is_computeunit_yaml_extension`] predicate
7865    // now gates `:servicos` entries on the ComputeUnit-CR YAML file-type
7866    // contract. The peer caixa-helm / caixa-flux renderers consume each
7867    // `:servicos` entry through `serde_yaml::from_str` as a typed
7868    // `ComputeUnit` CR — same downstream-consumer-shape lift as the peer
7869    // `:bibliotecas` `.lisp` gate (64772a9), here on the compound-suffix
7870    // axis `Path::extension` can't express on its own.
7871
7872    #[test]
7873    fn validate_code_paths_rejects_no_extension_servicos_entry() {
7874        // Canonical "I dragged the wrong file from the workspace tree"
7875        // footgun on the Servico axis. Without the gate the peer
7876        // caixa-helm / caixa-flux renderers hand the extensionless path
7877        // to `serde_yaml::from_str` and fail with a parser-shaped
7878        // diagnostic far from the source caixa.lisp, with no field
7879        // naming the offending `:servicos` entry.
7880        for relpath in ["servicos/demo", "demo", "servicos/sub/nested"] {
7881            let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
7882            let err = c.validate_code_paths().unwrap_err();
7883            let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
7884                panic!(
7885                    "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
7886                     got {err:?}"
7887                );
7888            };
7889            assert_eq!(slot, ":servicos");
7890            assert_eq!(path, PathBuf::from(relpath));
7891        }
7892    }
7893
7894    #[test]
7895    fn validate_code_paths_rejects_wrong_extension_servicos_entry() {
7896        // Wrong-extension sweep across common authoring footguns on the
7897        // Servico axis. Bare `.yaml` is the canonical "I forgot the
7898        // `.computeunit` segment" typo; the off-by-one-segment shapes
7899        // (`.computeunit-yaml` / `.computeunit_yaml`) silently pass the
7900        // bare `Path::extension` view but mismatch the typed compound
7901        // suffix the renderers' `serde_yaml::from_str` consumer demands.
7902        // Same sweep-posture as the peer
7903        // `validate_code_paths_rejects_wrong_extension_bibliotecas_entry`
7904        // (64772a9) on the sibling tatara-lisp-source axis.
7905        for relpath in [
7906            "servicos/demo.yaml",
7907            "servicos/demo.yml",
7908            "servicos/demo.json",
7909            "servicos/demo.toml",
7910            "servicos/demo.txt",
7911            "servicos/demo.computeunit.yaml.bak",
7912            "servicos/demo.computeunit.yam",
7913            "servicos/demo.computeunit",
7914            "servicos/demo-computeunit.yaml",
7915            "servicos/demo_computeunit.yaml",
7916        ] {
7917            let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
7918            let err = c.validate_code_paths().unwrap_err();
7919            let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
7920                panic!(
7921                    "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
7922                     got {err:?}"
7923                );
7924            };
7925            assert_eq!(slot, ":servicos");
7926            assert_eq!(path, PathBuf::from(relpath));
7927        }
7928    }
7929
7930    #[test]
7931    fn validate_code_paths_rejects_case_folded_extension_servicos_entry() {
7932        // Case-sensitivity sweep — pins the strict lowercase
7933        // `.computeunit.yaml` contract. A case-folded shape that the
7934        // layout's existence check would (case-insensitively, on
7935        // case-insensitive volumes) match the on-disk file still
7936        // mismatches the canonical form the codec emits, breaking the
7937        // THEORY.md §V.2.7 render-determinism contract. Mirrors the peer
7938        // `validate_code_paths_rejects_case_folded_extension_bibliotecas_entry`
7939        // (64772a9) sweep on the sibling tatara-lisp-source axis.
7940        for relpath in [
7941            "servicos/demo.ComputeUnit.yaml",
7942            "servicos/demo.COMPUTEUNIT.yaml",
7943            "servicos/demo.computeunit.YAML",
7944            "servicos/demo.computeunit.Yaml",
7945            "servicos/demo.COMPUTEUNIT.YAML",
7946        ] {
7947            let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
7948            let err = c.validate_code_paths().unwrap_err();
7949            let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
7950                panic!(
7951                    "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
7952                     got {err:?}"
7953                );
7954            };
7955            assert_eq!(slot, ":servicos");
7956            assert_eq!(path, PathBuf::from(relpath));
7957        }
7958    }
7959
7960    #[test]
7961    fn validate_code_paths_rejects_empty_stem_servicos_entry() {
7962        // Degenerate hidden-file shape: a file name exactly equal to the
7963        // suffix (`.computeunit.yaml` — no stem preceding the suffix) is
7964        // the structural "Servico declared with no identity" footgun.
7965        // The substrate identifies each ComputeUnit by the file-stem
7966        // segment that precedes `.computeunit.yaml` (the rendered
7967        // `lareira-<stem>` Helm chart, the per-Servico `metadata.name`,
7968        // the M3 `:contratos` membership lookup), so an empty stem
7969        // leaves the Servico unidentifiable. Pinned at the typed-axis
7970        // level so a future regression that drops the `name.len() >
7971        // SUFFIX.len()` bound at the predicate surfaces here, not
7972        // piecemeal as a `lareira-` chart-name collision at render time.
7973        for relpath in ["servicos/.computeunit.yaml"] {
7974            let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
7975            let err = c.validate_code_paths().unwrap_err();
7976            let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
7977                panic!(
7978                    "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
7979                     got {err:?}"
7980                );
7981            };
7982            assert_eq!(slot, ":servicos");
7983            assert_eq!(path, PathBuf::from(relpath));
7984        }
7985    }
7986
7987    #[test]
7988    fn validate_code_paths_accepts_canonical_computeunit_yaml_shapes() {
7989        // Positive-control sweep through every canonical authoring shape
7990        // every in-tree fixture and the `Caixa::template` scaffold use.
7991        // Mirrors the peer
7992        // `validate_code_paths_accepts_canonical_lisp_shapes` (64772a9)
7993        // and the lifted predicate's own
7994        // `computeunit_yaml_extension_accepts_canonical_shapes` sweep in
7995        // render.rs.
7996        for relpath in [
7997            "servicos/demo.computeunit.yaml",
7998            "servicos/hello-rio.computeunit.yaml",
7999            "servicos/my-service.computeunit.yaml",
8000            "servicos/a.computeunit.yaml",
8001            "./servicos/demo.computeunit.yaml",
8002            "servicos/./demo.computeunit.yaml",
8003            "servicos/sub/nested.computeunit.yaml",
8004            "servicos/v0.1.computeunit.yaml",
8005        ] {
8006            let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
8007            c.validate_code_paths()
8008                .unwrap_or_else(|e| panic!("canonical shape {relpath:?} must pass, got {e:?}"));
8009        }
8010    }
8011
8012    #[test]
8013    fn validate_code_paths_non_computeunit_yaml_extension_does_not_fire_on_bibliotecas_or_exe() {
8014        // The file-type gate is per-slot — only `:servicos` carries the
8015        // ComputeUnit-CR YAML contract. A canonical `.lisp` `:bibliotecas`
8016        // entry and an extensionless `:exe` entry are the canonical
8017        // shapes every in-tree fixture uses, and must continue to pass
8018        // validate. Peer of
8019        // `validate_code_paths_non_lisp_extension_does_not_fire_on_exe_or_servicos`
8020        // (64772a9) — together pin that the typed
8021        // [`CodePathFileType`] dispatch is exhaustively per-slot, with no
8022        // cross-axis leakage in either direction.
8023        let c = caixa_with_code_paths(
8024            vec!["lib/demo.lisp"],
8025            vec!["exe/demo", "exe/tool"],
8026            vec!["servicos/demo.computeunit.yaml"],
8027        );
8028        c.validate_code_paths().unwrap();
8029    }
8030
8031    #[test]
8032    fn validate_code_paths_sandbox_shape_arms_precede_non_computeunit_yaml_extension() {
8033        // Cross-arm precedence pin: a `:servicos` entry that is *both*
8034        // sandbox-escaping and wrong-extension surfaces the more
8035        // fundamental sandbox-shape diagnostic first (the
8036        // `.computeunit.yaml` remediation would be misleading when the
8037        // offending path can never resolve under the caixa root
8038        // anyway). Mirrors the peer
8039        // `validate_code_paths_sandbox_shape_arms_precede_non_lisp_extension`
8040        // (64772a9) ordering on the sibling `:bibliotecas` axis and the
8041        // peer `EmptyPath` → `AbsolutePath` → `ParentEscape` →
8042        // `NonComputeUnitYamlExtension` arm-ordering the dispatch
8043        // table establishes.
8044        //
8045        // Empty wins (the strictly-smaller-scope structural arm).
8046        let c = caixa_with_code_paths(vec![], vec![], vec![""]);
8047        assert!(
8048            matches!(
8049                c.validate_code_paths().unwrap_err(),
8050                ManifestError::CodePathEmpty { slot: ":servicos" }
8051            ),
8052            "empty must win over non-computeunit-yaml-extension",
8053        );
8054        // Absolute wins (the path can't resolve under the caixa root).
8055        let c = caixa_with_code_paths(vec![], vec![], vec!["/etc/foo.yaml"]);
8056        let err = c.validate_code_paths().unwrap_err();
8057        let ManifestError::CodePathAbsolute { slot, .. } = err else {
8058            panic!("absolute must win over non-computeunit-yaml-extension, got {err:?}");
8059        };
8060        assert_eq!(slot, ":servicos");
8061        // ParentEscape wins (the path escapes the caixa root).
8062        let c = caixa_with_code_paths(vec![], vec![], vec!["../sibling/x.yaml"]);
8063        let err = c.validate_code_paths().unwrap_err();
8064        let ManifestError::CodePathParentEscape { slot, .. } = err else {
8065            panic!("parent-escape must win over non-computeunit-yaml-extension, got {err:?}");
8066        };
8067        assert_eq!(slot, ":servicos");
8068    }
8069
8070    #[test]
8071    fn validate_code_paths_non_computeunit_yaml_extension_precedes_duplicate() {
8072        // Within-slot precedence pin: the per-entry file-type shape gate
8073        // fires before the cross-entry duplicate gate, so the narrower
8074        // structural defect dominates the uniqueness diagnostic. A
8075        // `("servicos/x.yaml" "servicos/x.yaml")` shape surfaces
8076        // `CodePathNonComputeUnitYamlExtension` on the first entry
8077        // rather than `CodePathDuplicate` on the pair — same posture
8078        // every per-entry shape-gate-precedes-duplicate cascade follows
8079        // on this surface, peer of the 64772a9 `:bibliotecas`
8080        // `("lib/x.txt" "lib/x.txt")` ordering.
8081        let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/x.yaml", "servicos/x.yaml"]);
8082        let err = c.validate_code_paths().unwrap_err();
8083        let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
8084            panic!("expected CodePathNonComputeUnitYamlExtension, got {err:?}");
8085        };
8086        assert_eq!(slot, ":servicos");
8087        assert_eq!(path, PathBuf::from("servicos/x.yaml"));
8088    }
8089
8090    #[test]
8091    fn validate_code_paths_non_computeunit_yaml_extension_diagnostic_carries_offending_slot_and_path()
8092     {
8093        // Diagnostic-shape pin (peer with
8094        // `validate_code_paths_non_lisp_extension_diagnostic_carries_offending_slot_and_path`
8095        // on the sibling tatara-lisp-source axis): the file-type-arm
8096        // Display surfaces both the offending `:slot` tag, the
8097        // offending path verbatim, and the expected
8098        // `.computeunit.yaml` compound suffix named in the remediation
8099        // text, so a `feira lint` run can render the diagnostic without
8100        // re-parsing.
8101        let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/demo.yaml"]);
8102        let rendered = c.validate_code_paths().unwrap_err().to_string();
8103        assert!(
8104            rendered.contains(":servicos"),
8105            "diagnostic must name the offending slot: {rendered}",
8106        );
8107        assert!(
8108            rendered.contains("servicos/demo.yaml"),
8109            "diagnostic must quote the offending path: {rendered}",
8110        );
8111        assert!(
8112            rendered.contains(".computeunit.yaml"),
8113            "diagnostic must name the expected compound suffix: {rendered}",
8114        );
8115    }
8116
8117    // ── validate_etiquetas — universal-axis registry-search-tag shape ──
8118
8119    fn caixa_with_etiquetas(etiquetas: Vec<&str>) -> Caixa {
8120        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8121        c.etiquetas = etiquetas.into_iter().map(String::from).collect();
8122        c
8123    }
8124
8125    #[test]
8126    fn validate_etiquetas_accepts_empty_list() {
8127        // The empty-list identity: every caixa with no declared tags
8128        // trivially passes — `Caixa::template` emits `:etiquetas ()`,
8129        // so the gate is non-disruptive against every existing manifest.
8130        let c = caixa_with_etiquetas(vec![]);
8131        c.validate_etiquetas().unwrap();
8132    }
8133
8134    #[test]
8135    fn validate_etiquetas_accepts_canonical_forms() {
8136        // Positive control sweep: a canonical-shaped non-empty distinct
8137        // tag list passes, mirroring the example checkout-aplicacao
8138        // (`:etiquetas ("example" "aplicacao" "mesh" "ecommerce" "demo")`)
8139        // and the hello-rio fixture (`("hello-world" "wasm" "rust")`).
8140        let c = caixa_with_etiquetas(vec!["example", "aplicacao", "mesh", "ecommerce", "demo"]);
8141        c.validate_etiquetas().unwrap();
8142    }
8143
8144    #[test]
8145    fn validate_etiquetas_rejects_empty_entry() {
8146        // Canonical paste-from-blank-doc footgun. Without the gate the
8147        // empty entry rendered as `keywords: [""]` in `Chart.yaml`, a
8148        // no-op tag indexing nothing in the future caixa-registry.
8149        let c = caixa_with_etiquetas(vec![""]);
8150        let err = c.validate_etiquetas().unwrap_err();
8151        assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
8152    }
8153
8154    #[test]
8155    fn validate_etiquetas_rejects_duplicate_entry() {
8156        // Canonical copy-paste-the-wrong-tag footgun. Without the gate
8157        // the duplicate was silently dedup'd by caixa-helm's BTreeSet
8158        // collect at chart render — a "second wins / one silently
8159        // disappears" shape divergent from every peer typed-graph set
8160        // gate. The duplicate-arm names the offending tag verbatim.
8161        let c = caixa_with_etiquetas(vec!["demo", "demo"]);
8162        let err = c.validate_etiquetas().unwrap_err();
8163        let ManifestError::EtiquetaDuplicate { etiqueta } = err else {
8164            panic!("expected EtiquetaDuplicate, got {err:?}");
8165        };
8166        assert_eq!(etiqueta, "demo");
8167    }
8168
8169    #[test]
8170    fn validate_etiquetas_empty_takes_precedence_over_duplicate() {
8171        // Empty-first cascade pin: `("" "demo" "demo")` surfaces
8172        // `EtiquetaEmpty` not `EtiquetaDuplicate` — the narrower
8173        // structural "this entry has no value" defect dominates the
8174        // cross-entry uniqueness diagnostic. Mirrors the peer
8175        // empty-before-duplicate cascades on `:caracteristicas`
8176        // (`CaracteristicaEmpty` before `CaracteristicaDuplicate`,
8177        // fc3b4d5) and `:membros :caixa` (`MembroCaixaEmpty` before
8178        // `MembroDuplicate`).
8179        let c = caixa_with_etiquetas(vec!["", "demo", "demo"]);
8180        let err = c.validate_etiquetas().unwrap_err();
8181        assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
8182    }
8183
8184    #[test]
8185    fn validate_etiquetas_duplicate_reports_first_collision() {
8186        // First-collision pin: `("a" "b" "a" "b")` surfaces the `"a"`
8187        // duplicate (the lexicographically-earliest offending position
8188        // — the second `"a"` at index 2 collides with the first `"a"`
8189        // at index 0), not the later `"b"` collision at index 3,
8190        // peer with every other first-collision diagnostic posture on
8191        // this surface (`validate_load_singularity_reports_first_collision`,
8192        // `validate_cleanup_singularity_reports_first_collision`).
8193        let c = caixa_with_etiquetas(vec!["a", "b", "a", "b"]);
8194        let err = c.validate_etiquetas().unwrap_err();
8195        let ManifestError::EtiquetaDuplicate { etiqueta } = err else {
8196            panic!("expected EtiquetaDuplicate, got {err:?}");
8197        };
8198        assert_eq!(etiqueta, "a");
8199    }
8200
8201    #[test]
8202    fn validate_etiquetas_case_sensitive() {
8203        // Case-sensitivity pin: `("Foo" "foo")` is two distinct entries,
8204        // mirroring the peer `:membros :caixa` / `:children :caixa`
8205        // exact-string-match discipline. The shape gate this routine
8206        // landed (`is_chart_keyword_shape`, Cargo crates.io keyword
8207        // grammar) accepts mixed case — crates.io's keyword rule is
8208        // "case-insensitive" at the index layer but admits mixed case
8209        // at the entry layer (the canonical Helm chart `keywords:`
8210        // shape is lowercase by convention, but the grammar admits
8211        // uppercase). Case-sensitivity at the duplicate-set layer
8212        // remains structural — two distinct strings are two distinct
8213        // entries.
8214        let c = caixa_with_etiquetas(vec!["Foo", "foo"]);
8215        c.validate_etiquetas().unwrap();
8216    }
8217
8218    #[test]
8219    fn validate_etiquetas_diagnostic_carries_offending_tag() {
8220        // Diagnostic-shape pin (peer with
8221        // `validate_code_paths_diagnostic_carries_offending_slot_and_path`):
8222        // the error's Display surfaces the offending tag verbatim, so a
8223        // `feira lint` run can render the diagnostic without re-parsing
8224        // and the author can grep their caixa.lisp for the offending
8225        // value.
8226        let c = caixa_with_etiquetas(vec!["demo", "demo"]);
8227        let rendered = c.validate_etiquetas().unwrap_err().to_string();
8228        assert!(
8229            rendered.contains(":etiquetas"),
8230            "diagnostic must name the offending slot: {rendered}",
8231        );
8232        assert!(
8233            rendered.contains("demo"),
8234            "diagnostic must quote the offending tag: {rendered}",
8235        );
8236    }
8237
8238    #[test]
8239    fn validate_etiquetas_rejects_leading_whitespace_entry() {
8240        // Canonical paste-from-aligned-doc footgun. Without the shape
8241        // gate `" mesh"` silently passed validate and landed as a
8242        // YAML plain-style scalar with leading whitespace in the
8243        // rendered Chart.yaml `keywords:` array — every YAML 1.2
8244        // dumper trims leading whitespace from plain-style scalars,
8245        // so the authored space round-tripped inconsistently back
8246        // through `caixa.lisp`. Mirrors the peer
8247        // `validate_autores_rejects_leading_whitespace_entry`.
8248        let c = caixa_with_etiquetas(vec![" mesh"]);
8249        let err = c.validate_etiquetas().unwrap_err();
8250        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8251            panic!("expected EtiquetaInvalid, got {err:?}");
8252        };
8253        assert_eq!(etiqueta, " mesh");
8254        assert!(reason.contains("whitespace"), "got: {reason}");
8255    }
8256
8257    #[test]
8258    fn validate_etiquetas_rejects_embedded_newline_entry() {
8259        // Canonical paste-from-multiline-doc footgun — the author
8260        // pasted a multi-tag block into one `:etiquetas` entry
8261        // instead of splitting into one entry per tag. Without the
8262        // shape gate `"mesh\nhttp"` silently passed validate and
8263        // landed as a YAML-illegal multi-line scalar in the rendered
8264        // Chart.yaml `keywords:` array.
8265        let c = caixa_with_etiquetas(vec!["mesh\nhttp"]);
8266        let err = c.validate_etiquetas().unwrap_err();
8267        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8268            panic!("expected EtiquetaInvalid, got {err:?}");
8269        };
8270        assert_eq!(etiqueta, "mesh\nhttp");
8271        assert!(reason.contains("newline"), "got: {reason}");
8272    }
8273
8274    #[test]
8275    fn validate_etiquetas_rejects_embedded_comma_entry() {
8276        // Canonical CSV-list-separator-confusion footgun: the author
8277        // confused the CSV-style separator convention with the
8278        // `:etiquetas` list grammar. Without the shape gate
8279        // `"mesh,http,grpc"` silently passed validate and landed as a
8280        // single malformed search tag in the rendered Chart.yaml
8281        // `keywords:` array — Artifact Hub's keyword index would
8282        // either silently drop the tag or index it as
8283        // `mesh,http,grpc` instead of three separate tags.
8284        let c = caixa_with_etiquetas(vec!["mesh,http,grpc"]);
8285        let err = c.validate_etiquetas().unwrap_err();
8286        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8287            panic!("expected EtiquetaInvalid, got {err:?}");
8288        };
8289        assert_eq!(etiqueta, "mesh,http,grpc");
8290        assert!(reason.contains('`'), "got: {reason}");
8291        assert!(reason.contains(','), "got: {reason}");
8292    }
8293
8294    #[test]
8295    fn validate_etiquetas_rejects_embedded_slash_entry() {
8296        // Canonical path-separator-confusion footgun: the author
8297        // confused namespace-path notation with the keyword grammar.
8298        let c = caixa_with_etiquetas(vec!["caixa/servico"]);
8299        let err = c.validate_etiquetas().unwrap_err();
8300        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8301            panic!("expected EtiquetaInvalid, got {err:?}");
8302        };
8303        assert_eq!(etiqueta, "caixa/servico");
8304        assert!(reason.contains('/'), "got: {reason}");
8305    }
8306
8307    #[test]
8308    fn validate_etiquetas_rejects_leading_digit_entry() {
8309        // Canonical paste-from-numbered-list footgun: the author
8310        // copied `1. mesh` from a numbered doc and the `1` leaked
8311        // into the tag.
8312        let c = caixa_with_etiquetas(vec!["1mesh"]);
8313        let err = c.validate_etiquetas().unwrap_err();
8314        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8315            panic!("expected EtiquetaInvalid, got {err:?}");
8316        };
8317        assert_eq!(etiqueta, "1mesh");
8318        assert!(reason.contains("digit"), "got: {reason}");
8319    }
8320
8321    #[test]
8322    fn validate_etiquetas_rejects_leading_hyphen_entry() {
8323        // Canonical kebab-leak footgun.
8324        let c = caixa_with_etiquetas(vec!["-foo"]);
8325        let err = c.validate_etiquetas().unwrap_err();
8326        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8327            panic!("expected EtiquetaInvalid, got {err:?}");
8328        };
8329        assert_eq!(etiqueta, "-foo");
8330        assert!(reason.contains('-'), "got: {reason}");
8331    }
8332
8333    #[test]
8334    fn validate_etiquetas_rejects_non_ascii_entry() {
8335        // Canonical paste-from-Unicode-doc footgun. Every legitimate
8336        // search tag is strict ASCII; raw non-ASCII silently
8337        // round-trips inconsistently across NFC/NFD normalization on
8338        // APFS / case-folding filesystems and breaks the Artifact Hub
8339        // keyword search index lookup.
8340        let c = caixa_with_etiquetas(vec!["café"]);
8341        let err = c.validate_etiquetas().unwrap_err();
8342        let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
8343            panic!("expected EtiquetaInvalid, got {err:?}");
8344        };
8345        assert_eq!(etiqueta, "café");
8346        assert!(reason.contains("non-ASCII"), "got: {reason}");
8347    }
8348
8349    #[test]
8350    fn validate_etiquetas_rejects_period_entry() {
8351        // Canonical namespace-confusion / version-suffix footgun
8352        // (`"http.1"` / `"v1.0"`): Cargo's crates.io keyword grammar
8353        // excludes `.` from the continuation set even though the
8354        // sibling `:caracteristicas` axis (Cargo's feature-name
8355        // grammar) admits it. Tighter than the sibling axis, peer
8356        // with Cargo's own crates.io keyword shape.
8357        let c = caixa_with_etiquetas(vec!["http.1"]);
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, "http.1");
8363        assert!(reason.contains('.'), "got: {reason}");
8364    }
8365
8366    #[test]
8367    fn validate_etiquetas_empty_takes_precedence_over_shape() {
8368        // Per-entry empty-first cascade pin: an entry that is both
8369        // empty *and* shape-invalid surfaces `EtiquetaEmpty` (the
8370        // narrower "this entry has no value" structural defect
8371        // dominates the broader shape-predicate diagnostic). The
8372        // empty arm fires before the shape predicate is consulted,
8373        // mirroring the peer `validate_autores_empty_takes_precedence_over_shape`
8374        // cascade established on the sibling universal-axis Vec<String>
8375        // surface.
8376        let c = caixa_with_etiquetas(vec![""]);
8377        let err = c.validate_etiquetas().unwrap_err();
8378        assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
8379    }
8380
8381    #[test]
8382    fn validate_etiquetas_shape_takes_precedence_over_duplicate() {
8383        // Per-entry shape-before-cross-entry-duplicate cascade pin: an
8384        // entry that is malformed surfaces `EtiquetaInvalid` even when
8385        // a later entry would have collided on duplicate. The
8386        // per-entry shape arm fires inside the same loop iteration as
8387        // the empty arm, before the seen-set insert at end-of-iteration
8388        // — structural per-entry defects dominate the cross-entry
8389        // uniqueness diagnostic. Mirrors the peer
8390        // `validate_autores_shape_takes_precedence_over_duplicate`.
8391        let c = caixa_with_etiquetas(vec!["mesh\nhttp", "mesh\nhttp"]);
8392        let err = c.validate_etiquetas().unwrap_err();
8393        assert!(
8394            matches!(err, ManifestError::EtiquetaInvalid { .. }),
8395            "got {err:?}",
8396        );
8397    }
8398
8399    #[test]
8400    fn validate_etiquetas_invalid_diagnostic_names_offending_slot_and_value() {
8401        // Diagnostic-shape pin on the new shape arm (peer with
8402        // `validate_autores_invalid_diagnostic_names_offending_slot_and_value`):
8403        // the rendered Display surfaces both the offending slot name
8404        // and the offending value verbatim, so a `feira lint` run
8405        // points the author at the exact `:etiquetas` entry to fix.
8406        let c = caixa_with_etiquetas(vec!["mesh\nhttp"]);
8407        let rendered = c.validate_etiquetas().unwrap_err().to_string();
8408        assert!(
8409            rendered.contains(":etiquetas"),
8410            "diagnostic must name the offending slot: {rendered}",
8411        );
8412        assert!(
8413            rendered.contains("mesh\\nhttp"),
8414            "diagnostic must quote the offending value (debug-escaped): {rendered}",
8415        );
8416    }
8417
8418    #[test]
8419    fn validate_etiquetas_rejects_at_21_byte_boundary() {
8420        // The 20-byte cap pin — boundary-exceeding case rejected,
8421        // boundary-accepting case passes. Mirrors the peer
8422        // `chart_keyword_shape_rejects_at_21_byte_boundary` substrate-
8423        // side pin, surfaced at the per-axis caller so the cap
8424        // propagates through validate end-to-end. Constructed as a
8425        // single all-`a` token so only the cap arm fires.
8426        let max_ok = "a".repeat(20);
8427        let c = caixa_with_etiquetas(vec![max_ok.as_str()]);
8428        c.validate_etiquetas().unwrap();
8429        let too_long = "a".repeat(21);
8430        let c = caixa_with_etiquetas(vec![too_long.as_str()]);
8431        let err = c.validate_etiquetas().unwrap_err();
8432        let ManifestError::EtiquetaInvalid { reason, .. } = err else {
8433            panic!("expected EtiquetaInvalid, got {err:?}");
8434        };
8435        assert!(reason.contains("20"), "got: {reason}");
8436        assert!(reason.contains("21"), "got: {reason}");
8437    }
8438
8439    #[test]
8440    fn validate_etiquetas_accepts_canonical_shaped_forms() {
8441        // Positive control sweep: every canonical-shaped tag from the
8442        // hello-rio / checkout-aplicacao / pangea-tatara-akeyless
8443        // example fixtures plus the substrate-fixed tags caixa-helm
8444        // unions in at chart render. Drift between this list and the
8445        // substrate-side `chart_keyword_shape_accepts_canonical_forms`
8446        // sweep surfaces here — one source of truth for the rule.
8447        let c = caixa_with_etiquetas(vec![
8448            "example",
8449            "aplicacao",
8450            "mesh",
8451            "ecommerce",
8452            "demo",
8453            "infrastructure",
8454            "aws",
8455            "akeyless",
8456            "pangea-native",
8457            "hello-world",
8458            "wasm",
8459            "rust",
8460            "tatara-lisp",
8461            "caixa-servico",
8462            "lareira",
8463        ]);
8464        c.validate_etiquetas().unwrap();
8465    }
8466
8467    // ── validate_autores — universal-axis maintainer shape ────────────
8468
8469    fn caixa_with_autores(autores: Vec<&str>) -> Caixa {
8470        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8471        c.autores = autores.into_iter().map(String::from).collect();
8472        c
8473    }
8474
8475    #[test]
8476    fn validate_autores_accepts_empty_list() {
8477        // The empty-list identity: `Caixa::template` emits `:autores ()`,
8478        // so the gate is non-disruptive against every existing manifest.
8479        let c = caixa_with_autores(vec![]);
8480        c.validate_autores().unwrap();
8481    }
8482
8483    #[test]
8484    fn validate_autores_accepts_canonical_forms() {
8485        // Positive control sweep: every canonical-shaped non-empty
8486        // distinct maintainer list passes — the hello-rio / checkout-
8487        // aplicacao fixtures' `:autores ("pleme-io")` shape, plus the
8488        // multi-author shape downstream packaging surfaces emit.
8489        let c = caixa_with_autores(vec!["pleme-io"]);
8490        c.validate_autores().unwrap();
8491        let c = caixa_with_autores(vec!["alice <alice@example.com>", "bob <bob@example.com>"]);
8492        c.validate_autores().unwrap();
8493    }
8494
8495    #[test]
8496    fn validate_autores_rejects_empty_entry() {
8497        // Canonical paste-from-blank-doc footgun. Without the gate the
8498        // empty entry rendered as `maintainers: [{name: "", email: null}]`
8499        // in `Chart.yaml`, a no-op maintainer the substrate cannot route
8500        // to.
8501        let c = caixa_with_autores(vec![""]);
8502        let err = c.validate_autores().unwrap_err();
8503        assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
8504    }
8505
8506    #[test]
8507    fn validate_autores_rejects_duplicate_entry() {
8508        // Canonical copy-paste-the-wrong-author footgun. Unlike the
8509        // `:etiquetas` peer (caixa-helm's `BTreeSet` collect silently
8510        // dedups the rendered `keywords:` array), the `maintainers:`
8511        // rendering has *no* dedup — duplicates stack verbatim. The
8512        // duplicate-arm names the offending author verbatim.
8513        let c = caixa_with_autores(vec!["pleme-io", "pleme-io"]);
8514        let err = c.validate_autores().unwrap_err();
8515        let ManifestError::AutorDuplicate { autor } = err else {
8516            panic!("expected AutorDuplicate, got {err:?}");
8517        };
8518        assert_eq!(autor, "pleme-io");
8519    }
8520
8521    #[test]
8522    fn validate_autores_empty_takes_precedence_over_duplicate() {
8523        // Empty-first cascade pin: `("" "pleme-io" "pleme-io")` surfaces
8524        // `AutorEmpty` not `AutorDuplicate` — the narrower structural
8525        // "this entry has no value" defect dominates the cross-entry
8526        // uniqueness diagnostic. Mirrors the peer empty-before-duplicate
8527        // cascades on `:etiquetas` (`EtiquetaEmpty` before
8528        // `EtiquetaDuplicate`, 360a499), `:caracteristicas`
8529        // (`CaracteristicaEmpty` before `CaracteristicaDuplicate`,
8530        // fc3b4d5), and `:membros :caixa` (`MembroCaixaEmpty` before
8531        // `MembroDuplicate`).
8532        let c = caixa_with_autores(vec!["", "pleme-io", "pleme-io"]);
8533        let err = c.validate_autores().unwrap_err();
8534        assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
8535    }
8536
8537    #[test]
8538    fn validate_autores_duplicate_reports_first_collision() {
8539        // First-collision pin: `("a" "b" "a" "b")` surfaces the `"a"`
8540        // duplicate (the lexicographically-earliest offending position
8541        // — the second `"a"` at index 2 collides with the first `"a"`
8542        // at index 0), not the later `"b"` collision at index 3,
8543        // peer with every other first-collision diagnostic posture on
8544        // this surface.
8545        let c = caixa_with_autores(vec!["a", "b", "a", "b"]);
8546        let err = c.validate_autores().unwrap_err();
8547        let ManifestError::AutorDuplicate { autor } = err else {
8548            panic!("expected AutorDuplicate, got {err:?}");
8549        };
8550        assert_eq!(autor, "a");
8551    }
8552
8553    #[test]
8554    fn validate_autores_case_sensitive() {
8555        // Case-sensitivity pin: `("Pleme-io" "pleme-io")` is two distinct
8556        // entries, mirroring the peer `:etiquetas` / `:membros :caixa`
8557        // / `:children :caixa` exact-string-match discipline.
8558        let c = caixa_with_autores(vec!["Pleme-io", "pleme-io"]);
8559        c.validate_autores().unwrap();
8560    }
8561
8562    #[test]
8563    fn validate_autores_diagnostic_carries_offending_author() {
8564        // Diagnostic-shape pin (peer with
8565        // `validate_etiquetas_diagnostic_carries_offending_tag`): the
8566        // error's Display surfaces the offending author verbatim, so a
8567        // `feira lint` run can render the diagnostic without re-parsing
8568        // and the author can grep their caixa.lisp for the offending
8569        // value.
8570        let c = caixa_with_autores(vec!["pleme-io", "pleme-io"]);
8571        let rendered = c.validate_autores().unwrap_err().to_string();
8572        assert!(
8573            rendered.contains(":autores"),
8574            "diagnostic must name the offending slot: {rendered}",
8575        );
8576        assert!(
8577            rendered.contains("pleme-io"),
8578            "diagnostic must quote the offending author: {rendered}",
8579        );
8580    }
8581
8582    #[test]
8583    fn validate_autores_rejects_leading_whitespace_entry() {
8584        // Canonical paste-from-aligned-doc footgun. Without the shape
8585        // gate `" pleme-io"` silently passed validate and landed as a
8586        // YAML plain-style scalar with leading whitespace in the
8587        // rendered Chart.yaml `maintainers:` array — every YAML 1.2
8588        // dumper trims leading whitespace from plain-style scalars, so
8589        // the authored space round-tripped inconsistently back through
8590        // `caixa.lisp`. Mirrors the peer
8591        // `validate_descricao_rejects_leading_whitespace`.
8592        let c = caixa_with_autores(vec![" pleme-io"]);
8593        let err = c.validate_autores().unwrap_err();
8594        let ManifestError::AutorInvalid { autor, reason } = err else {
8595            panic!("expected AutorInvalid, got {err:?}");
8596        };
8597        assert_eq!(autor, " pleme-io");
8598        assert!(reason.contains("whitespace"), "got: {reason}");
8599    }
8600
8601    #[test]
8602    fn validate_autores_rejects_trailing_whitespace_entry() {
8603        // Canonical paste-from-doc footgun.
8604        let c = caixa_with_autores(vec!["pleme-io "]);
8605        let err = c.validate_autores().unwrap_err();
8606        let ManifestError::AutorInvalid { autor, reason } = err else {
8607            panic!("expected AutorInvalid, got {err:?}");
8608        };
8609        assert_eq!(autor, "pleme-io ");
8610        assert!(reason.contains("whitespace"), "got: {reason}");
8611    }
8612
8613    #[test]
8614    fn validate_autores_rejects_embedded_newline_entry() {
8615        // Canonical paste-from-multiline-doc footgun — the author
8616        // pasted a multi-line block of author records into one
8617        // `:autores` entry instead of splitting into one entry per
8618        // author. Without the shape gate `"alice\nbob"` silently
8619        // passed validate and landed as a YAML-illegal multi-line
8620        // scalar in the rendered Chart.yaml `maintainers:` array.
8621        let c = caixa_with_autores(vec!["alice\nbob"]);
8622        let err = c.validate_autores().unwrap_err();
8623        let ManifestError::AutorInvalid { autor, reason } = err else {
8624            panic!("expected AutorInvalid, got {err:?}");
8625        };
8626        assert_eq!(autor, "alice\nbob");
8627        assert!(reason.contains("newline"), "got: {reason}");
8628    }
8629
8630    #[test]
8631    fn validate_autores_rejects_embedded_carriage_return_entry() {
8632        // Canonical paste-from-Windows-CRLF-doc footgun.
8633        let c = caixa_with_autores(vec!["alice\rbob"]);
8634        let err = c.validate_autores().unwrap_err();
8635        let ManifestError::AutorInvalid { autor, reason } = err else {
8636            panic!("expected AutorInvalid, got {err:?}");
8637        };
8638        assert_eq!(autor, "alice\rbob");
8639        assert!(reason.contains("carriage return"), "got: {reason}");
8640    }
8641
8642    #[test]
8643    fn validate_autores_rejects_embedded_tab_entry() {
8644        // Canonical tab-from-aligned-doc footgun.
8645        let c = caixa_with_autores(vec!["Pleme\tContributors"]);
8646        let err = c.validate_autores().unwrap_err();
8647        let ManifestError::AutorInvalid { autor, reason } = err else {
8648            panic!("expected AutorInvalid, got {err:?}");
8649        };
8650        assert_eq!(autor, "Pleme\tContributors");
8651        assert!(reason.contains("tab"), "got: {reason}");
8652    }
8653
8654    #[test]
8655    fn validate_autores_rejects_embedded_control_bytes_entry() {
8656        // Paste-from-binary-blob footguns: NUL, BEL, ESC, DEL all
8657        // surface the same control-byte arm.
8658        for entry in [
8659            "alice\x00bob",
8660            "alice\x07bob",
8661            "alice\x1bbob",
8662            "alice\x7fbob",
8663        ] {
8664            let c = caixa_with_autores(vec![entry]);
8665            let err = c.validate_autores().unwrap_err();
8666            let ManifestError::AutorInvalid { autor, reason } = err else {
8667                panic!("expected AutorInvalid for {entry:?}, got {err:?}");
8668            };
8669            assert_eq!(autor, entry);
8670            assert!(
8671                reason.contains("control character"),
8672                "{entry:?} reason: {reason}",
8673            );
8674        }
8675    }
8676
8677    #[test]
8678    fn validate_autores_accepts_unicode_entry() {
8679        // Unicode positive control: realistic maintainer names carry
8680        // Unicode (`François`, `日本語`, `naïve`). The predicate must
8681        // round-trip Unicode losslessly, peer with the
8682        // `chart_maintainer_name_shape_accepts_unicode` substrate-side
8683        // sweep.
8684        let c = caixa_with_autores(vec![
8685            "François Dupont",
8686            "日本語の名前",
8687            "naïve <naive@example.com>",
8688        ]);
8689        c.validate_autores().unwrap();
8690    }
8691
8692    #[test]
8693    fn validate_autores_empty_takes_precedence_over_shape() {
8694        // Per-entry empty-first cascade pin: an entry that is both
8695        // empty *and* shape-invalid surfaces `AutorEmpty` (the narrower
8696        // "this entry has no value" structural defect dominates the
8697        // broader shape-predicate diagnostic). The empty arm fires
8698        // before the shape predicate is consulted, mirroring the peer
8699        // `validate_repositorio_empty_takes_precedence_over_shape`
8700        // cascade on the universal `Option<String>` siblings — and now
8701        // established on the Vec<String> per-entry surface.
8702        let c = caixa_with_autores(vec![""]);
8703        let err = c.validate_autores().unwrap_err();
8704        assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
8705    }
8706
8707    #[test]
8708    fn validate_autores_shape_takes_precedence_over_duplicate() {
8709        // Per-entry shape-before-cross-entry-duplicate cascade pin: an
8710        // entry that is malformed surfaces `AutorInvalid` even when a
8711        // later entry would have collided on duplicate. The per-entry
8712        // shape arm fires inside the same loop iteration as the empty
8713        // arm, before the seen-set insert at end-of-iteration —
8714        // structural per-entry defects dominate the cross-entry
8715        // uniqueness diagnostic.
8716        let c = caixa_with_autores(vec!["alice\nbob", "alice\nbob"]);
8717        let err = c.validate_autores().unwrap_err();
8718        assert!(
8719            matches!(err, ManifestError::AutorInvalid { .. }),
8720            "got {err:?}",
8721        );
8722    }
8723
8724    #[test]
8725    fn validate_autores_invalid_diagnostic_names_offending_slot_and_value() {
8726        // Diagnostic-shape pin on the new shape arm (peer with
8727        // `validate_descricao_invalid_diagnostic_carries_offending_value`):
8728        // the rendered Display surfaces both the offending slot name
8729        // and the offending value verbatim, so a `feira lint` run
8730        // points the author at the exact `:autores` entry to fix.
8731        let c = caixa_with_autores(vec!["alice\nbob"]);
8732        let rendered = c.validate_autores().unwrap_err().to_string();
8733        assert!(
8734            rendered.contains(":autores"),
8735            "diagnostic must name the offending slot: {rendered}",
8736        );
8737        assert!(
8738            rendered.contains("alice\\nbob"),
8739            "diagnostic must quote the offending value (debug-escaped): {rendered}",
8740        );
8741    }
8742
8743    #[test]
8744    fn validate_autores_rejects_at_129_byte_boundary() {
8745        // The 128-byte cap pin — boundary-exceeding case rejected,
8746        // boundary-accepting case passes. Mirrors the peer
8747        // `chart_maintainer_name_shape_rejects_at_129_byte_boundary`
8748        // substrate-side pin, surfaced at the per-axis caller so the
8749        // cap propagates through validate end-to-end. Constructed as
8750        // a single all-`a` token so only the cap arm fires.
8751        let max_ok = "a".repeat(128);
8752        let c = caixa_with_autores(vec![max_ok.as_str()]);
8753        c.validate_autores().unwrap();
8754        let too_long = "a".repeat(129);
8755        let c = caixa_with_autores(vec![too_long.as_str()]);
8756        let err = c.validate_autores().unwrap_err();
8757        let ManifestError::AutorInvalid { reason, .. } = err else {
8758            panic!("expected AutorInvalid, got {err:?}");
8759        };
8760        assert!(reason.contains("128"), "got: {reason}");
8761        assert!(reason.contains("129"), "got: {reason}");
8762    }
8763
8764    // ── validate_repositorio — universal-axis git-repo-URL shape ──────
8765
8766    fn caixa_with_repositorio(repositorio: Option<&str>) -> Caixa {
8767        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8768        c.repositorio = repositorio.map(String::from);
8769        c
8770    }
8771
8772    #[test]
8773    fn validate_repositorio_accepts_none() {
8774        // The omit-the-slot identity: `:repositorio` is optional. The
8775        // gate is a no-op when the author didn't declare a value —
8776        // every caixa without a `:repositorio` line trivially passes,
8777        // and the substrate-side renderers fall back to their
8778        // documented placeholder (`caixa-helm`'s `home: None`,
8779        // `caixa-flux`'s `https://github.com/pleme-io/<nome>` derived
8780        // URL). Mirrors the peer `validate_restart_window_accepts_none`
8781        // posture on the other `Option<String>` Caixa slot.
8782        let c = caixa_with_repositorio(None);
8783        c.validate_repositorio().unwrap();
8784    }
8785
8786    #[test]
8787    fn validate_repositorio_accepts_canonical_forms() {
8788        // Positive control sweep across every documented `:repositorio`
8789        // authoring shape — the same union the shared
8790        // `crate::render::is_git_repo_url` predicate accepts and the
8791        // peer `:deps :fonte :repo` axis already routes through.
8792        // Covers the `github:` shorthand (the canonical pleme-io
8793        // convention used in the `:repositorio` field of every
8794        // manifest fixture across `caixa-helm` / `caixa-mesh` and the
8795        // `examples/`), the `https://…` URL the README quickstart uses,
8796        // the `ssh://`, `git://`, `git@host:path` scp-style SSH, and
8797        // `file://` URL schemes the shared predicate documents.
8798        for repo in [
8799            "github:pleme-io/hello-rio",
8800            "github:pleme-io/checkout",
8801            "https://github.com/pleme-io/hello-rio",
8802            "ssh://git@github.com/pleme-io/hello-rio.git",
8803            "git://github.com/pleme-io/hello-rio.git",
8804            "git@github.com:pleme-io/hello-rio.git",
8805            "file:///srv/pleme/hello-rio",
8806        ] {
8807            let c = caixa_with_repositorio(Some(repo));
8808            c.validate_repositorio()
8809                .unwrap_or_else(|err| panic!("canonical {repo:?} must pass: {err:?}"));
8810        }
8811    }
8812
8813    #[test]
8814    fn validate_repositorio_rejects_empty_some() {
8815        // Canonical paste-from-blank-doc footgun. The narrower
8816        // [`ManifestError::RepositorioEmpty`] arm fires before the
8817        // shape predicate is consulted, mirroring the empty-first
8818        // cascade every peer per-axis identity gate uses
8819        // (`NomeEmpty` → `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid`,
8820        // `FonteRepoEmpty` → `FonteRepoInvalid`). Without this gate
8821        // the empty `Some("")` silently passed the renderer's
8822        // `Option::unwrap_or_else(|| <fallback>)` (which only fires
8823        // on `None`) and landed as `home: ""` in `Chart.yaml` /
8824        // `url: ""` in the FluxCD `GitRepository`.
8825        let c = caixa_with_repositorio(Some(""));
8826        let err = c.validate_repositorio().unwrap_err();
8827        assert!(
8828            matches!(err, ManifestError::RepositorioEmpty),
8829            "got {err:?}",
8830        );
8831    }
8832
8833    #[test]
8834    fn validate_repositorio_rejects_whitespace() {
8835        // Paste-from-doc whitespace footgun. The shared
8836        // `is_git_repo_url` predicate refuses any whitespace byte; a
8837        // trailing space in a `:repositorio` value silently broke
8838        // `git clone '<value> '` at clone time. The diagnostic names
8839        // the offending value verbatim.
8840        let c = caixa_with_repositorio(Some("github:pleme-io/hello-rio "));
8841        let err = c.validate_repositorio().unwrap_err();
8842        let ManifestError::RepositorioInvalid { repositorio, .. } = err else {
8843            panic!("expected RepositorioInvalid, got {err:?}");
8844        };
8845        assert_eq!(repositorio, "github:pleme-io/hello-rio ");
8846    }
8847
8848    #[test]
8849    fn validate_repositorio_rejects_control_char() {
8850        // Paste-from-multiline-doc CRLF footgun — control characters
8851        // at the URL boundary are a class of subprocess-arg injection
8852        // and break git's URL parser at every porcelain entry point.
8853        let c = caixa_with_repositorio(Some("https://example.com/repo\n"));
8854        let err = c.validate_repositorio().unwrap_err();
8855        assert!(
8856            matches!(err, ManifestError::RepositorioInvalid { .. }),
8857            "got {err:?}",
8858        );
8859    }
8860
8861    #[test]
8862    fn validate_repositorio_rejects_leading_dash() {
8863        // Canonical CLI-argument-injection footgun: `git clone <repo>`
8864        // interprets a leading `-` as a CLI flag, so a
8865        // `-upload-pack=…` value escapes the subprocess argument
8866        // boundary. The shared predicate refuses every leading-`-`
8867        // shape at validate time.
8868        let c = caixa_with_repositorio(Some("-upload-pack=evil"));
8869        let err = c.validate_repositorio().unwrap_err();
8870        assert!(
8871            matches!(err, ManifestError::RepositorioInvalid { .. }),
8872            "got {err:?}",
8873        );
8874    }
8875
8876    #[test]
8877    fn validate_repositorio_rejects_missing_colon_separator() {
8878        // The bare `org/repo` ambiguity footgun — `git clone` reads
8879        // a no-`:` form as a relative filesystem path rather than the
8880        // GitHub-shorthand expansion the author probably intended.
8881        // The shared predicate refuses every shape without a `:`
8882        // separator.
8883        let c = caixa_with_repositorio(Some("pleme-io/hello-rio"));
8884        let err = c.validate_repositorio().unwrap_err();
8885        assert!(
8886            matches!(err, ManifestError::RepositorioInvalid { .. }),
8887            "got {err:?}",
8888        );
8889    }
8890
8891    #[test]
8892    fn validate_repositorio_rejects_fragment_anchor() {
8893        // Paste-from-browser-address-bar footgun on the
8894        // `:repositorio` axis — an author copies a GitHub permalink
8895        // to a README section / line-permalink and forgets to trim
8896        // the `#fragment` tail. The shared `is_git_repo_url`
8897        // predicate refuses the byte at the URL-grammar layer
8898        // (libcurl strips the fragment before opening the
8899        // transport, so the byte rides verbatim into the rendered
8900        // `Chart.yaml` `home:` and FluxCD `GitRepository` `url:`
8901        // fields but is silently dropped on the wire — two
8902        // manifest variants whose values differ only in their
8903        // fragment anchor lock to two distinct rendered artifacts
8904        // for the byte-identical clone, defeating the THEORY.md
8905        // §V.2 render-determinism contract on the `:repositorio`
8906        // axis the peer `:fonte :repo` axis already closes).
8907        let c = caixa_with_repositorio(Some("https://github.com/pleme-io/hello-rio#readme"));
8908        let err = c.validate_repositorio().unwrap_err();
8909        let ManifestError::RepositorioInvalid {
8910            repositorio,
8911            reason,
8912        } = err
8913        else {
8914            panic!("expected RepositorioInvalid, got {err:?}");
8915        };
8916        assert_eq!(repositorio, "https://github.com/pleme-io/hello-rio#readme");
8917        assert!(
8918            reason.contains("must not contain `#`"),
8919            "reason must surface the fragment-`#` arm, got {reason:?}"
8920        );
8921    }
8922
8923    #[test]
8924    fn validate_repositorio_rejects_query_string() {
8925        // Paste-from-browser-address-bar footgun on the
8926        // `:repositorio` axis (peer with the a68f818 fragment-`#`
8927        // arm on the same axis). An author copies a GitHub tab
8928        // deep-link out of the address bar and forgets to trim
8929        // the `?tab=…` query tail. The shared `is_git_repo_url`
8930        // predicate refuses the byte at the URL-grammar layer
8931        // (GitHub / GitLab / Bitbucket silently ignore the
8932        // `?query` tail and serve the same repo regardless, so
8933        // the byte rides verbatim into the rendered `Chart.yaml`
8934        // `home:` and FluxCD `GitRepository` `url:` fields but
8935        // is silently masked at the wire — two manifest variants
8936        // whose values differ only in their query tail lock to
8937        // two distinct rendered artifacts for the byte-identical
8938        // clone, defeating the THEORY.md §V.2 render-determinism
8939        // contract on the `:repositorio` axis the peer `:fonte
8940        // :repo` axis already closes).
8941        let c = caixa_with_repositorio(Some(
8942            "https://github.com/pleme-io/hello-rio?tab=readme-ov-file",
8943        ));
8944        let err = c.validate_repositorio().unwrap_err();
8945        let ManifestError::RepositorioInvalid {
8946            repositorio,
8947            reason,
8948        } = err
8949        else {
8950            panic!("expected RepositorioInvalid, got {err:?}");
8951        };
8952        assert_eq!(
8953            repositorio,
8954            "https://github.com/pleme-io/hello-rio?tab=readme-ov-file"
8955        );
8956        assert!(
8957            reason.contains("must not contain `?`"),
8958            "reason must surface the query-`?` arm, got {reason:?}"
8959        );
8960    }
8961
8962    #[test]
8963    fn validate_repositorio_rejects_embedded_backslash() {
8964        // Windows-file-path-confusion footgun on the `:repositorio`
8965        // axis (peer with the prior fragment-`#` / query-`?` arms on
8966        // the same axis, and peer with the new dep-level `:fonte :repo`
8967        // backslash arm on the URL-grammar trajectory). An author
8968        // pastes a Windows Explorer address-bar `file:///C:\Users\me\
8969        // hello-rio` into the `:repositorio` slot, expecting the
8970        // `lareira-<nome>` chart's `home:` field and the FluxCD
8971        // `GitRepository` `url:` field to render the canonical local
8972        // file-URI. The shared `is_git_repo_url` predicate refuses
8973        // the byte at the URL-grammar layer (libcurl silently
8974        // translates `\` → `/` on some platforms and refuses it on
8975        // others, so the byte rides verbatim into the rendered
8976        // artifacts but is silently rewritten or rejected at the wire
8977        // — two manifest variants whose values differ only in
8978        // backslash-vs-forward-slash lock to two distinct rendered
8979        // artifacts for the byte-identical clone, defeating the
8980        // THEORY.md §V.2 render-determinism contract on the
8981        // `:repositorio` axis the peer `:fonte :repo` axis already
8982        // closes).
8983        let c = caixa_with_repositorio(Some("file:///C:\\Users\\me\\hello-rio"));
8984        let err = c.validate_repositorio().unwrap_err();
8985        let ManifestError::RepositorioInvalid {
8986            repositorio,
8987            reason,
8988        } = err
8989        else {
8990            panic!("expected RepositorioInvalid, got {err:?}");
8991        };
8992        assert_eq!(repositorio, "file:///C:\\Users\\me\\hello-rio");
8993        assert!(
8994            reason.contains("must not contain `\\`"),
8995            "reason must surface the backslash-`\\` arm, got {reason:?}"
8996        );
8997    }
8998
8999    #[test]
9000    fn validate_repositorio_rejects_uri_template_placeholder() {
9001        // URI Template (RFC 6570) placeholder footgun on the
9002        // `:repositorio` axis (peer with the prior fragment-`#` /
9003        // query-`?` / backslash-`\` arms on the same axis, and peer
9004        // with the new dep-level `:fonte :repo` `{` / `}` arm on the
9005        // URL-grammar trajectory). An author pastes a quick-start
9006        // README snippet / OpenAPI `servers:` URL / Helm chart
9007        // `home:` template carrying unresolved `{org}` / `{repo}`
9008        // placeholders into the `:repositorio` slot, expecting the
9009        // substrate to resolve the placeholder downstream. The
9010        // shared `is_git_repo_url` predicate refuses the byte at the
9011        // URL-grammar layer (libcurl percent-encodes `{` / `}` to
9012        // `%7B` / `%7D` on the wire, so the byte round-trips
9013        // inconsistently between the rendered `Chart.yaml home:` /
9014        // FluxCD `GitRepository url:` and the resolver's `git clone`
9015        // invocation, defeating the THEORY.md §V.2 render-
9016        // determinism contract on the `:repositorio` axis the peer
9017        // `:fonte :repo` axis already closes; every git porcelain
9018        // entry-point additionally fetches a nonexistent literal-
9019        // `{placeholder}`-named path far from the source caixa.lisp).
9020        let c = caixa_with_repositorio(Some("https://github.com/{org}/hello-rio"));
9021        let err = c.validate_repositorio().unwrap_err();
9022        let ManifestError::RepositorioInvalid {
9023            repositorio,
9024            reason,
9025        } = err
9026        else {
9027            panic!("expected RepositorioInvalid, got {err:?}");
9028        };
9029        assert_eq!(repositorio, "https://github.com/{org}/hello-rio");
9030        assert!(
9031            reason.contains("must not contain `{`"),
9032            "reason must surface the open-brace `{{` arm, got {reason:?}"
9033        );
9034        assert!(
9035            reason.contains("URI Template") || reason.contains("RFC 6570"),
9036            "reason must name the RFC 6570 URI Template grammar, got {reason:?}"
9037        );
9038    }
9039
9040    #[test]
9041    fn validate_repositorio_empty_takes_precedence_over_shape() {
9042        // Empty-first cascade pin: the empty `Some("")` surfaces the
9043        // narrower `RepositorioEmpty` not the shape-predicate-wrapped
9044        // `RepositorioInvalid`, mirroring the peer
9045        // `NomeEmpty` → `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid`,
9046        // `FonteRepoEmpty` → `FonteRepoInvalid` cascades. The shared
9047        // `is_git_repo_url` predicate also rejects the empty input
9048        // (defensively, with its own `"must not be empty"` reason),
9049        // but the manifest-layer empty arm runs first to surface the
9050        // narrower diagnostic verbatim.
9051        let c = caixa_with_repositorio(Some(""));
9052        let err = c.validate_repositorio().unwrap_err();
9053        assert!(
9054            matches!(err, ManifestError::RepositorioEmpty),
9055            "got {err:?}",
9056        );
9057    }
9058
9059    #[test]
9060    fn validate_repositorio_diagnostic_carries_offending_value() {
9061        // Diagnostic-shape pin (peer with
9062        // `validate_autores_diagnostic_carries_offending_author`): the
9063        // error's Display surfaces the offending value + slot name
9064        // verbatim, so a `feira lint` run can render the diagnostic
9065        // without re-parsing and the author can grep their caixa.lisp
9066        // for the offending `:repositorio` value.
9067        let c = caixa_with_repositorio(Some("pleme-io/hello-rio"));
9068        let rendered = c.validate_repositorio().unwrap_err().to_string();
9069        assert!(
9070            rendered.contains(":repositorio"),
9071            "diagnostic must name the offending slot: {rendered}",
9072        );
9073        assert!(
9074            rendered.contains("pleme-io/hello-rio"),
9075            "diagnostic must quote the offending value: {rendered}",
9076        );
9077    }
9078
9079    // ── validate_descricao — universal-axis Chart.yaml description shape ──
9080
9081    fn caixa_with_descricao(descricao: Option<&str>) -> Caixa {
9082        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9083        c.descricao = descricao.map(String::from);
9084        c
9085    }
9086
9087    #[test]
9088    fn validate_descricao_accepts_none() {
9089        // The omit-the-slot identity: `:descricao` is optional. The
9090        // gate is a no-op when the author didn't declare a value —
9091        // every caixa without a `:descricao` line trivially passes,
9092        // and the substrate-side renderers fall back to their
9093        // documented `caixa.nome`-derived placeholder. Mirrors the
9094        // peer `validate_repositorio_accepts_none` posture on the
9095        // sibling `Option<String>` Caixa slot.
9096        let c = caixa_with_descricao(None);
9097        c.validate_descricao().unwrap();
9098    }
9099
9100    #[test]
9101    fn validate_descricao_accepts_canonical_summary() {
9102        // Positive control: the canonical pleme-io descricao shape —
9103        // a short free-form prose summary — passes the gate. Covers
9104        // the fixture shapes the `caixa-helm` / `caixa-flux` /
9105        // `caixa-mesh` test fixtures use (`"Canonical Rust→wasm32-
9106        // wasip2 caixa Servico."`, `"Checkout flow."`).
9107        for desc in [
9108            "Canonical Rust→wasm32-wasip2 caixa Servico.",
9109            "Checkout flow.",
9110            "AWS provider caixa for tatara-lisp",
9111            "FIXME — describe this caixa",
9112            "x",
9113        ] {
9114            let c = caixa_with_descricao(Some(desc));
9115            c.validate_descricao()
9116                .unwrap_or_else(|err| panic!("canonical {desc:?} must pass: {err:?}"));
9117        }
9118    }
9119
9120    #[test]
9121    fn validate_descricao_rejects_empty_some() {
9122        // Canonical paste-from-blank-doc footgun. Without this gate
9123        // the empty `Some("")` silently passed the renderer's
9124        // `Option::unwrap_or_else(|| <fallback>)` (which only fires
9125        // on `None`) and landed as `description: ""` in `Chart.yaml`
9126        // and a blank `README.md` header. Mirrors the peer
9127        // [`ManifestError::RepositorioEmpty`] empty-arm on the
9128        // sibling `Option<String>` Caixa slot.
9129        let c = caixa_with_descricao(Some(""));
9130        let err = c.validate_descricao().unwrap_err();
9131        assert!(matches!(err, ManifestError::DescricaoEmpty), "got {err:?}",);
9132    }
9133
9134    #[test]
9135    fn validate_descricao_rejects_leading_whitespace() {
9136        // Paste-from-aligned-doc footgun: a leading ASCII space the
9137        // bare empty-arm gate accepted, the shape predicate now
9138        // refuses. The diagnostic carries the offending value
9139        // verbatim (with the leading space preserved) so the author
9140        // can grep their caixa.lisp for the exact `:descricao` line
9141        // and fix the round-trip-inconsistent leading whitespace.
9142        // Mirrors the peer
9143        // `validate_licenca_rejects_leading_whitespace` arm on the
9144        // sibling `:licenca` axis.
9145        let c = caixa_with_descricao(Some(" Checkout flow."));
9146        let err = c.validate_descricao().unwrap_err();
9147        let ManifestError::DescricaoInvalid { descricao, reason } = err else {
9148            panic!("expected DescricaoInvalid, got {err:?}");
9149        };
9150        assert_eq!(descricao, " Checkout flow.");
9151        assert!(reason.contains("whitespace"), "got: {reason:?}");
9152    }
9153
9154    #[test]
9155    fn validate_descricao_rejects_trailing_whitespace() {
9156        // Paste-from-doc footgun: a trailing ASCII space the bare
9157        // empty-arm gate accepted, the shape predicate now refuses.
9158        let c = caixa_with_descricao(Some("Checkout flow. "));
9159        let err = c.validate_descricao().unwrap_err();
9160        let ManifestError::DescricaoInvalid { descricao, reason } = err else {
9161            panic!("expected DescricaoInvalid, got {err:?}");
9162        };
9163        assert_eq!(descricao, "Checkout flow. ");
9164        assert!(reason.contains("whitespace"), "got: {reason:?}");
9165    }
9166
9167    #[test]
9168    fn validate_descricao_rejects_embedded_newline() {
9169        // Paste-from-multiline-doc footgun: an embedded LF the bare
9170        // empty-arm gate accepted, the shape predicate now refuses.
9171        // Without this gate the embedded newline silently landed in
9172        // the rendered Chart.yaml as a multi-line YAML block scalar,
9173        // and every chart-aware UI (`helm list`, `helm search`,
9174        // Artifact Hub) renders the description in a single-line
9175        // column so the embedded newline is silently dropped at
9176        // every downstream consumer.
9177        let c = caixa_with_descricao(Some("Checkout\nflow."));
9178        let err = c.validate_descricao().unwrap_err();
9179        assert!(
9180            matches!(err, ManifestError::DescricaoInvalid { .. }),
9181            "got {err:?}",
9182        );
9183        assert!(err.to_string().contains("newline"), "got {err}");
9184    }
9185
9186    #[test]
9187    fn validate_descricao_rejects_embedded_carriage_return() {
9188        // Paste-from-Windows-CRLF-doc footgun.
9189        let c = caixa_with_descricao(Some("Checkout\rflow."));
9190        let err = c.validate_descricao().unwrap_err();
9191        assert!(
9192            matches!(err, ManifestError::DescricaoInvalid { .. }),
9193            "got {err:?}",
9194        );
9195        assert!(err.to_string().contains("carriage return"), "got {err}");
9196    }
9197
9198    #[test]
9199    fn validate_descricao_rejects_embedded_tab() {
9200        // Tab-from-aligned-doc footgun.
9201        let c = caixa_with_descricao(Some("Checkout\tflow."));
9202        let err = c.validate_descricao().unwrap_err();
9203        assert!(
9204            matches!(err, ManifestError::DescricaoInvalid { .. }),
9205            "got {err:?}",
9206        );
9207        assert!(err.to_string().contains("tab"), "got {err}");
9208    }
9209
9210    #[test]
9211    fn validate_descricao_rejects_embedded_control_bytes() {
9212        // Paste-from-binary-blob footgun: every other control byte
9213        // (NUL, BEL, ESC, DEL) is refused at validate time. Mirrors
9214        // the peer SPDX-expression control-byte arm.
9215        for s in [
9216            "Checkout\x00flow.",
9217            "Checkout\x07flow.",
9218            "Checkout\x1bflow.",
9219            "Checkout\x7fflow.",
9220        ] {
9221            let c = caixa_with_descricao(Some(s));
9222            let err = c.validate_descricao().unwrap_err();
9223            assert!(
9224                matches!(err, ManifestError::DescricaoInvalid { .. }),
9225                "{s:?} got {err:?}",
9226            );
9227            assert!(
9228                err.to_string().contains("control character"),
9229                "{s:?} got {err}",
9230            );
9231        }
9232    }
9233
9234    #[test]
9235    fn validate_descricao_accepts_unicode_prose() {
9236        // Positive control: Unicode prose is accepted — the
9237        // canonical fixtures carry `→` (U+2192) and `—` (U+2014),
9238        // and `Caixa::template`'s `"FIXME — describe this caixa"`
9239        // scaffold every `feira init` emits must continue to pass.
9240        for s in [
9241            "Canonical Rust→wasm32-wasip2 caixa Servico.",
9242            "FIXME — describe this caixa",
9243            "Caixa pour le projet tâche",
9244            "日本語の説明",
9245        ] {
9246            let c = caixa_with_descricao(Some(s));
9247            c.validate_descricao()
9248                .unwrap_or_else(|err| panic!("Unicode {s:?} must pass: {err:?}"));
9249        }
9250    }
9251
9252    #[test]
9253    fn validate_descricao_empty_takes_precedence_over_shape() {
9254        // Cascade pin: a `Some("")` surfaces the narrower
9255        // `DescricaoEmpty` arm, not the broader `DescricaoInvalid`
9256        // shape-predicate arm. Mirrors the peer
9257        // `validate_licenca_empty_takes_precedence_over_shape` pin
9258        // on the sibling `:licenca` axis.
9259        let c = caixa_with_descricao(Some(""));
9260        let err = c.validate_descricao().unwrap_err();
9261        assert!(matches!(err, ManifestError::DescricaoEmpty), "got {err:?}",);
9262    }
9263
9264    #[test]
9265    fn validate_descricao_invalid_diagnostic_carries_offending_value_and_slot() {
9266        // Diagnostic-shape pin: the error's Display surfaces both
9267        // the `:descricao` slot name and the offending value
9268        // verbatim, so a `feira lint` run can render the diagnostic
9269        // without re-parsing and the author can grep their caixa.lisp
9270        // for the offending `:descricao` line. Mirrors the peer
9271        // `validate_licenca_invalid_diagnostic_carries_offending_value_and_slot`
9272        // pin (ee2e888) on the sibling `:licenca` axis.
9273        // The `{descricao:?}` Debug format escapes embedded control
9274        // bytes; the quoted offending value surfaces as
9275        // `"Checkout\nflow."` (literal backslash-n) in the rendered
9276        // diagnostic. The author can grep their caixa.lisp for the
9277        // literal `Checkout` summary prefix.
9278        let c = caixa_with_descricao(Some("Checkout\nflow."));
9279        let rendered = c.validate_descricao().unwrap_err().to_string();
9280        assert!(
9281            rendered.contains(":descricao"),
9282            "diagnostic must name the offending slot: {rendered}",
9283        );
9284        assert!(
9285            rendered.contains("Checkout\\nflow."),
9286            "diagnostic must quote the offending value (debug-escaped): {rendered}",
9287        );
9288    }
9289
9290    #[test]
9291    fn validate_descricao_template_passes() {
9292        // Round-trip pin: the bare `Caixa::template` shape carries
9293        // `:descricao "FIXME — describe this caixa"` (a non-empty
9294        // sentinel), so the template-derived Caixa passes the gate by
9295        // construction. A future template-shape change that omits or
9296        // empties `:descricao` would surface here as a regression.
9297        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9298        c.validate_descricao().unwrap();
9299    }
9300
9301    #[test]
9302    fn validate_descricao_diagnostic_names_offending_slot() {
9303        // Diagnostic-shape pin (peer with
9304        // `validate_repositorio_diagnostic_carries_offending_value`):
9305        // the error's Display surfaces the `:descricao` slot name
9306        // verbatim, so a `feira lint` run can render the diagnostic
9307        // without re-parsing and the author can grep their caixa.lisp
9308        // for the offending `:descricao` line.
9309        let c = caixa_with_descricao(Some(""));
9310        let rendered = c.validate_descricao().unwrap_err().to_string();
9311        assert!(
9312            rendered.contains(":descricao"),
9313            "diagnostic must name the offending slot: {rendered}",
9314        );
9315    }
9316
9317    // ── validate_licenca — universal-axis chart README license shape ──
9318
9319    fn caixa_with_licenca(licenca: Option<&str>) -> Caixa {
9320        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9321        c.licenca = licenca.map(String::from);
9322        c
9323    }
9324
9325    #[test]
9326    fn validate_licenca_accepts_none() {
9327        // The omit-the-slot identity: `:licenca` is optional. The
9328        // gate is a no-op when the author didn't declare a value —
9329        // every caixa without a `:licenca` line trivially passes,
9330        // and the substrate-side `caixa-helm` renderer falls back to
9331        // the documented `"MIT"` placeholder. Mirrors the peer
9332        // `validate_descricao_accepts_none` posture on the sibling
9333        // `Option<String>` Caixa slot.
9334        let c = caixa_with_licenca(None);
9335        c.validate_licenca().unwrap();
9336    }
9337
9338    #[test]
9339    fn validate_licenca_accepts_canonical_expressions() {
9340        // Positive control: every canonical SPDX expression shape
9341        // pleme-io carries in its existing fixtures + the canonical
9342        // SPDX dual-license / with-exception / `+`-suffix / grouped /
9343        // user-defined-reference shapes all pass the gate. Covers
9344        // the single-license, `OR`-compound, `AND`-compound,
9345        // `WITH`-exception, parenthesis-grouped, `+`-suffix, and
9346        // `LicenseRef-` / `DocumentRef-:LicenseRef-` shapes — every
9347        // production the SPDX 2.1 expression grammar admits that
9348        // sits within the alphabet floor the
9349        // `is_spdx_expression_shape` predicate enforces.
9350        for lic in [
9351            "MIT",
9352            "Apache-2.0",
9353            "Apache-2.0 OR MIT",
9354            "Apache-2.0 AND MIT",
9355            "BSD-3-Clause",
9356            "MPL-2.0",
9357            "GPL-3.0-or-later",
9358            "GPL-2.0+",
9359            "Apache-2.0 WITH LLVM-exception",
9360            "(MIT OR Apache-2.0) AND BSD-3-Clause",
9361            "(MIT OR Apache-2.0) AND BSD-3-Clause AND ISC",
9362            "LicenseRef-MyLicense",
9363            "DocumentRef-spdx-tool:LicenseRef-MIT-Style",
9364            "x",
9365        ] {
9366            let c = caixa_with_licenca(Some(lic));
9367            c.validate_licenca()
9368                .unwrap_or_else(|err| panic!("canonical {lic:?} must pass: {err:?}"));
9369        }
9370    }
9371
9372    #[test]
9373    fn validate_licenca_rejects_trailing_whitespace() {
9374        // Paste-from-doc whitespace footgun. A trailing space in the
9375        // `:licenca` value would silently break a downstream SPDX
9376        // parser that splits on exact `AND` / `OR` / `WITH` keyword
9377        // boundaries. The shape predicate refuses every trailing
9378        // whitespace byte by construction. Peer with
9379        // `validate_repositorio_rejects_whitespace` and
9380        // `validate_edicao_rejects_trailing_whitespace`.
9381        let c = caixa_with_licenca(Some("MIT "));
9382        let err = c.validate_licenca().unwrap_err();
9383        let ManifestError::LicencaInvalid { licenca, .. } = err else {
9384            panic!("expected LicencaInvalid, got {err:?}");
9385        };
9386        assert_eq!(licenca, "MIT ");
9387    }
9388
9389    #[test]
9390    fn validate_licenca_rejects_leading_whitespace() {
9391        // Symmetric paste-from-doc whitespace footgun on the leading
9392        // boundary — the gate refuses every shape that starts with a
9393        // space byte by construction. Peer with
9394        // `validate_edicao_rejects_leading_whitespace`.
9395        let c = caixa_with_licenca(Some(" MIT"));
9396        let err = c.validate_licenca().unwrap_err();
9397        assert!(
9398            matches!(err, ManifestError::LicencaInvalid { .. }),
9399            "got {err:?}",
9400        );
9401    }
9402
9403    #[test]
9404    fn validate_licenca_rejects_control_char() {
9405        // Paste-from-multiline-doc CRLF footgun — control characters
9406        // at the value boundary land as a malformed line in the
9407        // rendered chart `README.md` `## License` section. Peer with
9408        // `validate_repositorio_rejects_control_char` and
9409        // `validate_edicao_rejects_control_char`.
9410        for lic in ["MIT\n", "MIT\r\n", "MIT\rApache-2.0"] {
9411            let c = caixa_with_licenca(Some(lic));
9412            let err = c.validate_licenca().unwrap_err();
9413            assert!(
9414                matches!(err, ManifestError::LicencaInvalid { .. }),
9415                "expected LicencaInvalid on {lic:?}, got {err:?}",
9416            );
9417        }
9418    }
9419
9420    #[test]
9421    fn validate_licenca_rejects_tab() {
9422        // Tab-from-aligned-doc footgun — SPDX expressions use a
9423        // single ASCII space between tokens; a tab breaks every
9424        // downstream SPDX parser that splits on exact `" "`
9425        // boundaries.
9426        let c = caixa_with_licenca(Some("MIT\tOR Apache-2.0"));
9427        let err = c.validate_licenca().unwrap_err();
9428        assert!(
9429            matches!(err, ManifestError::LicencaInvalid { .. }),
9430            "got {err:?}",
9431        );
9432    }
9433
9434    #[test]
9435    fn validate_licenca_rejects_non_ascii() {
9436        // Smart-quote / non-ASCII paste footgun — SPDX identifiers
9437        // are ASCII per the `idstring = 1*(ALPHA / DIGIT / "-" /
9438        // ".")` production. The shape predicate refuses every
9439        // non-ASCII byte by construction; peer with
9440        // `validate_edicao_rejects_non_ascii_lookalike`.
9441        for lic in ["MIT\u{a0}OR Apache-2.0", "MIT\u{2013}1.0", "Café-1.0"] {
9442            let c = caixa_with_licenca(Some(lic));
9443            let err = c.validate_licenca().unwrap_err();
9444            assert!(
9445                matches!(err, ManifestError::LicencaInvalid { .. }),
9446                "expected LicencaInvalid on {lic:?}, got {err:?}",
9447            );
9448        }
9449    }
9450
9451    #[test]
9452    fn validate_licenca_rejects_underscore() {
9453        // Underscore-instead-of-hyphen typo footgun — `Apache_2.0` /
9454        // `MIT_Style` / `BSD_3_Clause` are familiar shapes from
9455        // snake-case identifier conventions that don't apply to the
9456        // SPDX `idstring` grammar (which admits only `ALPHA / DIGIT /
9457        // "-" / "."`). The shape predicate refuses every underscore
9458        // byte by construction.
9459        for lic in ["Apache_2.0", "MIT_Style", "BSD_3_Clause"] {
9460            let c = caixa_with_licenca(Some(lic));
9461            let err = c.validate_licenca().unwrap_err();
9462            assert!(
9463                matches!(err, ManifestError::LicencaInvalid { .. }),
9464                "expected LicencaInvalid on {lic:?}, got {err:?}",
9465            );
9466        }
9467    }
9468
9469    #[test]
9470    fn validate_licenca_rejects_comma_separator() {
9471        // Comma-instead-of-`OR`-keyword colloquial idiom footgun —
9472        // SPDX expressions compose multiple licenses via `AND` / `OR`
9473        // keywords, not the comma separator. The shape predicate
9474        // refuses every comma byte by construction.
9475        for lic in ["MIT, Apache-2.0", "MIT,Apache-2.0"] {
9476            let c = caixa_with_licenca(Some(lic));
9477            let err = c.validate_licenca().unwrap_err();
9478            assert!(
9479                matches!(err, ManifestError::LicencaInvalid { .. }),
9480                "expected LicencaInvalid on {lic:?}, got {err:?}",
9481            );
9482        }
9483    }
9484
9485    #[test]
9486    fn validate_licenca_rejects_slash_dual_license() {
9487        // Slash-dual-license colloquial idiom footgun — the
9488        // `MIT/Apache-2.0` shape is common in Cargo's pre-SPDX
9489        // `package.license` field but non-SPDX; the SPDX equivalent
9490        // is `MIT OR Apache-2.0`. The shape predicate refuses every
9491        // forward-slash byte by construction.
9492        for lic in ["MIT/Apache-2.0", "MIT/BSD-3-Clause"] {
9493            let c = caixa_with_licenca(Some(lic));
9494            let err = c.validate_licenca().unwrap_err();
9495            assert!(
9496                matches!(err, ManifestError::LicencaInvalid { .. }),
9497                "expected LicencaInvalid on {lic:?}, got {err:?}",
9498            );
9499        }
9500    }
9501
9502    #[test]
9503    fn validate_licenca_rejects_semicolon_separator() {
9504        // Semicolon-list-separator confusion footgun — adjacent to
9505        // the comma-separator idiom, every list-separator-belongs-
9506        // to-list-grammar confusion lands here.
9507        let c = caixa_with_licenca(Some("MIT; Apache-2.0"));
9508        let err = c.validate_licenca().unwrap_err();
9509        assert!(
9510            matches!(err, ManifestError::LicencaInvalid { .. }),
9511            "got {err:?}",
9512        );
9513    }
9514
9515    #[test]
9516    fn validate_licenca_empty_takes_precedence_over_shape() {
9517        // Empty-first cascade pin: the empty `Some("")` surfaces the
9518        // narrower `LicencaEmpty` not the shape-predicate-wrapped
9519        // `LicencaInvalid`, mirroring the peer
9520        // `validate_edicao_empty_takes_precedence_over_shape` and
9521        // `validate_repositorio_empty_takes_precedence_over_shape`
9522        // (`RepositorioEmpty` → `RepositorioInvalid`), `NomeEmpty` →
9523        // `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid` cascades.
9524        // The shape predicate also refuses the empty input
9525        // (defensively — `"must not be empty"`), but the manifest-
9526        // layer empty arm runs first to surface the narrower
9527        // diagnostic verbatim.
9528        let c = caixa_with_licenca(Some(""));
9529        let err = c.validate_licenca().unwrap_err();
9530        assert!(matches!(err, ManifestError::LicencaEmpty), "got {err:?}",);
9531    }
9532
9533    #[test]
9534    fn validate_licenca_invalid_diagnostic_carries_offending_value() {
9535        // Diagnostic-shape pin on the shape-predicate arm (peer with
9536        // `validate_edicao_invalid_diagnostic_carries_offending_value`
9537        // and `validate_repositorio_diagnostic_carries_offending_value`):
9538        // the error's Display surfaces the offending value + slot
9539        // name verbatim, so a `feira lint` run can render the
9540        // diagnostic without re-parsing and the author can grep
9541        // their caixa.lisp for the offending `:licenca` value.
9542        let c = caixa_with_licenca(Some("Apache_2.0"));
9543        let rendered = c.validate_licenca().unwrap_err().to_string();
9544        assert!(
9545            rendered.contains(":licenca"),
9546            "diagnostic must name the offending slot: {rendered}",
9547        );
9548        assert!(
9549            rendered.contains("Apache_2.0"),
9550            "diagnostic must quote the offending value: {rendered}",
9551        );
9552    }
9553
9554    #[test]
9555    fn validate_licenca_rejects_empty_some() {
9556        // Canonical paste-from-blank-doc footgun. Without this gate
9557        // the empty `Some("")` silently passed the renderer's
9558        // `Option::unwrap_or_else(|| "MIT".into())` (which only
9559        // fires on `None`) and landed as a bare trailing period in
9560        // the rendered chart `README.md` `## License` section.
9561        // Mirrors the peer [`ManifestError::DescricaoEmpty`] empty-
9562        // arm on the sibling `Option<String>` Caixa slot.
9563        let c = caixa_with_licenca(Some(""));
9564        let err = c.validate_licenca().unwrap_err();
9565        assert!(matches!(err, ManifestError::LicencaEmpty), "got {err:?}",);
9566    }
9567
9568    #[test]
9569    fn validate_licenca_template_passes() {
9570        // Round-trip pin: the bare `Caixa::template` shape (whether
9571        // it carries `:licenca` or omits it) passes the gate by
9572        // construction. A future template-shape change that
9573        // introduced `(:licenca "")` would surface here as a
9574        // regression. Mirrors the peer
9575        // `validate_descricao_template_passes` pin.
9576        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9577        c.validate_licenca().unwrap();
9578    }
9579
9580    #[test]
9581    fn validate_licenca_diagnostic_names_offending_slot() {
9582        // Diagnostic-shape pin (peer with
9583        // `validate_descricao_diagnostic_names_offending_slot`):
9584        // the error's Display surfaces the `:licenca` slot name
9585        // verbatim, so a `feira lint` run can render the diagnostic
9586        // without re-parsing and the author can grep their caixa.lisp
9587        // for the offending `:licenca` line.
9588        let c = caixa_with_licenca(Some(""));
9589        let rendered = c.validate_licenca().unwrap_err().to_string();
9590        assert!(
9591            rendered.contains(":licenca"),
9592            "diagnostic must name the offending slot: {rendered}",
9593        );
9594    }
9595
9596    // ── Caixa::licenca — outer top-level Option<&str> scalar accessor ──
9597
9598    #[test]
9599    fn licenca_returns_licenca_byte_string_verbatim_across_permutations() {
9600        // The canonical per-`Caixa` `:licenca` SPDX-expression scalar
9601        // pin: [`Caixa::licenca`] must return the `:licenca` typed
9602        // byte-string verbatim as an `Option<&str>`, byte-equal to the
9603        // raw `self.licenca.as_deref()` access across every
9604        // representative value in the accept-set — `None` (the "omit
9605        // the slot to defer to the caixa-helm renderer's `MIT`
9606        // fallback" arm every existing fixture without a `:licenca`
9607        // line carries), `Some("")` (a past-the-guard sentinel that
9608        // pins the accessor doesn't perform a silent
9609        // `Some("") → None` collapse on the empty arm — validate
9610        // rejects `Some("")` through `LicencaEmpty` but the accessor
9611        // must ship the raw slot verbatim so a validate-time gate
9612        // regression surfaces at the caixa-helm emit boundary rather
9613        // than being silently absorbed into the fallback), `Some("MIT")`
9614        // (the canonical single-license shape every `feira init`
9615        // template scaffolds), `Some("Apache-2.0 OR MIT")` (the
9616        // canonical `OR`-compound shape the peer
9617        // `validate_licenca_accepts_canonical_expressions` positive
9618        // sweep exercises), `Some("(MIT OR Apache-2.0) AND
9619        // BSD-3-Clause")` (the canonical parenthesis-grouped shape),
9620        // `Some("MIT ")` / `Some(" MIT")` / `Some("MIT\n")` /
9621        // `Some("Apache_2.0")` / `Some("MIT,Apache-2.0")` (past-the-
9622        // guard sentinels — validate rejects each through
9623        // `LicencaInvalid` but the accessor must ship the raw slot
9624        // verbatim).
9625        //
9626        // First outer top-level [`Caixa`] `Option<&str>`-return scalar
9627        // accessor pin on the substrate primitive — opens the "outer
9628        // [`Caixa`] `Option<&str>` scalar" projection pattern the
9629        // sibling per-`Caixa` `:descricao` / `:repositorio` / `:edicao`
9630        // future lifts fold on. Sibling in shape to the peer per-`:placement`
9631        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
9632        // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
9633        // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
9634        // axes, extended onto the outer top-level [`Caixa`] universal-
9635        // axis surface. Pins against a future silent detour that
9636        // returned an owned `Option<String>` (which would type-check
9637        // but silently allocate on every accessor call, breaking the
9638        // zero-cost projection every peer sibling accessor carries), a
9639        // `Some("") → None` collapse (which would silently absorb the
9640        // `LicencaEmpty` refusal case at the accessor boundary and the
9641        // caixa-helm emit path would silently fall back to `"MIT"` on
9642        // a struct-literal `Caixa { licenca: Some(""), .. }`), or a
9643        // `None → Some("MIT")` collapse (which would silently reify
9644        // the caixa-helm renderer's `"MIT"` fallback at the accessor
9645        // boundary and every downstream consumer keying off the
9646        // `Option::is_none()` discriminator would lose the "author
9647        // omitted the slot" signal).
9648        for licenca in [
9649            None,
9650            Some(""),
9651            Some("MIT"),
9652            Some("Apache-2.0 OR MIT"),
9653            Some("(MIT OR Apache-2.0) AND BSD-3-Clause"),
9654            Some("MIT "),
9655            Some(" MIT"),
9656            Some("MIT\n"),
9657            Some("Apache_2.0"),
9658            Some("MIT,Apache-2.0"),
9659        ] {
9660            let c = caixa_with_licenca(licenca);
9661            assert_eq!(
9662                c.licenca(),
9663                licenca,
9664                "Caixa::licenca must return :licenca verbatim (got {:?}, \
9665                 expected {licenca:?})",
9666                c.licenca(),
9667            );
9668            assert_eq!(
9669                c.licenca(),
9670                c.licenca.as_deref(),
9671                "Caixa::licenca must byte-equal the raw \
9672                 `self.licenca.as_deref()` field access across every \
9673                 value in the Option<&str> accept-set",
9674            );
9675        }
9676    }
9677
9678    #[test]
9679    fn validate_licenca_empty_arm_routes_through_accessor() {
9680        // Composition pin: [`Caixa::validate_licenca`]'s empty-arm gate
9681        // must key off [`Caixa::licenca`], not the raw
9682        // `self.licenca.as_deref()` field access. Structurally: a
9683        // `Caixa { licenca: Some(""), .. }` must surface the
9684        // `LicencaEmpty` refusal exactly, and a
9685        // `Caixa { licenca: Some("MIT"), .. }` (the canonical
9686        // single-license form) must pass validate. The pair jointly
9687        // pins the accessor + validate-gate composition: any future
9688        // silent detour that had the accessor return `None` on the
9689        // empty arm (a `.filter(|s| !s.is_empty())` collapse) would
9690        // silently absorb the `LicencaEmpty` refusal at the accessor
9691        // boundary and the validate gate would accept a struct-literal
9692        // `Caixa { licenca: Some(""), .. }` — the composition pin
9693        // catches that at caixa-core build time.
9694        //
9695        // Peer of the per-`:politicas :circuit-breaker`
9696        // [`crate::aplicacao::CircuitBreaker::max_failures`] (3a74062)
9697        // accessor-composition pin
9698        // (`validate_politicas_max_failures_zero_floor_arm_routes_through_accessor`)
9699        // on the sibling per-M3-mesh-slot required-`u32` axis — same
9700        // "the validate / shape-gate predicate must route through the
9701        // substrate-primitive typed dispatch" discipline extended onto
9702        // the outer top-level [`Caixa`] universal-axis
9703        // `Option<&str>`-composition surface.
9704        let c = caixa_with_licenca(Some(""));
9705        assert!(
9706            matches!(c.validate_licenca(), Err(ManifestError::LicencaEmpty)),
9707            "validate_licenca must reject licenca == Some(\"\") with \
9708             LicencaEmpty — the accessor and the validate gate must \
9709             route through the same substrate-primitive typed dispatch \
9710             on the :licenca empty arm",
9711        );
9712        let c = caixa_with_licenca(Some("MIT"));
9713        assert!(
9714            c.validate_licenca().is_ok(),
9715            "validate_licenca must accept licenca == Some(\"MIT\") \
9716             (the canonical single-license SPDX shape)",
9717        );
9718    }
9719
9720    #[test]
9721    fn licenca_projects_option_str_by_borrow() {
9722        // The by-borrow pin: [`Caixa::licenca`] returns
9723        // `Option<&str>` by borrow — the `&str` borrows the underlying
9724        // `String` storage of the `Option<String>` slot and the
9725        // accessor must not allocate a fresh `String` on every call.
9726        // Peer of the per-`:placement`
9727        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
9728        // borrow pin on the peer per-M3-mesh-slot
9729        // `Option<&str>`-return axis, extended onto the outer top-
9730        // level [`Caixa`] universal-axis `Option<&str>` shape — the
9731        // accessor's returned `&str` must borrow from `&self` (the
9732        // returned reference's lifetime is tied to `&self`), and
9733        // calling the accessor twice on the same [`Caixa`] must yield
9734        // the same `Option<&str>` verbatim (idempotent, no side
9735        // effects on `&self`).
9736        //
9737        // Pins against a future silent detour that returned an owned
9738        // `Option<String>` (which would type-check but silently
9739        // allocate on every call, breaking the zero-cost projection
9740        // every peer sibling accessor carries), or a one-arm-only
9741        // accessor that returned a saturating value on some sentinel
9742        // input (breaking the pass-through invariant the sibling
9743        // required-scalar accessors carry).
9744        for licenca in [None, Some(""), Some("MIT"), Some("Apache-2.0 OR MIT")] {
9745            let c = caixa_with_licenca(licenca);
9746            let first = c.licenca();
9747            let second = c.licenca();
9748            assert_eq!(
9749                first, second,
9750                "Caixa::licenca must be idempotent — two successive \
9751                 calls on the same &self must return the same \
9752                 Option<&str>",
9753            );
9754            assert_eq!(
9755                first, licenca,
9756                "Caixa::licenca must return :licenca verbatim by \
9757                 borrow — got {first:?}, expected {licenca:?}",
9758            );
9759        }
9760    }
9761
9762    // ── Caixa::repositorio — outer top-level Option<&str> scalar accessor ──
9763
9764    #[test]
9765    fn repositorio_returns_repositorio_byte_string_verbatim_across_permutations() {
9766        // The canonical per-`Caixa` `:repositorio` git-repo-URL scalar
9767        // pin: [`Caixa::repositorio`] must return the `:repositorio`
9768        // typed byte-string verbatim as an `Option<&str>`, byte-equal
9769        // to the raw `self.repositorio.as_deref()` access across every
9770        // representative value in the accept-set — `None` (the "omit
9771        // the slot to defer to the per-renderer placeholder" arm every
9772        // existing fixture without a `:repositorio` line carries),
9773        // `Some("")` (a past-the-guard sentinel that pins the accessor
9774        // doesn't perform a silent `Some("") → None` collapse on the
9775        // empty arm — validate rejects `Some("")` through
9776        // `RepositorioEmpty` but the accessor must ship the raw slot
9777        // verbatim so a validate-time gate regression surfaces at the
9778        // caixa-helm / caixa-flux emit boundary rather than being
9779        // silently absorbed into the per-renderer fallback),
9780        // `Some("github:pleme-io/hello-rio")` (the canonical `github:`
9781        // shorthand every existing manifest fixture across
9782        // `caixa-helm` / `caixa-mesh` and the `examples/` uses),
9783        // `Some("https://github.com/pleme-io/checkout")` (the canonical
9784        // `https://` URL the README quickstart uses),
9785        // `Some("ssh://git@github.com/pleme-io/checkout.git")` /
9786        // `Some("git://github.com/pleme-io/checkout.git")` /
9787        // `Some("git@github.com:pleme-io/checkout.git")` /
9788        // `Some("file:///opt/mirrors/pleme-io/checkout")` (every non-
9789        // github scheme the shared `is_git_repo_url` predicate
9790        // documents), and five past-the-guard sentinels for the
9791        // `RepositorioInvalid` refusal cases (`Some("pleme-io/checkout")`
9792        // missing-colon, `Some("-upload-pack=evil")` leading-dash, /
9793        // `Some("github:pleme-io/checkout?ref=main")` query-string, /
9794        // `Some("github:pleme-io/checkout#main")` fragment-anchor, /
9795        // `Some("github:pleme-io/{tpl}")` URI-template-placeholder — the
9796        // sentinels pin the accessor doesn't silently absorb the
9797        // refusal cases into a fallback).
9798        //
9799        // Second outer top-level [`Caixa`] `Option<&str>`-return scalar
9800        // accessor pin on the substrate primitive — sibling of the peer
9801        // [`Caixa::licenca`] (6d5bc28) pin
9802        // (`licenca_returns_licenca_byte_string_verbatim_across_permutations`)
9803        // that opened the "outer [`Caixa`] `Option<&str>` scalar"
9804        // projection pin pattern this pin folds on. Sibling in shape to
9805        // the peer per-`:placement`
9806        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
9807        // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
9808        // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
9809        // axes, extended onto the outer top-level [`Caixa`] universal-
9810        // axis surface. Pins against a future silent detour that
9811        // returned an owned `Option<String>` (which would type-check
9812        // but silently allocate on every accessor call, breaking the
9813        // zero-cost projection every peer sibling accessor carries), a
9814        // `Some("") → None` collapse (which would silently absorb the
9815        // `RepositorioEmpty` refusal case at the accessor boundary and
9816        // the caixa-helm `Chart.yaml` `home:` fold would silently
9817        // render a `home: null` / omitted field on a struct-literal
9818        // `Caixa { repositorio: Some(""), .. }`), or a
9819        // `None → Some(<default>)` collapse (which would silently reify
9820        // the per-renderer fallback at the accessor boundary and every
9821        // downstream consumer keying off the `Option::is_none()`
9822        // discriminator would lose the "author omitted the slot"
9823        // signal).
9824        for repositorio in [
9825            None,
9826            Some(""),
9827            Some("github:pleme-io/hello-rio"),
9828            Some("https://github.com/pleme-io/checkout"),
9829            Some("ssh://git@github.com/pleme-io/checkout.git"),
9830            Some("git://github.com/pleme-io/checkout.git"),
9831            Some("git@github.com:pleme-io/checkout.git"),
9832            Some("file:///opt/mirrors/pleme-io/checkout"),
9833            Some("pleme-io/checkout"),
9834            Some("-upload-pack=evil"),
9835            Some("github:pleme-io/checkout?ref=main"),
9836            Some("github:pleme-io/checkout#main"),
9837            Some("github:pleme-io/{tpl}"),
9838        ] {
9839            let c = caixa_with_repositorio(repositorio);
9840            assert_eq!(
9841                c.repositorio(),
9842                repositorio,
9843                "Caixa::repositorio must return :repositorio verbatim \
9844                 (got {:?}, expected {repositorio:?})",
9845                c.repositorio(),
9846            );
9847            assert_eq!(
9848                c.repositorio(),
9849                c.repositorio.as_deref(),
9850                "Caixa::repositorio must byte-equal the raw \
9851                 `self.repositorio.as_deref()` field access across every \
9852                 value in the Option<&str> accept-set",
9853            );
9854        }
9855    }
9856
9857    #[test]
9858    fn validate_repositorio_empty_arm_routes_through_accessor() {
9859        // Composition pin: [`Caixa::validate_repositorio`]'s empty-arm
9860        // gate must key off [`Caixa::repositorio`], not the raw
9861        // `self.repositorio.as_deref()` field access. Structurally: a
9862        // `Caixa { repositorio: Some(""), .. }` must surface the
9863        // `RepositorioEmpty` refusal exactly, and a
9864        // `Caixa { repositorio: Some("github:pleme-io/hello-rio"), .. }`
9865        // (the canonical `github:` shorthand form) must pass validate.
9866        // The pair jointly pins the accessor + validate-gate
9867        // composition: any future silent detour that had the accessor
9868        // return `None` on the empty arm (a `.filter(|s| !s.is_empty())`
9869        // collapse) would silently absorb the `RepositorioEmpty` refusal
9870        // at the accessor boundary and the validate gate would accept a
9871        // struct-literal `Caixa { repositorio: Some(""), .. }` — the
9872        // composition pin catches that at caixa-core build time.
9873        //
9874        // Peer of the [`Caixa::licenca`] (6d5bc28)
9875        // `validate_licenca_empty_arm_routes_through_accessor`
9876        // composition pin on the sibling outer top-level [`Caixa`]
9877        // `Option<&str>` universal-axis surface — same "the validate /
9878        // shape-gate predicate must route through the substrate-
9879        // primitive typed dispatch" discipline extended onto the second
9880        // outer top-level [`Caixa`] universal-axis `Option<&str>`-
9881        // composition surface.
9882        let c = caixa_with_repositorio(Some(""));
9883        assert!(
9884            matches!(
9885                c.validate_repositorio(),
9886                Err(ManifestError::RepositorioEmpty),
9887            ),
9888            "validate_repositorio must reject repositorio == Some(\"\") \
9889             with RepositorioEmpty — the accessor and the validate gate \
9890             must route through the same substrate-primitive typed \
9891             dispatch on the :repositorio empty arm",
9892        );
9893        let c = caixa_with_repositorio(Some("github:pleme-io/hello-rio"));
9894        assert!(
9895            c.validate_repositorio().is_ok(),
9896            "validate_repositorio must accept repositorio == \
9897             Some(\"github:pleme-io/hello-rio\") (the canonical \
9898             `github:` shorthand git-repo-URL shape)",
9899        );
9900    }
9901
9902    #[test]
9903    fn repositorio_projects_option_str_by_borrow() {
9904        // The by-borrow pin: [`Caixa::repositorio`] returns
9905        // `Option<&str>` by borrow — the `&str` borrows the underlying
9906        // `String` storage of the `Option<String>` slot and the
9907        // accessor must not allocate a fresh `String` on every call.
9908        // Peer of the per-`:placement`
9909        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) and the
9910        // [`Caixa::licenca`] (6d5bc28) by-borrow pins on the peer
9911        // `Option<&str>`-return axes, extended onto the second outer
9912        // top-level [`Caixa`] universal-axis `Option<&str>` shape —
9913        // the accessor's returned `&str` must borrow from `&self` (the
9914        // returned reference's lifetime is tied to `&self`), and
9915        // calling the accessor twice on the same [`Caixa`] must yield
9916        // the same `Option<&str>` verbatim (idempotent, no side effects
9917        // on `&self`).
9918        //
9919        // Pins against a future silent detour that returned an owned
9920        // `Option<String>` (which would type-check but silently
9921        // allocate on every call, breaking the zero-cost projection
9922        // every peer sibling accessor carries), or a one-arm-only
9923        // accessor that returned a saturating value on some sentinel
9924        // input (breaking the pass-through invariant the sibling
9925        // required-scalar accessors carry).
9926        for repositorio in [
9927            None,
9928            Some(""),
9929            Some("github:pleme-io/hello-rio"),
9930            Some("https://github.com/pleme-io/checkout"),
9931        ] {
9932            let c = caixa_with_repositorio(repositorio);
9933            let first = c.repositorio();
9934            let second = c.repositorio();
9935            assert_eq!(
9936                first, second,
9937                "Caixa::repositorio must be idempotent — two successive \
9938                 calls on the same &self must return the same \
9939                 Option<&str>",
9940            );
9941            assert_eq!(
9942                first, repositorio,
9943                "Caixa::repositorio must return :repositorio verbatim by \
9944                 borrow — got {first:?}, expected {repositorio:?}",
9945            );
9946        }
9947    }
9948
9949    // ── Caixa::descricao — outer top-level Option<&str> scalar accessor ──
9950
9951    #[test]
9952    fn descricao_returns_descricao_byte_string_verbatim_across_permutations() {
9953        // The canonical per-`Caixa` `:descricao` free-form-prose scalar
9954        // pin: [`Caixa::descricao`] must return the `:descricao` typed
9955        // byte-string verbatim as an `Option<&str>`, byte-equal to the
9956        // raw `self.descricao.as_deref()` access across every
9957        // representative value in the accept-set — `None` (the "omit
9958        // the slot to defer to the per-renderer `caixa.nome`-derived
9959        // fallback" arm every existing fixture without a `:descricao`
9960        // line carries), `Some("")` (a past-the-guard sentinel that
9961        // pins the accessor doesn't perform a silent `Some("") → None`
9962        // collapse on the empty arm — validate rejects `Some("")`
9963        // through `DescricaoEmpty` but the accessor must ship the raw
9964        // slot verbatim so a validate-time gate regression surfaces at
9965        // the caixa-helm / caixa-feira emit boundary rather than being
9966        // silently absorbed into the per-renderer `caixa.nome`-derived
9967        // fallback), `Some("Checkout flow.")` (the canonical one-line
9968        // prose descriptor the peer
9969        // `validate_descricao_accepts_canonical_value` positive sweep
9970        // exercises), `Some("Canonical Rust→wasm32-wasip2 caixa
9971        // Servico.")` (the multi-byte Unicode continuation-byte shape
9972        // the `hello-rio` fixture carries), `Some("→ — · ✓")` (a
9973        // multi-glyph Unicode shape the peer
9974        // `is_chart_description_shape` predicate accepts), and five
9975        // past-the-guard sentinels for the `DescricaoInvalid` refusal
9976        // cases (`Some(" Checkout flow.")` leading-whitespace,
9977        // `Some("Checkout flow. ")` trailing-whitespace,
9978        // `Some("Checkout\nflow.")` embedded-LF,
9979        // `Some("Checkout\tflow.")` embedded-TAB, and
9980        // `Some("Checkout\x00flow.")` embedded-NUL — the sentinels pin
9981        // the accessor doesn't silently absorb the refusal cases into
9982        // a fallback).
9983        //
9984        // Third outer top-level [`Caixa`] `Option<&str>`-return scalar
9985        // accessor pin on the substrate primitive — sibling of the peer
9986        // [`Caixa::licenca`] (6d5bc28) and [`Caixa::repositorio`]
9987        // (cc7332d) pins that opened the "outer [`Caixa`]
9988        // `Option<&str>` scalar" projection pin pattern this pin folds
9989        // on. Sibling in shape to the peer per-`:placement`
9990        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
9991        // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
9992        // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
9993        // axes, extended onto the outer top-level [`Caixa`] universal-
9994        // axis surface. Pins against a future silent detour that
9995        // returned an owned `Option<String>` (which would type-check
9996        // but silently allocate on every accessor call, breaking the
9997        // zero-cost projection every peer sibling accessor carries), a
9998        // `Some("") → None` collapse (which would silently absorb the
9999        // `DescricaoEmpty` refusal case at the accessor boundary and
10000        // the caixa-helm `Chart.yaml` `description:` fold would
10001        // silently render a `caixa.nome`-derived fallback on a
10002        // struct-literal `Caixa { descricao: Some(""), .. }`), or a
10003        // `None → Some(<default>)` collapse (which would silently
10004        // reify the per-renderer `caixa.nome`-derived fallback at the
10005        // accessor boundary and every downstream consumer keying off
10006        // the `Option::is_none()` discriminator would lose the "author
10007        // omitted the slot" signal).
10008        for descricao in [
10009            None,
10010            Some(""),
10011            Some("Checkout flow."),
10012            Some("Canonical Rust→wasm32-wasip2 caixa Servico."),
10013            Some("→ — · ✓"),
10014            Some(" Checkout flow."),
10015            Some("Checkout flow. "),
10016            Some("Checkout\nflow."),
10017            Some("Checkout\tflow."),
10018            Some("Checkout\x00flow."),
10019        ] {
10020            let c = caixa_with_descricao(descricao);
10021            assert_eq!(
10022                c.descricao(),
10023                descricao,
10024                "Caixa::descricao must return :descricao verbatim (got \
10025                 {:?}, expected {descricao:?})",
10026                c.descricao(),
10027            );
10028            assert_eq!(
10029                c.descricao(),
10030                c.descricao.as_deref(),
10031                "Caixa::descricao must byte-equal the raw \
10032                 `self.descricao.as_deref()` field access across every \
10033                 value in the Option<&str> accept-set",
10034            );
10035        }
10036    }
10037
10038    #[test]
10039    fn validate_descricao_empty_arm_routes_through_accessor() {
10040        // Composition pin: [`Caixa::validate_descricao`]'s empty-arm
10041        // gate must key off [`Caixa::descricao`], not the raw
10042        // `self.descricao.as_deref()` field access. Structurally: a
10043        // `Caixa { descricao: Some(""), .. }` must surface the
10044        // `DescricaoEmpty` refusal exactly, and a
10045        // `Caixa { descricao: Some("Checkout flow."), .. }` (the
10046        // canonical one-line-prose form) must pass validate. The pair
10047        // jointly pins the accessor + validate-gate composition: any
10048        // future silent detour that had the accessor return `None` on
10049        // the empty arm (a `.filter(|s| !s.is_empty())` collapse) would
10050        // silently absorb the `DescricaoEmpty` refusal at the accessor
10051        // boundary and the validate gate would accept a struct-literal
10052        // `Caixa { descricao: Some(""), .. }` — the composition pin
10053        // catches that at caixa-core build time.
10054        //
10055        // Peer of the [`Caixa::licenca`] (6d5bc28)
10056        // `validate_licenca_empty_arm_routes_through_accessor` and
10057        // [`Caixa::repositorio`] (cc7332d)
10058        // `validate_repositorio_empty_arm_routes_through_accessor`
10059        // composition pins on the sibling outer top-level [`Caixa`]
10060        // `Option<&str>` universal-axis surface — same "the validate /
10061        // shape-gate predicate must route through the substrate-
10062        // primitive typed dispatch" discipline extended onto the third
10063        // outer top-level [`Caixa`] universal-axis `Option<&str>`-
10064        // composition surface.
10065        let c = caixa_with_descricao(Some(""));
10066        assert!(
10067            matches!(c.validate_descricao(), Err(ManifestError::DescricaoEmpty),),
10068            "validate_descricao must reject descricao == Some(\"\") \
10069             with DescricaoEmpty — the accessor and the validate gate \
10070             must route through the same substrate-primitive typed \
10071             dispatch on the :descricao empty arm",
10072        );
10073        let c = caixa_with_descricao(Some("Checkout flow."));
10074        assert!(
10075            c.validate_descricao().is_ok(),
10076            "validate_descricao must accept descricao == \
10077             Some(\"Checkout flow.\") (the canonical one-line-prose \
10078             chart-description shape)",
10079        );
10080    }
10081
10082    #[test]
10083    fn descricao_projects_option_str_by_borrow() {
10084        // The by-borrow pin: [`Caixa::descricao`] returns
10085        // `Option<&str>` by borrow — the `&str` borrows the underlying
10086        // `String` storage of the `Option<String>` slot and the
10087        // accessor must not allocate a fresh `String` on every call.
10088        // Peer of the [`Caixa::licenca`] (6d5bc28) and
10089        // [`Caixa::repositorio`] (cc7332d) by-borrow pins on the peer
10090        // outer top-level [`Caixa`] `Option<&str>`-return axes, and of
10091        // the per-`:placement`
10092        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
10093        // borrow pin on the peer per-M3-mesh-slot `Option<&str>`-
10094        // return axis, extended onto the third outer top-level
10095        // [`Caixa`] universal-axis `Option<&str>` shape — the
10096        // accessor's returned `&str` must borrow from `&self` (the
10097        // returned reference's lifetime is tied to `&self`), and
10098        // calling the accessor twice on the same [`Caixa`] must yield
10099        // the same `Option<&str>` verbatim (idempotent, no side
10100        // effects on `&self`).
10101        //
10102        // Pins against a future silent detour that returned an owned
10103        // `Option<String>` (which would type-check but silently
10104        // allocate on every call, breaking the zero-cost projection
10105        // every peer sibling accessor carries), or a one-arm-only
10106        // accessor that returned a saturating value on some sentinel
10107        // input (breaking the pass-through invariant the sibling
10108        // required-scalar accessors carry).
10109        for descricao in [
10110            None,
10111            Some(""),
10112            Some("Checkout flow."),
10113            Some("Canonical Rust→wasm32-wasip2 caixa Servico."),
10114        ] {
10115            let c = caixa_with_descricao(descricao);
10116            let first = c.descricao();
10117            let second = c.descricao();
10118            assert_eq!(
10119                first, second,
10120                "Caixa::descricao must be idempotent — two successive \
10121                 calls on the same &self must return the same \
10122                 Option<&str>",
10123            );
10124            assert_eq!(
10125                first, descricao,
10126                "Caixa::descricao must return :descricao verbatim by \
10127                 borrow — got {first:?}, expected {descricao:?}",
10128            );
10129        }
10130    }
10131
10132    // ── validate_edicao — universal-axis language-edition shape ──
10133
10134    fn caixa_with_edicao(edicao: Option<&str>) -> Caixa {
10135        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10136        c.edicao = edicao.map(String::from);
10137        c
10138    }
10139
10140    #[test]
10141    fn validate_edicao_accepts_none() {
10142        // The omit-the-slot identity: `:edicao` is optional. The
10143        // gate is a no-op when the author didn't declare a value —
10144        // every caixa without an `:edicao` line trivially passes,
10145        // and the substrate-side build pipeline falls back to the
10146        // documented default edition. Mirrors the peer
10147        // `validate_licenca_accepts_none` posture on the sibling
10148        // `Option<String>` Caixa slot.
10149        let c = caixa_with_edicao(None);
10150        c.validate_edicao().unwrap();
10151    }
10152
10153    #[test]
10154    fn validate_edicao_accepts_canonical_value() {
10155        // Positive control: the canonical `"2026"` edition every
10156        // existing renderer-side fixture (`caixa-helm`, `caixa-flux`,
10157        // `caixa-mesh`) carries by construction passes the gate.
10158        // Future-introduced sibling editions (`"2027"`, `"2030"`,
10159        // `"2049"`) that match the same 4-digit ASCII decimal year
10160        // shape must also trivially pass — the structural shape
10161        // predicate accepts every well-formed year regardless of
10162        // whether the substrate yet understands the specific value
10163        // (a future known-edition allowlist tightens that).
10164        for ed in ["2026", "2027", "2030", "2049"] {
10165            let c = caixa_with_edicao(Some(ed));
10166            c.validate_edicao()
10167                .unwrap_or_else(|err| panic!("canonical {ed:?} must pass: {err:?}"));
10168        }
10169    }
10170
10171    #[test]
10172    fn validate_edicao_rejects_empty_some() {
10173        // Canonical paste-from-blank-doc footgun. Without this gate
10174        // the empty `Some("")` silently lands as `(:edicao "")` in
10175        // the rendered caixa.lisp and a future renderer-side
10176        // consumer's `Option::unwrap_or_else` (which only fires on
10177        // `None`) skips its fallback. Mirrors the peer
10178        // [`ManifestError::LicencaEmpty`] empty-arm on the sibling
10179        // `Option<String>` Caixa slot.
10180        let c = caixa_with_edicao(Some(""));
10181        let err = c.validate_edicao().unwrap_err();
10182        assert!(matches!(err, ManifestError::EdicaoEmpty), "got {err:?}",);
10183    }
10184
10185    #[test]
10186    fn validate_edicao_rejects_free_form_non_year() {
10187        // Free-form non-year footgun: the bare `"x"` / `"latest"` /
10188        // `"nightly"` shapes carry no operational meaning on the
10189        // substrate's build-time edition selector. Until this gate
10190        // landed the bare empty-arm check let every such value
10191        // through and broke far from the source caixa.lisp. Peer
10192        // with the shape-predicate cascade
10193        // `validate_repositorio_rejects_missing_colon_separator`
10194        // establishes past its own empty arm.
10195        for ed in ["x", "latest", "nightly", "stable"] {
10196            let c = caixa_with_edicao(Some(ed));
10197            let err = c.validate_edicao().unwrap_err();
10198            assert!(
10199                matches!(err, ManifestError::EdicaoInvalid { .. }),
10200                "expected EdicaoInvalid on {ed:?}, got {err:?}",
10201            );
10202        }
10203    }
10204
10205    #[test]
10206    fn validate_edicao_rejects_trailing_whitespace() {
10207        // Paste-from-doc whitespace footgun. A trailing space in
10208        // the `:edicao` value would silently break the substrate's
10209        // build-time edition match-table lookup at the rendered
10210        // artifact's edition-selector consumer. The shape predicate
10211        // refuses every whitespace byte by construction (any byte
10212        // outside `0-9` fails `is_ascii_digit`). Peer with
10213        // `validate_repositorio_rejects_whitespace`.
10214        let c = caixa_with_edicao(Some("2026 "));
10215        let err = c.validate_edicao().unwrap_err();
10216        let ManifestError::EdicaoInvalid { edicao, .. } = err else {
10217            panic!("expected EdicaoInvalid, got {err:?}");
10218        };
10219        assert_eq!(edicao, "2026 ");
10220    }
10221
10222    #[test]
10223    fn validate_edicao_rejects_leading_whitespace() {
10224        // Symmetric paste-from-doc whitespace footgun on the leading
10225        // boundary — the gate refuses every shape with a non-digit
10226        // byte by construction.
10227        let c = caixa_with_edicao(Some(" 2026"));
10228        let err = c.validate_edicao().unwrap_err();
10229        assert!(
10230            matches!(err, ManifestError::EdicaoInvalid { .. }),
10231            "got {err:?}",
10232        );
10233    }
10234
10235    #[test]
10236    fn validate_edicao_rejects_control_char() {
10237        // Paste-from-multiline-doc CRLF footgun — control characters
10238        // at the value boundary break the substrate's build-time
10239        // edition-selector parser. Peer with
10240        // `validate_repositorio_rejects_control_char`.
10241        let c = caixa_with_edicao(Some("2026\n"));
10242        let err = c.validate_edicao().unwrap_err();
10243        assert!(
10244            matches!(err, ManifestError::EdicaoInvalid { .. }),
10245            "got {err:?}",
10246        );
10247    }
10248
10249    #[test]
10250    fn validate_edicao_rejects_non_ascii_lookalike() {
10251        // Fullwidth-keyboard look-alike footgun — `"2026"` is
10252        // the U+FF12 U+FF10 U+FF12 U+FF16 sequence (CJK fullwidth
10253        // digits), 4 codepoints but 12 UTF-8 bytes; the substrate's
10254        // edition selector wants an ASCII year, and the gate
10255        // refuses every non-ASCII shape by construction (length in
10256        // bytes is 12 ≠ 4, *and* every byte falls outside
10257        // `is_ascii_digit`'s `0-9` range).
10258        let c = caixa_with_edicao(Some("2026"));
10259        let err = c.validate_edicao().unwrap_err();
10260        assert!(
10261            matches!(err, ManifestError::EdicaoInvalid { .. }),
10262            "got {err:?}",
10263        );
10264    }
10265
10266    #[test]
10267    fn validate_edicao_rejects_version_tag_prefix() {
10268        // Common version-tag idiom footgun — `"v2026"` / `"e2026"`
10269        // / `"r2026"` are familiar shapes from git-tag / Rust
10270        // edition / release-tag conventions that don't apply to
10271        // the year-shaped edition axis. The shape predicate refuses
10272        // every leading non-digit prefix.
10273        for ed in ["v2026", "e2026", "r2026"] {
10274            let c = caixa_with_edicao(Some(ed));
10275            let err = c.validate_edicao().unwrap_err();
10276            assert!(
10277                matches!(err, ManifestError::EdicaoInvalid { .. }),
10278                "expected EdicaoInvalid on {ed:?}, got {err:?}",
10279            );
10280        }
10281    }
10282
10283    #[test]
10284    fn validate_edicao_rejects_decimal_shape() {
10285        // Decimal-shaped pseudo-version footgun — `"2026.1"` /
10286        // `"2026.0"` are familiar shapes from semver / float
10287        // conventions that don't apply to the year-shaped edition
10288        // axis. The shape predicate refuses every non-digit byte
10289        // (`.` falls outside `is_ascii_digit`).
10290        for ed in ["2026.1", "2026.0", "2026.0.1"] {
10291            let c = caixa_with_edicao(Some(ed));
10292            let err = c.validate_edicao().unwrap_err();
10293            assert!(
10294                matches!(err, ManifestError::EdicaoInvalid { .. }),
10295                "expected EdicaoInvalid on {ed:?}, got {err:?}",
10296            );
10297        }
10298    }
10299
10300    #[test]
10301    fn validate_edicao_rejects_wrong_length_numeric() {
10302        // Wrong-length numeric footgun — `"26"` (truncated) /
10303        // `"202"` (truncated) / `"20260"` (extra digit) / `"00026"`
10304        // (zero-padded too wide) all parse as integers but don't
10305        // name a 4-digit year. The shape predicate refuses every
10306        // value whose length isn't exactly 4 bytes.
10307        for ed in ["26", "202", "20260", "00026", "9"] {
10308            let c = caixa_with_edicao(Some(ed));
10309            let err = c.validate_edicao().unwrap_err();
10310            assert!(
10311                matches!(err, ManifestError::EdicaoInvalid { .. }),
10312                "expected EdicaoInvalid on {ed:?}, got {err:?}",
10313            );
10314        }
10315    }
10316
10317    #[test]
10318    fn validate_edicao_empty_takes_precedence_over_shape() {
10319        // Empty-first cascade pin: the empty `Some("")` surfaces
10320        // the narrower `EdicaoEmpty` not the shape-predicate-
10321        // wrapped `EdicaoInvalid`, mirroring the peer
10322        // `validate_repositorio_empty_takes_precedence_over_shape`
10323        // (`RepositorioEmpty` → `RepositorioInvalid`),
10324        // `NomeEmpty` → `NomeInvalid`, `VersaoEmpty` →
10325        // `VersaoInvalid`, `FonteRepoEmpty` → `FonteRepoInvalid`
10326        // cascades. The shape predicate also refuses the empty
10327        // input (defensively — `s.len() != 4`), but the
10328        // manifest-layer empty arm runs first to surface the
10329        // narrower diagnostic verbatim.
10330        let c = caixa_with_edicao(Some(""));
10331        let err = c.validate_edicao().unwrap_err();
10332        assert!(matches!(err, ManifestError::EdicaoEmpty), "got {err:?}",);
10333    }
10334
10335    #[test]
10336    fn validate_edicao_template_passes() {
10337        // Round-trip pin: the bare `Caixa::template` shape (which
10338        // carries `:edicao "2026"` verbatim) passes the gate by
10339        // construction. A future template-shape change that
10340        // introduced `(:edicao "")` or a non-year value would
10341        // surface here as a regression. Mirrors the peer
10342        // `validate_licenca_template_passes` pin.
10343        let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10344        c.validate_edicao().unwrap();
10345    }
10346
10347    #[test]
10348    fn validate_edicao_diagnostic_names_offending_slot() {
10349        // Diagnostic-shape pin (peer with
10350        // `validate_licenca_diagnostic_names_offending_slot`): the
10351        // error's Display surfaces the `:edicao` slot name verbatim,
10352        // so a `feira lint` run can render the diagnostic without
10353        // re-parsing and the author can grep their caixa.lisp for
10354        // the offending `:edicao` line.
10355        let c = caixa_with_edicao(Some(""));
10356        let rendered = c.validate_edicao().unwrap_err().to_string();
10357        assert!(
10358            rendered.contains(":edicao"),
10359            "diagnostic must name the offending slot: {rendered}",
10360        );
10361    }
10362
10363    #[test]
10364    fn validate_edicao_invalid_diagnostic_carries_offending_value() {
10365        // Diagnostic-shape pin on the shape-predicate arm (peer
10366        // with `validate_repositorio_diagnostic_carries_offending_value`):
10367        // the error's Display surfaces the offending value + slot
10368        // name verbatim, so a `feira lint` run can render the
10369        // diagnostic without re-parsing and the author can grep
10370        // their caixa.lisp for the offending `:edicao` value.
10371        let c = caixa_with_edicao(Some("v2026"));
10372        let rendered = c.validate_edicao().unwrap_err().to_string();
10373        assert!(
10374            rendered.contains(":edicao"),
10375            "diagnostic must name the offending slot: {rendered}",
10376        );
10377        assert!(
10378            rendered.contains("v2026"),
10379            "diagnostic must quote the offending value: {rendered}",
10380        );
10381    }
10382
10383    // ── Caixa::edicao — outer top-level Option<&str> scalar accessor ──
10384
10385    #[test]
10386    fn edicao_returns_edicao_byte_string_verbatim_across_permutations() {
10387        // The canonical per-`Caixa` `:edicao` language-edition scalar
10388        // pin: [`Caixa::edicao`] must return the `:edicao` typed
10389        // byte-string verbatim as an `Option<&str>`, byte-equal to the
10390        // raw `self.edicao.as_deref()` access across every representative
10391        // value in the accept-set — `None` (the "omit the slot to defer
10392        // to the substrate's default edition" arm every existing
10393        // [`caixa-resolver`] fixture without an `:edicao` line carries),
10394        // `Some("")` (a past-the-guard sentinel that pins the accessor
10395        // doesn't perform a silent `Some("") → None` collapse on the
10396        // empty arm — validate rejects `Some("")` through `EdicaoEmpty`
10397        // but the accessor must ship the raw slot verbatim so a
10398        // validate-time gate regression surfaces at any future edition-
10399        // aware consumer's boundary rather than being silently absorbed
10400        // into the substrate's default edition), `Some("2026")` (the
10401        // canonical 4-digit-ASCII-decimal-year shape every `feira init`
10402        // template scaffolds via [`Caixa::template`] and every
10403        // renderer-side fixture at `caixa-helm/src/lib.rs:978` /
10404        // `caixa-flux/src/lib.rs:2319` / `caixa-mesh/src/lib.rs:3208`
10405        // carries by construction), `Some("2018")` / `Some("2021")` /
10406        // `Some("2024")` (canonical 4-digit-ASCII-decimal-year shapes
10407        // peer with Cargo's `[package] edition` grammar every future-
10408        // introduced sibling to `"2026"` will follow), and eight
10409        // past-the-guard sentinels for the `EdicaoInvalid` refusal cases
10410        // (`Some("2026 ")` trailing-whitespace, `Some(" 2026")` leading-
10411        // whitespace, `Some("2026\n")` embedded-LF, `Some("2026")`
10412        // fullwidth-non-ASCII-lookalike, `Some("v2026")` version-tag-
10413        // prefix, `Some("2026.1")` decimal-shape, `Some("26")` wrong-
10414        // length-numeric, `Some("latest")` free-form-non-year — the
10415        // sentinels pin the accessor doesn't silently absorb the
10416        // refusal cases into a substrate-default-edition fallback).
10417        //
10418        // Fourth and final outer top-level [`Caixa`] `Option<&str>`-
10419        // return scalar accessor pin on the substrate primitive —
10420        // sibling of the peer [`Caixa::licenca`] (6d5bc28),
10421        // [`Caixa::repositorio`] (cc7332d), and [`Caixa::descricao`]
10422        // (3f16e2f) pins that opened the "outer [`Caixa`]
10423        // `Option<&str>` scalar" projection pin pattern this pin folds
10424        // on. Sibling in shape to the peer per-`:placement`
10425        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
10426        // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
10427        // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
10428        // axes, extended onto the outer top-level [`Caixa`] universal-
10429        // axis surface's last unlifted `Option<String>` slot. Pins
10430        // against a future silent detour that returned an owned
10431        // `Option<String>` (which would type-check but silently
10432        // allocate on every accessor call, breaking the zero-cost
10433        // projection every peer sibling accessor carries), a
10434        // `Some("") → None` collapse (which would silently absorb the
10435        // `EdicaoEmpty` refusal case at the accessor boundary and any
10436        // future edition-aware consumer would silently fall back to
10437        // the substrate's default edition on a struct-literal
10438        // `Caixa { edicao: Some(""), .. }`), or a
10439        // `None → Some("2026")` collapse (which would silently reify
10440        // the substrate's default edition at the accessor boundary
10441        // and every downstream consumer keying off the
10442        // `Option::is_none()` discriminator would lose the "author
10443        // omitted the slot" signal).
10444        for edicao in [
10445            None,
10446            Some(""),
10447            Some("2026"),
10448            Some("2018"),
10449            Some("2021"),
10450            Some("2024"),
10451            Some("2026 "),
10452            Some(" 2026"),
10453            Some("2026\n"),
10454            Some("2026"),
10455            Some("v2026"),
10456            Some("2026.1"),
10457            Some("26"),
10458            Some("latest"),
10459        ] {
10460            let c = caixa_with_edicao(edicao);
10461            assert_eq!(
10462                c.edicao(),
10463                edicao,
10464                "Caixa::edicao must return :edicao verbatim (got {:?}, \
10465                 expected {edicao:?})",
10466                c.edicao(),
10467            );
10468            assert_eq!(
10469                c.edicao(),
10470                c.edicao.as_deref(),
10471                "Caixa::edicao must byte-equal the raw \
10472                 `self.edicao.as_deref()` field access across every \
10473                 value in the Option<&str> accept-set",
10474            );
10475        }
10476    }
10477
10478    #[test]
10479    fn validate_edicao_empty_arm_routes_through_accessor() {
10480        // Composition pin: [`Caixa::validate_edicao`]'s empty-arm gate
10481        // must key off [`Caixa::edicao`], not the raw
10482        // `self.edicao.as_deref()` field access. Structurally: a
10483        // `Caixa { edicao: Some(""), .. }` must surface the
10484        // `EdicaoEmpty` refusal exactly, and a
10485        // `Caixa { edicao: Some("2026"), .. }` (the canonical
10486        // 4-digit-ASCII-decimal-year form) must pass validate. The
10487        // pair jointly pins the accessor + validate-gate composition:
10488        // any future silent detour that had the accessor return `None`
10489        // on the empty arm (a `.filter(|s| !s.is_empty())` collapse)
10490        // would silently absorb the `EdicaoEmpty` refusal at the
10491        // accessor boundary and the validate gate would accept a
10492        // struct-literal `Caixa { edicao: Some(""), .. }` — the
10493        // composition pin catches that at caixa-core build time.
10494        //
10495        // Peer of the [`Caixa::licenca`] (6d5bc28)
10496        // `validate_licenca_empty_arm_routes_through_accessor`,
10497        // [`Caixa::repositorio`] (cc7332d)
10498        // `validate_repositorio_empty_arm_routes_through_accessor`,
10499        // and [`Caixa::descricao`] (3f16e2f)
10500        // `validate_descricao_empty_arm_routes_through_accessor`
10501        // composition pins on the sibling outer top-level [`Caixa`]
10502        // `Option<&str>` universal-axis surface — same "the validate /
10503        // shape-gate predicate must route through the substrate-
10504        // primitive typed dispatch" discipline extended onto the
10505        // fourth and final outer top-level [`Caixa`] universal-axis
10506        // `Option<&str>`-composition surface, closing the accessor-
10507        // composition family.
10508        let c = caixa_with_edicao(Some(""));
10509        assert!(
10510            matches!(c.validate_edicao(), Err(ManifestError::EdicaoEmpty)),
10511            "validate_edicao must reject edicao == Some(\"\") with \
10512             EdicaoEmpty — the accessor and the validate gate must \
10513             route through the same substrate-primitive typed dispatch \
10514             on the :edicao empty arm",
10515        );
10516        let c = caixa_with_edicao(Some("2026"));
10517        assert!(
10518            c.validate_edicao().is_ok(),
10519            "validate_edicao must accept edicao == Some(\"2026\") \
10520             (the canonical 4-digit-ASCII-decimal-year shape)",
10521        );
10522    }
10523
10524    #[test]
10525    fn edicao_projects_option_str_by_borrow() {
10526        // The by-borrow pin: [`Caixa::edicao`] returns
10527        // `Option<&str>` by borrow — the `&str` borrows the underlying
10528        // `String` storage of the `Option<String>` slot and the
10529        // accessor must not allocate a fresh `String` on every call.
10530        // Peer of the [`Caixa::licenca`] (6d5bc28),
10531        // [`Caixa::repositorio`] (cc7332d), and [`Caixa::descricao`]
10532        // (3f16e2f) by-borrow pins on the peer outer top-level
10533        // [`Caixa`] `Option<&str>`-return axes, and of the
10534        // per-`:placement`
10535        // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
10536        // borrow pin on the peer per-M3-mesh-slot `Option<&str>`-
10537        // return axis, extended onto the fourth and final outer top-
10538        // level [`Caixa`] universal-axis `Option<&str>` shape — the
10539        // accessor's returned `&str` must borrow from `&self` (the
10540        // returned reference's lifetime is tied to `&self`), and
10541        // calling the accessor twice on the same [`Caixa`] must yield
10542        // the same `Option<&str>` verbatim (idempotent, no side
10543        // effects on `&self`).
10544        //
10545        // Pins against a future silent detour that returned an owned
10546        // `Option<String>` (which would type-check but silently
10547        // allocate on every call, breaking the zero-cost projection
10548        // every peer sibling accessor carries), or a one-arm-only
10549        // accessor that returned a saturating value on some sentinel
10550        // input (breaking the pass-through invariant the sibling
10551        // required-scalar accessors carry).
10552        for edicao in [None, Some(""), Some("2026"), Some("2018")] {
10553            let c = caixa_with_edicao(edicao);
10554            let first = c.edicao();
10555            let second = c.edicao();
10556            assert_eq!(
10557                first, second,
10558                "Caixa::edicao must be idempotent — two successive \
10559                 calls on the same &self must return the same \
10560                 Option<&str>",
10561            );
10562            assert_eq!(
10563                first, edicao,
10564                "Caixa::edicao must return :edicao verbatim by \
10565                 borrow — got {first:?}, expected {edicao:?}",
10566            );
10567        }
10568    }
10569
10570    #[test]
10571    fn nome_returns_nome_byte_string_verbatim_across_permutations() {
10572        // The canonical per-`Caixa` `:nome` universal-axis DNS-1123-
10573        // label caixa-identity scalar pin: [`Caixa::nome`] must return
10574        // the `:nome` typed `String` verbatim as `&str`, byte-equal to
10575        // the raw field access across every representative value in
10576        // the accept-set — the canonical `"demo"` template baseline
10577        // (the same `feira init`-scaffolded default the sibling
10578        // `validate_nome_accepts_canonical_template` positive-control
10579        // gate pins), plus every sibling per-typed-slot atom accessor's
10580        // canonical positive-arm byte-string (`"catalog"` per
10581        // [`crate::aplicacao::Membro::nome`], `"cart"` per the peer
10582        // per-`:contratos` `:de`, `"hello-rio"` per the canonical
10583        // `caixa-helm`/`caixa-flux` cross-crate integration-test
10584        // fixture, `"checkout"` per the M3 mesh-slot Aplicacao
10585        // canonical example), plus every past-the-guard sentinel for
10586        // the `NomeEmpty` / `NomeInvalid` / `NomeChartNameBudgetExceeded`
10587        // refusal cases (`""`, `"Bad_Name"`, `"a"` × 56 — 56 bytes fits
10588        // the bare DNS-1123 63-byte cap but overflows the joint
10589        // `lareira-<nome>` chart-name budget the sibling
10590        // [`Caixa::validate_nome_chart_name_budget`] gate closes on).
10591        //
10592        // The past-the-guard sentinels pin the accessor doesn't
10593        // silently absorb the refusal cases into a template-derived
10594        // fallback (a future `.nome().is_empty().then(|| "demo")`
10595        // collapse would silently absorb the `NomeEmpty` refusal at
10596        // the accessor boundary and the validate gate would accept a
10597        // struct-literal `Caixa { nome: "".into(), .. }` — the pin
10598        // catches that at caixa-core build time).
10599        //
10600        // First outer top-level [`Caixa`] `&str`-return required-
10601        // scalar accessor pin — opens the "outer [`Caixa`] `&str`
10602        // required-scalar" projection pattern the sibling per-`Caixa`
10603        // `:versao` future lift folds on. Sibling in shape to the peer
10604        // per-`:membros` [`crate::aplicacao::Membro::nome`] (4a32abf)
10605        // required-`String`-carry accessor pin on the sibling per-
10606        // sub-struct required-axis, extended onto the outer top-level
10607        // [`Caixa`] universal-axis required-`String`-carry axis.
10608        for nome in [
10609            "demo",
10610            "catalog",
10611            "cart",
10612            "hello-rio",
10613            "checkout",
10614            "",
10615            "Bad_Name",
10616            "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
10617        ] {
10618            let c = caixa_with_nome(nome);
10619            assert_eq!(
10620                c.nome(),
10621                nome,
10622                "Caixa::nome must return :nome verbatim (got {}, \
10623                 expected {nome})",
10624                c.nome(),
10625            );
10626            assert_eq!(
10627                c.nome(),
10628                c.nome.as_str(),
10629                "Caixa::nome must byte-equal the raw .nome field \
10630                 access across every value in the String accept-set",
10631            );
10632        }
10633    }
10634
10635    #[test]
10636    fn validate_nome_empty_arm_routes_through_accessor() {
10637        // Composition pin: [`Caixa::validate_nome`]'s empty-arm must
10638        // key off [`Caixa::nome`], not the raw `.nome` field access.
10639        // Structurally: a `Caixa { nome: "".into(), .. }` must surface
10640        // the `NomeEmpty` refusal exactly, and the canonical `"demo"`
10641        // template baseline (the peer positive-arm the sibling
10642        // `validate_nome_accepts_canonical_template` gate carves out)
10643        // must pass validate. The pair jointly pins the accessor +
10644        // validate-gate composition: any future silent detour that
10645        // had the accessor return a fresh `"demo"` on the empty arm
10646        // (a `.nome().is_empty().then(|| "demo")` fallback collapse)
10647        // would silently absorb the `NomeEmpty` refusal at the
10648        // accessor boundary and the validate gate would accept a
10649        // struct-literal `Caixa { nome: "".into(), .. }` — the
10650        // composition pin catches that at caixa-core build time.
10651        //
10652        // Peer of the sibling per-`Caixa`
10653        // `validate_licenca_empty_arm_routes_through_accessor` (6d5bc28)
10654        // / `validate_repositorio_empty_arm_routes_through_accessor`
10655        // (cc7332d) / `validate_descricao_empty_arm_routes_through_accessor`
10656        // (3f16e2f) / `validate_edicao_empty_arm_routes_through_accessor`
10657        // (2641cbd) composition pins on the sibling outer top-level
10658        // [`Caixa`] `Option<&str>` axes — same "the validate /
10659        // shape-gate predicate must route through the substrate-
10660        // primitive typed dispatch" discipline extended onto the peer
10661        // outer top-level [`Caixa`] required-`&str` composition axis.
10662        let c = caixa_with_nome("");
10663        assert!(
10664            matches!(c.validate_nome(), Err(ManifestError::NomeEmpty)),
10665            "validate_nome must reject nome == \"\" with NomeEmpty — \
10666             the accessor and the validate gate must route through the \
10667             same substrate-primitive typed dispatch on the :nome \
10668             empty-arm",
10669        );
10670        let c = caixa_with_nome("demo");
10671        assert!(
10672            c.validate_nome().is_ok(),
10673            "validate_nome must accept nome == \"demo\" (the canonical \
10674             DNS-1123-label template baseline)",
10675        );
10676    }
10677
10678    #[test]
10679    fn nome_projects_str_by_borrow() {
10680        // The by-borrow pin: [`Caixa::nome`] returns `&str` by borrow
10681        // — the `&str` borrows the underlying `String` storage of the
10682        // required `nome` slot and the accessor must not allocate a
10683        // fresh `String` on every call. Peer of the [`Caixa::licenca`]
10684        // (6d5bc28) / [`Caixa::repositorio`] (cc7332d) /
10685        // [`Caixa::descricao`] (3f16e2f) / [`Caixa::edicao`] (2641cbd)
10686        // by-borrow pins on the peer outer top-level [`Caixa`]
10687        // `Option<&str>`-return axes, extended onto the first outer
10688        // top-level [`Caixa`] required-`&str`-return axis — the
10689        // accessor's returned `&str` must borrow from `&self` (the
10690        // returned reference's lifetime is tied to `&self`), and
10691        // calling the accessor twice on the same [`Caixa`] must yield
10692        // the same `&str` verbatim (idempotent, no side effects on
10693        // `&self`).
10694        //
10695        // Pins against a future silent detour that returned an owned
10696        // `String` (which would type-check but silently allocate on
10697        // every call, breaking the zero-cost projection every peer
10698        // sibling accessor carries), an accidental
10699        // `.nome.to_lowercase()` detour that returned a fresh
10700        // allocation through an already-DNS-1123-lowercase-only
10701        // string (breaking a future `const fn` regression), or a
10702        // one-arm-only accessor that returned a canonicalized value
10703        // on some sentinel input (breaking the pass-through invariant
10704        // the sibling required-scalar accessors carry).
10705        for nome in ["demo", "catalog", "hello-rio", "checkout"] {
10706            let c = caixa_with_nome(nome);
10707            let first = c.nome();
10708            let second = c.nome();
10709            assert_eq!(
10710                first, second,
10711                "Caixa::nome must be idempotent — two successive calls \
10712                 on the same &self must return the same &str",
10713            );
10714            assert_eq!(
10715                first, nome,
10716                "Caixa::nome must return :nome verbatim by borrow — \
10717                 got {first}, expected {nome}",
10718            );
10719        }
10720    }
10721
10722    #[test]
10723    fn versao_returns_versao_byte_string_verbatim_across_permutations() {
10724        // The canonical per-`Caixa` `:versao` universal-axis SemVer-2
10725        // pinned-version scalar pin: [`Caixa::versao`] must return the
10726        // `:versao` typed `String` verbatim as `&str`, byte-equal to the
10727        // raw `.versao` field access across every representative value
10728        // in the accept-set — the canonical `"0.1.0"` template baseline
10729        // (the same `feira init`-scaffolded default the sibling
10730        // `validate_versao_accepts_canonical_template` positive-control
10731        // gate pins), plus every canonical SemVer-2 shape the sibling
10732        // `validate_versao_accepts_canonical_forms` positive-arm sweep
10733        // covers (`"0.0.0"`, `"1.0.0"`, `"0.2.0-rc.1"`,
10734        // `"1.0.0-alpha.0"`, `"1.0.0+build.42"`, `"1.0.0-rc.1+build.42"`,
10735        // `"10.20.30"`), plus every past-the-guard sentinel for the
10736        // `VersaoEmpty` / `VersaoInvalid` refusal cases (`""` the empty
10737        // arm, `"v0.1.0"` the git-tag-shape-leak footgun, `"0.1"` the
10738        // missing-patch footgun, `"^0.1"` the requirement-shape-leak
10739        // footgun, `"0.1.0.0"` the four-part-Java-convention footgun,
10740        // `"latest"` the docker-tag-shape footgun — the sentinels pin
10741        // the accessor doesn't silently absorb the refusal cases into a
10742        // template-derived fallback like `"0.1.0"`).
10743        //
10744        // The past-the-guard sentinels pin the accessor doesn't silently
10745        // absorb the refusal cases into a template-derived fallback (a
10746        // future `.versao().is_empty().then(|| "0.1.0")` collapse would
10747        // silently absorb the `VersaoEmpty` refusal at the accessor
10748        // boundary and the validate gate would accept a struct-literal
10749        // `Caixa { versao: "".into(), .. }` — the pin catches that at
10750        // caixa-core build time).
10751        //
10752        // Second outer top-level [`Caixa`] `&str`-return required-scalar
10753        // accessor pin — folds on the "outer [`Caixa`] `&str` required-
10754        // scalar" projection pattern the sibling per-`Caixa`
10755        // [`Caixa::nome`] (e6b7d97) opened. Sibling in shape to the peer
10756        // per-`:membros` [`crate::aplicacao::Membro::versao_requirement`]
10757        // (4127bb6) / per-`:children`
10758        // [`crate::supervisor::ChildSpec::versao_requirement`] (2c053c8)
10759        // / per-`:upgrade-from`
10760        // [`crate::UpgradeFromEntry::prior_versao`] (75d27a8) per-sub-
10761        // struct `:versao`-shaped `&str`-return accessor pins on the
10762        // sibling per-typed-slot version-carrier axes, extended onto the
10763        // second outer top-level [`Caixa`] universal-axis required-
10764        // `String`-carry axis so the two universal-axis identity-
10765        // carrying scalars every `defcaixa` form supplies (`:nome` +
10766        // `:versao`) share the same "one typed dispatch per axis" pin
10767        // discipline.
10768        for versao in [
10769            "0.1.0",
10770            "0.0.0",
10771            "1.0.0",
10772            "0.2.0-rc.1",
10773            "1.0.0-alpha.0",
10774            "1.0.0+build.42",
10775            "1.0.0-rc.1+build.42",
10776            "10.20.30",
10777            "",
10778            "v0.1.0",
10779            "0.1",
10780            "^0.1",
10781            "0.1.0.0",
10782            "latest",
10783        ] {
10784            let c = caixa_with_versao(versao);
10785            assert_eq!(
10786                c.versao(),
10787                versao,
10788                "Caixa::versao must return :versao verbatim (got {}, \
10789                 expected {versao})",
10790                c.versao(),
10791            );
10792            assert_eq!(
10793                c.versao(),
10794                c.versao.as_str(),
10795                "Caixa::versao must byte-equal the raw .versao field \
10796                 access across every value in the String accept-set",
10797            );
10798        }
10799    }
10800
10801    #[test]
10802    fn validate_versao_empty_arm_routes_through_accessor() {
10803        // Composition pin: [`Caixa::validate_versao`]'s empty-arm gate
10804        // must key off [`Caixa::versao`], not the raw `.versao` field
10805        // access. Structurally: a `Caixa { versao: "".into(), .. }` must
10806        // surface the `VersaoEmpty` refusal exactly, and the canonical
10807        // `"0.1.0"` template baseline (the peer positive-arm the sibling
10808        // `validate_versao_accepts_canonical_template` gate carves out)
10809        // must pass validate. The pair jointly pins the accessor +
10810        // validate-gate composition: any future silent detour that had
10811        // the accessor return a fresh `"0.1.0"` on the empty arm
10812        // (a `.versao().is_empty().then(|| "0.1.0")` fallback collapse)
10813        // would silently absorb the `VersaoEmpty` refusal at the
10814        // accessor boundary and the validate gate would accept a
10815        // struct-literal `Caixa { versao: "".into(), .. }` — the
10816        // composition pin catches that at caixa-core build time.
10817        //
10818        // Peer of the sibling per-`Caixa`
10819        // `validate_nome_empty_arm_routes_through_accessor` (e6b7d97)
10820        // composition pin on the sibling outer top-level [`Caixa`]
10821        // required-`&str` universal-axis surface — same "the validate /
10822        // shape-gate predicate must route through the substrate-
10823        // primitive typed dispatch" discipline extended onto the peer
10824        // outer top-level [`Caixa`] required-`&str` universal-axis
10825        // pinned-version composition axis, closing the second
10826        // coordinate of the "one canonical typed dispatch per per-Caixa
10827        // required-`&str` universal-axis" discipline.
10828        let c = caixa_with_versao("");
10829        assert!(
10830            matches!(c.validate_versao(), Err(ManifestError::VersaoEmpty)),
10831            "validate_versao must reject versao == \"\" with VersaoEmpty — \
10832             the accessor and the validate gate must route through the \
10833             same substrate-primitive typed dispatch on the :versao \
10834             empty-arm",
10835        );
10836        let c = caixa_with_versao("0.1.0");
10837        assert!(
10838            c.validate_versao().is_ok(),
10839            "validate_versao must accept versao == \"0.1.0\" (the \
10840             canonical SemVer-2 template baseline)",
10841        );
10842    }
10843
10844    #[test]
10845    fn versao_projects_str_by_borrow() {
10846        // The by-borrow pin: [`Caixa::versao`] returns `&str` by borrow
10847        // — the `&str` borrows the underlying `String` storage of the
10848        // required `versao` slot and the accessor must not allocate a
10849        // fresh `String` on every call. Peer of the [`Caixa::nome`]
10850        // (e6b7d97) by-borrow pin on the sibling outer top-level
10851        // [`Caixa`] required-`&str`-return axis, extended onto the
10852        // second outer top-level [`Caixa`] required-`&str`-return
10853        // universal-axis pinned-version surface — the accessor's
10854        // returned `&str` must borrow from `&self` (the returned
10855        // reference's lifetime is tied to `&self`), and calling the
10856        // accessor twice on the same [`Caixa`] must yield the same
10857        // `&str` verbatim (idempotent, no side effects on `&self`).
10858        //
10859        // Pins against a future silent detour that returned an owned
10860        // `String` (which would type-check but silently allocate on
10861        // every call, breaking the zero-cost projection every peer
10862        // sibling accessor carries), an accidental
10863        // `semver::Version::parse(&self.versao).unwrap().to_string()`
10864        // detour that returned a canonicalized fresh allocation through
10865        // an already-canonical byte-string (breaking a future `const fn`
10866        // regression and silently absorbing the `VersaoInvalid` refusal
10867        // at the accessor boundary), or a one-arm-only accessor that
10868        // returned a canonicalized value on some sentinel input
10869        // (breaking the pass-through invariant the sibling required-
10870        // scalar accessors carry).
10871        for versao in ["0.1.0", "1.0.0", "0.2.0-rc.1", "1.0.0+build.42"] {
10872            let c = caixa_with_versao(versao);
10873            let first = c.versao();
10874            let second = c.versao();
10875            assert_eq!(
10876                first, second,
10877                "Caixa::versao must be idempotent — two successive \
10878                 calls on the same &self must return the same &str",
10879            );
10880            assert_eq!(
10881                first, versao,
10882                "Caixa::versao must return :versao verbatim by borrow \
10883                 — got {first}, expected {versao}",
10884            );
10885        }
10886    }
10887
10888    fn caixa_with_kind(kind: CaixaKind) -> Caixa {
10889        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10890        c.kind = kind;
10891        c
10892    }
10893
10894    #[test]
10895    fn kind_returns_kind_variant_verbatim_across_permutations() {
10896        // The canonical per-`Caixa` `:kind` universal-axis closed-set-
10897        // enum discriminant pin: [`Caixa::kind`] must return the `:kind`
10898        // typed [`CaixaKind`] variant verbatim by `Copy`, byte-equal to
10899        // the raw `.kind` field access across every variant in the
10900        // closed accept-set (`Biblioteca` — the library kind that
10901        // exports lisp forms; `Binario` — the nix-built executable kind
10902        // under `exe/`; `Servico` — the wasm-component daemon kind
10903        // under `servicos/`; `Supervisor` — the OTP-shaped hierarchical
10904        // reconciliation kind; `Aplicacao` — the M3 typed-mesh
10905        // composition kind).
10906        //
10907        // Pins against a future silent detour that re-derived the kind
10908        // from a peer axis (an accidental fallback to
10909        // `if !servicos.is_empty() { Servico } else if
10910        // !membros.is_empty() { Aplicacao } else { Biblioteca }`
10911        // collapse that read the code-surface / mesh-slot columns into
10912        // the kind discriminator), a variant remap the operator
10913        // authors on one consumer without the other, or a stale-derive
10914        // detour that substituted [`CaixaKind::Biblioteca`] as the
10915        // default when the field held any other variant (which would
10916        // silently collapse the distinction between "author explicitly
10917        // declared `:kind Servico`" and "author declared any other
10918        // kind" every downstream renderer-dispatch site depends on).
10919        //
10920        // First outer top-level [`Caixa`] `Copy`-return required-enum-
10921        // discriminant accessor pin — opens the "outer [`Caixa`]
10922        // `Copy`-return required-discriminant" projection pattern.
10923        // Sibling in shape to the peer per-`:supervisor`
10924        // [`crate::supervisor::SupervisorSpec::estrategia`] (eafb619),
10925        // per-`:placement` [`crate::aplicacao::Placement::estrategia`]
10926        // (921fe1b), and per-`:children`
10927        // [`crate::supervisor::ChildSpec::restart`] (dfb4a81)
10928        // `Copy`-return closed-set-enum discriminant accessor pins on
10929        // the sibling nested-spec typed-slot discriminator axes,
10930        // extended here to the outer top-level [`Caixa`] universal-
10931        // axis surface.
10932        for kind in [
10933            CaixaKind::Biblioteca,
10934            CaixaKind::Binario,
10935            CaixaKind::Servico,
10936            CaixaKind::Supervisor,
10937            CaixaKind::Aplicacao,
10938        ] {
10939            let c = caixa_with_kind(kind);
10940            assert_eq!(
10941                c.kind(),
10942                kind,
10943                "Caixa::kind must return :kind verbatim (got {:?}, \
10944                 expected {kind:?})",
10945                c.kind(),
10946            );
10947            assert_eq!(
10948                c.kind(),
10949                c.kind,
10950                "Caixa::kind accessor and .kind field access must \
10951                 byte-equal — the accessor is the substrate-primitive \
10952                 typed dispatch every downstream kind-gate consumer \
10953                 must route through",
10954            );
10955        }
10956    }
10957
10958    #[test]
10959    fn require_kind_reads_through_lifted_kind_accessor() {
10960        // Two-consumer coherence pin: the [`crate::render::require_kind`]
10961        // entry-gate predicate (the canonical two-line
10962        // `require_kind(caixa, Servico)?` prelude every per-Servico /
10963        // per-Aplicacao renderer runs at its entry-point) and the
10964        // sibling [`crate::render::KindMismatch`] error carrier's
10965        // `actual:` field (which names the offending caixa's variant
10966        // in the diagnostic) must both key off the lifted accessor, so
10967        // any future rebrand on the typed slot's reader shape lands at
10968        // exactly one place. Pins the two-site coherence by exercising
10969        // every off-diagonal `(actual, expected)` pair across the
10970        // closed accept-set — the `KindMismatch { actual, expected }`
10971        // surfaced on the mismatch arm must byte-equal the pair the
10972        // accessor returns for each side.
10973        //
10974        // Peer of the sibling per-`:placement`
10975        // `validate_placement_reads_through_lifted_estrategia_accessor`
10976        // (921fe1b) two-arm consumer-coherence pin on the M3 mesh-slot
10977        // `Copy`-return discriminant axis — same "the entry-gate
10978        // predicate and the error carrier's `actual:` field must route
10979        // through the substrate-primitive typed dispatch" discipline
10980        // extended onto the outer top-level [`Caixa`] universal-axis
10981        // discriminant surface.
10982        for expected in [
10983            CaixaKind::Biblioteca,
10984            CaixaKind::Binario,
10985            CaixaKind::Servico,
10986            CaixaKind::Supervisor,
10987            CaixaKind::Aplicacao,
10988        ] {
10989            for actual in [
10990                CaixaKind::Biblioteca,
10991                CaixaKind::Binario,
10992                CaixaKind::Servico,
10993                CaixaKind::Supervisor,
10994                CaixaKind::Aplicacao,
10995            ] {
10996                let c = caixa_with_kind(actual);
10997                let result = crate::render::require_kind(&c, expected);
10998                if expected == actual {
10999                    assert!(
11000                        result.is_ok(),
11001                        "require_kind must accept when actual == expected \
11002                         (actual={actual:?}, expected={expected:?})",
11003                    );
11004                } else {
11005                    let err = result.expect_err("require_kind must reject when actual != expected");
11006                    assert_eq!(
11007                        err.actual,
11008                        c.kind(),
11009                        "KindMismatch.actual must byte-equal Caixa::kind() \
11010                         — the error carrier's `actual:` field reads \
11011                         through the lifted accessor",
11012                    );
11013                    assert_eq!(
11014                        err.expected, expected,
11015                        "KindMismatch.expected must byte-equal the \
11016                         expected variant passed to require_kind",
11017                    );
11018                }
11019            }
11020        }
11021    }
11022
11023    #[test]
11024    fn aplicacao_view_kind_gate_routes_through_accessor() {
11025        // Composition pin: [`Caixa::aplicacao_view`]'s kind-gate arm
11026        // must key off [`Caixa::kind`], not the raw `.kind` field
11027        // access. Structurally: a `Caixa { kind: X, .. }` for any
11028        // non-`Aplicacao` variant must fold to `None` on the
11029        // `aplicacao_view` composer (the "kind mismatch → no typed
11030        // view" contract every downstream Aplicacao consumer keys off
11031        // via `?`), and a `Caixa { kind: Aplicacao, .. }` must fold to
11032        // `Some(_)`. The pair jointly pins the accessor + view-gate
11033        // composition: any future silent detour that had the accessor
11034        // return a fresh [`CaixaKind::Aplicacao`] on some sentinel
11035        // input would silently absorb the kind-mismatch case at the
11036        // accessor boundary and every per-Aplicacao renderer would
11037        // silently render a non-Aplicacao caixa's mesh slots — the
11038        // composition pin catches that at caixa-core build time.
11039        //
11040        // Peer of the sibling per-`Caixa`
11041        // `validate_nome_empty_arm_routes_through_accessor` (e6b7d97) /
11042        // `validate_versao_empty_arm_routes_through_accessor` (20c0539)
11043        // composition pins on the sibling outer top-level [`Caixa`]
11044        // required-`&str` universal-axis surfaces — same "the
11045        // composer / validate gate must route through the substrate-
11046        // primitive typed dispatch" discipline extended onto the
11047        // outer top-level [`Caixa`] `Copy`-return required-
11048        // discriminant composition axis.
11049        for kind in [
11050            CaixaKind::Biblioteca,
11051            CaixaKind::Binario,
11052            CaixaKind::Servico,
11053            CaixaKind::Supervisor,
11054        ] {
11055            let c = caixa_with_kind(kind);
11056            assert!(
11057                c.aplicacao_view().is_none(),
11058                "aplicacao_view must return None on non-Aplicacao \
11059                 kind {kind:?} — the composer's kind-gate must route \
11060                 through Caixa::kind()",
11061            );
11062        }
11063        let c = caixa_with_kind(CaixaKind::Aplicacao);
11064        assert!(
11065            c.aplicacao_view().is_some(),
11066            "aplicacao_view must return Some on kind Aplicacao — \
11067             the composer's kind-gate must accept the matching arm \
11068             through Caixa::kind()",
11069        );
11070    }
11071
11072    #[test]
11073    fn supervisor_view_kind_gate_routes_through_accessor() {
11074        // Composition pin (mirror of the sibling
11075        // `aplicacao_view_kind_gate_routes_through_accessor` on the
11076        // second `_view` composer): [`Caixa::supervisor_view`]'s kind-
11077        // gate arm must key off [`Caixa::kind`], not the raw `.kind`
11078        // field access. A `Caixa { kind: X, .. }` for any non-
11079        // `Supervisor` variant must fold to `None` on the
11080        // `supervisor_view` composer, and a `Caixa { kind:
11081        // Supervisor, .. }` must fold to `Some(_)`. Same peer
11082        // composition pin discipline on the second `_view` composer
11083        // axis.
11084        for kind in [
11085            CaixaKind::Biblioteca,
11086            CaixaKind::Binario,
11087            CaixaKind::Servico,
11088            CaixaKind::Aplicacao,
11089        ] {
11090            let c = caixa_with_kind(kind);
11091            assert!(
11092                c.supervisor_view().is_none(),
11093                "supervisor_view must return None on non-Supervisor \
11094                 kind {kind:?} — the composer's kind-gate must route \
11095                 through Caixa::kind()",
11096            );
11097        }
11098        let mut c = caixa_with_kind(CaixaKind::Supervisor);
11099        // A Supervisor caixa needs a strategy + at least one child to
11100        // fold to a Some(_) that also validates; the composer itself
11101        // requires only the kind arm, so bare kind flip is enough to
11102        // pin the `Some(_)` return, but we populate the minimum
11103        // supervisor shape so a future strengthening of the composer
11104        // to reject an empty spec doesn't false-positive this pin.
11105        c.estrategia = Some(crate::supervisor::RestartStrategy::OneForOne);
11106        c.children = vec![crate::supervisor::ChildSpec {
11107            caixa: "child".into(),
11108            versao: "^0.1".into(),
11109            restart: crate::supervisor::RestartPolicy::Permanent,
11110        }];
11111        assert!(
11112            c.supervisor_view().is_some(),
11113            "supervisor_view must return Some on kind Supervisor — \
11114             the composer's kind-gate must accept the matching arm \
11115             through Caixa::kind()",
11116        );
11117    }
11118
11119    #[test]
11120    fn kind_projects_by_copy() {
11121        // The by-`Copy` pin: [`Caixa::kind`] returns a fresh
11122        // [`CaixaKind`] by `Copy` — the accessor must not borrow from
11123        // `&self` (the returned value is owned, `Copy`-projected from
11124        // the underlying [`CaixaKind`] storage; two calls on the same
11125        // [`Caixa`] must yield byte-equal values). Peer of the peer
11126        // per-`:placement` `Placement::estrategia` / per-`:supervisor`
11127        // `SupervisorSpec::estrategia` / per-`:children`
11128        // `ChildSpec::restart` `Copy`-return discriminant accessor
11129        // pins on the sibling nested-spec typed-slot discriminator
11130        // axes, extended onto the first outer top-level [`Caixa`]
11131        // required-`Copy`-return axis — pins against a future silent
11132        // detour that returned `&CaixaKind` (which would type-check
11133        // but silently constrain every consumer's callsite to a
11134        // borrow-shaped dispatch, breaking the zero-cost `Copy`
11135        // projection every peer sibling accessor carries).
11136        for kind in [
11137            CaixaKind::Biblioteca,
11138            CaixaKind::Binario,
11139            CaixaKind::Servico,
11140            CaixaKind::Supervisor,
11141            CaixaKind::Aplicacao,
11142        ] {
11143            let c = caixa_with_kind(kind);
11144            let first: CaixaKind = c.kind();
11145            let second: CaixaKind = c.kind();
11146            assert_eq!(
11147                first, second,
11148                "Caixa::kind must be idempotent — two successive \
11149                 calls on the same &self must return the same \
11150                 CaixaKind variant",
11151            );
11152            assert_eq!(
11153                first, kind,
11154                "Caixa::kind must return :kind verbatim by Copy — \
11155                 got {first:?}, expected {kind:?}",
11156            );
11157        }
11158    }
11159
11160    // ── Caixa::autores — outer top-level &[T] slice accessor ──────────
11161
11162    #[test]
11163    fn autores_returns_autores_slice_verbatim_across_permutations() {
11164        // The canonical per-`Caixa` `:autores` universal-axis maintainer-
11165        // name-list slice pin: [`Caixa::autores`] must return the
11166        // `:autores` typed [`Vec<String>`] list verbatim as a
11167        // `&[String]`, byte-equal to the raw `self.autores.as_slice()`
11168        // access across every representative value in the accept-set —
11169        // `[]` (the "no maintainers declared" arm every existing
11170        // fixture without an `:autores` line carries), `[""]` (a past-
11171        // the-guard sentinel that pins the accessor doesn't perform a
11172        // silent `[""] → []` collapse on the empty-entry arm — validate
11173        // rejects `[""]` through `AutorEmpty` but the accessor must
11174        // ship the raw slot verbatim so a validate-time gate regression
11175        // surfaces at the caixa-helm emit boundary rather than being
11176        // silently absorbed into a maintainer-drop), `["pleme-io"]` (the
11177        // canonical single-maintainer form every `feira init` template
11178        // scaffolds), `["alice", "bob"]` (a canonical multi-maintainer
11179        // form), `["alice <alice@example.com>", "bob <bob@example.com>"]`
11180        // (the canonical RFC-5322 `<name> <email>` form the
11181        // `is_chart_maintainer_name_shape` predicate accepts), and
11182        // `["pleme-io", "pleme-io"]` (a past-the-guard duplicate
11183        // sentinel — validate rejects through `AutorDuplicate` but the
11184        // accessor must ship the raw slot verbatim).
11185        //
11186        // First outer top-level [`Caixa`] `&[T]`-return slice accessor
11187        // pin on the substrate primitive — opens the "outer [`Caixa`]
11188        // `&[T]` slice" projection pattern the sibling per-`Caixa`
11189        // `:etiquetas` / `:deps` / `:deps-dev` / `:exe` / `:bibliotecas`
11190        // / `:servicos` / `:upgrade-from` / `:children` future lifts
11191        // fold on. Sibling in shape to the peer per-`:supervisor`
11192        // [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
11193        // per-`:placement` [`crate::aplicacao::Placement::clusters`]
11194        // (a6e18d7), per-`:membros`
11195        // [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
11196        // per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
11197        // (0dcc926), and per-`:upgrade-from :instructions`
11198        // [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
11199        // `&[T]`-return slice accessor pins on the sibling per-M2 /
11200        // per-M3 typed-slot list axes, extended onto the outer top-
11201        // level [`Caixa`] universal-axis surface. Pins against a future
11202        // silent detour that returned an owned `Vec<String>` (which
11203        // would type-check but silently clone on every accessor call,
11204        // breaking the zero-cost projection every peer sibling slice
11205        // accessor carries), a `[""] → []` collapse (which would
11206        // silently absorb the `AutorEmpty` refusal case at the accessor
11207        // boundary), or a `["a", "a"] → ["a"]` dedup collapse (which
11208        // would silently absorb the `AutorDuplicate` refusal case at
11209        // the accessor boundary and the caixa-helm `maintainers:` fold
11210        // would silently render a dedupped list on a struct-literal
11211        // `Caixa { autores: vec!["a".into(), "a".into()], .. }`).
11212        for autores in [
11213            vec![],
11214            vec![""],
11215            vec!["pleme-io"],
11216            vec!["alice", "bob"],
11217            vec!["alice <alice@example.com>", "bob <bob@example.com>"],
11218            vec!["pleme-io", "pleme-io"],
11219        ] {
11220            let c = caixa_with_autores(autores.clone());
11221            let expected: Vec<String> = autores.iter().map(|s| (*s).to_string()).collect();
11222            assert_eq!(
11223                c.autores(),
11224                expected.as_slice(),
11225                "Caixa::autores must return :autores verbatim (got {:?}, \
11226                 expected {expected:?})",
11227                c.autores(),
11228            );
11229            assert_eq!(
11230                c.autores(),
11231                c.autores.as_slice(),
11232                "Caixa::autores must byte-equal the raw \
11233                 `self.autores.as_slice()` field access across every \
11234                 value in the Vec<String> accept-set",
11235            );
11236        }
11237    }
11238
11239    #[test]
11240    fn validate_autores_empty_entry_arm_routes_through_accessor() {
11241        // Composition pin: [`Caixa::validate_autores`]'s per-entry
11242        // empty-arm gate must key off [`Caixa::autores`], not the raw
11243        // `&self.autores` field-borrow walk. Structurally: a
11244        // `Caixa { autores: vec!["".into()], .. }` must surface the
11245        // `AutorEmpty` refusal exactly, and a
11246        // `Caixa { autores: vec!["pleme-io".into()], .. }` (the
11247        // canonical single-maintainer form) must pass validate. The
11248        // pair jointly pins the accessor + validate-gate composition:
11249        // any future silent detour that had the accessor return an
11250        // empty slice on the `[""]` arm (a
11251        // `.iter().filter(|s| !s.is_empty()).collect()` collapse)
11252        // would silently absorb the `AutorEmpty` refusal at the
11253        // accessor boundary and the validate gate would accept a
11254        // struct-literal `Caixa { autores: vec!["".into()], .. }` —
11255        // the composition pin catches that at caixa-core build time.
11256        //
11257        // Peer of the per-`Caixa` [`Caixa::validate_licenca`] (6d5bc28)
11258        // accessor-composition pin
11259        // (`validate_licenca_empty_arm_routes_through_accessor`) on the
11260        // sibling `Option<&str>`-composition axis and the
11261        // per-`:politicas :circuit-breaker`
11262        // [`crate::aplicacao::CircuitBreaker::max_failures`] (3a74062)
11263        // accessor-composition pin
11264        // (`validate_politicas_max_failures_zero_floor_arm_routes_through_accessor`)
11265        // on the sibling required-`u32`-composition axis — same "the
11266        // validate / shape-gate predicate must route through the
11267        // substrate-primitive typed dispatch" discipline extended onto
11268        // the outer top-level [`Caixa`] universal-axis `&[T]`-
11269        // composition surface.
11270        let c = caixa_with_autores(vec![""]);
11271        assert!(
11272            matches!(c.validate_autores(), Err(ManifestError::AutorEmpty)),
11273            "validate_autores must reject autores == vec![\"\"] with \
11274             AutorEmpty — the accessor and the validate gate must \
11275             route through the same substrate-primitive typed dispatch \
11276             on the :autores per-entry empty arm",
11277        );
11278        let c = caixa_with_autores(vec!["pleme-io"]);
11279        assert!(
11280            c.validate_autores().is_ok(),
11281            "validate_autores must accept autores == vec![\"pleme-io\"] \
11282             (the canonical single-maintainer shape every `feira init` \
11283             template scaffolds)",
11284        );
11285    }
11286
11287    #[test]
11288    fn autores_projects_slice_by_borrow() {
11289        // The by-borrow pin: [`Caixa::autores`] returns `&[String]` by
11290        // borrow — the returned slice borrows the underlying
11291        // `Vec<String>` storage of the `:autores` slot and the
11292        // accessor must not clone the backing `Vec` on every call.
11293        // Peer of the per-`:membros`
11294        // [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36) /
11295        // per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
11296        // (0dcc926) / per-`:placement`
11297        // [`crate::aplicacao::Placement::clusters`] (a6e18d7) /
11298        // per-`:supervisor` [`crate::supervisor::SupervisorSpec::children`]
11299        // (bc92bce) by-borrow pins on the sibling per-M2 / per-M3
11300        // typed-slot `&[T]`-return axes, extended onto the outer top-
11301        // level [`Caixa`] universal-axis `&[String]` shape — the
11302        // accessor's returned slice must borrow from `&self` (the
11303        // returned reference's lifetime is tied to `&self`), and
11304        // calling the accessor twice on the same [`Caixa`] must yield
11305        // slices that are pointer-equal (the underlying byte-buffer is
11306        // the storage `Vec`'s allocation, not a fresh copy) as well as
11307        // value-equal (idempotent, no side effects on `&self`).
11308        //
11309        // Pins against a future silent detour that returned an owned
11310        // `Vec<String>` (which would type-check but silently clone on
11311        // every call, breaking the zero-cost projection every peer
11312        // sibling slice accessor carries), a `&Vec<String>` return
11313        // (which would leak the backing `Vec`'s grow/push/reserve
11314        // surface no downstream consumer reaches for), or a one-arm-
11315        // only accessor that returned a saturating value on some
11316        // sentinel input (breaking the pass-through invariant the
11317        // sibling slice accessors carry).
11318        for autores in [
11319            vec![],
11320            vec!["pleme-io"],
11321            vec!["alice", "bob"],
11322            vec!["pleme-io", "pleme-io"],
11323        ] {
11324            let c = caixa_with_autores(autores.clone());
11325            let expected: Vec<String> = autores.iter().map(|s| (*s).to_string()).collect();
11326            let first = c.autores();
11327            let second = c.autores();
11328            assert_eq!(
11329                first, second,
11330                "Caixa::autores must be idempotent — two successive \
11331                 calls on the same &self must return the same \
11332                 &[String]",
11333            );
11334            assert_eq!(
11335                first.as_ptr(),
11336                second.as_ptr(),
11337                "Caixa::autores must borrow the underlying Vec<String> \
11338                 storage — two successive calls must return slices \
11339                 with the same backing pointer (a fresh Vec<String> \
11340                 clone would change the pointer on every call)",
11341            );
11342            assert_eq!(
11343                first,
11344                expected.as_slice(),
11345                "Caixa::autores must return :autores verbatim by \
11346                 borrow — got {first:?}, expected {expected:?}",
11347            );
11348        }
11349    }
11350
11351    // ── Caixa::etiquetas — outer top-level &[T] slice accessor ────────
11352
11353    #[test]
11354    fn etiquetas_returns_etiquetas_slice_verbatim_across_permutations() {
11355        // The canonical per-`Caixa` `:etiquetas` universal-axis
11356        // registry-search-tag-list slice pin: [`Caixa::etiquetas`] must
11357        // return the `:etiquetas` typed [`Vec<String>`] list verbatim
11358        // as a `&[String]`, byte-equal to the raw
11359        // `self.etiquetas.as_slice()` access across every representative
11360        // value in the accept-set — `[]` (the "no tags declared" arm
11361        // every existing fixture without an `:etiquetas` line carries),
11362        // `[""]` (a past-the-guard sentinel that pins the accessor
11363        // doesn't perform a silent `[""] → []` collapse on the empty-
11364        // entry arm — validate rejects `[""]` through `EtiquetaEmpty`
11365        // but the accessor must ship the raw slot verbatim so a
11366        // validate-time gate regression surfaces at the caixa-helm emit
11367        // boundary rather than being silently absorbed into a keyword-
11368        // drop), `["demo"]` (the canonical single-tag form every
11369        // `feira init` template scaffolds), `["example", "aplicacao",
11370        // "mesh", "ecommerce", "demo"]` (the canonical multi-tag form
11371        // the checkout-aplicacao fixture emits), and `["demo", "demo"]`
11372        // (a past-the-guard duplicate sentinel — validate rejects
11373        // through `EtiquetaDuplicate` but the accessor must ship the
11374        // raw slot verbatim so the caixa-helm `BTreeSet::collect` dedup
11375        // at chart-render time isn't silently promoted into the
11376        // accessor boundary and struct-literal
11377        // `Caixa { etiquetas: vec!["demo".into(), "demo".into()], .. }`
11378        // fixtures continue to expose the duplicate at the accessor).
11379        //
11380        // Second outer top-level [`Caixa`] `&[T]`-return slice accessor
11381        // pin on the substrate primitive — folds on the "outer
11382        // [`Caixa`] `&[T]` slice" projection pattern
11383        // `autores_returns_autores_slice_verbatim_across_permutations`
11384        // (b5d813f) opened, sibling in shape and idiom. Pins against a
11385        // future silent detour that returned an owned `Vec<String>`
11386        // (which would type-check but silently clone on every accessor
11387        // call, breaking the zero-cost projection every peer sibling
11388        // slice accessor carries), a `[""] → []` collapse (which would
11389        // silently absorb the `EtiquetaEmpty` refusal case at the
11390        // accessor boundary), or a `["a", "a"] → ["a"]` dedup collapse
11391        // (which would silently absorb the `EtiquetaDuplicate` refusal
11392        // case at the accessor boundary — the caixa-helm chart-render
11393        // `BTreeSet::collect` dedup is downstream of the accessor and
11394        // must not be silently promoted into it).
11395        for etiquetas in [
11396            vec![],
11397            vec![""],
11398            vec!["demo"],
11399            vec!["example", "aplicacao", "mesh", "ecommerce", "demo"],
11400            vec!["demo", "demo"],
11401        ] {
11402            let c = caixa_with_etiquetas(etiquetas.clone());
11403            let expected: Vec<String> = etiquetas.iter().map(|s| (*s).to_string()).collect();
11404            assert_eq!(
11405                c.etiquetas(),
11406                expected.as_slice(),
11407                "Caixa::etiquetas must return :etiquetas verbatim (got \
11408                 {:?}, expected {expected:?})",
11409                c.etiquetas(),
11410            );
11411            assert_eq!(
11412                c.etiquetas(),
11413                c.etiquetas.as_slice(),
11414                "Caixa::etiquetas must byte-equal the raw \
11415                 `self.etiquetas.as_slice()` field access across every \
11416                 value in the Vec<String> accept-set",
11417            );
11418        }
11419    }
11420
11421    #[test]
11422    fn validate_etiquetas_empty_entry_arm_routes_through_accessor() {
11423        // Composition pin: [`Caixa::validate_etiquetas`]'s per-entry
11424        // empty-arm gate must key off [`Caixa::etiquetas`], not the raw
11425        // `&self.etiquetas` field-borrow walk. Structurally: a
11426        // `Caixa { etiquetas: vec!["".into()], .. }` must surface the
11427        // `EtiquetaEmpty` refusal exactly, and a
11428        // `Caixa { etiquetas: vec!["demo".into()], .. }` (the canonical
11429        // single-tag form) must pass validate. The pair jointly pins
11430        // the accessor + validate-gate composition: any future silent
11431        // detour that had the accessor return an empty slice on the
11432        // `[""]` arm (a
11433        // `.iter().filter(|s| !s.is_empty()).collect()` collapse) would
11434        // silently absorb the `EtiquetaEmpty` refusal at the accessor
11435        // boundary and the validate gate would accept a struct-literal
11436        // `Caixa { etiquetas: vec!["".into()], .. }` — the composition
11437        // pin catches that at caixa-core build time.
11438        //
11439        // Peer of the per-`Caixa` `validate_autores_empty_arm_routes_
11440        // through_accessor` (b5d813f) accessor-composition pin on the
11441        // sibling `&[T]`-composition axis — same "the validate / shape-
11442        // gate predicate must route through the substrate-primitive
11443        // typed dispatch" discipline extended onto the sibling outer
11444        // top-level [`Caixa`] `&[T]`-composition surface.
11445        let c = caixa_with_etiquetas(vec![""]);
11446        assert!(
11447            matches!(c.validate_etiquetas(), Err(ManifestError::EtiquetaEmpty)),
11448            "validate_etiquetas must reject etiquetas == vec![\"\"] \
11449             with EtiquetaEmpty — the accessor and the validate gate \
11450             must route through the same substrate-primitive typed \
11451             dispatch on the :etiquetas per-entry empty arm",
11452        );
11453        let c = caixa_with_etiquetas(vec!["demo"]);
11454        assert!(
11455            c.validate_etiquetas().is_ok(),
11456            "validate_etiquetas must accept etiquetas == vec![\"demo\"] \
11457             (the canonical single-tag shape every `feira init` \
11458             template scaffolds)",
11459        );
11460    }
11461
11462    #[test]
11463    fn etiquetas_projects_slice_by_borrow() {
11464        // The by-borrow pin: [`Caixa::etiquetas`] returns `&[String]`
11465        // by borrow — the returned slice borrows the underlying
11466        // `Vec<String>` storage of the `:etiquetas` slot and the
11467        // accessor must not clone the backing `Vec` on every call.
11468        // Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
11469        // (b5d813f) by-borrow pin on the sibling outer top-level
11470        // [`Caixa`] `&[String]`-return axis — the accessor's returned
11471        // slice must borrow from `&self` (the returned reference's
11472        // lifetime is tied to `&self`), and calling the accessor twice
11473        // on the same [`Caixa`] must yield slices that are pointer-
11474        // equal (the underlying byte-buffer is the storage `Vec`'s
11475        // allocation, not a fresh copy) as well as value-equal
11476        // (idempotent, no side effects on `&self`).
11477        //
11478        // Pins against a future silent detour that returned an owned
11479        // `Vec<String>` (which would type-check but silently clone on
11480        // every call, breaking the zero-cost projection every peer
11481        // sibling slice accessor carries), a `&Vec<String>` return
11482        // (which would leak the backing `Vec`'s grow/push/reserve
11483        // surface no downstream consumer reaches for), or a one-arm-
11484        // only accessor that returned a saturating value on some
11485        // sentinel input (breaking the pass-through invariant the
11486        // sibling slice accessors carry).
11487        for etiquetas in [
11488            vec![],
11489            vec!["demo"],
11490            vec!["example", "aplicacao", "mesh"],
11491            vec!["demo", "demo"],
11492        ] {
11493            let c = caixa_with_etiquetas(etiquetas.clone());
11494            let expected: Vec<String> = etiquetas.iter().map(|s| (*s).to_string()).collect();
11495            let first = c.etiquetas();
11496            let second = c.etiquetas();
11497            assert_eq!(
11498                first, second,
11499                "Caixa::etiquetas must be idempotent — two successive \
11500                 calls on the same &self must return the same \
11501                 &[String]",
11502            );
11503            assert_eq!(
11504                first.as_ptr(),
11505                second.as_ptr(),
11506                "Caixa::etiquetas must borrow the underlying \
11507                 Vec<String> storage — two successive calls must \
11508                 return slices with the same backing pointer (a fresh \
11509                 Vec<String> clone would change the pointer on every \
11510                 call)",
11511            );
11512            assert_eq!(
11513                first,
11514                expected.as_slice(),
11515                "Caixa::etiquetas must return :etiquetas verbatim by \
11516                 borrow — got {first:?}, expected {expected:?}",
11517            );
11518        }
11519    }
11520
11521    // ── Caixa::bibliotecas — outer top-level &[T] slice accessor ──────
11522
11523    #[test]
11524    fn bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations() {
11525        // The canonical per-`Caixa` `:bibliotecas` universal-axis
11526        // library-source-path-list slice pin: [`Caixa::bibliotecas`]
11527        // must return the `:bibliotecas` typed [`Vec<String>`] list
11528        // verbatim as a `&[String]`, byte-equal to the raw
11529        // `self.bibliotecas.as_slice()` access across every
11530        // representative value in the accept-set — `[]` (the "no
11531        // libraries declared" arm every `:kind` other than `Biblioteca`
11532        // + every `Biblioteca` relying on the canonical
11533        // `lib/<nome>.lisp` implicit-default path carries; the
11534        // layout's [`crate::LayoutInvariants`] `MissingLib` arm-gate
11535        // fires exactly on this empty-slot + `Biblioteca`-kind
11536        // combination), `[""]` (a past-the-guard sentinel that pins
11537        // the accessor doesn't perform a silent `[""] → []` collapse
11538        // on the empty-entry arm — validate rejects `[""]` through
11539        // `CodePathEmpty { slot: ":bibliotecas" }` but the accessor
11540        // must ship the raw slot verbatim so a validate-time gate
11541        // regression surfaces at the `feira build` phase-1 parse
11542        // boundary rather than being silently absorbed into a
11543        // library-drop), `["lib/demo.lisp"]` (the canonical single-
11544        // entry form `Caixa::template` scaffolds and every `feira init`
11545        // template emits), `["lib/demo.lisp", "lib/helpers.lisp"]`
11546        // (the canonical multi-library form the
11547        // `validate_code_paths_accepts_explicit_relative_paths_on_
11548        // every_slot` fixture emits), and `["lib/foo.lisp",
11549        // "lib/foo.lisp"]` (a past-the-guard duplicate sentinel —
11550        // validate rejects through `CodePathDuplicate { slot:
11551        // ":bibliotecas" }` per the per-slot set-not-multiset gate,
11552        // but the accessor must ship the raw slot verbatim so the
11553        // `feira build` `for entry in caixa.bibliotecas()` parse walk
11554        // sees the duplicate at the accessor boundary and struct-
11555        // literal `Caixa { bibliotecas: vec!["lib/foo.lisp".into(),
11556        // "lib/foo.lisp".into()], .. }` fixtures continue to expose
11557        // the duplicate at the accessor).
11558        //
11559        // Third outer top-level [`Caixa`] `&[T]`-return slice accessor
11560        // pin on the substrate primitive — folds on the "outer
11561        // [`Caixa`] `&[T]` slice" projection pattern
11562        // `autores_returns_autores_slice_verbatim_across_permutations`
11563        // (b5d813f) opened and
11564        // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
11565        // (78c7d3c) folded on, sibling in shape and idiom. Pins
11566        // against a future silent detour that returned an owned
11567        // `Vec<String>` (which would type-check but silently clone on
11568        // every accessor call, breaking the zero-cost projection
11569        // every peer sibling slice accessor carries), a `[""] → []`
11570        // collapse (which would silently absorb the `CodePathEmpty`
11571        // refusal case at the accessor boundary), or a `["lib/foo.lisp",
11572        // "lib/foo.lisp"] → ["lib/foo.lisp"]` dedup collapse (which
11573        // would silently absorb the `CodePathDuplicate` refusal case
11574        // at the accessor boundary — the per-slot set-not-multiset
11575        // gate is downstream of the accessor and must not be silently
11576        // promoted into it).
11577        for bibliotecas in [
11578            vec![],
11579            vec![""],
11580            vec!["lib/demo.lisp"],
11581            vec!["lib/demo.lisp", "lib/helpers.lisp"],
11582            vec!["lib/foo.lisp", "lib/foo.lisp"],
11583        ] {
11584            let c = caixa_with_code_paths(bibliotecas.clone(), vec![], vec![]);
11585            let expected: Vec<String> = bibliotecas.iter().map(|s| (*s).to_string()).collect();
11586            assert_eq!(
11587                c.bibliotecas(),
11588                expected.as_slice(),
11589                "Caixa::bibliotecas must return :bibliotecas verbatim \
11590                 (got {:?}, expected {expected:?})",
11591                c.bibliotecas(),
11592            );
11593            assert_eq!(
11594                c.bibliotecas(),
11595                c.bibliotecas.as_slice(),
11596                "Caixa::bibliotecas must byte-equal the raw \
11597                 `self.bibliotecas.as_slice()` field access across \
11598                 every value in the Vec<String> accept-set",
11599            );
11600        }
11601    }
11602
11603    #[test]
11604    fn validate_code_paths_bibliotecas_empty_arm_routes_through_accessor() {
11605        // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
11606        // empty-arm gate on the `:bibliotecas` slot must key off
11607        // [`Caixa::bibliotecas`], not a divergent raw
11608        // `&self.bibliotecas` field-borrow walk. Structurally: a
11609        // `Caixa { bibliotecas: vec!["".into()], .. }` must surface
11610        // the `CodePathEmpty { slot: ":bibliotecas" }` refusal
11611        // exactly, and a `Caixa { bibliotecas: vec!["lib/demo.lisp".
11612        // into()], .. }` (the canonical single-library form
11613        // `Caixa::template` scaffolds) must pass validate. The pair
11614        // jointly pins the accessor + validate-gate composition: any
11615        // future silent detour that had the accessor return an empty
11616        // slice on the `[""]` arm (a `.iter().filter(|s|
11617        // !s.is_empty()).collect()` collapse) would silently absorb
11618        // the `CodePathEmpty` refusal at the accessor boundary and
11619        // the validate gate would accept a struct-literal
11620        // `Caixa { bibliotecas: vec!["".into()], .. }` — the
11621        // composition pin catches that at caixa-core build time.
11622        //
11623        // Peer of the per-`Caixa` `validate_autores_empty_arm_routes_
11624        // through_accessor` (b5d813f) and
11625        // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
11626        // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
11627        // composition axes — same "the validate / shape-gate
11628        // predicate must route through the substrate-primitive typed
11629        // dispatch" discipline extended onto the sibling outer top-
11630        // level [`Caixa`] `&[T]`-composition surface. Nominally the
11631        // in-tree `validate_code_paths` production body still keys
11632        // off the internal `[(":bibliotecas", &self.bibliotecas,
11633        // CodePathFileType::LispSource), (":exe", &self.exe, ..),
11634        // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
11635        // (the tuple's homogeneous slice-typed shape blocks a per-
11636        // element accessor swap in isolation — a future companion
11637        // lift for `:exe` and `:servicos` on the same outer-`Caixa`
11638        // `&[T]` slice-accessor axis closes that tuple onto the
11639        // triple of typed dispatches as a unit); the composition pin
11640        // catches any future accessor-side silent filter drop against
11641        // that eventual tuple-closure regardless of whether the
11642        // `:bibliotecas` slot is threaded through the accessor or the
11643        // raw field access at the tuple's construction site.
11644        let c = caixa_with_code_paths(vec![""], vec![], vec![]);
11645        assert!(
11646            matches!(
11647                c.validate_code_paths(),
11648                Err(ManifestError::CodePathEmpty {
11649                    slot: ":bibliotecas"
11650                })
11651            ),
11652            "validate_code_paths must reject bibliotecas == vec![\"\"] \
11653             with CodePathEmpty {{ slot: \":bibliotecas\" }} — the \
11654             accessor and the validate gate must route through the \
11655             same substrate-primitive typed dispatch on the \
11656             :bibliotecas per-entry empty arm",
11657        );
11658        let c = caixa_with_code_paths(vec!["lib/demo.lisp"], vec![], vec![]);
11659        assert!(
11660            c.validate_code_paths().is_ok(),
11661            "validate_code_paths must accept bibliotecas == \
11662             vec![\"lib/demo.lisp\"] (the canonical single-library \
11663             shape every `feira init` template scaffolds)",
11664        );
11665    }
11666
11667    #[test]
11668    fn bibliotecas_projects_slice_by_borrow() {
11669        // The by-borrow pin: [`Caixa::bibliotecas`] returns
11670        // `&[String]` by borrow — the returned slice borrows the
11671        // underlying `Vec<String>` storage of the `:bibliotecas` slot
11672        // and the accessor must not clone the backing `Vec` on every
11673        // call. Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
11674        // (b5d813f) and `etiquetas_projects_slice_by_borrow` (78c7d3c)
11675        // by-borrow pins on the sibling outer top-level [`Caixa`]
11676        // `&[String]`-return axes — the accessor's returned slice
11677        // must borrow from `&self` (the returned reference's lifetime
11678        // is tied to `&self`), and calling the accessor twice on the
11679        // same [`Caixa`] must yield slices that are pointer-equal
11680        // (the underlying byte-buffer is the storage `Vec`'s
11681        // allocation, not a fresh copy) as well as value-equal
11682        // (idempotent, no side effects on `&self`).
11683        //
11684        // Pins against a future silent detour that returned an owned
11685        // `Vec<String>` (which would type-check but silently clone on
11686        // every call, breaking the zero-cost projection every peer
11687        // sibling slice accessor carries), a `&Vec<String>` return
11688        // (which would leak the backing `Vec`'s grow/push/reserve
11689        // surface no downstream consumer reaches for), or a one-arm-
11690        // only accessor that returned a saturating value on some
11691        // sentinel input (breaking the pass-through invariant the
11692        // sibling slice accessors carry).
11693        for bibliotecas in [
11694            vec![],
11695            vec!["lib/demo.lisp"],
11696            vec!["lib/demo.lisp", "lib/helpers.lisp"],
11697            vec!["lib/foo.lisp", "lib/foo.lisp"],
11698        ] {
11699            let c = caixa_with_code_paths(bibliotecas.clone(), vec![], vec![]);
11700            let expected: Vec<String> = bibliotecas.iter().map(|s| (*s).to_string()).collect();
11701            let first = c.bibliotecas();
11702            let second = c.bibliotecas();
11703            assert_eq!(
11704                first, second,
11705                "Caixa::bibliotecas must be idempotent — two \
11706                 successive calls on the same &self must return the \
11707                 same &[String]",
11708            );
11709            assert_eq!(
11710                first.as_ptr(),
11711                second.as_ptr(),
11712                "Caixa::bibliotecas must borrow the underlying \
11713                 Vec<String> storage — two successive calls must \
11714                 return slices with the same backing pointer (a \
11715                 fresh Vec<String> clone would change the pointer on \
11716                 every call)",
11717            );
11718            assert_eq!(
11719                first,
11720                expected.as_slice(),
11721                "Caixa::bibliotecas must return :bibliotecas verbatim \
11722                 by borrow — got {first:?}, expected {expected:?}",
11723            );
11724        }
11725    }
11726
11727    // ── Caixa::exe — outer top-level &[T] slice accessor ──────────────
11728
11729    #[test]
11730    fn exe_returns_exe_slice_verbatim_across_permutations() {
11731        // The canonical per-`Caixa` `:exe` universal-axis
11732        // nix-built-executable-entry-path-list slice pin: [`Caixa::exe`]
11733        // must return the `:exe` typed [`Vec<String>`] list verbatim as
11734        // a `&[String]`, byte-equal to the raw `self.exe.as_slice()`
11735        // access across every representative value in the accept-set —
11736        // `[]` (the "no executable declared" arm every `:kind` other
11737        // than `Binario` carries; the layout's [`crate::LayoutInvariants`]
11738        // `BinarioWithoutExe` arm-gate fires exactly on this empty-slot
11739        // + `Binario`-kind combination), `[""]` (a past-the-guard
11740        // sentinel that pins the accessor doesn't perform a silent
11741        // `[""] → []` collapse on the empty-entry arm — validate rejects
11742        // `[""]` through `CodePathEmpty { slot: ":exe" }` but the
11743        // accessor must ship the raw slot verbatim so a validate-time
11744        // gate regression surfaces at the layout / `feira nix` boundary
11745        // rather than being silently absorbed into an executable-drop),
11746        // `["exe/cli"]` (the canonical single-entry Binario form every
11747        // in-tree `caixa_with_code_paths` positive control uses),
11748        // `["exe/cli", "exe/serve"]` (the canonical multi-executable
11749        // form the `validate_code_paths_accepts_explicit_relative_paths_
11750        // on_every_slot` fixture emits), and `["exe/cli", "exe/cli"]`
11751        // (a past-the-guard duplicate sentinel — validate rejects
11752        // through `CodePathDuplicate { slot: ":exe" }` per the per-slot
11753        // set-not-multiset gate, but the accessor must ship the raw
11754        // slot verbatim so struct-literal `Caixa { exe: vec!["exe/cli".
11755        // into(), "exe/cli".into()], .. }` fixtures continue to expose
11756        // the duplicate at the accessor).
11757        //
11758        // Fourth outer top-level [`Caixa`] `&[T]`-return slice accessor
11759        // pin on the substrate primitive — folds on the "outer
11760        // [`Caixa`] `&[T]` slice" projection pattern
11761        // `autores_returns_autores_slice_verbatim_across_permutations`
11762        // (b5d813f) opened,
11763        // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
11764        // (78c7d3c) folded on, and
11765        // `bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
11766        // (8a36c23) closed the universal-axis text-tag family of.
11767        // Opens the outer-`Caixa` foreign-code-slot `&[T]` sub-family
11768        // the sibling `:servicos` future lift closes onto. Pins against
11769        // a future silent detour that returned an owned `Vec<String>`
11770        // (which would type-check but silently clone on every accessor
11771        // call, breaking the zero-cost projection every peer sibling
11772        // slice accessor carries), a `[""] → []` collapse (which would
11773        // silently absorb the `CodePathEmpty` refusal case at the
11774        // accessor boundary), or an `["exe/cli", "exe/cli"] →
11775        // ["exe/cli"]` dedup collapse (which would silently absorb the
11776        // `CodePathDuplicate` refusal case at the accessor boundary —
11777        // the per-slot set-not-multiset gate is downstream of the
11778        // accessor and must not be silently promoted into it).
11779        for exe in [
11780            vec![],
11781            vec![""],
11782            vec!["exe/cli"],
11783            vec!["exe/cli", "exe/serve"],
11784            vec!["exe/cli", "exe/cli"],
11785        ] {
11786            let c = caixa_with_code_paths(vec![], exe.clone(), vec![]);
11787            let expected: Vec<String> = exe.iter().map(|s| (*s).to_string()).collect();
11788            assert_eq!(
11789                c.exe(),
11790                expected.as_slice(),
11791                "Caixa::exe must return :exe verbatim (got {:?}, \
11792                 expected {expected:?})",
11793                c.exe(),
11794            );
11795            assert_eq!(
11796                c.exe(),
11797                c.exe.as_slice(),
11798                "Caixa::exe must byte-equal the raw \
11799                 `self.exe.as_slice()` field access across every value \
11800                 in the Vec<String> accept-set",
11801            );
11802        }
11803    }
11804
11805    #[test]
11806    fn validate_code_paths_exe_empty_arm_routes_through_accessor() {
11807        // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
11808        // empty-arm gate on the `:exe` slot must key off
11809        // [`Caixa::exe`], not a divergent raw `&self.exe` field-borrow
11810        // walk. Structurally: a `Caixa { exe: vec!["".into()], .. }`
11811        // must surface the `CodePathEmpty { slot: ":exe" }` refusal
11812        // exactly, and a `Caixa { exe: vec!["exe/cli".into()], .. }`
11813        // (the canonical single-executable form every in-tree
11814        // `caixa_with_code_paths` positive control uses) must pass
11815        // validate. The pair jointly pins the accessor + validate-gate
11816        // composition: any future silent detour that had the accessor
11817        // return an empty slice on the `[""]` arm (a
11818        // `.iter().filter(|s| !s.is_empty()).collect()` collapse) would
11819        // silently absorb the `CodePathEmpty` refusal at the accessor
11820        // boundary and the validate gate would accept a struct-literal
11821        // `Caixa { exe: vec!["".into()], .. }` — the composition pin
11822        // catches that at caixa-core build time.
11823        //
11824        // Peer of the per-`Caixa`
11825        // `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
11826        // (8a36c23), `validate_autores_empty_arm_routes_through_accessor`
11827        // (b5d813f), and
11828        // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
11829        // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
11830        // composition axes — same "the validate / shape-gate predicate
11831        // must route through the substrate-primitive typed dispatch"
11832        // discipline extended onto the sibling outer top-level [`Caixa`]
11833        // `&[T]`-composition surface. Nominally the in-tree
11834        // `validate_code_paths` production body still keys off the
11835        // internal `[(":bibliotecas", &self.bibliotecas,
11836        // CodePathFileType::LispSource), (":exe", &self.exe, ..),
11837        // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
11838        // (the tuple's homogeneous slice-typed shape blocks a per-
11839        // element accessor swap in isolation — a future companion lift
11840        // for `:servicos` on the same outer-`Caixa` `&[T]` slice-
11841        // accessor axis closes that tuple onto the triple of typed
11842        // dispatches as a unit); the composition pin catches any future
11843        // accessor-side silent filter drop against that eventual tuple-
11844        // closure regardless of whether the `:exe` slot is threaded
11845        // through the accessor or the raw field access at the tuple's
11846        // construction site.
11847        let c = caixa_with_code_paths(vec![], vec![""], vec![]);
11848        assert!(
11849            matches!(
11850                c.validate_code_paths(),
11851                Err(ManifestError::CodePathEmpty { slot: ":exe" })
11852            ),
11853            "validate_code_paths must reject exe == vec![\"\"] \
11854             with CodePathEmpty {{ slot: \":exe\" }} — the \
11855             accessor and the validate gate must route through the \
11856             same substrate-primitive typed dispatch on the \
11857             :exe per-entry empty arm",
11858        );
11859        let c = caixa_with_code_paths(vec![], vec!["exe/cli"], vec![]);
11860        assert!(
11861            c.validate_code_paths().is_ok(),
11862            "validate_code_paths must accept exe == vec![\"exe/cli\"] \
11863             (the canonical single-executable shape every in-tree \
11864             `caixa_with_code_paths` positive control uses)",
11865        );
11866    }
11867
11868    #[test]
11869    fn exe_projects_slice_by_borrow() {
11870        // The by-borrow pin: [`Caixa::exe`] returns `&[String]` by
11871        // borrow — the returned slice borrows the underlying
11872        // `Vec<String>` storage of the `:exe` slot and the accessor
11873        // must not clone the backing `Vec` on every call. Peer of the
11874        // per-`Caixa` `autores_projects_slice_by_borrow` (b5d813f),
11875        // `etiquetas_projects_slice_by_borrow` (78c7d3c), and
11876        // `bibliotecas_projects_slice_by_borrow` (8a36c23) by-borrow
11877        // pins on the sibling outer top-level [`Caixa`] `&[String]`-
11878        // return axes — the accessor's returned slice must borrow from
11879        // `&self` (the returned reference's lifetime is tied to
11880        // `&self`), and calling the accessor twice on the same
11881        // [`Caixa`] must yield slices that are pointer-equal (the
11882        // underlying byte-buffer is the storage `Vec`'s allocation,
11883        // not a fresh copy) as well as value-equal (idempotent, no
11884        // side effects on `&self`).
11885        //
11886        // Pins against a future silent detour that returned an owned
11887        // `Vec<String>` (which would type-check but silently clone on
11888        // every call, breaking the zero-cost projection every peer
11889        // sibling slice accessor carries), a `&Vec<String>` return
11890        // (which would leak the backing `Vec`'s grow/push/reserve
11891        // surface no downstream consumer reaches for), or a one-arm-
11892        // only accessor that returned a saturating value on some
11893        // sentinel input (breaking the pass-through invariant the
11894        // sibling slice accessors carry).
11895        for exe in [
11896            vec![],
11897            vec!["exe/cli"],
11898            vec!["exe/cli", "exe/serve"],
11899            vec!["exe/cli", "exe/cli"],
11900        ] {
11901            let c = caixa_with_code_paths(vec![], exe.clone(), vec![]);
11902            let expected: Vec<String> = exe.iter().map(|s| (*s).to_string()).collect();
11903            let first = c.exe();
11904            let second = c.exe();
11905            assert_eq!(
11906                first, second,
11907                "Caixa::exe must be idempotent — two successive calls \
11908                 on the same &self must return the same &[String]",
11909            );
11910            assert_eq!(
11911                first.as_ptr(),
11912                second.as_ptr(),
11913                "Caixa::exe must borrow the underlying Vec<String> \
11914                 storage — two successive calls must return slices \
11915                 with the same backing pointer (a fresh Vec<String> \
11916                 clone would change the pointer on every call)",
11917            );
11918            assert_eq!(
11919                first,
11920                expected.as_slice(),
11921                "Caixa::exe must return :exe verbatim by borrow — \
11922                 got {first:?}, expected {expected:?}",
11923            );
11924        }
11925    }
11926
11927    // ── Caixa::servicos — outer top-level &[T] slice accessor ─────────
11928
11929    #[test]
11930    fn servicos_returns_servicos_slice_verbatim_across_permutations() {
11931        // The canonical per-`Caixa` `:servicos` universal-axis
11932        // ComputeUnit-CR-YAML-entry-path-list slice pin:
11933        // [`Caixa::servicos`] must return the `:servicos` typed
11934        // [`Vec<String>`] list verbatim as a `&[String]`, byte-equal to
11935        // the raw `self.servicos.as_slice()` access across every
11936        // representative value in the accept-set — `[]` (the "no
11937        // ComputeUnit-CR declared" arm every `:kind` other than
11938        // `Servico` carries; the layout's [`crate::LayoutInvariants`]
11939        // `ServicoWithoutServicos` arm-gate fires exactly on this
11940        // empty-slot + `Servico`-kind combination), `[""]` (a past-the-
11941        // guard sentinel that pins the accessor doesn't perform a
11942        // silent `[""] → []` collapse on the empty-entry arm — validate
11943        // rejects `[""]` through `CodePathEmpty { slot: ":servicos" }`
11944        // but the accessor must ship the raw slot verbatim so a
11945        // validate-time gate regression surfaces at the layout /
11946        // per-Servico renderer boundary rather than being silently
11947        // absorbed into a component-drop),
11948        // `["servicos/demo.computeunit.yaml"]` (the canonical
11949        // singleton V0-shape every in-tree `caixa_with_code_paths`
11950        // positive control uses; the same shape
11951        // [`crate::require_single_servico`] admits),
11952        // `["servicos/a.computeunit.yaml", "servicos/b.computeunit.
11953        // yaml"]` (a past-the-guard `len != 1` sentinel — the V0
11954        // singularity gate rejects through `ServicoCountMismatch
11955        // { count: 2 }` but the accessor must ship the raw slot
11956        // verbatim so struct-literal `Caixa { servicos: vec![...,
11957        // ...], .. }` fixtures continue to expose the count at the
11958        // accessor), and `["servicos/a.computeunit.yaml",
11959        // "servicos/a.computeunit.yaml"]` (a past-the-guard duplicate
11960        // sentinel — validate rejects through
11961        // `CodePathDuplicate { slot: ":servicos" }` per the per-slot
11962        // set-not-multiset gate, but the accessor must ship the raw
11963        // slot verbatim so struct-literal fixtures continue to expose
11964        // the duplicate at the accessor).
11965        //
11966        // Fifth and final outer top-level [`Caixa`] `&[T]`-return
11967        // slice accessor pin on the substrate primitive — folds on the
11968        // "outer [`Caixa`] `&[T]` slice" projection pattern
11969        // `autores_returns_autores_slice_verbatim_across_permutations`
11970        // (b5d813f) opened,
11971        // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
11972        // (78c7d3c) folded on,
11973        // `bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
11974        // (8a36c23) closed the universal-axis text-tag family of, and
11975        // `exe_returns_exe_slice_verbatim_across_permutations`
11976        // (65d9527) opened the foreign-code-slot sub-family of. Closes
11977        // the outer-`Caixa` foreign-code-slot `&[T]` sub-family — the
11978        // trio of code-surface list slots (`:bibliotecas` + `:exe` +
11979        // `:servicos`) now each carries a substrate-canonical slice
11980        // accessor. Pins against a future silent detour that returned
11981        // an owned `Vec<String>` (which would type-check but silently
11982        // clone on every accessor call, breaking the zero-cost
11983        // projection every peer sibling slice accessor carries), a
11984        // `[""] → []` collapse (which would silently absorb the
11985        // `CodePathEmpty` refusal case at the accessor boundary), an
11986        // `[a, a] → [a]` dedup collapse (which would silently absorb
11987        // the `CodePathDuplicate` refusal case at the accessor
11988        // boundary — the per-slot set-not-multiset gate is downstream
11989        // of the accessor and must not be silently promoted into it),
11990        // or a `[a, b] → [a]` singleton collapse (which would silently
11991        // absorb the V0 `ServicoCountMismatch` refusal case at the
11992        // accessor boundary — the V0 singularity gate is downstream of
11993        // the accessor and must not be silently promoted into it).
11994        for servicos in [
11995            vec![],
11996            vec![""],
11997            vec!["servicos/demo.computeunit.yaml"],
11998            vec!["servicos/a.computeunit.yaml", "servicos/b.computeunit.yaml"],
11999            vec!["servicos/a.computeunit.yaml", "servicos/a.computeunit.yaml"],
12000        ] {
12001            let c = caixa_with_code_paths(vec![], vec![], servicos.clone());
12002            let expected: Vec<String> = servicos.iter().map(|s| (*s).to_string()).collect();
12003            assert_eq!(
12004                c.servicos(),
12005                expected.as_slice(),
12006                "Caixa::servicos must return :servicos verbatim (got \
12007                 {:?}, expected {expected:?})",
12008                c.servicos(),
12009            );
12010            assert_eq!(
12011                c.servicos(),
12012                c.servicos.as_slice(),
12013                "Caixa::servicos must byte-equal the raw \
12014                 `self.servicos.as_slice()` field access across every \
12015                 value in the Vec<String> accept-set",
12016            );
12017        }
12018    }
12019
12020    #[test]
12021    fn validate_code_paths_servicos_empty_arm_routes_through_accessor() {
12022        // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
12023        // empty-arm gate on the `:servicos` slot must key off
12024        // [`Caixa::servicos`], not a divergent raw `&self.servicos`
12025        // field-borrow walk. Structurally: a `Caixa { servicos:
12026        // vec!["".into()], .. }` must surface the `CodePathEmpty
12027        // { slot: ":servicos" }` refusal exactly, and a `Caixa
12028        // { servicos: vec!["servicos/demo.computeunit.yaml".into()],
12029        // .. }` (the canonical singleton V0-shape every in-tree
12030        // `caixa_with_code_paths` positive control uses) must pass
12031        // validate. The pair jointly pins the accessor + validate-gate
12032        // composition: any future silent detour that had the accessor
12033        // return an empty slice on the `[""]` arm (a `.iter().filter
12034        // (|s| !s.is_empty()).collect()` collapse) would silently
12035        // absorb the `CodePathEmpty` refusal at the accessor boundary
12036        // and the validate gate would accept a struct-literal
12037        // `Caixa { servicos: vec!["".into()], .. }` — the composition
12038        // pin catches that at caixa-core build time.
12039        //
12040        // Peer of the per-`Caixa`
12041        // `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
12042        // (8a36c23), `validate_code_paths_exe_empty_arm_routes_through_accessor`
12043        // (65d9527), `validate_autores_empty_arm_routes_through_accessor`
12044        // (b5d813f), and
12045        // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
12046        // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
12047        // composition axes — same "the validate / shape-gate predicate
12048        // must route through the substrate-primitive typed dispatch"
12049        // discipline extended onto the sibling outer top-level
12050        // [`Caixa`] `&[T]`-composition surface, closing the trio of
12051        // code-surface accessor-composition pins on the same axis.
12052        // Nominally the in-tree `validate_code_paths` production body
12053        // still keys off the internal
12054        // `[(":bibliotecas", &self.bibliotecas,
12055        // CodePathFileType::LispSource), (":exe", &self.exe, ..),
12056        // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
12057        // (the tuple's homogeneous `&Vec<String>`-typed shape blocks a
12058        // per-element accessor swap in isolation — a future companion
12059        // lift promotes the tuple's element type to `&[String]` and
12060        // threads the triple of typed dispatches through as a unit);
12061        // the composition pin catches any future accessor-side silent
12062        // filter drop against that eventual tuple-closure regardless
12063        // of whether the `:servicos` slot is threaded through the
12064        // accessor or the raw field access at the tuple's construction
12065        // site.
12066        let c = caixa_with_code_paths(vec![], vec![], vec![""]);
12067        assert!(
12068            matches!(
12069                c.validate_code_paths(),
12070                Err(ManifestError::CodePathEmpty { slot: ":servicos" })
12071            ),
12072            "validate_code_paths must reject servicos == vec![\"\"] \
12073             with CodePathEmpty {{ slot: \":servicos\" }} — the \
12074             accessor and the validate gate must route through the \
12075             same substrate-primitive typed dispatch on the \
12076             :servicos per-entry empty arm",
12077        );
12078        let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/demo.computeunit.yaml"]);
12079        assert!(
12080            c.validate_code_paths().is_ok(),
12081            "validate_code_paths must accept servicos == \
12082             vec![\"servicos/demo.computeunit.yaml\"] (the canonical \
12083             singleton V0-shape every in-tree `caixa_with_code_paths` \
12084             positive control uses)",
12085        );
12086    }
12087
12088    #[test]
12089    fn servicos_projects_slice_by_borrow() {
12090        // The by-borrow pin: [`Caixa::servicos`] returns `&[String]` by
12091        // borrow — the returned slice borrows the underlying
12092        // `Vec<String>` storage of the `:servicos` slot and the
12093        // accessor must not clone the backing `Vec` on every call.
12094        // Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
12095        // (b5d813f), `etiquetas_projects_slice_by_borrow` (78c7d3c),
12096        // `bibliotecas_projects_slice_by_borrow` (8a36c23), and
12097        // `exe_projects_slice_by_borrow` (65d9527) by-borrow pins on
12098        // the sibling outer top-level [`Caixa`] `&[String]`-return
12099        // axes — the accessor's returned slice must borrow from
12100        // `&self` (the returned reference's lifetime is tied to
12101        // `&self`), and calling the accessor twice on the same
12102        // [`Caixa`] must yield slices that are pointer-equal (the
12103        // underlying byte-buffer is the storage `Vec`'s allocation,
12104        // not a fresh copy) as well as value-equal (idempotent, no
12105        // side effects on `&self`).
12106        //
12107        // Pins against a future silent detour that returned an owned
12108        // `Vec<String>` (which would type-check but silently clone on
12109        // every call, breaking the zero-cost projection every peer
12110        // sibling slice accessor carries), a `&Vec<String>` return
12111        // (which would leak the backing `Vec`'s grow/push/reserve
12112        // surface no downstream consumer reaches for), or a one-arm-
12113        // only accessor that returned a saturating value on some
12114        // sentinel input (breaking the pass-through invariant the
12115        // sibling slice accessors carry).
12116        for servicos in [
12117            vec![],
12118            vec!["servicos/demo.computeunit.yaml"],
12119            vec!["servicos/a.computeunit.yaml", "servicos/b.computeunit.yaml"],
12120            vec!["servicos/a.computeunit.yaml", "servicos/a.computeunit.yaml"],
12121        ] {
12122            let c = caixa_with_code_paths(vec![], vec![], servicos.clone());
12123            let expected: Vec<String> = servicos.iter().map(|s| (*s).to_string()).collect();
12124            let first = c.servicos();
12125            let second = c.servicos();
12126            assert_eq!(
12127                first, second,
12128                "Caixa::servicos must be idempotent — two successive \
12129                 calls on the same &self must return the same &[String]",
12130            );
12131            assert_eq!(
12132                first.as_ptr(),
12133                second.as_ptr(),
12134                "Caixa::servicos must borrow the underlying \
12135                 Vec<String> storage — two successive calls must \
12136                 return slices with the same backing pointer (a fresh \
12137                 Vec<String> clone would change the pointer on every \
12138                 call)",
12139            );
12140            assert_eq!(
12141                first,
12142                expected.as_slice(),
12143                "Caixa::servicos must return :servicos verbatim by \
12144                 borrow — got {first:?}, expected {expected:?}",
12145            );
12146        }
12147    }
12148
12149    // ── Caixa::deps — outer top-level &[Dep] slice accessor ───────────
12150
12151    fn caixa_with_deps(deps: Vec<Dep>) -> Caixa {
12152        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12153        c.deps = deps;
12154        c
12155    }
12156
12157    #[test]
12158    fn deps_returns_deps_slice_verbatim_across_permutations() {
12159        // The canonical per-`Caixa` `:deps` universal-axis runtime-
12160        // dependency-declaration-list slice pin: [`Caixa::deps`] must
12161        // return the `:deps` typed [`Vec<Dep>`] list verbatim as a
12162        // `&[Dep]`, element-equal to the raw `self.deps.as_slice()`
12163        // access across every representative value in the accept-set —
12164        // `[]` (the "no runtime deps declared" arm every existing
12165        // fixture without a `:deps` line carries; the
12166        // [`Caixa::template`] scaffold emits `:deps ()`), a canonical
12167        // single-entry list (the shape most consumer caixas carry), a
12168        // canonical two-entry list (the multi-dep runtime closure), and
12169        // two past-the-guard sentinels — a `[""]`-`:nome` entry
12170        // ([`Self::validate_deps`] rejects through `NomeEmpty` /
12171        // `NomeInvalid` but the accessor must ship the raw slot
12172        // verbatim) and a `[a, a]` duplicate (validate rejects through
12173        // `DuplicateNome { list: ":deps" }` but the accessor must ship
12174        // the raw slot verbatim so struct-literal fixtures continue to
12175        // expose the duplicate at the accessor).
12176        //
12177        // First outer top-level [`Caixa`] `&[Dep]`-return slice accessor
12178        // pin on the substrate primitive — opens the outer-`Caixa`
12179        // dependency-slot `&[Dep]` sub-family the sibling `:deps-dev`
12180        // future lift closes on. Peer of the closed outer-`Caixa`
12181        // foreign-code-slot `&[String]` sub-family
12182        // (`bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
12183        // 8a36c23, `exe_returns_exe_slice_verbatim_across_permutations`
12184        // 65d9527, `servicos_returns_servicos_slice_verbatim_across_permutations`
12185        // 611f78b) and the outer-`Caixa` universal-axis text-tag family
12186        // (`autores_returns_autores_slice_verbatim_across_permutations`
12187        // b5d813f, `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
12188        // 78c7d3c) — extends the "outer [`Caixa`] `&[T]` slice"
12189        // projection pattern onto a novel element-type axis (`Dep`
12190        // composite vs the prior sibling family's `String` scalar).
12191        // Pins against a future silent detour that returned an owned
12192        // `Vec<Dep>` (which would type-check but silently clone on every
12193        // accessor call, breaking the zero-cost projection every peer
12194        // sibling slice accessor carries), a `[""] → []` collapse (which
12195        // would silently absorb the `NomeEmpty` refusal case at the
12196        // accessor boundary), or a `[a, a] → [a]` dedup collapse (which
12197        // would silently absorb the `DuplicateNome` refusal case at the
12198        // accessor boundary).
12199        for deps in [
12200            vec![],
12201            vec![Dep::simple("", "^0.1")],
12202            vec![Dep::simple("caixa-teia", "^0.1")],
12203            vec![
12204                Dep::simple("caixa-teia", "^0.1"),
12205                Dep::simple("caixa-core", "^0.1"),
12206            ],
12207            vec![
12208                Dep::simple("caixa-teia", "^0.1"),
12209                Dep::simple("caixa-teia", "^0.2"),
12210            ],
12211        ] {
12212            let c = caixa_with_deps(deps.clone());
12213            assert_eq!(
12214                c.deps(),
12215                deps.as_slice(),
12216                "Caixa::deps must return :deps verbatim (got {:?}, \
12217                 expected {deps:?})",
12218                c.deps(),
12219            );
12220            assert_eq!(
12221                c.deps(),
12222                c.deps.as_slice(),
12223                "Caixa::deps must element-equal the raw \
12224                 `self.deps.as_slice()` field access across every \
12225                 value in the Vec<Dep> accept-set",
12226            );
12227        }
12228    }
12229
12230    #[test]
12231    fn validate_deps_duplicate_arm_routes_through_accessor() {
12232        // Composition pin: [`Caixa::validate_deps`]'s within-`:deps`
12233        // duplicate-`:nome` gate must key off [`Caixa::deps`], not the
12234        // raw `&self.deps` field-borrow walk. Structurally: a `Caixa
12235        // { deps: vec![Dep::simple("d", "^0.1"), Dep::simple("d",
12236        // "^0.2")], .. }` must surface the `DuplicateNome { list:
12237        // ":deps" }` refusal exactly, and a `Caixa { deps: vec![
12238        // Dep::simple("d", "^0.1")], .. }` (the canonical single-entry
12239        // form) must pass validate. The pair jointly pins the accessor +
12240        // validate-gate composition: any future silent detour that had
12241        // the accessor return a dedupped slice on the `[a, a]` arm (a
12242        // `.iter().unique_by(|d| d.nome.as_str()).collect()` collapse)
12243        // would silently absorb the `DuplicateNome` refusal at the
12244        // accessor boundary and the validate gate would accept a
12245        // struct-literal `Caixa` carrying the drift — the composition
12246        // pin catches that at caixa-core build time.
12247        //
12248        // Peer of the per-`Caixa`
12249        // `validate_autores_empty_entry_arm_routes_through_accessor`
12250        // (b5d813f), `validate_etiquetas_empty_entry_arm_routes_through_accessor`
12251        // (78c7d3c), `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
12252        // (8a36c23), `validate_code_paths_exe_empty_arm_routes_through_accessor`
12253        // (65d9527), and `validate_code_paths_servicos_empty_arm_routes_through_accessor`
12254        // (611f78b) accessor-composition pins on the sibling `&[T]`-
12255        // composition axes — same "the validate gate must route through
12256        // the substrate-primitive typed dispatch" discipline extended
12257        // onto the sibling outer top-level [`Caixa`] `&[Dep]`-
12258        // composition surface, opening the outer-`Caixa` dependency-slot
12259        // arm of the composition-pin family.
12260        let c = caixa_with_deps(vec![Dep::simple("d", "^0.1"), Dep::simple("d", "^0.2")]);
12261        let err = c.validate_deps().unwrap_err();
12262        assert!(
12263            matches!(
12264                err,
12265                DepError::DuplicateNome { ref nome, list } if nome == "d"
12266                    && list == crate::render::DEP_AUTHOR_KEY_DEPS
12267            ),
12268            "validate_deps must reject deps == \
12269             vec![Dep(\"d\",\"^0.1\"), Dep(\"d\",\"^0.2\")] with \
12270             DuplicateNome {{ nome: \"d\", list: \":deps\" }} — the \
12271             accessor and the validate gate must route through the \
12272             same substrate-primitive typed dispatch on the :deps \
12273             within-list duplicate arm (got {err:?})",
12274        );
12275        let c = caixa_with_deps(vec![Dep::simple("d", "^0.1")]);
12276        assert!(
12277            c.validate_deps().is_ok(),
12278            "validate_deps must accept deps == vec![Dep(\"d\",\"^0.1\")] \
12279             (the canonical single-entry form)",
12280        );
12281    }
12282
12283    #[test]
12284    fn deps_projects_slice_by_borrow() {
12285        // The by-borrow pin: [`Caixa::deps`] returns `&[Dep]` by borrow
12286        // — the returned slice borrows the underlying `Vec<Dep>` storage
12287        // of the `:deps` slot and the accessor must not clone the
12288        // backing `Vec` on every call. Peer of the per-`Caixa`
12289        // `autores_projects_slice_by_borrow` (b5d813f),
12290        // `etiquetas_projects_slice_by_borrow` (78c7d3c),
12291        // `bibliotecas_projects_slice_by_borrow` (8a36c23),
12292        // `exe_projects_slice_by_borrow` (65d9527), and
12293        // `servicos_projects_slice_by_borrow` (611f78b) by-borrow pins
12294        // on the sibling outer top-level [`Caixa`] `&[String]`-return
12295        // axes — the accessor's returned slice must borrow from `&self`
12296        // (the returned reference's lifetime is tied to `&self`), and
12297        // calling the accessor twice on the same [`Caixa`] must yield
12298        // slices that are pointer-equal (the underlying byte-buffer is
12299        // the storage `Vec`'s allocation, not a fresh copy) as well as
12300        // value-equal (idempotent, no side effects on `&self`).
12301        //
12302        // Pins against a future silent detour that returned an owned
12303        // `Vec<Dep>` (which would type-check but silently clone on
12304        // every call), a `&Vec<Dep>` return (which would leak the
12305        // backing `Vec`'s grow/push/reserve surface no downstream
12306        // consumer reaches for), or a one-arm-only accessor that
12307        // returned a saturating value on some sentinel input.
12308        for deps in [
12309            vec![],
12310            vec![Dep::simple("caixa-teia", "^0.1")],
12311            vec![
12312                Dep::simple("caixa-teia", "^0.1"),
12313                Dep::simple("caixa-core", "^0.1"),
12314            ],
12315        ] {
12316            let c = caixa_with_deps(deps.clone());
12317            let first = c.deps();
12318            let second = c.deps();
12319            assert_eq!(
12320                first, second,
12321                "Caixa::deps must be idempotent — two successive calls \
12322                 on the same &self must return the same &[Dep]",
12323            );
12324            assert_eq!(
12325                first.as_ptr(),
12326                second.as_ptr(),
12327                "Caixa::deps must borrow the underlying Vec<Dep> \
12328                 storage — two successive calls must return slices \
12329                 with the same backing pointer (a fresh Vec<Dep> clone \
12330                 would change the pointer on every call)",
12331            );
12332            assert_eq!(
12333                first,
12334                deps.as_slice(),
12335                "Caixa::deps must return :deps verbatim by borrow — \
12336                 got {first:?}, expected {deps:?}",
12337            );
12338        }
12339    }
12340
12341    // ── Caixa::deps_dev — outer top-level &[Dep] slice accessor ──────
12342
12343    fn caixa_with_deps_dev(deps_dev: Vec<Dep>) -> Caixa {
12344        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12345        c.deps_dev = deps_dev;
12346        c
12347    }
12348
12349    #[test]
12350    fn deps_dev_returns_deps_dev_slice_verbatim_across_permutations() {
12351        // The canonical per-`Caixa` `:deps-dev` universal-axis dev-only-
12352        // dependency-declaration-list slice pin: [`Caixa::deps_dev`]
12353        // must return the `:deps-dev` typed [`Vec<Dep>`] list verbatim as
12354        // a `&[Dep]`, element-equal to the raw `self.deps_dev.as_slice()`
12355        // access across every representative value in the accept-set —
12356        // `[]` (the "no dev deps declared" arm every existing fixture
12357        // without a `:deps-dev` line carries; the [`Caixa::template`]
12358        // scaffold emits `:deps-dev ()`), a canonical single-entry list
12359        // (the shape most consumer caixas carry — a `tatara-check` dev
12360        // pin), a canonical two-entry list (the multi-dev-dep closure),
12361        // and two past-the-guard sentinels — a `[""]`-`:nome` entry
12362        // ([`Self::validate_deps`] rejects through `NomeEmpty` /
12363        // `NomeInvalid` but the accessor must ship the raw slot
12364        // verbatim) and a `[a, a]` duplicate (validate rejects through
12365        // `DuplicateNome { list: ":deps-dev" }` but the accessor must
12366        // ship the raw slot verbatim so struct-literal fixtures continue
12367        // to expose the duplicate at the accessor).
12368        //
12369        // Second outer top-level [`Caixa`] `&[Dep]`-return slice-accessor
12370        // pin on the substrate primitive — closes the outer-`Caixa`
12371        // dependency-slot `&[Dep]` sub-family the sibling
12372        // `deps_returns_deps_slice_verbatim_across_permutations`
12373        // (ad34b4e) opened on. Folds the "outer [`Caixa`] `&[Dep]`
12374        // slice" projection pattern onto the sibling dev-dep axis —
12375        // pins against a future silent detour that returned an owned
12376        // `Vec<Dep>` (which would type-check but silently clone on every
12377        // accessor call, breaking the zero-cost projection every peer
12378        // sibling slice accessor carries), a `[""] → []` collapse (which
12379        // would silently absorb the `NomeEmpty` refusal case at the
12380        // accessor boundary), or a `[a, a] → [a]` dedup collapse (which
12381        // would silently absorb the `DuplicateNome` refusal case at the
12382        // accessor boundary).
12383        for deps_dev in [
12384            vec![],
12385            vec![Dep::simple("", "^0.1")],
12386            vec![Dep::simple("tatara-check", "^0.1")],
12387            vec![
12388                Dep::simple("tatara-check", "^0.1"),
12389                Dep::simple("caixa-lint", "^0.1"),
12390            ],
12391            vec![
12392                Dep::simple("tatara-check", "^0.1"),
12393                Dep::simple("tatara-check", "^0.2"),
12394            ],
12395        ] {
12396            let c = caixa_with_deps_dev(deps_dev.clone());
12397            assert_eq!(
12398                c.deps_dev(),
12399                deps_dev.as_slice(),
12400                "Caixa::deps_dev must return :deps-dev verbatim (got \
12401                 {:?}, expected {deps_dev:?})",
12402                c.deps_dev(),
12403            );
12404            assert_eq!(
12405                c.deps_dev(),
12406                c.deps_dev.as_slice(),
12407                "Caixa::deps_dev must element-equal the raw \
12408                 `self.deps_dev.as_slice()` field access across every \
12409                 value in the Vec<Dep> accept-set",
12410            );
12411        }
12412    }
12413
12414    #[test]
12415    fn validate_deps_duplicate_deps_dev_arm_routes_through_accessor() {
12416        // Composition pin: [`Caixa::validate_deps`]'s within-`:deps-dev`
12417        // duplicate-`:nome` gate must key off [`Caixa::deps_dev`], not
12418        // the raw `&self.deps_dev` field-borrow walk. Structurally: a
12419        // `Caixa { deps_dev: vec![Dep::simple("d", "^0.1"),
12420        // Dep::simple("d", "^0.2")], .. }` must surface the
12421        // `DuplicateNome { list: ":deps-dev" }` refusal exactly, and a
12422        // `Caixa { deps_dev: vec![Dep::simple("d", "^0.1")], .. }` (the
12423        // canonical single-entry form) must pass validate. The pair
12424        // jointly pins the accessor + validate-gate composition: any
12425        // future silent detour that had the accessor return a dedupped
12426        // slice on the `[a, a]` arm (a
12427        // `.iter().unique_by(|d| d.nome.as_str()).collect()` collapse)
12428        // would silently absorb the `DuplicateNome` refusal at the
12429        // accessor boundary and the validate gate would accept a
12430        // struct-literal `Caixa` carrying the drift — the composition
12431        // pin catches that at caixa-core build time.
12432        //
12433        // Peer of `validate_deps_duplicate_arm_routes_through_accessor`
12434        // (ad34b4e) on the sibling `:deps` axis — same "the validate
12435        // gate must route through the substrate-primitive typed
12436        // dispatch" discipline folded onto the sibling `:deps-dev`
12437        // axis, closing the two-list dep-graph composition-pin family.
12438        // The `:deps-dev` diagnostic must carry the
12439        // `DEP_AUTHOR_KEY_DEPS_DEV` list-tag (not
12440        // `DEP_AUTHOR_KEY_DEPS`) so the emitted error names the
12441        // offending list unambiguously.
12442        let c = caixa_with_deps_dev(vec![Dep::simple("d", "^0.1"), Dep::simple("d", "^0.2")]);
12443        let err = c.validate_deps().unwrap_err();
12444        assert!(
12445            matches!(
12446                err,
12447                DepError::DuplicateNome { ref nome, list } if nome == "d"
12448                    && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
12449            ),
12450            "validate_deps must reject deps_dev == \
12451             vec![Dep(\"d\",\"^0.1\"), Dep(\"d\",\"^0.2\")] with \
12452             DuplicateNome {{ nome: \"d\", list: \":deps-dev\" }} — the \
12453             accessor and the validate gate must route through the \
12454             same substrate-primitive typed dispatch on the :deps-dev \
12455             within-list duplicate arm (got {err:?})",
12456        );
12457        let c = caixa_with_deps_dev(vec![Dep::simple("d", "^0.1")]);
12458        assert!(
12459            c.validate_deps().is_ok(),
12460            "validate_deps must accept deps_dev == \
12461             vec![Dep(\"d\",\"^0.1\")] (the canonical single-entry form)",
12462        );
12463    }
12464
12465    #[test]
12466    fn deps_dev_projects_slice_by_borrow() {
12467        // The by-borrow pin: [`Caixa::deps_dev`] returns `&[Dep]` by
12468        // borrow — the returned slice borrows the underlying `Vec<Dep>`
12469        // storage of the `:deps-dev` slot and the accessor must not
12470        // clone the backing `Vec` on every call. Peer of
12471        // `deps_projects_slice_by_borrow` (ad34b4e) on the sibling
12472        // `:deps` axis, and of the per-`Caixa`
12473        // `autores_projects_slice_by_borrow` (b5d813f),
12474        // `etiquetas_projects_slice_by_borrow` (78c7d3c),
12475        // `bibliotecas_projects_slice_by_borrow` (8a36c23),
12476        // `exe_projects_slice_by_borrow` (65d9527), and
12477        // `servicos_projects_slice_by_borrow` (611f78b) by-borrow pins
12478        // on the sibling outer top-level [`Caixa`] `&[String]`-return
12479        // axes — the accessor's returned slice must borrow from `&self`
12480        // (the returned reference's lifetime is tied to `&self`), and
12481        // calling the accessor twice on the same [`Caixa`] must yield
12482        // slices that are pointer-equal (the underlying byte-buffer is
12483        // the storage `Vec`'s allocation, not a fresh copy) as well as
12484        // value-equal (idempotent, no side effects on `&self`).
12485        //
12486        // Pins against a future silent detour that returned an owned
12487        // `Vec<Dep>` (which would type-check but silently clone on
12488        // every call), a `&Vec<Dep>` return (which would leak the
12489        // backing `Vec`'s grow/push/reserve surface no downstream
12490        // consumer reaches for), or a one-arm-only accessor that
12491        // returned a saturating value on some sentinel input.
12492        for deps_dev in [
12493            vec![],
12494            vec![Dep::simple("tatara-check", "^0.1")],
12495            vec![
12496                Dep::simple("tatara-check", "^0.1"),
12497                Dep::simple("caixa-lint", "^0.1"),
12498            ],
12499        ] {
12500            let c = caixa_with_deps_dev(deps_dev.clone());
12501            let first = c.deps_dev();
12502            let second = c.deps_dev();
12503            assert_eq!(
12504                first, second,
12505                "Caixa::deps_dev must be idempotent — two successive \
12506                 calls on the same &self must return the same &[Dep]",
12507            );
12508            assert_eq!(
12509                first.as_ptr(),
12510                second.as_ptr(),
12511                "Caixa::deps_dev must borrow the underlying Vec<Dep> \
12512                 storage — two successive calls must return slices \
12513                 with the same backing pointer (a fresh Vec<Dep> clone \
12514                 would change the pointer on every call)",
12515            );
12516            assert_eq!(
12517                first,
12518                deps_dev.as_slice(),
12519                "Caixa::deps_dev must return :deps-dev verbatim by \
12520                 borrow — got {first:?}, expected {deps_dev:?}",
12521            );
12522        }
12523    }
12524
12525    // ── Caixa::limits — outer top-level Option<&LimitsSpec> composite-reference accessor ──
12526
12527    fn caixa_with_limits(limits: Option<crate::LimitsSpec>) -> Caixa {
12528        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12529        c.limits = limits;
12530        c
12531    }
12532
12533    #[test]
12534    fn limits_returns_limits_option_ref_verbatim_across_permutations() {
12535        // The canonical per-`Caixa` `:limits` M2 typed-slot outer-
12536        // composite optional-composite-reference-shape pin:
12537        // [`Caixa::limits`] must return the `:limits` typed
12538        // `Option<LimitsSpec>` verbatim as an `Option<&LimitsSpec>`
12539        // reference over the same backing storage the raw
12540        // `self.limits.as_ref()` field access borrows from, byte-equal
12541        // across every representative fixture in the accept-set — the
12542        // author-omitted `None` shape (the "engine-default applies"
12543        // partition every downstream Servico M2 overlay emitter treats
12544        // as "emit nothing"), the empty-composite `Some(LimitsSpec {
12545        // .. default })` shape ([`LimitsSpec::is_empty`] holds — every
12546        // per-axis cap is `None`, so the peer M2 overlay emitter's
12547        // `.is_empty()`-gated projection still emits nothing but the
12548        // outer presence-bit is `Some`, so [`Caixa::declared_servico_slots`]
12549        // still pushes the `M2_AUTHOR_KEY_LIMITS` label), a single-axis
12550        // fixture (only `:memory` set — the canonical shape most
12551        // memory-heavy Servicos carry), and a fully-populated composite
12552        // (every per-axis cap set — the canonical shape a
12553        // sandboxed-by-default Servico carries).
12554        //
12555        // Pins against a future silent detour that returned a fresh-
12556        // cloned [`LimitsSpec`] copy (which would type-check via the
12557        // `Clone` impl but silently break every downstream caller that
12558        // relied on the reference sharing the composite's backing
12559        // identity), a reference to an operator-resolved overlay (the
12560        // future per-cluster `:limits-overrides` slot — its resolution
12561        // must land at exactly this accessor body, not silently divert
12562        // the raw slot away from a second consumer), a
12563        // `None` → `Some(LimitsSpec::default)` cluster-default
12564        // projection (which would collapse the load-bearing
12565        // "author-omitted `:limits` ⇒ engine-default applies" partition
12566        // the peer [`crate::render::servico_m2_overlay`] emitter and
12567        // the peer [`Caixa::declared_servico_slots`] enumerator both
12568        // read), or an axis-shuffled projection (a future detour that
12569        // swapped `memory` and `fuel` through the accessor would
12570        // silently split the paired [`crate::StandardLayout::verify`]
12571        // per-`:limits` shape gate's traversal input from the peer
12572        // `servico_m2_overlay` emitter's projection input).
12573        //
12574        // First outer top-level [`Caixa`] `Option<&Composite>`-return
12575        // composite-reference accessor pin on the substrate primitive
12576        // — opens the outer-`Caixa` `Option<&Composite>` composite-
12577        // reference projection pattern the sibling `:behavior`
12578        // [`crate::BehaviorSpec`] / `:politicas`
12579        // [`crate::aplicacao::MeshPolicy`] / `:placement`
12580        // [`crate::aplicacao::Placement`] / `:entrada`
12581        // [`crate::aplicacao::Entrada`] future outer-composite lifts
12582        // fold on. Peer of the closed M3 outer-composite family the
12583        // sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
12584        // [`crate::AplicacaoSpec::placement`] (9abb8f0) /
12585        // [`crate::AplicacaoSpec::entrada`] (d32111c) composite-
12586        // reference accessor pins already carry on the outer
12587        // [`crate::AplicacaoSpec`] altitude — extends the outer-
12588        // accessor byte-equal-projection discipline onto the outer
12589        // top-level [`Caixa`] M2 Servico-runtime slot altitude.
12590        use crate::LimitsSpec;
12591        use std::time::Duration;
12592        let fixtures: Vec<Option<LimitsSpec>> = vec![
12593            None,
12594            Some(LimitsSpec::default()),
12595            Some(LimitsSpec {
12596                memory: Some(64 * 1024 * 1024),
12597                ..Default::default()
12598            }),
12599            Some(LimitsSpec {
12600                memory: Some(64 * 1024 * 1024),
12601                fuel: Some(1_000_000),
12602                wall_clock: Some(Duration::from_secs(30)),
12603                cpu: Some(500),
12604            }),
12605        ];
12606        for limits in fixtures {
12607            let c = caixa_with_limits(limits.clone());
12608            assert_eq!(
12609                c.limits(),
12610                limits.as_ref(),
12611                "Caixa::limits must return :limits verbatim (got {:?}, \
12612                 expected {:?})",
12613                c.limits(),
12614                limits.as_ref(),
12615            );
12616            match (c.limits(), c.limits.as_ref()) {
12617                (Some(a), Some(b)) => assert!(
12618                    std::ptr::eq(a, b),
12619                    "Caixa::limits accessor and self.limits.as_ref() \
12620                     field access must borrow the same backing storage \
12621                     — the accessor is the substrate-primitive typed \
12622                     dispatch every downstream Servico-M2-overlay \
12623                     composite consumer must route through, and a \
12624                     reference-identity split would silently break \
12625                     every consumer that relied on the borrow sharing \
12626                     the composite's storage",
12627                ),
12628                (None, None) => {}
12629                _ => panic!(
12630                    "Caixa::limits presence bit must byte-equal \
12631                     self.limits.is_some() — a presence-bit drift would \
12632                     silently split the paired StandardLayout::verify \
12633                     per-`:limits` shape gate's traversal head from \
12634                     the peer render::servico_m2_overlay M2 overlay \
12635                     emitter's traversal head from the peer \
12636                     Caixa::declared_servico_slots M2 declared-slot \
12637                     enumerator's presence probe",
12638                ),
12639            }
12640            assert_eq!(
12641                c.limits().is_some(),
12642                c.limits.is_some(),
12643                "Caixa::limits().is_some() must byte-equal \
12644                 self.limits.is_some() — a presence-bit drift would \
12645                 silently split every downstream Option<&LimitsSpec> \
12646                 consumer's partition on the engine-default arm",
12647            );
12648        }
12649    }
12650
12651    #[test]
12652    fn declared_servico_slots_limits_arm_routes_through_accessor() {
12653        // Composition pin: [`Caixa::declared_servico_slots`]'s
12654        // `:limits` presence-probe arm must key off [`Caixa::limits`],
12655        // not the raw `self.limits.is_some()` field-probe. Structurally:
12656        // a `Caixa { limits: Some(LimitsSpec::default()), .. }` must
12657        // still push `M2_AUTHOR_KEY_LIMITS` onto the declared-slot list
12658        // (the presence bit is `Some`, so the M2 kind-coherence gate
12659        // must surface the slot as "declared" even when every per-axis
12660        // cap is unset), and a `Caixa { limits: None, .. }` must NOT
12661        // push the label (the "author omitted the slot entirely"
12662        // partition). The pair jointly pins the accessor + declared-
12663        // slot enumerator composition: any future silent detour that
12664        // had the accessor collapse `Some(LimitsSpec::default())` to
12665        // `None` (a `.filter(|l| !l.is_empty())` projection) would
12666        // silently absorb the "declared but empty" arm at the
12667        // accessor boundary and the [`crate::LayoutError::ServicoSlotsOnNonServico`]
12668        // kind-coherence gate would silently accept a
12669        // struct-literal `Caixa` carrying the drift.
12670        //
12671        // Peer of the sibling per-`Caixa`
12672        // `validate_deps_duplicate_arm_routes_through_accessor` (ad34b4e)
12673        // and `validate_deps_duplicate_deps_dev_arm_routes_through_accessor`
12674        // (f7fd81e) accessor-composition pins on the sibling `:deps` /
12675        // `:deps-dev` outer-`&[Dep]`-composition axes — same "the
12676        // enumerator gate must route through the substrate-primitive
12677        // typed dispatch" discipline extended onto the outer top-level
12678        // [`Caixa`] `Option<&LimitsSpec>`-composition surface, opening
12679        // the outer-`Caixa` M2 Servico-runtime-slot arm of the
12680        // composition-pin family.
12681        use crate::LimitsSpec;
12682        let c = caixa_with_limits(Some(LimitsSpec::default()));
12683        let slots = c.declared_servico_slots();
12684        assert!(
12685            slots.contains(&crate::render::M2_AUTHOR_KEY_LIMITS),
12686            "declared_servico_slots must push M2_AUTHOR_KEY_LIMITS \
12687             when `:limits` is Some (even for LimitsSpec::default()) \
12688             — the accessor and the enumerator gate must route through \
12689             the same substrate-primitive typed dispatch on the outer \
12690             :limits presence bit (got slots={slots:?})",
12691        );
12692        let c = caixa_with_limits(None);
12693        let slots = c.declared_servico_slots();
12694        assert!(
12695            !slots.contains(&crate::render::M2_AUTHOR_KEY_LIMITS),
12696            "declared_servico_slots must NOT push M2_AUTHOR_KEY_LIMITS \
12697             when `:limits` is None — the author-omitted arm must \
12698             route through the accessor's None-return unchanged (got \
12699             slots={slots:?})",
12700        );
12701    }
12702
12703    #[test]
12704    fn servico_m2_overlay_limits_arm_routes_through_accessor() {
12705        // Composition pin: [`crate::render::servico_m2_overlay`]'s
12706        // per-`:limits` M2 overlay emit arm must key off
12707        // [`Caixa::limits`], not the raw `&caixa.limits` field-borrow.
12708        // Structurally: a `Caixa { limits: Some(LimitsSpec { memory:
12709        // Some(64 MiB), .. default }), .. }` must surface the
12710        // `M2_KEY_LIMITS` key with the per-axis
12711        // `memory: "64MiB"` sub-mapping in the overlay, a `Caixa {
12712        // limits: Some(LimitsSpec::default()), .. }` must omit the
12713        // key entirely (the `.is_empty()`-gated inner arm elides an
12714        // empty composite even when the outer presence bit is `Some`),
12715        // and a `Caixa { limits: None, .. }` must also omit the key
12716        // (the "author omitted the slot entirely" partition). The
12717        // three-fixture family jointly pins the accessor + M2 overlay
12718        // emitter composition: any future silent detour that had the
12719        // accessor return a fresh-cloned copy on the `Some` arm (a
12720        // `LimitsSpec::clone()` projection) would silently break the
12721        // reference-identity pin the peer per-axis
12722        // `serde_yaml::to_value(limits)` projection reads from.
12723        use crate::LimitsSpec;
12724        use crate::render::{M2_KEY_LIMITS, servico_m2_overlay};
12725        let c = caixa_with_limits(Some(LimitsSpec {
12726            memory: Some(64 * 1024 * 1024),
12727            ..Default::default()
12728        }));
12729        let overlay = servico_m2_overlay(&c).unwrap();
12730        assert!(
12731            overlay.contains_key(M2_KEY_LIMITS),
12732            "servico_m2_overlay must surface M2_KEY_LIMITS when \
12733             `:limits` carries a non-empty composite — the accessor \
12734             and the M2 overlay emitter must route through the same \
12735             substrate-primitive typed dispatch on the outer :limits \
12736             composite (got overlay={overlay:?})",
12737        );
12738        let c = caixa_with_limits(Some(LimitsSpec::default()));
12739        let overlay = servico_m2_overlay(&c).unwrap();
12740        assert!(
12741            !overlay.contains_key(M2_KEY_LIMITS),
12742            "servico_m2_overlay must omit M2_KEY_LIMITS when \
12743             `:limits` is Some(LimitsSpec::default()) — the empty \
12744             composite's `.is_empty()`-gated inner arm must elide \
12745             the key regardless of the outer presence bit (got \
12746             overlay={overlay:?})",
12747        );
12748        let c = caixa_with_limits(None);
12749        let overlay = servico_m2_overlay(&c).unwrap();
12750        assert!(
12751            !overlay.contains_key(M2_KEY_LIMITS),
12752            "servico_m2_overlay must omit M2_KEY_LIMITS when \
12753             `:limits` is None — the author-omitted arm must route \
12754             through the accessor's None-return unchanged (got \
12755             overlay={overlay:?})",
12756        );
12757    }
12758
12759    #[test]
12760    fn limits_projects_option_ref_by_borrow() {
12761        // The by-borrow pin: [`Caixa::limits`] returns
12762        // `Option<&LimitsSpec>` by borrow — the returned reference
12763        // borrows the underlying `Option<LimitsSpec>` storage of the
12764        // `:limits` slot and the accessor must not clone the backing
12765        // composite on every call. Peer of the sibling
12766        // `deps_projects_slice_by_borrow` (ad34b4e) /
12767        // `deps_dev_projects_slice_by_borrow` (f7fd81e) by-borrow pins
12768        // on the outer top-level [`Caixa`] `&[Dep]`-return axes —
12769        // extended here to the outer [`Caixa`] `Option<&Composite>`-
12770        // return axis: the accessor's returned reference must borrow
12771        // from `&self` (the returned reference's lifetime is tied to
12772        // `&self`), and calling the accessor twice on the same
12773        // [`Caixa`] must yield references that are pointer-equal (the
12774        // underlying byte-buffer is the storage `LimitsSpec`'s
12775        // allocation, not a fresh copy) as well as value-equal
12776        // (idempotent, no side effects on `&self`).
12777        //
12778        // Pins against a future silent detour that returned an owned
12779        // `LimitsSpec` (which would type-check via the `Clone` impl
12780        // but silently clone on every call), a `&LimitsSpec` panic-
12781        // return on the `None` arm (which would collapse the load-
12782        // bearing `Option` presence-bit into a runtime panic), or a
12783        // one-arm-only accessor that returned a saturating composite
12784        // on some sentinel input.
12785        use crate::LimitsSpec;
12786        use std::time::Duration;
12787        for limits in [
12788            Some(LimitsSpec::default()),
12789            Some(LimitsSpec {
12790                memory: Some(64 * 1024 * 1024),
12791                fuel: Some(1_000_000),
12792                wall_clock: Some(Duration::from_secs(30)),
12793                cpu: Some(500),
12794            }),
12795        ] {
12796            let c = caixa_with_limits(limits.clone());
12797            let first = c.limits().unwrap();
12798            let second = c.limits().unwrap();
12799            assert_eq!(
12800                first, second,
12801                "Caixa::limits must be idempotent — two successive \
12802                 calls on the same &self must return the same \
12803                 &LimitsSpec",
12804            );
12805            assert!(
12806                std::ptr::eq(first, second),
12807                "Caixa::limits must borrow the underlying \
12808                 Option<LimitsSpec> storage — two successive calls \
12809                 must return references with the same backing pointer \
12810                 (a fresh LimitsSpec clone would change the pointer \
12811                 on every call)",
12812            );
12813            assert_eq!(
12814                Some(first),
12815                limits.as_ref(),
12816                "Caixa::limits must return :limits verbatim by borrow \
12817                 — got {first:?}, expected {:?}",
12818                limits.as_ref(),
12819            );
12820        }
12821        let c = caixa_with_limits(None);
12822        assert!(
12823            c.limits().is_none(),
12824            "Caixa::limits must return None when :limits is absent — \
12825             the author-omitted arm must project through the \
12826             accessor's Option::None unchanged",
12827        );
12828    }
12829
12830    // ── Caixa::behavior — outer top-level Option<&BehaviorSpec> composite-reference accessor ──
12831
12832    fn caixa_with_behavior(behavior: Option<crate::BehaviorSpec>) -> Caixa {
12833        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12834        c.behavior = behavior;
12835        c
12836    }
12837
12838    #[test]
12839    fn behavior_returns_behavior_option_ref_verbatim_across_permutations() {
12840        // The canonical per-`Caixa` `:behavior` M2 typed-slot outer-
12841        // composite optional-composite-reference-shape pin:
12842        // [`Caixa::behavior`] must return the `:behavior` typed
12843        // `Option<BehaviorSpec>` verbatim as an `Option<&BehaviorSpec>`
12844        // reference over the same backing storage the raw
12845        // `self.behavior.as_ref()` field access borrows from, byte-equal
12846        // across every representative fixture in the accept-set — the
12847        // author-omitted `None` shape (the "runtime-default applies"
12848        // partition every downstream Servico M2 overlay emitter treats
12849        // as "emit nothing"), the empty-composite `Some(BehaviorSpec {
12850        // .. default })` shape ([`BehaviorSpec::is_empty`] holds —
12851        // every per-callback path is `None`, so the peer M2 overlay
12852        // emitter's `.is_empty()`-gated projection still emits nothing
12853        // but the outer presence-bit is `Some`, so
12854        // [`Caixa::declared_servico_slots`] still pushes the
12855        // `M2_AUTHOR_KEY_BEHAVIOR` label), a single-callback fixture
12856        // (only `:on-state-change` set — the canonical shape a caixa
12857        // that only wires the hot-upgrade migration path carries), and
12858        // a fully-populated composite (every per-callback path set —
12859        // the canonical shape a fully-instrumented gen_server-shaped
12860        // Servico carries).
12861        //
12862        // Peer of the sibling
12863        // `limits_returns_limits_option_ref_verbatim_across_permutations`
12864        // (b2bd9d7) opening fixture-family + reference-identity +
12865        // presence-bit tetrad pin on the outer top-level [`Caixa`]
12866        // `Option<&Composite>`-return sub-family — extended here to the
12867        // second axis of that sub-family so both of the currently-lifted
12868        // M2 Servico-runtime `Option<&Composite>` slots (`:limits` /
12869        // `:behavior`) carry the same "byte-equal, borrow-shared,
12870        // presence-bit-preserved" outer-accessor discipline.
12871        //
12872        // Pins against a future silent detour that returned a fresh-
12873        // cloned [`crate::BehaviorSpec`] copy (which would type-check
12874        // via the `Clone` impl but silently break every downstream
12875        // caller that relied on the reference sharing the composite's
12876        // backing identity), a reference to an operator-resolved
12877        // overlay (a future per-cluster `:behavior-overrides` slot —
12878        // its resolution must land at exactly this accessor body, not
12879        // silently divert the raw slot away from a second consumer), a
12880        // `None` → `Some(BehaviorSpec::default)` cluster-default
12881        // projection (which would collapse the load-bearing
12882        // "author-omitted `:behavior` ⇒ runtime-default applies"
12883        // partition the peer [`crate::render::servico_m2_overlay`]
12884        // emitter, the peer [`Caixa::declared_servico_slots`]
12885        // enumerator, and the cross-slot
12886        // [`crate::upgrade::validate_upgrade_from_against_behavior`]
12887        // gate all read), or a callback-shuffled projection (a future
12888        // detour that swapped `on_init` and `on_terminate` through the
12889        // accessor would silently split the paired
12890        // [`crate::StandardLayout::verify`] per-`:behavior` shape gate's
12891        // traversal input from the peer `servico_m2_overlay` emitter's
12892        // projection input from the cross-slot `:state-change`
12893        // composition gate's traversal input).
12894        use crate::BehaviorSpec;
12895        use std::path::PathBuf;
12896        let fixtures: Vec<Option<BehaviorSpec>> = vec![
12897            None,
12898            Some(BehaviorSpec::default()),
12899            Some(BehaviorSpec {
12900                on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
12901                ..Default::default()
12902            }),
12903            Some(BehaviorSpec {
12904                on_init: Some(PathBuf::from("lib/init.lisp")),
12905                on_call: Some(PathBuf::from("lib/handlers.lisp")),
12906                on_cast: Some(PathBuf::from("lib/handlers.lisp")),
12907                on_info: Some(PathBuf::from("lib/handlers.lisp")),
12908                on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
12909                on_terminate: Some(PathBuf::from("lib/cleanup.lisp")),
12910            }),
12911        ];
12912        for behavior in fixtures {
12913            let c = caixa_with_behavior(behavior.clone());
12914            assert_eq!(
12915                c.behavior(),
12916                behavior.as_ref(),
12917                "Caixa::behavior must return :behavior verbatim (got \
12918                 {:?}, expected {:?})",
12919                c.behavior(),
12920                behavior.as_ref(),
12921            );
12922            match (c.behavior(), c.behavior.as_ref()) {
12923                (Some(a), Some(b)) => assert!(
12924                    std::ptr::eq(a, b),
12925                    "Caixa::behavior accessor and self.behavior.as_ref() \
12926                     field access must borrow the same backing storage \
12927                     — the accessor is the substrate-primitive typed \
12928                     dispatch every downstream Servico-M2-overlay \
12929                     composite consumer must route through, and a \
12930                     reference-identity split would silently break \
12931                     every consumer that relied on the borrow sharing \
12932                     the composite's storage",
12933                ),
12934                (None, None) => {}
12935                _ => panic!(
12936                    "Caixa::behavior presence bit must byte-equal \
12937                     self.behavior.is_some() — a presence-bit drift \
12938                     would silently split the paired \
12939                     StandardLayout::verify per-`:behavior` shape \
12940                     gate's traversal head from the peer \
12941                     render::servico_m2_overlay M2 overlay emitter's \
12942                     traversal head from the cross-slot \
12943                     validate_upgrade_from_against_behavior \
12944                     composition gate's traversal head from the peer \
12945                     Caixa::declared_servico_slots M2 declared-slot \
12946                     enumerator's presence probe",
12947                ),
12948            }
12949            assert_eq!(
12950                c.behavior().is_some(),
12951                c.behavior.is_some(),
12952                "Caixa::behavior().is_some() must byte-equal \
12953                 self.behavior.is_some() — a presence-bit drift would \
12954                 silently split every downstream Option<&BehaviorSpec> \
12955                 consumer's partition on the runtime-default arm",
12956            );
12957        }
12958    }
12959
12960    #[test]
12961    fn declared_servico_slots_behavior_arm_routes_through_accessor() {
12962        // Composition pin: [`Caixa::declared_servico_slots`]'s
12963        // `:behavior` presence-probe arm must key off
12964        // [`Caixa::behavior`], not the raw `self.behavior.is_some()`
12965        // field-probe. Structurally: a `Caixa { behavior:
12966        // Some(BehaviorSpec::default()), .. }` must still push
12967        // `M2_AUTHOR_KEY_BEHAVIOR` onto the declared-slot list (the
12968        // presence bit is `Some`, so the M2 kind-coherence gate must
12969        // surface the slot as "declared" even when every per-callback
12970        // path is unset), and a `Caixa { behavior: None, .. }` must
12971        // NOT push the label (the "author omitted the slot entirely"
12972        // partition). The pair jointly pins the accessor + declared-
12973        // slot enumerator composition: any future silent detour that
12974        // had the accessor collapse `Some(BehaviorSpec::default())`
12975        // to `None` (a `.filter(|b| !b.is_empty())` projection) would
12976        // silently absorb the "declared but empty" arm at the
12977        // accessor boundary and the
12978        // [`crate::LayoutError::ServicoSlotsOnNonServico`]
12979        // kind-coherence gate would silently accept a struct-literal
12980        // `Caixa` carrying the drift.
12981        //
12982        // Peer of the sibling
12983        // `declared_servico_slots_limits_arm_routes_through_accessor`
12984        // (b2bd9d7) composition pin on the sibling `:limits` outer-
12985        // `Option<&LimitsSpec>` arm of the same
12986        // [`Caixa::declared_servico_slots`] M2 declared-slot
12987        // enumerator's traversal — same "the enumerator gate must
12988        // route through the substrate-primitive typed dispatch"
12989        // discipline extended onto the outer top-level [`Caixa`]
12990        // `Option<&BehaviorSpec>`-composition surface.
12991        use crate::BehaviorSpec;
12992        let c = caixa_with_behavior(Some(BehaviorSpec::default()));
12993        let slots = c.declared_servico_slots();
12994        assert!(
12995            slots.contains(&crate::render::M2_AUTHOR_KEY_BEHAVIOR),
12996            "declared_servico_slots must push M2_AUTHOR_KEY_BEHAVIOR \
12997             when `:behavior` is Some (even for BehaviorSpec::default()) \
12998             — the accessor and the enumerator gate must route through \
12999             the same substrate-primitive typed dispatch on the outer \
13000             :behavior presence bit (got slots={slots:?})",
13001        );
13002        let c = caixa_with_behavior(None);
13003        let slots = c.declared_servico_slots();
13004        assert!(
13005            !slots.contains(&crate::render::M2_AUTHOR_KEY_BEHAVIOR),
13006            "declared_servico_slots must NOT push M2_AUTHOR_KEY_BEHAVIOR \
13007             when `:behavior` is None — the author-omitted arm must \
13008             route through the accessor's None-return unchanged (got \
13009             slots={slots:?})",
13010        );
13011    }
13012
13013    #[test]
13014    fn servico_m2_overlay_behavior_arm_routes_through_accessor() {
13015        // Composition pin: [`crate::render::servico_m2_overlay`]'s
13016        // per-`:behavior` M2 overlay emit arm must key off
13017        // [`Caixa::behavior`], not the raw `&caixa.behavior`
13018        // field-borrow. Structurally: a `Caixa { behavior:
13019        // Some(BehaviorSpec { on_state_change: Some(...), .. default
13020        // }), .. }` must surface the `M2_KEY_BEHAVIOR` key with the
13021        // per-callback `onStateChange` sub-mapping in the overlay, a
13022        // `Caixa { behavior: Some(BehaviorSpec::default()), .. }`
13023        // must omit the key entirely (the `.is_empty()`-gated inner
13024        // arm elides an empty composite even when the outer presence
13025        // bit is `Some`), and a `Caixa { behavior: None, .. }` must
13026        // also omit the key (the "author omitted the slot entirely"
13027        // partition). The three-fixture family jointly pins the
13028        // accessor + M2 overlay emitter composition: any future
13029        // silent detour that had the accessor return a fresh-cloned
13030        // copy on the `Some` arm (a `BehaviorSpec::clone()`
13031        // projection) would silently break the reference-identity
13032        // pin the peer per-callback `serde_yaml::to_value(behavior)`
13033        // projection reads from.
13034        //
13035        // Peer of the sibling
13036        // `servico_m2_overlay_limits_arm_routes_through_accessor`
13037        // (b2bd9d7) composition pin on the sibling `:limits` outer-
13038        // `Option<&LimitsSpec>` arm of the same
13039        // [`crate::render::servico_m2_overlay`] M2 overlay emitter's
13040        // traversal — same "the emitter must route through the
13041        // substrate-primitive typed dispatch on the outer composite"
13042        // discipline extended onto the outer top-level [`Caixa`]
13043        // `Option<&BehaviorSpec>`-composition surface.
13044        use crate::BehaviorSpec;
13045        use crate::render::{M2_KEY_BEHAVIOR, servico_m2_overlay};
13046        use std::path::PathBuf;
13047        let c = caixa_with_behavior(Some(BehaviorSpec {
13048            on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
13049            ..Default::default()
13050        }));
13051        let overlay = servico_m2_overlay(&c).unwrap();
13052        assert!(
13053            overlay.contains_key(M2_KEY_BEHAVIOR),
13054            "servico_m2_overlay must surface M2_KEY_BEHAVIOR when \
13055             `:behavior` carries a non-empty composite — the accessor \
13056             and the M2 overlay emitter must route through the same \
13057             substrate-primitive typed dispatch on the outer :behavior \
13058             composite (got overlay={overlay:?})",
13059        );
13060        let c = caixa_with_behavior(Some(BehaviorSpec::default()));
13061        let overlay = servico_m2_overlay(&c).unwrap();
13062        assert!(
13063            !overlay.contains_key(M2_KEY_BEHAVIOR),
13064            "servico_m2_overlay must omit M2_KEY_BEHAVIOR when \
13065             `:behavior` is Some(BehaviorSpec::default()) — the empty \
13066             composite's `.is_empty()`-gated inner arm must elide the \
13067             key regardless of the outer presence bit (got \
13068             overlay={overlay:?})",
13069        );
13070        let c = caixa_with_behavior(None);
13071        let overlay = servico_m2_overlay(&c).unwrap();
13072        assert!(
13073            !overlay.contains_key(M2_KEY_BEHAVIOR),
13074            "servico_m2_overlay must omit M2_KEY_BEHAVIOR when \
13075             `:behavior` is None — the author-omitted arm must route \
13076             through the accessor's None-return unchanged (got \
13077             overlay={overlay:?})",
13078        );
13079    }
13080
13081    #[test]
13082    fn behavior_projects_option_ref_by_borrow() {
13083        // The by-borrow pin: [`Caixa::behavior`] returns
13084        // `Option<&BehaviorSpec>` by borrow — the returned reference
13085        // borrows the underlying `Option<BehaviorSpec>` storage of the
13086        // `:behavior` slot and the accessor must not clone the backing
13087        // composite on every call. Peer of the sibling
13088        // `limits_projects_option_ref_by_borrow` (b2bd9d7) by-borrow
13089        // pin on the outer top-level [`Caixa`] `Option<&Composite>`-
13090        // return sub-family — extended here to the second axis of the
13091        // same sub-family: the accessor's returned reference must
13092        // borrow from `&self` (the returned reference's lifetime is
13093        // tied to `&self`), and calling the accessor twice on the same
13094        // [`Caixa`] must yield references that are pointer-equal (the
13095        // underlying byte-buffer is the storage `BehaviorSpec`'s
13096        // allocation, not a fresh copy) as well as value-equal
13097        // (idempotent, no side effects on `&self`).
13098        //
13099        // Pins against a future silent detour that returned an owned
13100        // `BehaviorSpec` (which would type-check via the `Clone` impl
13101        // but silently clone on every call), a `&BehaviorSpec` panic-
13102        // return on the `None` arm (which would collapse the load-
13103        // bearing `Option` presence-bit into a runtime panic), or a
13104        // one-arm-only accessor that returned a saturating composite
13105        // on some sentinel input.
13106        use crate::BehaviorSpec;
13107        use std::path::PathBuf;
13108        for behavior in [
13109            Some(BehaviorSpec::default()),
13110            Some(BehaviorSpec {
13111                on_init: Some(PathBuf::from("lib/init.lisp")),
13112                on_call: Some(PathBuf::from("lib/handlers.lisp")),
13113                on_cast: Some(PathBuf::from("lib/handlers.lisp")),
13114                on_info: Some(PathBuf::from("lib/handlers.lisp")),
13115                on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
13116                on_terminate: Some(PathBuf::from("lib/cleanup.lisp")),
13117            }),
13118        ] {
13119            let c = caixa_with_behavior(behavior.clone());
13120            let first = c.behavior().unwrap();
13121            let second = c.behavior().unwrap();
13122            assert_eq!(
13123                first, second,
13124                "Caixa::behavior must be idempotent — two successive \
13125                 calls on the same &self must return the same \
13126                 &BehaviorSpec",
13127            );
13128            assert!(
13129                std::ptr::eq(first, second),
13130                "Caixa::behavior must borrow the underlying \
13131                 Option<BehaviorSpec> storage — two successive calls \
13132                 must return references with the same backing pointer \
13133                 (a fresh BehaviorSpec clone would change the pointer \
13134                 on every call)",
13135            );
13136            assert_eq!(
13137                Some(first),
13138                behavior.as_ref(),
13139                "Caixa::behavior must return :behavior verbatim by \
13140                 borrow — got {first:?}, expected {:?}",
13141                behavior.as_ref(),
13142            );
13143        }
13144        let c = caixa_with_behavior(None);
13145        assert!(
13146            c.behavior().is_none(),
13147            "Caixa::behavior must return None when :behavior is absent \
13148             — the author-omitted arm must project through the \
13149             accessor's Option::None unchanged",
13150        );
13151    }
13152
13153    // ── Caixa::politicas — outer top-level Option<&MeshPolicy> composite-reference accessor ──
13154
13155    fn caixa_aplicacao_with_politicas(politicas: Option<crate::aplicacao::MeshPolicy>) -> Caixa {
13156        use crate::aplicacao::{Membro, WitContract};
13157        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
13158        c.kind = CaixaKind::Aplicacao;
13159        c.membros = vec![Membro {
13160            caixa: "a".into(),
13161            versao: "^0.1".into(),
13162        }];
13163        c.contratos = vec![WitContract {
13164            de: "a".into(),
13165            para: "a".into(),
13166            wit: "wasi:http/proxy".into(),
13167            endpoint: Some("/x".into()),
13168            subject: None,
13169            slot: None,
13170        }];
13171        c.politicas = politicas;
13172        c
13173    }
13174
13175    #[test]
13176    fn politicas_returns_politicas_option_ref_verbatim_across_permutations() {
13177        // The canonical per-`Caixa` `:politicas` M3 mesh-slot outer-
13178        // composite optional-composite-reference-shape pin:
13179        // [`Caixa::politicas`] must return the `:politicas` typed
13180        // `Option<MeshPolicy>` verbatim as an `Option<&MeshPolicy>`
13181        // reference over the same backing storage the raw
13182        // `self.politicas.as_ref()` field access borrows from,
13183        // byte-equal across every representative fixture in the
13184        // accept-set — the author-omitted `None` shape (the "cluster-
13185        // default applies" partition every downstream mesh-artifact
13186        // emitter treats as "emit no `:politicas` overlay"), the
13187        // empty-composite `Some(MeshPolicy { .. default })` shape
13188        // ([`crate::aplicacao::MeshPolicy::is_empty`] holds — every
13189        // per-axis mesh-policy scalar is `None`, so the peer inner
13190        // [`crate::AplicacaoSpec::politicas`] `.is_empty()`-gated
13191        // caixa-mesh overlay elides every per-axis emit but the outer
13192        // presence-bit is `Some`, so [`Caixa::declared_mesh_slots`]
13193        // still pushes the `M3_AUTHOR_KEY_POLITICAS` label), a
13194        // single-axis fixture (only `:timeout` set — the canonical
13195        // shape a latency-sensitive Aplicacao carries), and a
13196        // fully-populated composite (every per-axis mesh-policy
13197        // scalar set — the canonical shape a fully-governed
13198        // Aplicacao carries).
13199        //
13200        // Pins against a future silent detour that returned a fresh-
13201        // cloned [`crate::aplicacao::MeshPolicy`] copy (which would
13202        // type-check via the `Clone` impl but silently break every
13203        // downstream caller that relied on the reference sharing the
13204        // composite's backing identity), a reference to an operator-
13205        // resolved overlay (the future per-cluster
13206        // `:politicas-overrides` slot — its resolution must land at
13207        // exactly this accessor body, not silently divert the raw
13208        // slot away from the peer [`Caixa::declared_mesh_slots`]
13209        // enumerator's presence probe), a
13210        // `None` → `Some(MeshPolicy::default)` cluster-default
13211        // projection (which would collapse the load-bearing
13212        // "author-omitted `:politicas` ⇒ cluster-default applies"
13213        // partition the peer [`Caixa::declared_mesh_slots`]
13214        // enumerator and the peer [`Caixa::aplicacao_view`]
13215        // Aplicacao-composition seed both read), or an axis-shuffled
13216        // projection (a future detour that swapped `timeout` and
13217        // `retries` through the accessor would silently split the
13218        // paired [`Caixa::aplicacao_view`] seed's fold input from the
13219        // sibling M3 mesh-artifact emitter's projection input).
13220        //
13221        // Third outer top-level [`Caixa`] `Option<&Composite>`-return
13222        // composite-reference accessor pin on the substrate primitive
13223        // — peer of the sibling
13224        // `limits_returns_limits_option_ref_verbatim_across_permutations`
13225        // (b2bd9d7) and
13226        // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
13227        // (35d8b52) opening tetrad pins on the outer top-level
13228        // [`Caixa`] `Option<&Composite>`-return sub-family — extended
13229        // here to the first of the three M3 mesh-slot axes so the
13230        // opening third of the outer `Option<&Composite>` sub-family
13231        // carries the same "byte-equal, borrow-shared, presence-bit-
13232        // preserved" outer-accessor discipline.
13233        use crate::aplicacao::{CircuitBreaker, MeshPolicy, RateLimit};
13234        use std::time::Duration;
13235        let fixtures: Vec<Option<MeshPolicy>> = vec![
13236            None,
13237            Some(MeshPolicy::default()),
13238            Some(MeshPolicy {
13239                timeout: Some(Duration::from_secs(30)),
13240                ..Default::default()
13241            }),
13242            Some(MeshPolicy {
13243                timeout: Some(Duration::from_secs(30)),
13244                retries: Some(3),
13245                circuit_breaker: Some(CircuitBreaker {
13246                    max_failures: 5,
13247                    window: Duration::from_secs(60),
13248                }),
13249                mtls_required: Some(true),
13250                rate_limit: Some(RateLimit {
13251                    rate: 100,
13252                    window: Duration::from_secs(1),
13253                }),
13254            }),
13255        ];
13256        for politicas in fixtures {
13257            let c = caixa_aplicacao_with_politicas(politicas.clone());
13258            assert_eq!(
13259                c.politicas(),
13260                politicas.as_ref(),
13261                "Caixa::politicas must return :politicas verbatim (got \
13262                 {:?}, expected {:?})",
13263                c.politicas(),
13264                politicas.as_ref(),
13265            );
13266            match (c.politicas(), c.politicas.as_ref()) {
13267                (Some(a), Some(b)) => assert!(
13268                    std::ptr::eq(a, b),
13269                    "Caixa::politicas accessor and self.politicas.as_ref() \
13270                     field access must borrow the same backing storage \
13271                     — the accessor is the substrate-primitive typed \
13272                     dispatch every downstream Aplicacao-mesh-overlay \
13273                     composite consumer must route through, and a \
13274                     reference-identity split would silently break \
13275                     every consumer that relied on the borrow sharing \
13276                     the composite's storage",
13277                ),
13278                (None, None) => {}
13279                _ => panic!(
13280                    "Caixa::politicas presence bit must byte-equal \
13281                     self.politicas.is_some() — a presence-bit drift \
13282                     would silently split the paired \
13283                     Caixa::aplicacao_view Aplicacao-composition seed's \
13284                     traversal head from the peer \
13285                     Caixa::declared_mesh_slots M3 declared-slot \
13286                     enumerator's presence probe",
13287                ),
13288            }
13289            assert_eq!(
13290                c.politicas().is_some(),
13291                c.politicas.is_some(),
13292                "Caixa::politicas().is_some() must byte-equal \
13293                 self.politicas.is_some() — a presence-bit drift would \
13294                 silently split every downstream Option<&MeshPolicy> \
13295                 consumer's partition on the cluster-default arm",
13296            );
13297        }
13298    }
13299
13300    #[test]
13301    fn declared_mesh_slots_politicas_arm_routes_through_accessor() {
13302        // Composition pin: [`Caixa::declared_mesh_slots`]'s
13303        // `:politicas` presence-probe arm must key off
13304        // [`Caixa::politicas`], not the raw `self.politicas.is_some()`
13305        // field-probe. Structurally: a `Caixa { politicas:
13306        // Some(MeshPolicy::default()), .. }` must still push
13307        // `M3_AUTHOR_KEY_POLITICAS` onto the declared-slot list (the
13308        // presence bit is `Some`, so the M3 kind-coherence gate must
13309        // surface the slot as "declared" even when every per-axis
13310        // scalar is unset), and a `Caixa { politicas: None, .. }` must
13311        // NOT push the label (the "author omitted the slot entirely"
13312        // partition). The pair jointly pins the accessor + declared-
13313        // slot enumerator composition: any future silent detour that
13314        // had the accessor collapse `Some(MeshPolicy::default())` to
13315        // `None` (a `.filter(|p| !p.is_empty())` projection) would
13316        // silently absorb the "declared but empty" arm at the
13317        // accessor boundary and the
13318        // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
13319        // coherence gate would silently accept a struct-literal
13320        // `Caixa` carrying the drift.
13321        //
13322        // Peer of the sibling
13323        // `declared_servico_slots_limits_arm_routes_through_accessor`
13324        // (b2bd9d7) and
13325        // `declared_servico_slots_behavior_arm_routes_through_accessor`
13326        // (35d8b52) composition pins on the sibling `:limits` /
13327        // `:behavior` outer-`Option<&Composite>` arms of the peer
13328        // [`Caixa::declared_servico_slots`] M2 declared-slot
13329        // enumerator's traversal — same "the enumerator gate must
13330        // route through the substrate-primitive typed dispatch"
13331        // discipline extended onto the outer top-level [`Caixa`] M3
13332        // mesh-slot family so the [`Caixa::declared_mesh_slots`]
13333        // enumerator carries the same routing invariant as its M2
13334        // sibling.
13335        use crate::aplicacao::MeshPolicy;
13336        let c = caixa_aplicacao_with_politicas(Some(MeshPolicy::default()));
13337        let slots = c.declared_mesh_slots();
13338        assert!(
13339            slots.contains(&crate::render::M3_AUTHOR_KEY_POLITICAS),
13340            "declared_mesh_slots must push M3_AUTHOR_KEY_POLITICAS \
13341             when `:politicas` is Some (even for MeshPolicy::default()) \
13342             — the accessor and the enumerator gate must route through \
13343             the same substrate-primitive typed dispatch on the outer \
13344             :politicas presence bit (got slots={slots:?})",
13345        );
13346        let c = caixa_aplicacao_with_politicas(None);
13347        let slots = c.declared_mesh_slots();
13348        assert!(
13349            !slots.contains(&crate::render::M3_AUTHOR_KEY_POLITICAS),
13350            "declared_mesh_slots must NOT push M3_AUTHOR_KEY_POLITICAS \
13351             when `:politicas` is None — the author-omitted arm must \
13352             route through the accessor's None-return unchanged (got \
13353             slots={slots:?})",
13354        );
13355    }
13356
13357    #[test]
13358    fn aplicacao_view_politicas_arm_folds_through_accessor() {
13359        // Composition pin: [`Caixa::aplicacao_view`]'s per-`:politicas`
13360        // Aplicacao-composition seed must fold through
13361        // [`Caixa::politicas`], not the raw
13362        // `self.politicas.clone().unwrap_or_default()` field-borrow.
13363        // Structurally: a `Caixa { politicas: Some(MeshPolicy {
13364        // timeout: Some(30s), .. default }), kind: Aplicacao, .. }`
13365        // must surface a projected [`crate::AplicacaoSpec`] whose
13366        // `politicas().timeout()` field byte-equals the outer
13367        // composite's `timeout` scalar (the fold must project the
13368        // authored composite verbatim), a `Caixa { politicas:
13369        // Some(MeshPolicy::default()), kind: Aplicacao, .. }` must
13370        // surface an [`crate::AplicacaoSpec`] whose `politicas()`
13371        // byte-equals [`crate::aplicacao::MeshPolicy::default`] (the
13372        // fold's empty-composite arm collapses to the same default the
13373        // author-omitted arm does), and a `Caixa { politicas: None,
13374        // kind: Aplicacao, .. }` must surface an
13375        // [`crate::AplicacaoSpec`] whose `politicas()` byte-equals
13376        // [`crate::aplicacao::MeshPolicy::default`] (the "author
13377        // omitted the slot entirely" arm folds through the
13378        // `unwrap_or_default` onto the cluster-default). The triad
13379        // jointly pins the accessor + Aplicacao-composition seed
13380        // composition: any future silent detour that had the accessor
13381        // divert the raw slot away from the seed's fold (an operator-
13382        // resolved overlay's default-fold arm silently differing from
13383        // the raw slot's default-fold arm) would silently split the
13384        // build-time mesh-artifact emission gate from the caixa-mesh
13385        // renderer's Aplicacao-view input at the composition boundary.
13386        use crate::aplicacao::MeshPolicy;
13387        use std::time::Duration;
13388        let c = caixa_aplicacao_with_politicas(Some(MeshPolicy {
13389            timeout: Some(Duration::from_secs(30)),
13390            ..Default::default()
13391        }));
13392        let view = c.aplicacao_view().unwrap();
13393        assert_eq!(
13394            view.politicas().timeout(),
13395            Some(Duration::from_secs(30)),
13396            "Caixa::aplicacao_view must fold the authored :politicas \
13397             :timeout scalar through the accessor verbatim onto the \
13398             projected AplicacaoSpec — a future silent detour at the \
13399             seed's fold arm would surface here as a projected-scalar \
13400             drift (got {:?})",
13401            view.politicas().timeout(),
13402        );
13403        let c = caixa_aplicacao_with_politicas(Some(MeshPolicy::default()));
13404        let view = c.aplicacao_view().unwrap();
13405        assert_eq!(
13406            view.politicas(),
13407            &MeshPolicy::default(),
13408            "Caixa::aplicacao_view must fold Some(MeshPolicy::default()) \
13409             through the accessor onto MeshPolicy::default — the empty- \
13410             composite arm collapses to the same default the author- \
13411             omitted arm does (got {:?})",
13412            view.politicas(),
13413        );
13414        let c = caixa_aplicacao_with_politicas(None);
13415        let view = c.aplicacao_view().unwrap();
13416        assert_eq!(
13417            view.politicas(),
13418            &MeshPolicy::default(),
13419            "Caixa::aplicacao_view must fold None through the accessor's \
13420             unwrap_or_default onto MeshPolicy::default — the author- \
13421             omitted arm must route through the accessor's None-return \
13422             unchanged (got {:?})",
13423            view.politicas(),
13424        );
13425    }
13426
13427    #[test]
13428    fn politicas_projects_option_ref_by_borrow() {
13429        // The by-borrow pin: [`Caixa::politicas`] returns
13430        // `Option<&MeshPolicy>` by borrow — the returned reference
13431        // borrows the underlying `Option<MeshPolicy>` storage of the
13432        // `:politicas` slot and the accessor must not clone the
13433        // backing composite on every call. Peer of the sibling
13434        // `limits_projects_option_ref_by_borrow` (b2bd9d7) and
13435        // `behavior_projects_option_ref_by_borrow` (35d8b52) by-borrow
13436        // pins on the outer top-level [`Caixa`]
13437        // `Option<&Composite>`-return sub-family — extended here to
13438        // the third axis of the same sub-family: the accessor's
13439        // returned reference must borrow from `&self` (the returned
13440        // reference's lifetime is tied to `&self`), and calling the
13441        // accessor twice on the same [`Caixa`] must yield references
13442        // that are pointer-equal (the underlying byte-buffer is the
13443        // storage `MeshPolicy`'s allocation, not a fresh copy) as
13444        // well as value-equal (idempotent, no side effects on
13445        // `&self`).
13446        //
13447        // Pins against a future silent detour that returned an owned
13448        // `MeshPolicy` (which would type-check via the `Clone` impl
13449        // but silently clone on every call), a `&MeshPolicy` panic-
13450        // return on the `None` arm (which would collapse the load-
13451        // bearing `Option` presence-bit into a runtime panic), or a
13452        // one-arm-only accessor that returned a saturating composite
13453        // on some sentinel input.
13454        use crate::aplicacao::{CircuitBreaker, MeshPolicy, RateLimit};
13455        use std::time::Duration;
13456        for politicas in [
13457            Some(MeshPolicy::default()),
13458            Some(MeshPolicy {
13459                timeout: Some(Duration::from_secs(30)),
13460                retries: Some(3),
13461                circuit_breaker: Some(CircuitBreaker {
13462                    max_failures: 5,
13463                    window: Duration::from_secs(60),
13464                }),
13465                mtls_required: Some(true),
13466                rate_limit: Some(RateLimit {
13467                    rate: 100,
13468                    window: Duration::from_secs(1),
13469                }),
13470            }),
13471        ] {
13472            let c = caixa_aplicacao_with_politicas(politicas.clone());
13473            let first = c.politicas().unwrap();
13474            let second = c.politicas().unwrap();
13475            assert_eq!(
13476                first, second,
13477                "Caixa::politicas must be idempotent — two successive \
13478                 calls on the same &self must return the same \
13479                 &MeshPolicy",
13480            );
13481            assert!(
13482                std::ptr::eq(first, second),
13483                "Caixa::politicas must borrow the underlying \
13484                 Option<MeshPolicy> storage — two successive calls \
13485                 must return references with the same backing pointer \
13486                 (a fresh MeshPolicy clone would change the pointer on \
13487                 every call)",
13488            );
13489            assert_eq!(
13490                Some(first),
13491                politicas.as_ref(),
13492                "Caixa::politicas must return :politicas verbatim by \
13493                 borrow — got {first:?}, expected {:?}",
13494                politicas.as_ref(),
13495            );
13496        }
13497        let c = caixa_aplicacao_with_politicas(None);
13498        assert!(
13499            c.politicas().is_none(),
13500            "Caixa::politicas must return None when :politicas is \
13501             absent — the author-omitted arm must project through the \
13502             accessor's Option::None unchanged",
13503        );
13504    }
13505
13506    // ── Caixa::placement — outer top-level Option<&Placement> composite-reference accessor ──
13507
13508    fn caixa_aplicacao_with_placement(placement: Option<crate::aplicacao::Placement>) -> Caixa {
13509        use crate::aplicacao::{Membro, WitContract};
13510        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
13511        c.kind = CaixaKind::Aplicacao;
13512        c.membros = vec![Membro {
13513            caixa: "a".into(),
13514            versao: "^0.1".into(),
13515        }];
13516        c.contratos = vec![WitContract {
13517            de: "a".into(),
13518            para: "a".into(),
13519            wit: "wasi:http/proxy".into(),
13520            endpoint: Some("/x".into()),
13521            subject: None,
13522            slot: None,
13523        }];
13524        c.placement = placement;
13525        c
13526    }
13527
13528    #[test]
13529    fn placement_returns_placement_option_ref_verbatim_across_permutations() {
13530        // The canonical per-`Caixa` `:placement` M3 mesh-slot outer-
13531        // composite optional-composite-reference-shape pin:
13532        // [`Caixa::placement`] must return the `:placement` typed
13533        // `Option<Placement>` verbatim as an `Option<&Placement>`
13534        // reference over the same backing storage the raw
13535        // `self.placement.as_ref()` field access borrows from,
13536        // byte-equal across every representative fixture in the
13537        // accept-set — the author-omitted `None` shape (the
13538        // "cluster-default applies" partition every downstream mesh-
13539        // artifact emitter treats as "emit no `:placement` overlay"),
13540        // the empty-composite `Some(Placement { .. default })` shape
13541        // (`estrategia: SingleNode`, empty clusters, no shard-key /
13542        // affinity — the outer presence-bit is `Some` so
13543        // [`Caixa::declared_mesh_slots`] still pushes the
13544        // `M3_AUTHOR_KEY_PLACEMENT` label), a single-axis
13545        // `Replicated`-on-two-clusters fixture (the canonical shape a
13546        // stateless HTTP Aplicacao carries), and a fully-populated
13547        // `Sharded`-with-shard-key-and-affinity fixture (the canonical
13548        // shape a stateful Akka-style cluster-sharding Aplicacao
13549        // carries).
13550        //
13551        // Pins against a future silent detour that returned a fresh-
13552        // cloned [`crate::aplicacao::Placement`] copy (which would
13553        // type-check via the `Clone` impl but silently break every
13554        // downstream caller that relied on the reference sharing the
13555        // composite's backing identity), a reference to an operator-
13556        // resolved overlay (the future per-cluster
13557        // `:placement-overrides` slot — its resolution must land at
13558        // exactly this accessor body, not silently divert the raw
13559        // slot away from the peer [`Caixa::declared_mesh_slots`]
13560        // enumerator's presence probe), a `None` →
13561        // `Some(Placement::default)` cluster-default projection (which
13562        // would collapse the load-bearing "author-omitted `:placement`
13563        // ⇒ cluster-default applies" partition the peer
13564        // [`Caixa::declared_mesh_slots`] enumerator and the peer
13565        // [`Caixa::aplicacao_view`] Aplicacao-composition seed both
13566        // read), or an axis-shuffled projection (a future detour that
13567        // swapped `clusters` and `affinity` through the accessor would
13568        // silently split the paired [`Caixa::aplicacao_view`] seed's
13569        // fold input from the sibling M3 mesh-artifact emitter's
13570        // projection input).
13571        //
13572        // Fourth outer top-level [`Caixa`] `Option<&Composite>`-return
13573        // composite-reference accessor pin on the substrate primitive
13574        // — peer of the sibling
13575        // `limits_returns_limits_option_ref_verbatim_across_permutations`
13576        // (b2bd9d7),
13577        // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
13578        // (35d8b52), and
13579        // `politicas_returns_politicas_option_ref_verbatim_across_permutations`
13580        // (5d23d29) opening triad pins on the outer top-level
13581        // [`Caixa`] `Option<&Composite>`-return sub-family — extended
13582        // here to the second of the three M3 mesh-slot axes so the
13583        // opening four-fifths of the outer `Option<&Composite>` sub-
13584        // family carries the same "byte-equal, borrow-shared,
13585        // presence-bit-preserved" outer-accessor discipline.
13586        use crate::aplicacao::{Placement, PlacementStrategy};
13587        let fixtures: Vec<Option<Placement>> = vec![
13588            None,
13589            Some(Placement::default()),
13590            Some(Placement {
13591                estrategia: PlacementStrategy::Replicated,
13592                clusters: vec!["rio".into(), "sao-paulo".into()],
13593                affinity: None,
13594                shard_key: None,
13595            }),
13596            Some(Placement {
13597                estrategia: PlacementStrategy::Sharded,
13598                clusters: vec!["rio".into(), "sao-paulo".into(), "brasilia".into()],
13599                affinity: Some("data-locality".into()),
13600                shard_key: Some("$tenantId".into()),
13601            }),
13602        ];
13603        for placement in fixtures {
13604            let c = caixa_aplicacao_with_placement(placement.clone());
13605            assert_eq!(
13606                c.placement(),
13607                placement.as_ref(),
13608                "Caixa::placement must return :placement verbatim (got \
13609                 {:?}, expected {:?})",
13610                c.placement(),
13611                placement.as_ref(),
13612            );
13613            match (c.placement(), c.placement.as_ref()) {
13614                (Some(a), Some(b)) => assert!(
13615                    std::ptr::eq(a, b),
13616                    "Caixa::placement accessor and self.placement.as_ref() \
13617                     field access must borrow the same backing storage \
13618                     — the accessor is the substrate-primitive typed \
13619                     dispatch every downstream Aplicacao-distribution- \
13620                     overlay composite consumer must route through, and \
13621                     a reference-identity split would silently break \
13622                     every consumer that relied on the borrow sharing \
13623                     the composite's storage",
13624                ),
13625                (None, None) => {}
13626                _ => panic!(
13627                    "Caixa::placement presence bit must byte-equal \
13628                     self.placement.is_some() — a presence-bit drift \
13629                     would silently split the paired \
13630                     Caixa::aplicacao_view Aplicacao-composition seed's \
13631                     traversal head from the peer \
13632                     Caixa::declared_mesh_slots M3 declared-slot \
13633                     enumerator's presence probe",
13634                ),
13635            }
13636            assert_eq!(
13637                c.placement().is_some(),
13638                c.placement.is_some(),
13639                "Caixa::placement().is_some() must byte-equal \
13640                 self.placement.is_some() — a presence-bit drift would \
13641                 silently split every downstream Option<&Placement> \
13642                 consumer's partition on the cluster-default arm",
13643            );
13644        }
13645    }
13646
13647    #[test]
13648    fn declared_mesh_slots_placement_arm_routes_through_accessor() {
13649        // Composition pin: [`Caixa::declared_mesh_slots`]'s
13650        // `:placement` presence-probe arm must key off
13651        // [`Caixa::placement`], not the raw `self.placement.is_some()`
13652        // field-probe. Structurally: a `Caixa { placement:
13653        // Some(Placement::default()), .. }` must still push
13654        // `M3_AUTHOR_KEY_PLACEMENT` onto the declared-slot list (the
13655        // presence bit is `Some`, so the M3 kind-coherence gate must
13656        // surface the slot as "declared" even when every per-axis
13657        // scalar defers to the cluster-default arm), and a `Caixa {
13658        // placement: None, .. }` must NOT push the label (the "author
13659        // omitted the slot entirely" partition). The pair jointly pins
13660        // the accessor + declared-slot enumerator composition: any
13661        // future silent detour that had the accessor collapse
13662        // `Some(Placement::default())` to `None` (a `.filter(|p|
13663        // p.clusters().is_empty().not())` projection) would silently
13664        // absorb the "declared but empty" arm at the accessor boundary
13665        // and the [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
13666        // kind-coherence gate would silently accept a struct-literal
13667        // `Caixa` carrying the drift.
13668        //
13669        // Peer of the sibling
13670        // `declared_servico_slots_limits_arm_routes_through_accessor`
13671        // (b2bd9d7),
13672        // `declared_servico_slots_behavior_arm_routes_through_accessor`
13673        // (35d8b52), and
13674        // `declared_mesh_slots_politicas_arm_routes_through_accessor`
13675        // (5d23d29) composition pins on the sibling `:limits` /
13676        // `:behavior` / `:politicas` outer-`Option<&Composite>` arms
13677        // — same "the enumerator gate must route through the
13678        // substrate-primitive typed dispatch" discipline extended onto
13679        // the second of the three M3 mesh-slot axes so the
13680        // [`Caixa::declared_mesh_slots`] enumerator carries the same
13681        // routing invariant on the `:placement` arm as the peer
13682        // `:politicas` arm.
13683        use crate::aplicacao::Placement;
13684        let c = caixa_aplicacao_with_placement(Some(Placement::default()));
13685        let slots = c.declared_mesh_slots();
13686        assert!(
13687            slots.contains(&crate::render::M3_AUTHOR_KEY_PLACEMENT),
13688            "declared_mesh_slots must push M3_AUTHOR_KEY_PLACEMENT \
13689             when `:placement` is Some (even for Placement::default()) \
13690             — the accessor and the enumerator gate must route through \
13691             the same substrate-primitive typed dispatch on the outer \
13692             :placement presence bit (got slots={slots:?})",
13693        );
13694        let c = caixa_aplicacao_with_placement(None);
13695        let slots = c.declared_mesh_slots();
13696        assert!(
13697            !slots.contains(&crate::render::M3_AUTHOR_KEY_PLACEMENT),
13698            "declared_mesh_slots must NOT push M3_AUTHOR_KEY_PLACEMENT \
13699             when `:placement` is None — the author-omitted arm must \
13700             route through the accessor's None-return unchanged (got \
13701             slots={slots:?})",
13702        );
13703    }
13704
13705    #[test]
13706    fn aplicacao_view_placement_arm_folds_through_accessor() {
13707        // Composition pin: [`Caixa::aplicacao_view`]'s per-`:placement`
13708        // Aplicacao-composition seed must fold through
13709        // [`Caixa::placement`], not the raw
13710        // `self.placement.clone().unwrap_or_default()` field-borrow.
13711        // Structurally: a `Caixa { placement: Some(Placement {
13712        // estrategia: Replicated, clusters: ["rio"], .. default }),
13713        // kind: Aplicacao, .. }` must surface a projected
13714        // [`crate::AplicacaoSpec`] whose `placement().estrategia()` +
13715        // `placement().clusters()` byte-equal the outer composite's
13716        // authored values (the fold must project the authored
13717        // composite verbatim), a `Caixa { placement:
13718        // Some(Placement::default()), kind: Aplicacao, .. }` must
13719        // surface an [`crate::AplicacaoSpec`] whose `placement()`
13720        // byte-equals [`crate::aplicacao::Placement::default`] (the
13721        // fold's empty-composite arm collapses to the same default
13722        // the author-omitted arm does), and a `Caixa { placement:
13723        // None, kind: Aplicacao, .. }` must surface an
13724        // [`crate::AplicacaoSpec`] whose `placement()` byte-equals
13725        // [`crate::aplicacao::Placement::default`] (the "author
13726        // omitted the slot entirely" arm folds through the
13727        // `unwrap_or_default` onto the cluster-default). The triad
13728        // jointly pins the accessor + Aplicacao-composition seed
13729        // composition: any future silent detour that had the accessor
13730        // divert the raw slot away from the seed's fold (an operator-
13731        // resolved overlay's default-fold arm silently differing from
13732        // the raw slot's default-fold arm) would silently split the
13733        // build-time distribution-artifact emission gate from the
13734        // caixa-mesh renderer's Aplicacao-view input at the
13735        // composition boundary.
13736        use crate::aplicacao::{Placement, PlacementStrategy};
13737        let c = caixa_aplicacao_with_placement(Some(Placement {
13738            estrategia: PlacementStrategy::Replicated,
13739            clusters: vec!["rio".into()],
13740            affinity: None,
13741            shard_key: None,
13742        }));
13743        let view = c.aplicacao_view().unwrap();
13744        assert_eq!(
13745            view.placement().estrategia(),
13746            PlacementStrategy::Replicated,
13747            "Caixa::aplicacao_view must fold the authored :placement \
13748             :estrategia scalar through the accessor verbatim onto the \
13749             projected AplicacaoSpec — a future silent detour at the \
13750             seed's fold arm would surface here as a projected-scalar \
13751             drift (got {:?})",
13752            view.placement().estrategia(),
13753        );
13754        assert_eq!(
13755            view.placement().clusters(),
13756            &["rio"],
13757            "Caixa::aplicacao_view must fold the authored :placement \
13758             :clusters list through the accessor verbatim onto the \
13759             projected AplicacaoSpec — a future silent detour at the \
13760             seed's fold arm would surface here as a projected-list \
13761             drift (got {:?})",
13762            view.placement().clusters(),
13763        );
13764        let c = caixa_aplicacao_with_placement(Some(Placement::default()));
13765        let view = c.aplicacao_view().unwrap();
13766        assert_eq!(
13767            view.placement(),
13768            &Placement::default(),
13769            "Caixa::aplicacao_view must fold Some(Placement::default()) \
13770             through the accessor onto Placement::default — the empty- \
13771             composite arm collapses to the same default the author- \
13772             omitted arm does (got {:?})",
13773            view.placement(),
13774        );
13775        let c = caixa_aplicacao_with_placement(None);
13776        let view = c.aplicacao_view().unwrap();
13777        assert_eq!(
13778            view.placement(),
13779            &Placement::default(),
13780            "Caixa::aplicacao_view must fold None through the accessor's \
13781             unwrap_or_default onto Placement::default — the author- \
13782             omitted arm must route through the accessor's None-return \
13783             unchanged (got {:?})",
13784            view.placement(),
13785        );
13786    }
13787
13788    #[test]
13789    fn placement_projects_option_ref_by_borrow() {
13790        // The by-borrow pin: [`Caixa::placement`] returns
13791        // `Option<&Placement>` by borrow — the returned reference
13792        // borrows the underlying `Option<Placement>` storage of the
13793        // `:placement` slot and the accessor must not clone the
13794        // backing composite on every call. Peer of the sibling
13795        // `limits_projects_option_ref_by_borrow` (b2bd9d7),
13796        // `behavior_projects_option_ref_by_borrow` (35d8b52), and
13797        // `politicas_projects_option_ref_by_borrow` (5d23d29) by-borrow
13798        // pins on the outer top-level [`Caixa`]
13799        // `Option<&Composite>`-return sub-family — extended here to
13800        // the fourth axis of the same sub-family: the accessor's
13801        // returned reference must borrow from `&self` (the returned
13802        // reference's lifetime is tied to `&self`), and calling the
13803        // accessor twice on the same [`Caixa`] must yield references
13804        // that are pointer-equal (the underlying byte-buffer is the
13805        // storage `Placement`'s allocation, not a fresh copy) as well
13806        // as value-equal (idempotent, no side effects on `&self`).
13807        //
13808        // Pins against a future silent detour that returned an owned
13809        // `Placement` (which would type-check via the `Clone` impl
13810        // but silently clone on every call), a `&Placement` panic-
13811        // return on the `None` arm (which would collapse the load-
13812        // bearing `Option` presence-bit into a runtime panic), or a
13813        // one-arm-only accessor that returned a saturating composite
13814        // on some sentinel input.
13815        use crate::aplicacao::{Placement, PlacementStrategy};
13816        for placement in [
13817            Some(Placement::default()),
13818            Some(Placement {
13819                estrategia: PlacementStrategy::Sharded,
13820                clusters: vec!["rio".into(), "sao-paulo".into()],
13821                affinity: Some("data-locality".into()),
13822                shard_key: Some("$tenantId".into()),
13823            }),
13824        ] {
13825            let c = caixa_aplicacao_with_placement(placement.clone());
13826            let first = c.placement().unwrap();
13827            let second = c.placement().unwrap();
13828            assert_eq!(
13829                first, second,
13830                "Caixa::placement must be idempotent — two successive \
13831                 calls on the same &self must return the same \
13832                 &Placement",
13833            );
13834            assert!(
13835                std::ptr::eq(first, second),
13836                "Caixa::placement must borrow the underlying \
13837                 Option<Placement> storage — two successive calls \
13838                 must return references with the same backing pointer \
13839                 (a fresh Placement clone would change the pointer on \
13840                 every call)",
13841            );
13842            assert_eq!(
13843                Some(first),
13844                placement.as_ref(),
13845                "Caixa::placement must return :placement verbatim by \
13846                 borrow — got {first:?}, expected {:?}",
13847                placement.as_ref(),
13848            );
13849        }
13850        let c = caixa_aplicacao_with_placement(None);
13851        assert!(
13852            c.placement().is_none(),
13853            "Caixa::placement must return None when :placement is \
13854             absent — the author-omitted arm must project through the \
13855             accessor's Option::None unchanged",
13856        );
13857    }
13858
13859    // ── Caixa::entrada — outer top-level Option<&Entrada> composite-reference accessor ──
13860
13861    fn caixa_aplicacao_with_entrada(entrada: Option<crate::aplicacao::Entrada>) -> Caixa {
13862        use crate::aplicacao::{Membro, WitContract};
13863        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
13864        c.kind = CaixaKind::Aplicacao;
13865        c.membros = vec![Membro {
13866            caixa: "a".into(),
13867            versao: "^0.1".into(),
13868        }];
13869        c.contratos = vec![WitContract {
13870            de: "a".into(),
13871            para: "a".into(),
13872            wit: "wasi:http/proxy".into(),
13873            endpoint: Some("/x".into()),
13874            subject: None,
13875            slot: None,
13876        }];
13877        c.entrada = entrada;
13878        c
13879    }
13880
13881    #[test]
13882    fn entrada_returns_entrada_option_ref_verbatim_across_permutations() {
13883        // The canonical per-`Caixa` `:entrada` M3 mesh-slot outer-
13884        // composite optional-composite-reference-shape pin:
13885        // [`Caixa::entrada`] must return the `:entrada` typed
13886        // `Option<Entrada>` verbatim as an `Option<&Entrada>`
13887        // reference over the same backing storage the raw
13888        // `self.entrada.as_ref()` field access borrows from,
13889        // byte-equal across every representative fixture in the
13890        // accept-set — the author-omitted `None` shape (the
13891        // "cluster-internal Aplicacao" partition every downstream
13892        // Gateway-API emitter treats as "emit no listener + no
13893        // HTTPRoute"), a bare-`host`/`para` minimum-composite fixture
13894        // (empty `paths` — the resolved-paths fallback the peer
13895        // [`crate::aplicacao::Entrada::resolved_paths`] cascade folds
13896        // onto the substrate catch-all), and a fully-populated
13897        // multi-path-with-non-default-port fixture (the canonical
13898        // shape a public HTTP Aplicacao carries).
13899        //
13900        // Pins against a future silent detour that returned a fresh-
13901        // cloned [`crate::aplicacao::Entrada`] copy (which would
13902        // type-check via the `Clone` impl but silently break every
13903        // downstream caller that relied on the reference sharing the
13904        // composite's backing identity), a reference to an operator-
13905        // resolved overlay (the future per-cluster
13906        // `:entrada-overrides` slot — its resolution must land at
13907        // exactly this accessor body, not silently divert the raw
13908        // slot away from the peer [`Caixa::declared_mesh_slots`]
13909        // enumerator's presence probe), or an axis-shuffled projection
13910        // (a future detour that swapped `host` and `para` through the
13911        // accessor would silently split the paired
13912        // [`Caixa::aplicacao_view`] seed's forward input from the
13913        // sibling M3 gateway-artifact emitter's projection input).
13914        //
13915        // Fifth and final outer top-level [`Caixa`]
13916        // `Option<&Composite>`-return composite-reference accessor pin
13917        // on the substrate primitive — peer of the sibling
13918        // `limits_returns_limits_option_ref_verbatim_across_permutations`
13919        // (b2bd9d7),
13920        // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
13921        // (35d8b52),
13922        // `politicas_returns_politicas_option_ref_verbatim_across_permutations`
13923        // (5d23d29), and
13924        // `placement_returns_placement_option_ref_verbatim_across_permutations`
13925        // (4fb8074) opening tetrad pins on the outer top-level
13926        // [`Caixa`] `Option<&Composite>`-return sub-family — extended
13927        // here to the third and final M3 mesh-slot axis so the closed
13928        // outer `Option<&Composite>` sub-family carries the same
13929        // "byte-equal, borrow-shared, presence-bit-preserved" outer-
13930        // accessor discipline across all five arms.
13931        use crate::aplicacao::Entrada;
13932        let fixtures: Vec<Option<Entrada>> = vec![
13933            None,
13934            Some(Entrada {
13935                host: "checkout.quero.cloud".into(),
13936                para: "gateway".into(),
13937                paths: Vec::new(),
13938                port: crate::DEFAULT_SERVICO_PORT,
13939            }),
13940            Some(Entrada {
13941                host: "api.pleme.io".into(),
13942                para: "public-api".into(),
13943                paths: vec!["/v1".into(), "/v2".into()],
13944                port: 8080,
13945            }),
13946        ];
13947        for entrada in fixtures {
13948            let c = caixa_aplicacao_with_entrada(entrada.clone());
13949            assert_eq!(
13950                c.entrada(),
13951                entrada.as_ref(),
13952                "Caixa::entrada must return :entrada verbatim (got \
13953                 {:?}, expected {:?})",
13954                c.entrada(),
13955                entrada.as_ref(),
13956            );
13957            match (c.entrada(), c.entrada.as_ref()) {
13958                (Some(a), Some(b)) => assert!(
13959                    std::ptr::eq(a, b),
13960                    "Caixa::entrada accessor and self.entrada.as_ref() \
13961                     field access must borrow the same backing storage \
13962                     — the accessor is the substrate-primitive typed \
13963                     dispatch every downstream Aplicacao-external- \
13964                     gateway composite consumer must route through, and \
13965                     a reference-identity split would silently break \
13966                     every consumer that relied on the borrow sharing \
13967                     the composite's storage",
13968                ),
13969                (None, None) => {}
13970                _ => panic!(
13971                    "Caixa::entrada presence bit must byte-equal \
13972                     self.entrada.is_some() — a presence-bit drift \
13973                     would silently split the paired \
13974                     Caixa::aplicacao_view Aplicacao-composition seed's \
13975                     traversal head from the peer \
13976                     Caixa::declared_mesh_slots M3 declared-slot \
13977                     enumerator's presence probe",
13978                ),
13979            }
13980            assert_eq!(
13981                c.entrada().is_some(),
13982                c.entrada.is_some(),
13983                "Caixa::entrada().is_some() must byte-equal \
13984                 self.entrada.is_some() — a presence-bit drift would \
13985                 silently split every downstream Option<&Entrada> \
13986                 consumer's partition on the cluster-internal arm",
13987            );
13988        }
13989    }
13990
13991    #[test]
13992    fn declared_mesh_slots_entrada_arm_routes_through_accessor() {
13993        // Composition pin: [`Caixa::declared_mesh_slots`]'s `:entrada`
13994        // presence-probe arm must key off [`Caixa::entrada`], not the
13995        // raw `self.entrada.is_some()` field-probe. Structurally: a
13996        // `Caixa { entrada: Some(Entrada { host: "...", para: "...",
13997        // paths: [], port: DEFAULT_SERVICO_PORT }), .. }` must push
13998        // `M3_AUTHOR_KEY_ENTRADA` onto the declared-slot list (the
13999        // presence bit is `Some`, so the M3 kind-coherence gate must
14000        // surface the slot as "declared" even when every per-axis
14001        // scalar defers to the substrate catch-all / default port),
14002        // and a `Caixa { entrada: None, .. }` must NOT push the label
14003        // (the "author omitted the slot entirely" partition). The pair
14004        // jointly pins the accessor + declared-slot enumerator
14005        // composition: any future silent detour that had the accessor
14006        // collapse `Some(Entrada { paths: [], .. })` to `None` (a
14007        // `.filter(|e| !e.paths.is_empty())` projection) would silently
14008        // absorb the "declared but empty-paths" arm at the accessor
14009        // boundary and the
14010        // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
14011        // coherence gate would silently accept a struct-literal
14012        // `Caixa` carrying the drift.
14013        //
14014        // Peer of the sibling
14015        // `declared_servico_slots_limits_arm_routes_through_accessor`
14016        // (b2bd9d7),
14017        // `declared_servico_slots_behavior_arm_routes_through_accessor`
14018        // (35d8b52),
14019        // `declared_mesh_slots_politicas_arm_routes_through_accessor`
14020        // (5d23d29), and
14021        // `declared_mesh_slots_placement_arm_routes_through_accessor`
14022        // (4fb8074) composition pins on the sibling `:limits` /
14023        // `:behavior` / `:politicas` / `:placement` outer-
14024        // `Option<&Composite>` arms — same "the enumerator gate must
14025        // route through the substrate-primitive typed dispatch"
14026        // discipline extended onto the third and final M3 mesh-slot
14027        // axis so the [`Caixa::declared_mesh_slots`] enumerator now
14028        // carries the routing invariant on every M3 mesh-slot arm.
14029        use crate::aplicacao::Entrada;
14030        let c = caixa_aplicacao_with_entrada(Some(Entrada {
14031            host: "checkout.quero.cloud".into(),
14032            para: "gateway".into(),
14033            paths: Vec::new(),
14034            port: crate::DEFAULT_SERVICO_PORT,
14035        }));
14036        let slots = c.declared_mesh_slots();
14037        assert!(
14038            slots.contains(&crate::render::M3_AUTHOR_KEY_ENTRADA),
14039            "declared_mesh_slots must push M3_AUTHOR_KEY_ENTRADA when \
14040             `:entrada` is Some (even for empty-paths / default-port) \
14041             — the accessor and the enumerator gate must route through \
14042             the same substrate-primitive typed dispatch on the outer \
14043             :entrada presence bit (got slots={slots:?})",
14044        );
14045        let c = caixa_aplicacao_with_entrada(None);
14046        let slots = c.declared_mesh_slots();
14047        assert!(
14048            !slots.contains(&crate::render::M3_AUTHOR_KEY_ENTRADA),
14049            "declared_mesh_slots must NOT push M3_AUTHOR_KEY_ENTRADA \
14050             when `:entrada` is None — the author-omitted arm must \
14051             route through the accessor's None-return unchanged (got \
14052             slots={slots:?})",
14053        );
14054    }
14055
14056    #[test]
14057    fn aplicacao_view_entrada_arm_folds_through_accessor() {
14058        // Composition pin: [`Caixa::aplicacao_view`]'s per-`:entrada`
14059        // Aplicacao-composition seed must fold through
14060        // [`Caixa::entrada`], not the raw `self.entrada.clone()` field-
14061        // borrow. Structurally: a `Caixa { entrada: Some(Entrada {
14062        // host: "api.pleme.io", para: "public-api", paths: ["/v1"],
14063        // port: 8080 }), kind: Aplicacao, .. }` must surface a projected
14064        // [`crate::AplicacaoSpec`] whose `entrada().unwrap()` byte-
14065        // equals the outer composite's authored value (the fold must
14066        // project the authored composite verbatim), and a `Caixa {
14067        // entrada: None, kind: Aplicacao, .. }` must surface an
14068        // [`crate::AplicacaoSpec`] whose `entrada()` is `None` (the
14069        // "author omitted the slot entirely" arm folds through the
14070        // accessor's `Option::cloned` onto the same `None` presence
14071        // bit — unlike the peer `:politicas` / `:placement` arms
14072        // `:entrada` has no cluster-default fold, the omitted arm
14073        // stays omitted). The pair jointly pins the accessor +
14074        // Aplicacao-composition seed composition: any future silent
14075        // detour that had the accessor divert the raw slot away from
14076        // the seed's fold (an operator-resolved overlay's forward arm
14077        // silently differing from the raw slot's forward arm) would
14078        // silently split the build-time gateway-artifact emission gate
14079        // from the caixa-mesh renderer's Aplicacao-view input at the
14080        // composition boundary.
14081        use crate::aplicacao::Entrada;
14082        let authored = Entrada {
14083            host: "api.pleme.io".into(),
14084            para: "public-api".into(),
14085            paths: vec!["/v1".into()],
14086            port: 8080,
14087        };
14088        let c = caixa_aplicacao_with_entrada(Some(authored.clone()));
14089        let view = c.aplicacao_view().unwrap();
14090        assert_eq!(
14091            view.entrada(),
14092            Some(&authored),
14093            "Caixa::aplicacao_view must fold the authored :entrada \
14094             composite through the accessor verbatim onto the \
14095             projected AplicacaoSpec — a future silent detour at the \
14096             seed's fold arm would surface here as a projected- \
14097             composite drift (got {:?})",
14098            view.entrada(),
14099        );
14100        let c = caixa_aplicacao_with_entrada(None);
14101        let view = c.aplicacao_view().unwrap();
14102        assert!(
14103            view.entrada().is_none(),
14104            "Caixa::aplicacao_view must fold None through the \
14105             accessor's Option::cloned onto None — the author- \
14106             omitted arm must route through the accessor's None-return \
14107             unchanged (got {:?})",
14108            view.entrada(),
14109        );
14110    }
14111
14112    #[test]
14113    fn entrada_projects_option_ref_by_borrow() {
14114        // The by-borrow pin: [`Caixa::entrada`] returns
14115        // `Option<&Entrada>` by borrow — the returned reference
14116        // borrows the underlying `Option<Entrada>` storage of the
14117        // `:entrada` slot and the accessor must not clone the backing
14118        // composite on every call. Peer of the sibling
14119        // `limits_projects_option_ref_by_borrow` (b2bd9d7),
14120        // `behavior_projects_option_ref_by_borrow` (35d8b52),
14121        // `politicas_projects_option_ref_by_borrow` (5d23d29), and
14122        // `placement_projects_option_ref_by_borrow` (4fb8074) by-
14123        // borrow pins on the outer top-level [`Caixa`]
14124        // `Option<&Composite>`-return sub-family — extended here to
14125        // the fifth and final axis of the same sub-family, closing
14126        // the discipline: the accessor's returned reference must
14127        // borrow from `&self` (the returned reference's lifetime is
14128        // tied to `&self`), and calling the accessor twice on the
14129        // same [`Caixa`] must yield references that are pointer-equal
14130        // (the underlying byte-buffer is the storage `Entrada`'s
14131        // allocation, not a fresh copy) as well as value-equal
14132        // (idempotent, no side effects on `&self`).
14133        //
14134        // Pins against a future silent detour that returned an owned
14135        // `Entrada` (which would type-check via the `Clone` impl but
14136        // silently clone on every call), a `&Entrada` panic-return on
14137        // the `None` arm (which would collapse the load-bearing
14138        // `Option` presence-bit into a runtime panic), or a one-arm-
14139        // only accessor that returned a saturating composite on some
14140        // sentinel input.
14141        use crate::aplicacao::Entrada;
14142        for entrada in [
14143            Some(Entrada {
14144                host: "checkout.quero.cloud".into(),
14145                para: "gateway".into(),
14146                paths: Vec::new(),
14147                port: crate::DEFAULT_SERVICO_PORT,
14148            }),
14149            Some(Entrada {
14150                host: "api.pleme.io".into(),
14151                para: "public-api".into(),
14152                paths: vec!["/v1".into(), "/v2".into()],
14153                port: 8080,
14154            }),
14155        ] {
14156            let c = caixa_aplicacao_with_entrada(entrada.clone());
14157            let first = c.entrada().unwrap();
14158            let second = c.entrada().unwrap();
14159            assert_eq!(
14160                first, second,
14161                "Caixa::entrada must be idempotent — two successive \
14162                 calls on the same &self must return the same &Entrada",
14163            );
14164            assert!(
14165                std::ptr::eq(first, second),
14166                "Caixa::entrada must borrow the underlying \
14167                 Option<Entrada> storage — two successive calls must \
14168                 return references with the same backing pointer (a \
14169                 fresh Entrada clone would change the pointer on every \
14170                 call)",
14171            );
14172            assert_eq!(
14173                Some(first),
14174                entrada.as_ref(),
14175                "Caixa::entrada must return :entrada verbatim by \
14176                 borrow — got {first:?}, expected {:?}",
14177                entrada.as_ref(),
14178            );
14179        }
14180        let c = caixa_aplicacao_with_entrada(None);
14181        assert!(
14182            c.entrada().is_none(),
14183            "Caixa::entrada must return None when :entrada is absent \
14184             — the author-omitted arm must project through the \
14185             accessor's Option::None unchanged",
14186        );
14187    }
14188
14189    // ── Caixa::estrategia — outer top-level Option<RestartStrategy> flat-spread supervisor-tree accessor ──
14190
14191    fn caixa_with_estrategia(estrategia: Option<crate::supervisor::RestartStrategy>) -> Caixa {
14192        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
14193        c.estrategia = estrategia;
14194        c
14195    }
14196
14197    #[test]
14198    fn estrategia_returns_estrategia_option_verbatim_across_permutations() {
14199        // The canonical per-`Caixa` `:estrategia` M2 supervisor-tree-slot
14200        // flat-spread `Option<RestartStrategy>`-return `Copy`-composite-
14201        // enum-arm scalar shape pin: [`Caixa::estrategia`] must return
14202        // the `:estrategia` typed `Option<crate::supervisor::RestartStrategy>`
14203        // verbatim as an `Option<RestartStrategy>` `Copy`-projected value
14204        // over the same discriminant the raw `self.estrategia` field
14205        // access carries, byte-equal across every representative fixture
14206        // in the accept-set — the author-omitted `None` shape (the
14207        // "defer to [`RestartStrategy::default`] through the
14208        // [`Self::supervisor_view`] `unwrap_or_default()` fold" partition
14209        // every non-`Supervisor`-kind `defcaixa` carries by
14210        // `#[serde(default)]`), and each of the four closed-set variants
14211        // [`RestartStrategy::OneForOne`] / [`RestartStrategy::OneForAll`]
14212        // / [`RestartStrategy::RestForOne`] /
14213        // [`RestartStrategy::SimpleOneForOne`] the author-declared arm
14214        // partitions on.
14215        //
14216        // Pins against a future silent detour that re-derived the
14217        // strategy from a peer axis (an accidental fallback to
14218        // `if children.is_empty() { SimpleOneForOne } else { OneForOne }`
14219        // collapse that read the outer `:children` list-length axis into
14220        // the strategy discriminator at the accessor boundary), a
14221        // stale-derive detour that substituted [`RestartStrategy::default`]
14222        // when the outer `Option` held `None` (which would silently
14223        // collapse the load-bearing "author explicitly declared
14224        // `:estrategia OneForOne`" vs "author omitted the slot and
14225        // inherited the default" partition the [`Self::declared_supervisor_slots`]
14226        // presence-probe reads — the enumerator gate would still push
14227        // `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA` on the omitted arm, silently
14228        // splitting the paired [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
14229        // kind-coherence gate's traversal head from the
14230        // [`Self::supervisor_view`] `unwrap_or_default()` fold's
14231        // composition head), a reference to an operator-resolved overlay
14232        // (the future per-cluster `:estrategia-overrides` slot — its
14233        // resolution must land at exactly this accessor body, not
14234        // silently divert the raw slot away from a second consumer), or
14235        // an axis-remap projection (a future detour that mapped
14236        // `OneForAll` through the accessor onto `OneForOne` would
14237        // silently split every downstream sibling-restart-strategy
14238        // consumer's per-arm fan-out).
14239        //
14240        // First outer top-level [`Caixa`] `Option<Copy>`-return
14241        // supervisor-tree-slot flat-spread accessor pin on the substrate
14242        // primitive — opens the outer-`Caixa` `Option<Copy>` flat-spread
14243        // projection pattern the sibling per-`Caixa` `:max-restarts` /
14244        // `:restart-window` future outer-scalar pins fold on. Peer of
14245        // the inner-altitude
14246        // `supervisor_spec_estrategia_returns_estrategia_verbatim_across_permutations`
14247        // (eafb619) pin on the post-composition [`SupervisorSpec`]
14248        // altitude — same "the substrate-primitive accessor must byte-
14249        // equal the raw field access verbatim across every author-
14250        // declared value" discipline extended onto the pre-composition
14251        // outer author-surface [`Caixa`] altitude. Peer of the closed
14252        // outer-`Caixa` `Option<&Composite>` composite-reference family
14253        // the sibling `limits` / `behavior` / `politicas` / `placement` /
14254        // `entrada`
14255        // `..._returns_..._option_ref_verbatim_across_permutations` pins
14256        // already carry on the outer `Option<&Composite>` altitude.
14257        use crate::supervisor::RestartStrategy;
14258        let fixtures: Vec<Option<RestartStrategy>> = vec![
14259            None,
14260            Some(RestartStrategy::OneForOne),
14261            Some(RestartStrategy::OneForAll),
14262            Some(RestartStrategy::RestForOne),
14263            Some(RestartStrategy::SimpleOneForOne),
14264        ];
14265        for estrategia in fixtures {
14266            let c = caixa_with_estrategia(estrategia);
14267            assert_eq!(
14268                c.estrategia(),
14269                estrategia,
14270                "Caixa::estrategia must return :estrategia verbatim (got \
14271                 {:?}, expected {:?})",
14272                c.estrategia(),
14273                estrategia,
14274            );
14275            assert_eq!(
14276                c.estrategia(),
14277                c.estrategia,
14278                "Caixa::estrategia accessor and self.estrategia field \
14279                 access must byte-equal — the accessor is the substrate-\
14280                 primitive typed dispatch every downstream supervisor-\
14281                 tree flat-spread consumer must route through, and a \
14282                 discriminant split would silently break every consumer \
14283                 that relied on the accessor sharing the field's own \
14284                 Option<Copy> shape",
14285            );
14286            assert_eq!(
14287                c.estrategia().is_some(),
14288                c.estrategia.is_some(),
14289                "Caixa::estrategia().is_some() must byte-equal \
14290                 self.estrategia.is_some() — a presence-bit drift would \
14291                 silently split the paired Caixa::declared_supervisor_slots \
14292                 presence-probe arm from the Caixa::supervisor_view \
14293                 unwrap_or_default() fold's composition input",
14294            );
14295        }
14296    }
14297
14298    #[test]
14299    fn declared_supervisor_slots_estrategia_arm_routes_through_accessor() {
14300        // Composition pin: [`Caixa::declared_supervisor_slots`]'s
14301        // `:estrategia` presence-probe arm must key off
14302        // [`Caixa::estrategia`], not the raw `self.estrategia.is_some()`
14303        // field-probe. Structurally: every `Caixa { estrategia:
14304        // Some(RestartStrategy::_), .. }` variant must push
14305        // `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA` onto the declared-slot list
14306        // (the presence bit is `Some` for every closed-set variant, so
14307        // the M2 supervisor-tree kind-coherence gate must surface the
14308        // slot as "declared" regardless of which variant the author
14309        // picked), and a `Caixa { estrategia: None, .. }` must NOT push
14310        // the label (the "author omitted the slot entirely, deferring
14311        // to [`RestartStrategy::default`] through the supervisor_view
14312        // fold" partition). The pair jointly pins the accessor +
14313        // declared-slot enumerator composition: any future silent detour
14314        // that had the accessor collapse `Some(RestartStrategy::default())`
14315        // to `None` (a `.filter(|e| *e != RestartStrategy::default())`
14316        // projection) would silently absorb the "declared but default-
14317        // valued" arm at the accessor boundary and the
14318        // [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
14319        // coherence gate would silently accept a struct-literal `Caixa`
14320        // carrying the drift.
14321        //
14322        // Peer of the sibling per-`Caixa`
14323        // `declared_servico_slots_limits_arm_routes_through_accessor`
14324        // (b2bd9d7) accessor-composition pin on the sibling outer-`Caixa`
14325        // `Option<&LimitsSpec>` composition axis — same "the enumerator
14326        // gate must route through the substrate-primitive typed
14327        // dispatch" discipline extended onto the flat-spread M2
14328        // supervisor-tree `Option<RestartStrategy>`-composition surface,
14329        // opening the outer-`Caixa` supervisor-tree-slot arm of the
14330        // composition-pin family.
14331        use crate::supervisor::RestartStrategy;
14332        for estrategia in [
14333            RestartStrategy::OneForOne,
14334            RestartStrategy::OneForAll,
14335            RestartStrategy::RestForOne,
14336            RestartStrategy::SimpleOneForOne,
14337        ] {
14338            let c = caixa_with_estrategia(Some(estrategia));
14339            let slots = c.declared_supervisor_slots();
14340            assert!(
14341                slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA),
14342                "declared_supervisor_slots must push \
14343                 SUPERVISOR_AUTHOR_KEY_ESTRATEGIA when `:estrategia` is \
14344                 Some({estrategia:?}) — the accessor and the enumerator \
14345                 gate must route through the same substrate-primitive \
14346                 typed dispatch on the outer :estrategia presence bit \
14347                 (got slots={slots:?})",
14348            );
14349        }
14350        let c = caixa_with_estrategia(None);
14351        let slots = c.declared_supervisor_slots();
14352        assert!(
14353            !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA),
14354            "declared_supervisor_slots must NOT push \
14355             SUPERVISOR_AUTHOR_KEY_ESTRATEGIA when `:estrategia` is None \
14356             — the author-omitted arm must route through the accessor's \
14357             None-return unchanged (got slots={slots:?})",
14358        );
14359    }
14360
14361    #[test]
14362    fn supervisor_view_estrategia_arm_routes_through_accessor() {
14363        // Composition pin: [`Caixa::supervisor_view`]'s per-`:estrategia`
14364        // [`SupervisorSpec`] construction arm must key off
14365        // [`Caixa::estrategia`]'s `unwrap_or_default()` fold, not the raw
14366        // `self.estrategia.unwrap_or_default()` field-fold. Structurally:
14367        // for every `:kind Supervisor` `Caixa` carrying an author-
14368        // declared `Some(RestartStrategy::_)` variant, the composed
14369        // [`SupervisorSpec`]'s `.estrategia` field must byte-equal the
14370        // outer accessor's declared variant unchanged; and for a
14371        // `:kind Supervisor` `Caixa` carrying `None`, the composed
14372        // [`SupervisorSpec`]'s `.estrategia` field must byte-equal
14373        // [`RestartStrategy::default`] (the [`RestartStrategy::OneForOne`]
14374        // arm the flat-spread `unwrap_or_default()` fold projects to on
14375        // the author-omitted arm — this is the *composition* between the
14376        // outer `Option<RestartStrategy>` accessor's presence-bit
14377        // surface and the inner post-composition non-`Option`
14378        // [`SupervisorSpec::estrategia`] altitude). The pair jointly
14379        // pins the accessor + supervisor_view composition: any future
14380        // silent detour that had the accessor promote `None` to
14381        // `Some(RestartStrategy::default())` (a `.or_else(|| Some(RestartStrategy::default()))`
14382        // projection) would silently collapse the two arms into one at
14383        // the accessor boundary and the [`Self::declared_supervisor_slots`]
14384        // presence probe would silently drift from the composition site.
14385        //
14386        // Peer of the sibling M2 supervisor-slot post-composition
14387        // `validate_reads_through_lifted_estrategia_accessor` (eafb619)
14388        // pin on the [`SupervisorSpec::validate`] altitude — this pin
14389        // extends that inner-altitude accessor-routing discipline onto
14390        // the pre-composition outer author-surface [`Caixa`] altitude,
14391        // pinning the composition edge between the flat-spread outer
14392        // `Option<RestartStrategy>` and the composed [`SupervisorSpec`]
14393        // `RestartStrategy` axes.
14394        use crate::CaixaKind;
14395        use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
14396        for estrategia in [
14397            RestartStrategy::OneForOne,
14398            RestartStrategy::OneForAll,
14399            RestartStrategy::RestForOne,
14400            RestartStrategy::SimpleOneForOne,
14401        ] {
14402            let mut c = caixa_with_estrategia(Some(estrategia));
14403            c.kind = CaixaKind::Supervisor;
14404            // Route the `SimpleOneForOne ↔ non-SimpleOneForOne` fixture-
14405            // shape partition through the [`gen_platform::IsVariant`]
14406            // derive-generated
14407            // [`RestartStrategy::is_simple_one_for_one`] predicate rather
14408            // than the raw `matches!(estrategia, RestartStrategy::
14409            // SimpleOneForOne)` open-coded pattern-match — same closed-
14410            // set-typed-enum arm-discriminator dispatch discipline the
14411            // sibling [`crate::upgrade::UpgradeInstruction::is_restart`]
14412            // convergence (915a934) extended onto its two paired positive
14413            // / negated `matches!` sites and the peer
14414            // [`crate::aplicacao::PlacementStrategy`] `IsVariant`-derived
14415            // predicate convergence (766ec63) extended onto the M3 mesh-
14416            // slot per-`:placement` distribution-strategy discriminator
14417            // axis. See the sibling `supervisor::tests::
14418            // round_trip_all_strategies` and
14419            // `supervisor::tests::supervisor_spec_estrategia_returns_estrategia_verbatim_across_permutations`
14420            // fixtures — the three sites (all test-only,
14421            // acknowledged in 915a934's Prior-commits footnote as the
14422            // outstanding follow-up) now consult one typed dispatch on
14423            // the substrate primitive.
14424            c.children = if estrategia.is_simple_one_for_one() {
14425                Vec::new()
14426            } else {
14427                vec![ChildSpec {
14428                    caixa: "worker".into(),
14429                    versao: "^0.1".into(),
14430                    restart: RestartPolicy::Permanent,
14431                }]
14432            };
14433            let view = c.supervisor_view().expect(
14434                "supervisor_view must materialize a SupervisorSpec for a \
14435                 :kind Supervisor Caixa carrying a Some(:estrategia) slot",
14436            );
14437            assert_eq!(
14438                view.estrategia(),
14439                c.estrategia().unwrap(),
14440                "supervisor_view must carry the outer Caixa::estrategia() \
14441                 declared variant onto the composed SupervisorSpec.estrategia \
14442                 field verbatim on the Some arm (got {:?}, expected {:?})",
14443                view.estrategia(),
14444                c.estrategia().unwrap(),
14445            );
14446        }
14447        // The author-omitted arm: outer `None` → composed
14448        // `RestartStrategy::default()` through the flat-spread
14449        // `unwrap_or_default()` fold.
14450        let mut c = caixa_with_estrategia(None);
14451        c.kind = CaixaKind::Supervisor;
14452        // Populate children so the sibling supervisor slots are coherent
14453        // for the [`Self::supervisor_view`] projection; the `:estrategia`
14454        // arm still defers to [`RestartStrategy::default`] on the
14455        // author-omitted arm even when the sibling slots carry values.
14456        c.children = vec![ChildSpec {
14457            caixa: "worker".into(),
14458            versao: "^0.1".into(),
14459            restart: RestartPolicy::Permanent,
14460        }];
14461        let view = c.supervisor_view().expect(
14462            "supervisor_view must materialize a SupervisorSpec for a \
14463             :kind Supervisor Caixa carrying a None `:estrategia` slot",
14464        );
14465        assert_eq!(
14466            view.estrategia(),
14467            RestartStrategy::default(),
14468            "supervisor_view must project the outer Caixa::estrategia() \
14469             None arm onto RestartStrategy::default() through the flat-\
14470             spread unwrap_or_default() fold (got {:?}, expected {:?})",
14471            view.estrategia(),
14472            RestartStrategy::default(),
14473        );
14474        assert!(
14475            c.estrategia().is_none(),
14476            "Caixa::estrategia() must remain None on the author-omitted \
14477             arm — the supervisor_view fold must not mutate the outer \
14478             flat-spread presence bit",
14479        );
14480    }
14481
14482    #[test]
14483    fn estrategia_projects_option_by_copy() {
14484        // The by-`Copy` pin: [`Caixa::estrategia`] returns
14485        // `Option<RestartStrategy>` by value (`RestartStrategy: Copy`) —
14486        // the accessor does not borrow `&self` past the call (no
14487        // lifetime on the return type), and calling the accessor twice
14488        // on the same [`Caixa`] must yield discriminant-equal values
14489        // (idempotent, no side effects on `&self`). Peer of the sibling
14490        // outer-`Caixa` `Option<&Composite>` by-borrow
14491        // `limits_projects_option_ref_by_borrow` (b2bd9d7) /
14492        // `behavior_projects_option_ref_by_borrow` (35d8b52) /
14493        // `politicas_projects_option_ref_by_borrow` (5d23d29) /
14494        // `placement_projects_option_ref_by_borrow` (4fb8074) /
14495        // `entrada_projects_option_ref_by_borrow` (e4128e4) by-borrow
14496        // pins on the outer-`Caixa` `Option<&Composite>`-return axes —
14497        // extended here to the outer-`Caixa` `Option<Copy>`-return
14498        // flat-spread axis. The `Copy` discipline replaces the pointer-
14499        // equality claim the by-borrow siblings pin (a fresh `Copy` of a
14500        // `Copy` discriminant is definitionally the same discriminant, so
14501        // the axis reduces to discriminant equality).
14502        //
14503        // Pins against a future silent detour that returned a fresh
14504        // `Option<&RestartStrategy>` (which would type-check but silently
14505        // introduce a borrow of `&self` past the call, collapsing the
14506        // load-bearing "no lifetime on the return type" `Copy` projection
14507        // the flat-spread axis's `Option<Copy>` shape carries), a stale-
14508        // read side effect that flipped the outer discriminant on
14509        // successive calls, or an axis-remap projection that returned a
14510        // different variant than the field storage.
14511        use crate::supervisor::RestartStrategy;
14512        for estrategia in [
14513            Some(RestartStrategy::OneForOne),
14514            Some(RestartStrategy::OneForAll),
14515            Some(RestartStrategy::RestForOne),
14516            Some(RestartStrategy::SimpleOneForOne),
14517        ] {
14518            let c = caixa_with_estrategia(estrategia);
14519            let first = c.estrategia();
14520            let second = c.estrategia();
14521            assert_eq!(
14522                first, second,
14523                "Caixa::estrategia must be idempotent — two successive \
14524                 calls on the same &self must return the same \
14525                 Option<RestartStrategy>",
14526            );
14527            assert_eq!(
14528                first, estrategia,
14529                "Caixa::estrategia must return :estrategia verbatim by \
14530                 Copy — got {first:?}, expected {estrategia:?}",
14531            );
14532        }
14533        let c = caixa_with_estrategia(None);
14534        assert!(
14535            c.estrategia().is_none(),
14536            "Caixa::estrategia must return None when :estrategia is \
14537             absent — the author-omitted arm must project through the \
14538             accessor's Option::None unchanged",
14539        );
14540    }
14541
14542    // ── Caixa::max_restarts / Caixa::restart_window —
14543    //    outer top-level M2 supervisor-tree-slot flat-spread accessors
14544    //    (Option<u32> / Option<&str>) folding on the ed04d3c
14545    //    Caixa::estrategia Option<Copy> sub-family ─────────────────────
14546
14547    fn caixa_with_max_restarts(max_restarts: Option<u32>) -> Caixa {
14548        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
14549        c.max_restarts = max_restarts;
14550        c
14551    }
14552
14553    fn caixa_supervisor_with_max_restarts_and_window(
14554        max_restarts: Option<u32>,
14555        restart_window: Option<&str>,
14556    ) -> Caixa {
14557        use crate::CaixaKind;
14558        use crate::supervisor::{ChildSpec, RestartPolicy};
14559        let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
14560        c.kind = CaixaKind::Supervisor;
14561        c.max_restarts = max_restarts;
14562        c.restart_window = restart_window.map(str::to_string);
14563        c.children = vec![ChildSpec {
14564            caixa: "worker".into(),
14565            versao: "^0.1".into(),
14566            restart: RestartPolicy::Permanent,
14567        }];
14568        c
14569    }
14570
14571    #[test]
14572    fn max_restarts_returns_max_restarts_option_verbatim_across_permutations() {
14573        // Value-shape pin: [`Caixa::max_restarts`] returns the
14574        // `:max-restarts` typed `Option<u32>` verbatim, `Copy`-projected
14575        // from the typed slot's own storage, byte-equal across the
14576        // author-omitted `None` arm (the "defer to the
14577        // [`Self::supervisor_view`] `unwrap_or(5)` OTP-canonical
14578        // `{intensity, 5, 60}` default" partition every
14579        // non-`Supervisor`-kind caixa carries by `#[serde(default)]`)
14580        // and each of the representative fixtures in the accept-set —
14581        // `0` (the zero-floor arm the peer
14582        // [`crate::supervisor::SupervisorSpec::validate`]
14583        // [`crate::SupervisorError::ZeroMaxRestarts`] gate refuses on
14584        // the post-composition altitude — the accessor must ship the
14585        // raw slot verbatim so struct-literal fixtures continue to
14586        // expose the zero at the accessor boundary), the OTP-canonical
14587        // `5` default (`{intensity, 5, 60}` worker-supervisor from
14588        // Learn You Some Erlang), `1000` (the
14589        // [`SUPERVISOR_MAX_RESTARTS_MAX`] cap the peer post-composition
14590        // upper-bound gate accepts on the boundary), `u32::MAX` (a
14591        // past-the-cap sentinel that the substrate-primitive accessor
14592        // must still ship verbatim). Second outer top-level
14593        // [`Caixa`] `Option<Copy>`-return supervisor-tree flat-spread
14594        // pin — folds on the sibling
14595        // `estrategia_returns_estrategia_option_verbatim_across_permutations`
14596        // (ed04d3c) pin's `Option<Copy>` shape, extending the sub-family
14597        // onto the sibling `Option<u32>` restart-budget-count arm.
14598        let fixtures: Vec<Option<u32>> = vec![None, Some(0), Some(5), Some(1000), Some(u32::MAX)];
14599        for max_restarts in fixtures {
14600            let c = caixa_with_max_restarts(max_restarts);
14601            assert_eq!(
14602                c.max_restarts(),
14603                max_restarts,
14604                "Caixa::max_restarts must return :max-restarts verbatim \
14605                 (got {:?}, expected {max_restarts:?})",
14606                c.max_restarts(),
14607            );
14608            assert_eq!(
14609                c.max_restarts(),
14610                c.max_restarts,
14611                "Caixa::max_restarts accessor and self.max_restarts \
14612                 field access must byte-equal — a presence-bit or count \
14613                 drift would silently split the paired \
14614                 Caixa::declared_supervisor_slots presence-probe arm \
14615                 from the Caixa::supervisor_view unwrap_or(5) fold's \
14616                 composition input",
14617            );
14618        }
14619    }
14620
14621    #[test]
14622    fn max_restarts_projects_option_by_copy() {
14623        // The by-`Copy` pin: [`Caixa::max_restarts`] returns
14624        // `Option<u32>` by value (`u32: Copy`) — the accessor does not
14625        // borrow `&self` past the call (no lifetime on the return type),
14626        // and calling the accessor twice on the same [`Caixa`] must
14627        // yield equal values (idempotent, no side effects). Peer of the
14628        // sibling `estrategia_projects_option_by_copy` (ed04d3c) pin on
14629        // the outer-`Caixa` `Option<Copy>`-return flat-spread axis.
14630        for max_restarts in [Some(0u32), Some(5), Some(1000), Some(u32::MAX), None] {
14631            let c = caixa_with_max_restarts(max_restarts);
14632            let first = c.max_restarts();
14633            let second = c.max_restarts();
14634            assert_eq!(
14635                first, second,
14636                "Caixa::max_restarts must be idempotent — two successive \
14637                 calls on the same &self must return the same Option<u32>",
14638            );
14639            assert_eq!(
14640                first, max_restarts,
14641                "Caixa::max_restarts must return :max-restarts verbatim \
14642                 by Copy — got {first:?}, expected {max_restarts:?}",
14643            );
14644        }
14645    }
14646
14647    #[test]
14648    fn declared_supervisor_slots_max_restarts_arm_routes_through_accessor() {
14649        // Composition pin: [`Caixa::declared_supervisor_slots`]'s
14650        // `:max-restarts` presence-probe arm must key off
14651        // [`Caixa::max_restarts`], not the raw
14652        // `self.max_restarts.is_some()` field-probe. Structurally: every
14653        // `Caixa { max_restarts: Some(_), .. }` variant must push
14654        // `SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS` onto the declared-slot
14655        // list (the presence bit is `Some` for every representative
14656        // count, so the M2 kind-coherence gate must surface the slot as
14657        // "declared"), and a `Caixa { max_restarts: None, .. }` must
14658        // NOT push the label. Peer of the sibling
14659        // `declared_supervisor_slots_estrategia_arm_routes_through_accessor`
14660        // (ed04d3c) composition pin — same routing-through-accessor
14661        // discipline extended onto the sibling flat-spread `Option<u32>`
14662        // arm.
14663        for max_restarts in [0u32, 5, 1000, u32::MAX] {
14664            let c = caixa_with_max_restarts(Some(max_restarts));
14665            let slots = c.declared_supervisor_slots();
14666            assert!(
14667                slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS),
14668                "declared_supervisor_slots must push \
14669                 SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS when `:max-restarts` \
14670                 is Some({max_restarts}) — the accessor and the \
14671                 enumerator gate must route through the same \
14672                 substrate-primitive typed dispatch on the outer \
14673                 :max-restarts presence bit (got slots={slots:?})",
14674            );
14675        }
14676        let c = caixa_with_max_restarts(None);
14677        let slots = c.declared_supervisor_slots();
14678        assert!(
14679            !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS),
14680            "declared_supervisor_slots must NOT push \
14681             SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS when `:max-restarts` is \
14682             None — the author-omitted arm must route through the \
14683             accessor's None-return unchanged (got slots={slots:?})",
14684        );
14685    }
14686
14687    #[test]
14688    fn supervisor_view_max_restarts_arm_routes_through_accessor() {
14689        // Composition pin: [`Caixa::supervisor_view`]'s per-`:max-restarts`
14690        // [`SupervisorSpec`] construction arm must key off
14691        // [`Caixa::max_restarts`]'s `unwrap_or(5)` fold, not the raw
14692        // `self.max_restarts.unwrap_or(5)` field-fold. Structurally: for
14693        // every `:kind Supervisor` `Caixa` carrying an author-declared
14694        // `Some(n)`, the composed [`SupervisorSpec`]'s `.max_restarts()`
14695        // must byte-equal `n`; and for a `:kind Supervisor` `Caixa`
14696        // carrying `None`, the composed [`SupervisorSpec`]'s
14697        // `.max_restarts()` must byte-equal the OTP-canonical `5`. Peer
14698        // of the sibling
14699        // `supervisor_view_estrategia_arm_routes_through_accessor`
14700        // (ed04d3c) composition pin.
14701        for max_restarts in [1u32, 5, 1000] {
14702            let c = caixa_supervisor_with_max_restarts_and_window(Some(max_restarts), None);
14703            let view = c.supervisor_view().expect(
14704                "supervisor_view must materialize a SupervisorSpec for a \
14705                 :kind Supervisor Caixa carrying a Some(:max-restarts)",
14706            );
14707            assert_eq!(
14708                view.max_restarts(),
14709                max_restarts,
14710                "supervisor_view must carry the outer \
14711                 Caixa::max_restarts() Some arm onto the composed \
14712                 SupervisorSpec.max_restarts field verbatim (got {}, \
14713                 expected {max_restarts})",
14714                view.max_restarts(),
14715            );
14716        }
14717        let c = caixa_supervisor_with_max_restarts_and_window(None, None);
14718        let view = c.supervisor_view().expect(
14719            "supervisor_view must materialize a SupervisorSpec for a \
14720             :kind Supervisor Caixa carrying a None :max-restarts",
14721        );
14722        assert_eq!(
14723            view.max_restarts(),
14724            5,
14725            "supervisor_view must project the outer \
14726             Caixa::max_restarts() None arm onto the OTP-canonical \
14727             {{intensity, 5, 60}} default (5) through the flat-spread \
14728             unwrap_or(5) fold (got {})",
14729            view.max_restarts(),
14730        );
14731        assert!(
14732            c.max_restarts().is_none(),
14733            "Caixa::max_restarts() must remain None on the author-\
14734             omitted arm — the supervisor_view fold must not mutate \
14735             the outer flat-spread presence bit",
14736        );
14737    }
14738
14739    #[test]
14740    fn restart_window_returns_restart_window_option_verbatim_across_permutations() {
14741        // Value-shape pin: [`Caixa::restart_window`] returns the
14742        // `:restart-window` typed `Option<String>` verbatim as an
14743        // `Option<&str>`, borrowed from the typed slot's own storage,
14744        // byte-equal across the author-omitted `None` arm and each of
14745        // the representative fixtures in the accept-set — the canonical
14746        // `"60s"` from `{intensity, 5, 60}`, the sibling
14747        // canonical-magnitude forms (`"5m"` / `"1h"` / `"500ms"` / `"30"`
14748        // / `"0s"`) the shared codec's positive-set sweep pin covers,
14749        // plus a past-the-guard sentinel (`"1.5s"` — the fractional-
14750        // seconds drift the sibling [`Self::validate_restart_window`]
14751        // gate refuses; the accessor must ship the raw slot verbatim
14752        // so struct-literal fixtures continue to expose the drift at
14753        // the accessor boundary). Third outer top-level [`Caixa`]
14754        // supervisor-tree flat-spread pin — extends the sub-family onto
14755        // the sibling `Option<&str>` raw-duration-string arm.
14756        for window in [
14757            None,
14758            Some("60s"),
14759            Some("5m"),
14760            Some("1h"),
14761            Some("500ms"),
14762            Some("1.5s"),
14763            Some(""),
14764        ] {
14765            let c = caixa_with_restart_window(window);
14766            assert_eq!(
14767                c.restart_window(),
14768                window,
14769                "Caixa::restart_window must return :restart-window \
14770                 verbatim as Option<&str> (got {:?}, expected {window:?})",
14771                c.restart_window(),
14772            );
14773            assert_eq!(
14774                c.restart_window(),
14775                c.restart_window.as_deref(),
14776                "Caixa::restart_window accessor and \
14777                 self.restart_window.as_deref() field access must \
14778                 byte-equal — a byte-level drift would silently split \
14779                 the paired Caixa::declared_supervisor_slots \
14780                 presence-probe arm from the \
14781                 Caixa::validate_restart_window shared-codec gate and \
14782                 the Caixa::supervisor_view soft-swallowing fold",
14783            );
14784        }
14785    }
14786
14787    #[test]
14788    fn restart_window_projects_slice_by_borrow() {
14789        // The by-borrow pin: [`Caixa::restart_window`] returns
14790        // `Option<&str>` by borrow — the returned string slice borrows
14791        // the underlying `Option<String>` storage of the `:restart-window`
14792        // slot and the accessor must not clone on every call. Peer of
14793        // the sibling outer top-level [`Caixa`] `Option<&str>`-return
14794        // by-borrow pins on the universal-axis scalar family
14795        // (`licenca_projects_option_ref_by_borrow` /
14796        // `descricao_projects_option_ref_by_borrow` and siblings) —
14797        // extended onto the M2 supervisor-tree flat-spread
14798        // `Option<&str>` raw-duration-string axis.
14799        for window in [None, Some("60s"), Some("5m"), Some("")] {
14800            let c = caixa_with_restart_window(window);
14801            let first = c.restart_window();
14802            let second = c.restart_window();
14803            assert_eq!(
14804                first, second,
14805                "Caixa::restart_window must be idempotent — two \
14806                 successive calls on the same &self must return the \
14807                 same Option<&str>",
14808            );
14809            if let (Some(a), Some(b)) = (first, second) {
14810                assert_eq!(
14811                    a.as_ptr(),
14812                    b.as_ptr(),
14813                    "Caixa::restart_window must borrow the underlying \
14814                     String storage — two successive Some-arm calls must \
14815                     return slices with the same backing pointer (a fresh \
14816                     String clone would change the pointer on every call)",
14817                );
14818            }
14819            assert_eq!(
14820                first, window,
14821                "Caixa::restart_window must return :restart-window \
14822                 verbatim by borrow — got {first:?}, expected {window:?}",
14823            );
14824        }
14825    }
14826
14827    #[test]
14828    fn declared_supervisor_slots_restart_window_arm_routes_through_accessor() {
14829        // Composition pin: [`Caixa::declared_supervisor_slots`]'s
14830        // `:restart-window` presence-probe arm must key off
14831        // [`Caixa::restart_window`], not the raw
14832        // `self.restart_window.is_some()` field-probe. Structurally:
14833        // every `Caixa { restart_window: Some(_), .. }` must push
14834        // `SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW` onto the declared-slot
14835        // list, and a `Caixa { restart_window: None, .. }` must NOT
14836        // push the label. Peer of the sibling
14837        // `declared_supervisor_slots_max_restarts_arm_routes_through_accessor`
14838        // routing pin.
14839        for window in ["60s", "5m", "1h", "500ms", "1.5s", ""] {
14840            let c = caixa_with_restart_window(Some(window));
14841            let slots = c.declared_supervisor_slots();
14842            assert!(
14843                slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW),
14844                "declared_supervisor_slots must push \
14845                 SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW when \
14846                 `:restart-window` is Some({window:?}) — the accessor \
14847                 and the enumerator gate must route through the same \
14848                 substrate-primitive typed dispatch on the outer \
14849                 :restart-window presence bit (got slots={slots:?})",
14850            );
14851        }
14852        let c = caixa_with_restart_window(None);
14853        let slots = c.declared_supervisor_slots();
14854        assert!(
14855            !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW),
14856            "declared_supervisor_slots must NOT push \
14857             SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW when `:restart-window` \
14858             is None — the author-omitted arm must route through the \
14859             accessor's None-return unchanged (got slots={slots:?})",
14860        );
14861    }
14862
14863    #[test]
14864    fn validate_restart_window_arm_routes_through_accessor() {
14865        // Composition pin: [`Caixa::validate_restart_window`]'s
14866        // shared-codec fold arm must key off [`Caixa::restart_window`],
14867        // not the raw `self.restart_window.as_deref()` field-projection.
14868        // Structurally: (1) `None` → `Ok(())` (the "omit the slot to
14869        // express no reset" canonical shape); (2) a canonical `Some`
14870        // arm (`"60s"`) → `Ok(())`; (3) a codec-rejected `Some` arm
14871        // (`"1.5s"`) → `Err(RestartWindowMalformed { restart_window,
14872        // .. })` carrying the offending raw string verbatim. The three
14873        // arms jointly pin that the validator's raw-string binding is
14874        // the accessor's return, not a peer projection — any future
14875        // silent detour that had the accessor collapse `Some("")` to
14876        // `None` would silently absorb the empty-after-trim refusal
14877        // case at the accessor boundary.
14878        caixa_with_restart_window(None)
14879            .validate_restart_window()
14880            .expect("None :restart-window must validate through the accessor");
14881        caixa_with_restart_window(Some("60s"))
14882            .validate_restart_window()
14883            .expect("canonical :restart-window \"60s\" must validate through the accessor");
14884        let err = caixa_with_restart_window(Some("1.5s"))
14885            .validate_restart_window()
14886            .expect_err("fractional-seconds :restart-window must fail through the accessor");
14887        assert!(
14888            matches!(
14889                err,
14890                ManifestError::RestartWindowMalformed { ref restart_window, .. }
14891                    if restart_window == "1.5s"
14892            ),
14893            "validator must carry the offending raw string verbatim \
14894             from the accessor's borrowed &str (got {err:?})",
14895        );
14896    }
14897
14898    #[test]
14899    fn supervisor_view_restart_window_arm_routes_through_accessor() {
14900        // Composition pin: [`Caixa::supervisor_view`]'s
14901        // per-`:restart-window` [`SupervisorSpec`] construction arm
14902        // must key off [`Caixa::restart_window`]'s soft-swallowing
14903        // `.and_then(|s| duration_codec::parse(s).ok())` fold, not the
14904        // raw `self.restart_window.as_deref().and_then(…)` field-fold.
14905        // Structurally: (1) `None` → `SupervisorSpec.restart_window ==
14906        // None` (the "never reset" sentinel); (2) canonical `Some("60s")`
14907        // → `SupervisorSpec.restart_window == Some(Duration::from_secs(60))`
14908        // (the shared codec's canonical parse); (3) codec-rejected
14909        // `Some("1.5s")` → `SupervisorSpec.restart_window == None`
14910        // (the soft-swallow preserving the view's best-effort shape).
14911        let c = caixa_supervisor_with_max_restarts_and_window(None, None);
14912        let view = c.supervisor_view().expect("Supervisor kind has a view");
14913        assert_eq!(
14914            view.restart_window(),
14915            None,
14916            "supervisor_view must project outer None :restart-window \
14917             onto None on the composed SupervisorSpec (never-reset \
14918             sentinel) through the accessor's None-return unchanged",
14919        );
14920
14921        let c = caixa_supervisor_with_max_restarts_and_window(None, Some("60s"));
14922        let view = c.supervisor_view().expect("Supervisor kind has a view");
14923        assert_eq!(
14924            view.restart_window(),
14925            Some(std::time::Duration::from_secs(60)),
14926            "supervisor_view must fold outer Some(\"60s\") through the \
14927             shared duration_codec into Duration::from_secs(60) on the \
14928             composed SupervisorSpec (accessor's Some(&str) → codec \
14929             parse → Some(Duration))",
14930        );
14931
14932        let c = caixa_supervisor_with_max_restarts_and_window(None, Some("1.5s"));
14933        let view = c.supervisor_view().expect("Supervisor kind has a view");
14934        assert_eq!(
14935            view.restart_window(),
14936            None,
14937            "supervisor_view must soft-swallow the shared-codec parse \
14938             failure to None (the view's best-effort shape the sibling \
14939             manifest-level validate_restart_window surfaces as \
14940             RestartWindowMalformed); the accessor's raw-string return \
14941             is the single input every downstream consumer keys off",
14942        );
14943    }
14944
14945    // ── Caixa::upgrade_from — outer top-level &[UpgradeFromEntry] composite-slice accessor ──
14946
14947    fn caixa_with_upgrade_from(upgrade_from: Vec<crate::upgrade::UpgradeFromEntry>) -> Caixa {
14948        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
14949        c.upgrade_from = upgrade_from;
14950        c
14951    }
14952
14953    #[test]
14954    fn upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations() {
14955        // The canonical per-`Caixa` `:upgrade-from` M2 typed-slot
14956        // outer-composite `&[UpgradeFromEntry]`-return slice-shape
14957        // pin: [`Caixa::upgrade_from`] must return the `:upgrade-from`
14958        // typed `Vec<UpgradeFromEntry>` verbatim as a
14959        // `&[UpgradeFromEntry]` slice-view over the same backing
14960        // buffer the raw `self.upgrade_from.as_slice()` field access
14961        // borrows from, element-equal across every representative
14962        // fixture in the accept-set — `[]` (the "no hot-upgrade path
14963        // declared" arm every `defcaixa` without an `:upgrade-from`
14964        // block carries; `#[serde(default)]` folds an omitted slot
14965        // onto `Vec::new()`), a canonical single-entry `Restart`
14966        // fixture (the shape most Servicos carry — a single prior
14967        // version with the fallback strategy), a canonical multi-
14968        // entry list carrying every typed instruction variant
14969        // (`LoadModule` / `StateChange` / `SoftPurge` / `Purge` /
14970        // `Restart`), and a past-the-guard sentinel — a duplicate-
14971        // `:from` `[(0.1.0, Restart), (0.1.0, Restart)]` entry pair
14972        // ([`crate::upgrade::validate_upgrade_from`] rejects through
14973        // `DuplicateFrom { from: "0.1.0" }` but the accessor must
14974        // ship the raw slot verbatim so struct-literal fixtures
14975        // continue to expose the duplicate at the accessor boundary).
14976        //
14977        // Pins against a future silent detour that returned an owned
14978        // `Vec<UpgradeFromEntry>` (which would type-check but silently
14979        // clone on every accessor call, breaking the zero-cost
14980        // projection every peer sibling slice accessor carries), a
14981        // `[dup, dup] → [dup]` dedup collapse (which would silently
14982        // absorb the `DuplicateFrom` refusal case at the accessor
14983        // boundary and the [`crate::StandardLayout::verify`] cross-
14984        // entry gate would silently accept a struct-literal `Caixa`
14985        // carrying the drift), a reference to an operator-resolved
14986        // overlay (the future per-cluster `:upgrade-overrides` slot
14987        // — its resolution must land at exactly this accessor body,
14988        // not silently divert the raw slot away from a second
14989        // consumer), or an axis-shuffled projection (a future detour
14990        // that reordered entries through the accessor would silently
14991        // split the paired [`crate::StandardLayout::verify`] per-
14992        // `:upgrade-from` shape gate's traversal input from the peer
14993        // [`crate::render::servico_m2_overlay`] emitter's projection
14994        // input, since the operator's hot-upgrade dispatch matches
14995        // per-`:from` and axis reordering would silently split the
14996        // per-entry script-path existence probe's iteration order
14997        // from the M2 overlay emitter's serialized-entry order).
14998        //
14999        // First outer top-level [`Caixa`] `&[Composite]`-return
15000        // slice accessor pin on the substrate primitive for M2 / M3
15001        // typed-slot vec-carry axes — opens the outer-`Caixa`
15002        // `&[Composite]` composite-slice projection pattern the
15003        // sibling `:children` [`crate::supervisor::ChildSpec`] /
15004        // `:membros` [`crate::aplicacao::Membro`] / `:contratos`
15005        // [`crate::aplicacao::WitContract`] future outer-composite-
15006        // slice pins fold on. Peer of the closed outer-`Caixa`
15007        // scalar `Option<&Composite>` composite-reference family the
15008        // sibling `limits` / `behavior` / `politicas` / `placement`
15009        // / `entrada` `..._returns_..._option_ref_verbatim_across_
15010        // permutations` pins closed (b2bd9d7 → e4128e4) — extends
15011        // the "byte-equal, borrow-shared" outer-accessor discipline
15012        // onto the outer-`Caixa` `&[Composite]` vec-carry altitude.
15013        use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
15014        let fixtures: Vec<Vec<UpgradeFromEntry>> = vec![
15015            vec![],
15016            vec![UpgradeFromEntry {
15017                from: "0.0.1".into(),
15018                instructions: vec![UpgradeInstruction::Restart],
15019            }],
15020            vec![
15021                UpgradeFromEntry {
15022                    from: "0.0.1".into(),
15023                    instructions: vec![
15024                        UpgradeInstruction::LoadModule {
15025                            module: "demo".into(),
15026                        },
15027                        UpgradeInstruction::SoftPurge {
15028                            module: "demo".into(),
15029                        },
15030                    ],
15031                },
15032                UpgradeFromEntry {
15033                    from: "0.0.2".into(),
15034                    instructions: vec![
15035                        UpgradeInstruction::StateChange {
15036                            script: "servicos/upgrade.lisp".into(),
15037                        },
15038                        UpgradeInstruction::Purge {
15039                            module: "demo".into(),
15040                        },
15041                        UpgradeInstruction::Restart,
15042                    ],
15043                },
15044            ],
15045            vec![
15046                UpgradeFromEntry {
15047                    from: "0.1.0".into(),
15048                    instructions: vec![UpgradeInstruction::Restart],
15049                },
15050                UpgradeFromEntry {
15051                    from: "0.1.0".into(),
15052                    instructions: vec![UpgradeInstruction::Restart],
15053                },
15054            ],
15055        ];
15056        for upgrade_from in fixtures {
15057            let c = caixa_with_upgrade_from(upgrade_from.clone());
15058            assert_eq!(
15059                c.upgrade_from(),
15060                upgrade_from.as_slice(),
15061                "Caixa::upgrade_from must return :upgrade-from \
15062                 verbatim (got {:?}, expected {upgrade_from:?})",
15063                c.upgrade_from(),
15064            );
15065            assert_eq!(
15066                c.upgrade_from(),
15067                c.upgrade_from.as_slice(),
15068                "Caixa::upgrade_from must element-equal the raw \
15069                 `self.upgrade_from.as_slice()` field access across \
15070                 every value in the Vec<UpgradeFromEntry> accept-set",
15071            );
15072            assert_eq!(
15073                c.upgrade_from().is_empty(),
15074                c.upgrade_from.is_empty(),
15075                "Caixa::upgrade_from().is_empty() must byte-equal \
15076                 self.upgrade_from.is_empty() — a presence-bit drift \
15077                 would silently split the paired \
15078                 Caixa::declared_servico_slots M2 declared-slot \
15079                 enumerator's presence probe from the peer \
15080                 crate::render::servico_m2_overlay M2 overlay \
15081                 emitter's presence gate",
15082            );
15083        }
15084    }
15085
15086    #[test]
15087    fn declared_servico_slots_upgrade_from_arm_routes_through_accessor() {
15088        // Composition pin: [`Caixa::declared_servico_slots`]'s
15089        // `:upgrade-from` presence-probe arm must key off
15090        // [`Caixa::upgrade_from`], not the raw
15091        // `self.upgrade_from.is_empty()` field-probe. Structurally: a
15092        // `Caixa { upgrade_from: vec![UpgradeFromEntry { from: "0.0.1",
15093        // instructions: vec![Restart] }], .. }` must push
15094        // `M2_AUTHOR_KEY_UPGRADE_FROM` onto the declared-slot list
15095        // (the presence bit is non-empty, so the M2 kind-coherence
15096        // gate must surface the slot as "declared"), and a `Caixa {
15097        // upgrade_from: vec![], .. }` must NOT push the label (the
15098        // "author omitted the slot entirely" arm — the empty-slice
15099        // partition the serde-default folds onto). The pair jointly
15100        // pins the accessor + declared-slot enumerator composition:
15101        // any future silent detour that had the accessor collapse
15102        // `[Restart]` to `[]` (a `.filter(|e| !e.instructions.
15103        // is_empty())` projection) would silently absorb the
15104        // "declared but degenerate" arm at the accessor boundary and
15105        // the [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-
15106        // coherence gate would silently accept a struct-literal
15107        // `Caixa` carrying the drift.
15108        //
15109        // Peer of the sibling
15110        // `declared_servico_slots_limits_arm_routes_through_accessor`
15111        // (b2bd9d7) and
15112        // `declared_servico_slots_behavior_arm_routes_through_accessor`
15113        // (35d8b52) composition pins on the sibling `:limits` /
15114        // `:behavior` outer-`Option<&Composite>` arms — same "the
15115        // enumerator gate must route through the substrate-primitive
15116        // typed dispatch" discipline extended onto the third M2
15117        // Servico-runtime slot axis, closing the enumerator's routing
15118        // invariant on every M2 arm.
15119        use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
15120        let c = caixa_with_upgrade_from(vec![UpgradeFromEntry {
15121            from: "0.0.1".into(),
15122            instructions: vec![UpgradeInstruction::Restart],
15123        }]);
15124        let slots = c.declared_servico_slots();
15125        assert!(
15126            slots.contains(&crate::render::M2_AUTHOR_KEY_UPGRADE_FROM),
15127            "declared_servico_slots must push \
15128             M2_AUTHOR_KEY_UPGRADE_FROM when `:upgrade-from` is \
15129             non-empty — the accessor and the enumerator gate must \
15130             route through the same substrate-primitive typed \
15131             dispatch on the outer :upgrade-from presence bit (got \
15132             slots={slots:?})",
15133        );
15134        let c = caixa_with_upgrade_from(vec![]);
15135        let slots = c.declared_servico_slots();
15136        assert!(
15137            !slots.contains(&crate::render::M2_AUTHOR_KEY_UPGRADE_FROM),
15138            "declared_servico_slots must NOT push \
15139             M2_AUTHOR_KEY_UPGRADE_FROM when `:upgrade-from` is \
15140             empty — the author-omitted arm must route through the \
15141             accessor's empty-slice return unchanged (got \
15142             slots={slots:?})",
15143        );
15144    }
15145
15146    #[test]
15147    fn servico_m2_overlay_upgrade_from_arm_routes_through_accessor() {
15148        // Composition pin: [`crate::render::servico_m2_overlay`]'s
15149        // per-`:upgrade-from` M2 overlay emit arm must key off
15150        // [`Caixa::upgrade_from`], not the raw
15151        // `!caixa.upgrade_from.is_empty()` presence gate + the
15152        // `serde_yaml::to_value(&caixa.upgrade_from)` projection.
15153        // Structurally: a `Caixa { upgrade_from: vec![UpgradeFromEntry
15154        // { from: "0.0.1", instructions: vec![Restart] }], .. }` must
15155        // surface the `M2_KEY_UPGRADE_FROM` key with a per-entry
15156        // sequence in the overlay (the emitter fans onto the serde
15157        // slice-serialization), and a `Caixa { upgrade_from: vec![],
15158        // .. }` must omit the key entirely (the empty-slice
15159        // partition — the `!.is_empty()` outer gate elides the key
15160        // when the author omitted the slot). The pair jointly pins
15161        // the accessor + M2 overlay emitter composition: any future
15162        // silent detour that had the accessor return a fresh-cloned
15163        // `Vec<UpgradeFromEntry>` copy would silently break the
15164        // reference-identity pin the peer per-entry
15165        // `serde_yaml::to_value(caixa.upgrade_from())` projection
15166        // reads from — the projection would clone once per accessor
15167        // call instead of borrowing the storage buffer verbatim.
15168        //
15169        // Peer of the sibling
15170        // `servico_m2_overlay_limits_arm_routes_through_accessor`
15171        // (b2bd9d7) and
15172        // `servico_m2_overlay_behavior_arm_routes_through_accessor`
15173        // (35d8b52) composition pins on the sibling `:limits` /
15174        // `:behavior` outer-`Option<&Composite>` arms — same "the
15175        // M2 overlay emitter must route through the substrate-
15176        // primitive typed dispatch" discipline extended onto the
15177        // third M2 Servico-runtime slot axis, closing the overlay
15178        // emitter's routing invariant on every M2 arm.
15179        use crate::render::{M2_KEY_UPGRADE_FROM, servico_m2_overlay};
15180        use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
15181        let c = caixa_with_upgrade_from(vec![UpgradeFromEntry {
15182            from: "0.0.1".into(),
15183            instructions: vec![UpgradeInstruction::Restart],
15184        }]);
15185        let overlay = servico_m2_overlay(&c).unwrap();
15186        assert!(
15187            overlay.contains_key(M2_KEY_UPGRADE_FROM),
15188            "servico_m2_overlay must surface M2_KEY_UPGRADE_FROM when \
15189             `:upgrade-from` is non-empty — the accessor and the M2 \
15190             overlay emitter must route through the same substrate- \
15191             primitive typed dispatch on the outer :upgrade-from \
15192             slice (got overlay={overlay:?})",
15193        );
15194        let c = caixa_with_upgrade_from(vec![]);
15195        let overlay = servico_m2_overlay(&c).unwrap();
15196        assert!(
15197            !overlay.contains_key(M2_KEY_UPGRADE_FROM),
15198            "servico_m2_overlay must omit M2_KEY_UPGRADE_FROM when \
15199             `:upgrade-from` is empty — the empty-slice partition \
15200             must route through the accessor's empty-slice return \
15201             unchanged (got overlay={overlay:?})",
15202        );
15203    }
15204
15205    #[test]
15206    fn upgrade_from_projects_slice_by_borrow() {
15207        // The by-borrow pin: [`Caixa::upgrade_from`] returns
15208        // `&[UpgradeFromEntry]` by borrow — the returned slice
15209        // borrows the underlying `Vec<UpgradeFromEntry>` storage of
15210        // the `:upgrade-from` slot and the accessor must not clone
15211        // the backing `Vec` on every call. Peer of the sibling
15212        // outer top-level [`Caixa`] `&[T]`-return by-borrow pins
15213        // (`autores_projects_slice_by_borrow` b5d813f,
15214        // `etiquetas_projects_slice_by_borrow` 78c7d3c,
15215        // `bibliotecas_projects_slice_by_borrow` 8a36c23,
15216        // `exe_projects_slice_by_borrow` 65d9527,
15217        // `servicos_projects_slice_by_borrow` 611f78b,
15218        // `deps_projects_slice_by_borrow` ad34b4e,
15219        // `deps_dev_projects_slice_by_borrow` f7fd81e) on the
15220        // sibling outer top-level [`Caixa`] scalar-element `&[T]`
15221        // axes — extended here to the first outer-`Caixa`
15222        // composite-element `&[Composite]` axis: the accessor's
15223        // returned slice must borrow from `&self` (the returned
15224        // reference's lifetime is tied to `&self`), and calling the
15225        // accessor twice on the same [`Caixa`] must yield slices
15226        // that are pointer-equal (the underlying byte-buffer is the
15227        // storage `Vec`'s allocation, not a fresh copy) as well as
15228        // value-equal (idempotent, no side effects on `&self`).
15229        //
15230        // Pins against a future silent detour that returned an owned
15231        // `Vec<UpgradeFromEntry>` (which would type-check but
15232        // silently clone on every call), a `&Vec<UpgradeFromEntry>`
15233        // return (which would leak the backing `Vec`'s
15234        // grow/push/reserve surface no downstream consumer reaches
15235        // for), or a one-arm-only accessor that returned a
15236        // saturating value on some sentinel input.
15237        use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
15238        for upgrade_from in [
15239            vec![],
15240            vec![UpgradeFromEntry {
15241                from: "0.0.1".into(),
15242                instructions: vec![UpgradeInstruction::Restart],
15243            }],
15244            vec![
15245                UpgradeFromEntry {
15246                    from: "0.0.1".into(),
15247                    instructions: vec![UpgradeInstruction::Restart],
15248                },
15249                UpgradeFromEntry {
15250                    from: "0.0.2".into(),
15251                    instructions: vec![UpgradeInstruction::SoftPurge {
15252                        module: "demo".into(),
15253                    }],
15254                },
15255            ],
15256        ] {
15257            let c = caixa_with_upgrade_from(upgrade_from.clone());
15258            let first = c.upgrade_from();
15259            let second = c.upgrade_from();
15260            assert_eq!(
15261                first, second,
15262                "Caixa::upgrade_from must be idempotent — two \
15263                 successive calls on the same &self must return the \
15264                 same &[UpgradeFromEntry]",
15265            );
15266            assert_eq!(
15267                first.as_ptr(),
15268                second.as_ptr(),
15269                "Caixa::upgrade_from must borrow the underlying \
15270                 Vec<UpgradeFromEntry> storage — two successive calls \
15271                 must return slices with the same backing pointer (a \
15272                 fresh Vec<UpgradeFromEntry> clone would change the \
15273                 pointer on every call)",
15274            );
15275            assert_eq!(
15276                first,
15277                upgrade_from.as_slice(),
15278                "Caixa::upgrade_from must return :upgrade-from \
15279                 verbatim by borrow — got {first:?}, expected \
15280                 {upgrade_from:?}",
15281            );
15282        }
15283    }
15284
15285    // ── Caixa::children — outer top-level &[ChildSpec] composite-slice accessor ──
15286
15287    fn caixa_with_children(children: Vec<crate::supervisor::ChildSpec>) -> Caixa {
15288        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15289        c.children = children;
15290        c
15291    }
15292
15293    #[test]
15294    fn children_returns_children_slice_verbatim_across_permutations() {
15295        // The canonical per-`Caixa` `:children` M2 supervisor-tree-slot
15296        // outer-composite `&[ChildSpec]`-return slice-shape pin:
15297        // [`Caixa::children`] must return the `:children` typed
15298        // `Vec<ChildSpec>` verbatim as a `&[ChildSpec]` slice-view over
15299        // the same backing buffer the raw `self.children.as_slice()`
15300        // field access borrows from, element-equal across every
15301        // representative fixture in the accept-set — `[]` (the "no
15302        // static children declared" arm every non-`Supervisor`-kind
15303        // `defcaixa` carries by `#[serde(default)]` and every
15304        // `SimpleOneForOne` supervisor carries by cross-slot refusal),
15305        // a canonical single-child `Permanent` fixture (the shape
15306        // most `OneForOne` supervisors carry — a single long-running
15307        // worker child), a canonical multi-child list carrying every
15308        // typed restart-policy variant (`Permanent` / `Transient` /
15309        // `Temporary`), and a past-the-guard sentinel — a duplicate
15310        // `:caixa` `[("w", ...), ("w", ...)]` entry pair
15311        // ([`crate::SupervisorSpec::validate`] rejects through
15312        // `DuplicateChildNome { nome: "w" }` but the accessor must
15313        // ship the raw slot verbatim so struct-literal fixtures
15314        // continue to expose the duplicate at the accessor boundary).
15315        //
15316        // Pins against a future silent detour that returned an owned
15317        // `Vec<ChildSpec>` (which would type-check but silently clone
15318        // on every accessor call, breaking the zero-cost projection
15319        // every peer sibling slice accessor carries), a `[dup, dup] →
15320        // [dup]` dedup collapse (which would silently absorb the
15321        // `DuplicateChildNome` refusal case at the accessor boundary
15322        // and the [`crate::StandardLayout::verify`] cross-child gate
15323        // would silently accept a struct-literal `Caixa` carrying the
15324        // drift), a reference to an operator-resolved overlay (the
15325        // future per-cluster `:children-overrides` slot — its
15326        // resolution must land at exactly this accessor body, not
15327        // silently divert the raw slot away from a second consumer),
15328        // or an axis-shuffled projection (a future detour that
15329        // reordered children through the accessor would silently
15330        // split the paired [`crate::StandardLayout::verify`] per-
15331        // supervisor gate's traversal input from the peer
15332        // [`Self::supervisor_view`] fold-in path's clone-order input,
15333        // since the OTP `RestForOne` restart strategy dispatches on
15334        // declared child order and axis reordering would silently
15335        // split the operator's per-cluster restart-fan-out order
15336        // from the caixa.lisp source-order).
15337        //
15338        // Second outer top-level [`Caixa`] `&[Composite]`-return slice
15339        // accessor pin on the substrate primitive for M2 / M3 typed-
15340        // slot vec-carry axes — folds on the outer-`Caixa`
15341        // `&[Composite]` composite-slice sub-family the sibling
15342        // `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
15343        // (2a1f907) pin opened, peer at the outer altitude of the
15344        // closed inner-`SupervisorSpec` `SupervisorSpec::children`
15345        // (bc92bce) accessor on the same OTP-supervisor static-child-
15346        // list axis.
15347        use crate::supervisor::{ChildSpec, RestartPolicy};
15348        let fixtures: Vec<Vec<ChildSpec>> = vec![
15349            vec![],
15350            vec![ChildSpec {
15351                caixa: "worker".into(),
15352                versao: "^0.1".into(),
15353                restart: RestartPolicy::Permanent,
15354            }],
15355            vec![
15356                ChildSpec {
15357                    caixa: "worker-a".into(),
15358                    versao: "^0.1".into(),
15359                    restart: RestartPolicy::Permanent,
15360                },
15361                ChildSpec {
15362                    caixa: "worker-b".into(),
15363                    versao: "^0.1".into(),
15364                    restart: RestartPolicy::Transient,
15365                },
15366                ChildSpec {
15367                    caixa: "worker-c".into(),
15368                    versao: "^0.1".into(),
15369                    restart: RestartPolicy::Temporary,
15370                },
15371            ],
15372            vec![
15373                ChildSpec {
15374                    caixa: "w".into(),
15375                    versao: "^0.1".into(),
15376                    restart: RestartPolicy::Permanent,
15377                },
15378                ChildSpec {
15379                    caixa: "w".into(),
15380                    versao: "^0.1".into(),
15381                    restart: RestartPolicy::Permanent,
15382                },
15383            ],
15384        ];
15385        for children in fixtures {
15386            let c = caixa_with_children(children.clone());
15387            assert_eq!(
15388                c.children(),
15389                children.as_slice(),
15390                "Caixa::children must return :children verbatim \
15391                 (got {:?}, expected {children:?})",
15392                c.children(),
15393            );
15394            assert_eq!(
15395                c.children(),
15396                c.children.as_slice(),
15397                "Caixa::children must element-equal the raw \
15398                 `self.children.as_slice()` field access across \
15399                 every value in the Vec<ChildSpec> accept-set",
15400            );
15401            assert_eq!(
15402                c.children().is_empty(),
15403                c.children.is_empty(),
15404                "Caixa::children().is_empty() must byte-equal \
15405                 self.children.is_empty() — a presence-bit drift \
15406                 would silently split the paired \
15407                 Caixa::declared_supervisor_slots supervisor-tree \
15408                 declared-slot enumerator's presence probe from the \
15409                 peer Caixa::supervisor_view typed-view composer's \
15410                 fold-in path",
15411            );
15412        }
15413    }
15414
15415    #[test]
15416    fn declared_supervisor_slots_children_arm_routes_through_accessor() {
15417        // Composition pin: [`Caixa::declared_supervisor_slots`]'s
15418        // `:children` presence-probe arm must key off
15419        // [`Caixa::children`], not the raw
15420        // `!self.children.is_empty()` field-probe. Structurally: a
15421        // `Caixa { children: vec![ChildSpec { caixa: "w", versao:
15422        // "^0.1", restart: Permanent }], .. }` must push
15423        // `SUPERVISOR_AUTHOR_KEY_CHILDREN` onto the declared-slot list
15424        // (the presence bit is non-empty, so the supervisor-tree
15425        // kind-coherence gate must surface the slot as "declared"),
15426        // and a `Caixa { children: vec![], .. }` must NOT push the
15427        // label (the "author omitted the slot entirely" arm — the
15428        // empty-slice partition the serde-default folds onto). The
15429        // pair jointly pins the accessor + declared-slot enumerator
15430        // composition: any future silent detour that had the accessor
15431        // collapse `[Permanent]` to `[]` (a `.filter(|c| c.nome() !=
15432        // "__reserved__")` projection) would silently absorb the
15433        // "declared but degenerate" arm at the accessor boundary and
15434        // the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
15435        // kind-coherence gate would silently accept a struct-literal
15436        // `Caixa` carrying the drift.
15437        //
15438        // Peer of the sibling
15439        // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
15440        // (2a1f907) on the M2 `:upgrade-from` composite-slice arm —
15441        // same "the enumerator gate must route through the substrate-
15442        // primitive typed dispatch" discipline extended onto the
15443        // supervisor-tree `:children` composite-slice arm.
15444        use crate::supervisor::{ChildSpec, RestartPolicy};
15445        let c = caixa_with_children(vec![ChildSpec {
15446            caixa: "w".into(),
15447            versao: "^0.1".into(),
15448            restart: RestartPolicy::Permanent,
15449        }]);
15450        let slots = c.declared_supervisor_slots();
15451        assert!(
15452            slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN),
15453            "declared_supervisor_slots must push \
15454             SUPERVISOR_AUTHOR_KEY_CHILDREN when `:children` is \
15455             non-empty — the accessor and the enumerator gate must \
15456             route through the same substrate-primitive typed \
15457             dispatch on the outer :children presence bit (got \
15458             slots={slots:?})",
15459        );
15460        let c = caixa_with_children(vec![]);
15461        let slots = c.declared_supervisor_slots();
15462        assert!(
15463            !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN),
15464            "declared_supervisor_slots must NOT push \
15465             SUPERVISOR_AUTHOR_KEY_CHILDREN when `:children` is \
15466             empty — the author-omitted arm must route through the \
15467             accessor's empty-slice return unchanged (got \
15468             slots={slots:?})",
15469        );
15470    }
15471
15472    #[test]
15473    fn supervisor_view_children_arm_routes_through_accessor() {
15474        // Composition pin: [`Caixa::supervisor_view`]'s per-`:children`
15475        // fold-in arm must key off [`Caixa::children`], not the raw
15476        // `self.children.clone()` field-clone. Structurally: a `Caixa {
15477        // kind: Supervisor, estrategia: Some(OneForOne), children:
15478        // vec![ChildSpec { caixa: "w", .. }], .. }` must fold the
15479        // per-child list through the accessor into the typed
15480        // [`SupervisorSpec`] view's `children` field verbatim — every
15481        // entry the accessor surfaces must land in the view's
15482        // `children` slot in the same order. The pair jointly pins the
15483        // accessor + view-composer composition: any future silent
15484        // detour that had the accessor return a fresh-cloned
15485        // `Vec<ChildSpec>` copy would silently break the reference-
15486        // identity pin the peer `supervisor_view` fold-in path reads
15487        // from — the fold would clone once more per accessor call
15488        // instead of borrowing the storage buffer verbatim once.
15489        //
15490        // Peer of the sibling
15491        // `supervisor_view_kind_gate_routes_through_accessor` (35d8b52-
15492        // family) composition pin on the peer kind-gate arm — same
15493        // "the view composer must route through the substrate-
15494        // primitive typed dispatch" discipline extended onto the
15495        // per-`:children` fold-in arm, closing the supervisor-view
15496        // composer's routing invariant on the composite-slice input.
15497        use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
15498        let mut c = caixa_with_children(vec![
15499            ChildSpec {
15500                caixa: "worker-a".into(),
15501                versao: "^0.1".into(),
15502                restart: RestartPolicy::Permanent,
15503            },
15504            ChildSpec {
15505                caixa: "worker-b".into(),
15506                versao: "^0.1".into(),
15507                restart: RestartPolicy::Transient,
15508            },
15509        ]);
15510        c.kind = crate::CaixaKind::Supervisor;
15511        c.estrategia = Some(RestartStrategy::OneForOne);
15512        let view = c
15513            .supervisor_view()
15514            .expect("Supervisor kind must produce a supervisor_view");
15515        assert_eq!(
15516            view.children(),
15517            c.children(),
15518            "supervisor_view must fold Caixa::children verbatim into \
15519             SupervisorSpec::children — the accessor and the view \
15520             composer must route through the same substrate-primitive \
15521             typed dispatch on the outer :children slice (got view \
15522             children={:?}, expected {:?})",
15523            view.children(),
15524            c.children(),
15525        );
15526    }
15527
15528    #[test]
15529    fn children_projects_slice_by_borrow() {
15530        // The by-borrow pin: [`Caixa::children`] returns
15531        // `&[ChildSpec]` by borrow — the returned slice borrows the
15532        // underlying `Vec<ChildSpec>` storage of the `:children` slot
15533        // and the accessor must not clone the backing `Vec` on every
15534        // call. Peer of the sibling outer top-level [`Caixa`]
15535        // `&[T]`-return by-borrow pins (`autores_projects_slice_by_borrow`
15536        // b5d813f, `etiquetas_projects_slice_by_borrow` 78c7d3c,
15537        // `bibliotecas_projects_slice_by_borrow` 8a36c23,
15538        // `exe_projects_slice_by_borrow` 65d9527,
15539        // `servicos_projects_slice_by_borrow` 611f78b,
15540        // `deps_projects_slice_by_borrow` ad34b4e,
15541        // `deps_dev_projects_slice_by_borrow` f7fd81e,
15542        // `upgrade_from_projects_slice_by_borrow` 2a1f907) on the
15543        // sibling outer top-level [`Caixa`] scalar-element and
15544        // composite-element `&[T]` axes — folds on the outer-`Caixa`
15545        // composite-element `&[Composite]` axis: the accessor's
15546        // returned slice must borrow from `&self` (the returned
15547        // reference's lifetime is tied to `&self`), and calling the
15548        // accessor twice on the same [`Caixa`] must yield slices
15549        // that are pointer-equal (the underlying byte-buffer is the
15550        // storage `Vec`'s allocation, not a fresh copy) as well as
15551        // value-equal (idempotent, no side effects on `&self`).
15552        //
15553        // Pins against a future silent detour that returned an owned
15554        // `Vec<ChildSpec>` (which would type-check but silently clone
15555        // on every call), a `&Vec<ChildSpec>` return (which would leak
15556        // the backing `Vec`'s grow/push/reserve surface no downstream
15557        // consumer reaches for), or a one-arm-only accessor that
15558        // returned a saturating value on some sentinel input.
15559        use crate::supervisor::{ChildSpec, RestartPolicy};
15560        for children in [
15561            vec![],
15562            vec![ChildSpec {
15563                caixa: "w".into(),
15564                versao: "^0.1".into(),
15565                restart: RestartPolicy::Permanent,
15566            }],
15567            vec![
15568                ChildSpec {
15569                    caixa: "worker-a".into(),
15570                    versao: "^0.1".into(),
15571                    restart: RestartPolicy::Permanent,
15572                },
15573                ChildSpec {
15574                    caixa: "worker-b".into(),
15575                    versao: "^0.1".into(),
15576                    restart: RestartPolicy::Transient,
15577                },
15578            ],
15579        ] {
15580            let c = caixa_with_children(children.clone());
15581            let first = c.children();
15582            let second = c.children();
15583            assert_eq!(
15584                first, second,
15585                "Caixa::children must be idempotent — two successive \
15586                 calls on the same &self must return the same \
15587                 &[ChildSpec]",
15588            );
15589            assert_eq!(
15590                first.as_ptr(),
15591                second.as_ptr(),
15592                "Caixa::children must borrow the underlying \
15593                 Vec<ChildSpec> storage — two successive calls must \
15594                 return slices with the same backing pointer (a fresh \
15595                 Vec<ChildSpec> clone would change the pointer on \
15596                 every call)",
15597            );
15598            assert_eq!(
15599                first,
15600                children.as_slice(),
15601                "Caixa::children must return :children verbatim by \
15602                 borrow — got {first:?}, expected {children:?}",
15603            );
15604        }
15605    }
15606
15607    // ── Caixa::membros — outer top-level &[Membro] composite-slice accessor ──
15608
15609    fn caixa_aplicacao_with_membros(membros: Vec<crate::aplicacao::Membro>) -> Caixa {
15610        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15611        c.kind = CaixaKind::Aplicacao;
15612        c.membros = membros;
15613        c
15614    }
15615
15616    #[test]
15617    fn membros_returns_membros_slice_verbatim_across_permutations() {
15618        // The canonical per-`Caixa` `:membros` M3 mesh-slot outer-
15619        // composite `&[Membro]`-return slice-shape pin:
15620        // [`Caixa::membros`] must return the `:membros` typed
15621        // `Vec<Membro>` verbatim as a `&[Membro]` slice-view over the
15622        // same backing buffer the raw `self.membros.as_slice()` field
15623        // access borrows from, element-equal across every
15624        // representative fixture in the accept-set — `[]` (the "no
15625        // members declared" arm every non-`Aplicacao`-kind `defcaixa`
15626        // carries by `#[serde(default)]` and every partially-authored
15627        // Aplicacao carries before the
15628        // [`crate::AplicacaoError::MembrosEmpty`] gate fires), a
15629        // canonical single-member fixture (the shape a minimal
15630        // Aplicacao carries — one Servico wrapping one contained
15631        // computation), a canonical multi-member list carrying three
15632        // distinct entries (the canonical checkout-shape Aplicacao —
15633        // cart / pricing / auth — every canonical example carries), and
15634        // a past-the-guard sentinel — a duplicate `:caixa`
15635        // `[("cart", ...), ("cart", ...)]` entry pair
15636        // ([`crate::AplicacaoSpec::validate`] rejects through
15637        // `DuplicateMembro { nome: "cart" }` but the accessor must ship
15638        // the raw slot verbatim so struct-literal fixtures continue to
15639        // expose the duplicate at the accessor boundary).
15640        //
15641        // Pins against a future silent detour that returned an owned
15642        // `Vec<Membro>` (which would type-check but silently clone on
15643        // every accessor call, breaking the zero-cost projection every
15644        // peer sibling slice accessor carries), a `[dup, dup] → [dup]`
15645        // dedup collapse (which would silently absorb the
15646        // `DuplicateMembro` refusal case at the accessor boundary and
15647        // the [`crate::StandardLayout::verify`] cross-member gate would
15648        // silently accept a struct-literal `Caixa` carrying the drift),
15649        // a reference to an operator-resolved overlay (the future per-
15650        // cluster `:membros-overrides` slot — its resolution must land
15651        // at exactly this accessor body, not silently divert the raw
15652        // slot away from a second consumer), or an axis-shuffled
15653        // projection (a future detour that reordered members through
15654        // the accessor would silently split the paired
15655        // [`crate::StandardLayout::verify`] per-Aplicacao gate's
15656        // traversal input from the peer [`Self::aplicacao_view`] fold-
15657        // in path's clone-order input, since the canonical `:contratos`
15658        // `:de`/`:para` and `:entrada :para` cross-slot refusal probes
15659        // read the member set through the same slice).
15660        //
15661        // Third outer top-level [`Caixa`] `&[Composite]`-return slice
15662        // accessor pin on the substrate primitive for M2 / M3 typed-
15663        // slot vec-carry axes — opens the outer-`Caixa` M3 mesh-slot
15664        // arm of the `&[Composite]` composite-slice sub-family the
15665        // sibling M2 `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
15666        // (2a1f907) and
15667        // `children_returns_children_slice_verbatim_across_permutations`
15668        // (c17b51e) pins opened, peer at the outer altitude of the
15669        // closed inner-[`crate::AplicacaoSpec::membros`] (6c77e36)
15670        // accessor on the same MESH-COMPOSITION per-Aplicacao member-
15671        // list axis.
15672        use crate::aplicacao::Membro;
15673        let fixtures: Vec<Vec<Membro>> = vec![
15674            vec![],
15675            vec![Membro {
15676                caixa: "cart".into(),
15677                versao: "^0.1".into(),
15678            }],
15679            vec![
15680                Membro {
15681                    caixa: "cart".into(),
15682                    versao: "^0.1".into(),
15683                },
15684                Membro {
15685                    caixa: "pricing".into(),
15686                    versao: "^0.2".into(),
15687                },
15688                Membro {
15689                    caixa: "auth".into(),
15690                    versao: "^1.0".into(),
15691                },
15692            ],
15693            vec![
15694                Membro {
15695                    caixa: "cart".into(),
15696                    versao: "^0.1".into(),
15697                },
15698                Membro {
15699                    caixa: "cart".into(),
15700                    versao: "^0.1".into(),
15701                },
15702            ],
15703        ];
15704        for membros in fixtures {
15705            let c = caixa_aplicacao_with_membros(membros.clone());
15706            assert_eq!(
15707                c.membros(),
15708                membros.as_slice(),
15709                "Caixa::membros must return :membros verbatim \
15710                 (got {:?}, expected {membros:?})",
15711                c.membros(),
15712            );
15713            assert_eq!(
15714                c.membros(),
15715                c.membros.as_slice(),
15716                "Caixa::membros must element-equal the raw \
15717                 `self.membros.as_slice()` field access across every \
15718                 value in the Vec<Membro> accept-set",
15719            );
15720            assert_eq!(
15721                c.membros().is_empty(),
15722                c.membros.is_empty(),
15723                "Caixa::membros().is_empty() must byte-equal \
15724                 self.membros.is_empty() — a presence-bit drift would \
15725                 silently split the paired Caixa::declared_mesh_slots \
15726                 mesh declared-slot enumerator's presence probe from \
15727                 the peer Caixa::aplicacao_view typed-view composer's \
15728                 fold-in path",
15729            );
15730        }
15731    }
15732
15733    #[test]
15734    fn declared_mesh_slots_membros_arm_routes_through_accessor() {
15735        // Composition pin: [`Caixa::declared_mesh_slots`]'s `:membros`
15736        // presence-probe arm must key off [`Caixa::membros`], not the
15737        // raw `!self.membros.is_empty()` field-probe. Structurally: a
15738        // `Caixa { membros: vec![Membro { caixa: "cart", versao:
15739        // "^0.1" }], .. }` must push `M3_AUTHOR_KEY_MEMBROS` onto the
15740        // declared-slot list (the presence bit is non-empty, so the
15741        // mesh kind-coherence gate must surface the slot as
15742        // "declared"), and a `Caixa { membros: vec![], .. }` must NOT
15743        // push the label (the "author omitted the slot entirely" arm
15744        // — the empty-slice partition the serde-default folds onto).
15745        // The pair jointly pins the accessor + declared-slot
15746        // enumerator composition: any future silent detour that had
15747        // the accessor collapse `[Membro { .. }]` to `[]` (a
15748        // `.filter(|m| m.nome() != "__reserved__")` projection) would
15749        // silently absorb the "declared but degenerate" arm at the
15750        // accessor boundary and the
15751        // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
15752        // coherence gate would silently accept a struct-literal
15753        // `Caixa` carrying the drift.
15754        //
15755        // Peer of the sibling
15756        // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
15757        // (2a1f907) and
15758        // `declared_supervisor_slots_children_arm_routes_through_accessor`
15759        // (c17b51e) composition pins on the M2 `:upgrade-from` /
15760        // `:children` composite-slice arms — same "the enumerator gate
15761        // must route through the substrate-primitive typed dispatch"
15762        // discipline extended onto the M3 `:membros` composite-slice
15763        // arm, opening the M3 arm of the declared-slot enumerator's
15764        // routing invariant.
15765        use crate::aplicacao::Membro;
15766        let c = caixa_aplicacao_with_membros(vec![Membro {
15767            caixa: "cart".into(),
15768            versao: "^0.1".into(),
15769        }]);
15770        let slots = c.declared_mesh_slots();
15771        assert!(
15772            slots.contains(&crate::render::M3_AUTHOR_KEY_MEMBROS),
15773            "declared_mesh_slots must push M3_AUTHOR_KEY_MEMBROS when \
15774             `:membros` is non-empty — the accessor and the enumerator \
15775             gate must route through the same substrate-primitive \
15776             typed dispatch on the outer :membros presence bit (got \
15777             slots={slots:?})",
15778        );
15779        let c = caixa_aplicacao_with_membros(vec![]);
15780        let slots = c.declared_mesh_slots();
15781        assert!(
15782            !slots.contains(&crate::render::M3_AUTHOR_KEY_MEMBROS),
15783            "declared_mesh_slots must NOT push M3_AUTHOR_KEY_MEMBROS \
15784             when `:membros` is empty — the author-omitted arm must \
15785             route through the accessor's empty-slice return unchanged \
15786             (got slots={slots:?})",
15787        );
15788    }
15789
15790    #[test]
15791    fn aplicacao_view_membros_arm_routes_through_accessor() {
15792        // Composition pin: [`Caixa::aplicacao_view`]'s per-`:membros`
15793        // fold-in arm must key off [`Caixa::membros`], not the raw
15794        // `self.membros.clone()` field-clone. Structurally: a `Caixa {
15795        // kind: Aplicacao, membros: vec![Membro { caixa: "cart", .. },
15796        // Membro { caixa: "pricing", .. }], .. }` must fold the per-
15797        // member list through the accessor into the typed
15798        // [`crate::AplicacaoSpec`] view's `membros` slot verbatim —
15799        // every entry the accessor surfaces must land in the view's
15800        // `membros` slot in the same order. The pair jointly pins the
15801        // accessor + view-composer composition: any future silent
15802        // detour that had the accessor return a fresh-cloned
15803        // `Vec<Membro>` copy would silently break the reference-
15804        // identity pin the peer `aplicacao_view` fold-in path reads
15805        // from — the fold would clone once more per accessor call
15806        // instead of borrowing the storage buffer verbatim once.
15807        //
15808        // Peer of the sibling
15809        // `aplicacao_view_politicas_arm_folds_through_accessor`
15810        // (5d23d29) /
15811        // `aplicacao_view_placement_arm_folds_through_accessor`
15812        // (4fb8074) /
15813        // `aplicacao_view_entrada_arm_folds_through_accessor` (e4128e4)
15814        // composition pins on the M3 `:politicas` / `:placement` /
15815        // `:entrada` outer-`Option<&Composite>` arms — extended here to
15816        // the M3 `:membros` outer-`&[Composite]` composite-slice arm,
15817        // closing the aplicacao-view composer's routing invariant on
15818        // the composite-slice input.
15819        use crate::aplicacao::Membro;
15820        let c = caixa_aplicacao_with_membros(vec![
15821            Membro {
15822                caixa: "cart".into(),
15823                versao: "^0.1".into(),
15824            },
15825            Membro {
15826                caixa: "pricing".into(),
15827                versao: "^0.2".into(),
15828            },
15829        ]);
15830        let view = c
15831            .aplicacao_view()
15832            .expect("Aplicacao kind must produce an aplicacao_view");
15833        assert_eq!(
15834            view.membros(),
15835            c.membros(),
15836            "aplicacao_view must fold Caixa::membros verbatim into \
15837             AplicacaoSpec::membros — the accessor and the view \
15838             composer must route through the same substrate-primitive \
15839             typed dispatch on the outer :membros slice (got view \
15840             membros={:?}, expected {:?})",
15841            view.membros(),
15842            c.membros(),
15843        );
15844    }
15845
15846    #[test]
15847    fn membros_projects_slice_by_borrow() {
15848        // The by-borrow pin: [`Caixa::membros`] returns `&[Membro]` by
15849        // borrow — the returned slice borrows the underlying
15850        // `Vec<Membro>` storage of the `:membros` slot and the
15851        // accessor must not clone the backing `Vec` on every call.
15852        // Peer of the sibling outer top-level [`Caixa`] `&[T]`-return
15853        // by-borrow pins (`autores_projects_slice_by_borrow` b5d813f,
15854        // `etiquetas_projects_slice_by_borrow` 78c7d3c,
15855        // `bibliotecas_projects_slice_by_borrow` 8a36c23,
15856        // `exe_projects_slice_by_borrow` 65d9527,
15857        // `servicos_projects_slice_by_borrow` 611f78b,
15858        // `deps_projects_slice_by_borrow` ad34b4e,
15859        // `deps_dev_projects_slice_by_borrow` f7fd81e,
15860        // `upgrade_from_projects_slice_by_borrow` 2a1f907,
15861        // `children_projects_slice_by_borrow` c17b51e) on the sibling
15862        // outer top-level [`Caixa`] scalar-element and composite-
15863        // element `&[T]` axes — folds on the outer-`Caixa` M3 mesh-
15864        // slot composite-element `&[Composite]` axis: the accessor's
15865        // returned slice must borrow from `&self` (the returned
15866        // reference's lifetime is tied to `&self`), and calling the
15867        // accessor twice on the same [`Caixa`] must yield slices that
15868        // are pointer-equal (the underlying byte-buffer is the storage
15869        // `Vec`'s allocation, not a fresh copy) as well as value-equal
15870        // (idempotent, no side effects on `&self`).
15871        //
15872        // Pins against a future silent detour that returned an owned
15873        // `Vec<Membro>` (which would type-check but silently clone on
15874        // every call), a `&Vec<Membro>` return (which would leak the
15875        // backing `Vec`'s grow/push/reserve surface no downstream
15876        // consumer reaches for), or a one-arm-only accessor that
15877        // returned a saturating value on some sentinel input.
15878        use crate::aplicacao::Membro;
15879        for membros in [
15880            vec![],
15881            vec![Membro {
15882                caixa: "cart".into(),
15883                versao: "^0.1".into(),
15884            }],
15885            vec![
15886                Membro {
15887                    caixa: "cart".into(),
15888                    versao: "^0.1".into(),
15889                },
15890                Membro {
15891                    caixa: "pricing".into(),
15892                    versao: "^0.2".into(),
15893                },
15894            ],
15895        ] {
15896            let c = caixa_aplicacao_with_membros(membros.clone());
15897            let first = c.membros();
15898            let second = c.membros();
15899            assert_eq!(
15900                first, second,
15901                "Caixa::membros must be idempotent — two successive \
15902                 calls on the same &self must return the same &[Membro]",
15903            );
15904            assert_eq!(
15905                first.as_ptr(),
15906                second.as_ptr(),
15907                "Caixa::membros must borrow the underlying Vec<Membro> \
15908                 storage — two successive calls must return slices with \
15909                 the same backing pointer (a fresh Vec<Membro> clone \
15910                 would change the pointer on every call)",
15911            );
15912            assert_eq!(
15913                first,
15914                membros.as_slice(),
15915                "Caixa::membros must return :membros verbatim by borrow \
15916                 — got {first:?}, expected {membros:?}",
15917            );
15918        }
15919    }
15920
15921    // ── Caixa::contratos — outer top-level &[WitContract] composite-slice accessor ──
15922
15923    fn caixa_aplicacao_with_contratos(contratos: Vec<crate::aplicacao::WitContract>) -> Caixa {
15924        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15925        c.kind = CaixaKind::Aplicacao;
15926        c.contratos = contratos;
15927        c
15928    }
15929
15930    fn contrato_http_for_test(
15931        de: &str,
15932        para: &str,
15933        endpoint: &str,
15934    ) -> crate::aplicacao::WitContract {
15935        crate::aplicacao::WitContract {
15936            de: de.into(),
15937            para: para.into(),
15938            wit: "wasi:http/proxy".into(),
15939            endpoint: Some(endpoint.into()),
15940            subject: None,
15941            slot: None,
15942        }
15943    }
15944
15945    #[test]
15946    fn contratos_returns_contratos_slice_verbatim_across_permutations() {
15947        // The canonical per-`Caixa` `:contratos` M3 mesh-slot outer-
15948        // composite `&[WitContract]`-return slice-shape pin:
15949        // [`Caixa::contratos`] must return the `:contratos` typed
15950        // `Vec<WitContract>` verbatim as a `&[WitContract]` slice-view
15951        // over the same backing buffer the raw
15952        // `self.contratos.as_slice()` field access borrows from,
15953        // element-equal across every representative fixture in the
15954        // accept-set — `[]` (the "no contracts declared" arm every
15955        // non-`Aplicacao`-kind `defcaixa` carries by
15956        // `#[serde(default)]` and every leaf-Aplicacao with a single
15957        // member carries), a canonical single-edge fixture (the
15958        // minimal directed-graph shape: one HTTP-shape `(cart → catalog)`
15959        // edge), and a canonical multi-edge fixture with three distinct
15960        // edges (the checkout-shape Aplicacao's HTTP-fan pattern:
15961        // `(cart → catalog)`, `(cart → pricing)`, `(cart → auth)`).
15962        //
15963        // Pins against a future silent detour that returned an owned
15964        // `Vec<WitContract>` (which would type-check but silently clone
15965        // on every accessor call, breaking the zero-cost projection
15966        // every peer sibling slice accessor carries), an axis-shuffled
15967        // projection (a future detour that reordered edges through the
15968        // accessor would silently split the paired
15969        // [`crate::StandardLayout::verify`] per-Aplicacao gate's
15970        // traversal input from the peer [`Self::aplicacao_view`] fold-
15971        // in path's clone-order input, since every canonical
15972        // `caixa-mesh` renderer's per-`(:de, :para)` adjacency-list
15973        // seed dispatch reads the edge set through the same slice),
15974        // or a reference to an operator-resolved overlay (the future
15975        // per-cluster `:contratos-overrides` slot — its resolution
15976        // must land at exactly this accessor body, not silently divert
15977        // the raw slot away from a second consumer).
15978        //
15979        // Fourth outer top-level [`Caixa`] `&[Composite]`-return slice
15980        // accessor pin on the substrate primitive for M2 / M3 typed-
15981        // slot vec-carry axes — closes the outer-`Caixa`
15982        // `&[Composite]` composite-slice sub-family the sibling M2
15983        // `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
15984        // (2a1f907) and
15985        // `children_returns_children_slice_verbatim_across_permutations`
15986        // (c17b51e) pins opened and the M3
15987        // `membros_returns_membros_slice_verbatim_across_permutations`
15988        // (0f26987) pin folded on, closing the outer-`Caixa` M3 mesh-
15989        // slot arm of the composite-slice sub-family. Peer at the outer
15990        // altitude of the closed inner-
15991        // [`crate::AplicacaoSpec::contratos`] (0dcc926) accessor on the
15992        // same MESH-COMPOSITION per-Aplicacao contract-list axis.
15993        let fixtures: Vec<Vec<crate::aplicacao::WitContract>> = vec![
15994            vec![],
15995            vec![contrato_http_for_test("cart", "catalog", "/items")],
15996            vec![
15997                contrato_http_for_test("cart", "catalog", "/items"),
15998                contrato_http_for_test("cart", "pricing", "/price"),
15999                contrato_http_for_test("cart", "auth", "/whoami"),
16000            ],
16001        ];
16002        for contratos in fixtures {
16003            let c = caixa_aplicacao_with_contratos(contratos.clone());
16004            assert_eq!(
16005                c.contratos(),
16006                contratos.as_slice(),
16007                "Caixa::contratos must return :contratos verbatim \
16008                 (got {:?}, expected {contratos:?})",
16009                c.contratos(),
16010            );
16011            assert_eq!(
16012                c.contratos(),
16013                c.contratos.as_slice(),
16014                "Caixa::contratos must element-equal the raw \
16015                 `self.contratos.as_slice()` field access across every \
16016                 value in the Vec<WitContract> accept-set",
16017            );
16018            assert_eq!(
16019                c.contratos().is_empty(),
16020                c.contratos.is_empty(),
16021                "Caixa::contratos().is_empty() must byte-equal \
16022                 self.contratos.is_empty() — a presence-bit drift would \
16023                 silently split the paired Caixa::declared_mesh_slots \
16024                 mesh declared-slot enumerator's presence probe from \
16025                 the peer Caixa::aplicacao_view typed-view composer's \
16026                 fold-in path",
16027            );
16028        }
16029    }
16030
16031    #[test]
16032    fn declared_mesh_slots_contratos_arm_routes_through_accessor() {
16033        // Composition pin: [`Caixa::declared_mesh_slots`]'s `:contratos`
16034        // presence-probe arm must key off [`Caixa::contratos`], not the
16035        // raw `!self.contratos.is_empty()` field-probe. Structurally: a
16036        // `Caixa { contratos: vec![WitContract { .. }], .. }` must push
16037        // `M3_AUTHOR_KEY_CONTRATOS` onto the declared-slot list (the
16038        // presence bit is non-empty, so the mesh kind-coherence gate
16039        // must surface the slot as "declared"), and a `Caixa {
16040        // contratos: vec![], .. }` must NOT push the label (the "author
16041        // omitted the slot entirely" arm — the empty-slice partition
16042        // the serde-default folds onto). The pair jointly pins the
16043        // accessor + declared-slot enumerator composition: any future
16044        // silent detour that had the accessor collapse
16045        // `[WitContract { .. }]` to `[]` (a `.filter(|c| c.de() !=
16046        // "__reserved__")` projection) would silently absorb the
16047        // "declared but degenerate" arm at the accessor boundary and
16048        // the [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
16049        // coherence gate would silently accept a struct-literal
16050        // `Caixa` carrying the drift.
16051        //
16052        // Peer of the sibling
16053        // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
16054        // (2a1f907),
16055        // `declared_supervisor_slots_children_arm_routes_through_accessor`
16056        // (c17b51e), and
16057        // `declared_mesh_slots_membros_arm_routes_through_accessor`
16058        // (0f26987) composition pins on the M2 `:upgrade-from` /
16059        // `:children` / M3 `:membros` composite-slice arms — same "the
16060        // enumerator gate must route through the substrate-primitive
16061        // typed dispatch" discipline extended onto the M3 `:contratos`
16062        // composite-slice arm, closing the M3 mesh-slot arm of the
16063        // declared-slot enumerator's routing invariant on the
16064        // composite-slice inputs.
16065        let c = caixa_aplicacao_with_contratos(vec![contrato_http_for_test(
16066            "cart", "catalog", "/items",
16067        )]);
16068        let slots = c.declared_mesh_slots();
16069        assert!(
16070            slots.contains(&crate::render::M3_AUTHOR_KEY_CONTRATOS),
16071            "declared_mesh_slots must push M3_AUTHOR_KEY_CONTRATOS when \
16072             `:contratos` is non-empty — the accessor and the enumerator \
16073             gate must route through the same substrate-primitive \
16074             typed dispatch on the outer :contratos presence bit (got \
16075             slots={slots:?})",
16076        );
16077        let c = caixa_aplicacao_with_contratos(vec![]);
16078        let slots = c.declared_mesh_slots();
16079        assert!(
16080            !slots.contains(&crate::render::M3_AUTHOR_KEY_CONTRATOS),
16081            "declared_mesh_slots must NOT push M3_AUTHOR_KEY_CONTRATOS \
16082             when `:contratos` is empty — the author-omitted arm must \
16083             route through the accessor's empty-slice return unchanged \
16084             (got slots={slots:?})",
16085        );
16086    }
16087
16088    #[test]
16089    fn aplicacao_view_contratos_arm_routes_through_accessor() {
16090        // Composition pin: [`Caixa::aplicacao_view`]'s per-`:contratos`
16091        // fold-in arm must key off [`Caixa::contratos`], not the raw
16092        // `self.contratos.clone()` field-clone. Structurally: a `Caixa
16093        // { kind: Aplicacao, contratos: vec![WitContract { de: "cart",
16094        // .. }, WitContract { de: "pricing", .. }], .. }` must fold the
16095        // per-edge list through the accessor into the typed
16096        // [`crate::AplicacaoSpec`] view's `contratos` slot verbatim —
16097        // every entry the accessor surfaces must land in the view's
16098        // `contratos` slot in the same order. The pair jointly pins
16099        // the accessor + view-composer composition: a future silent
16100        // detour that had the accessor shuffle or drop an edge would
16101        // silently split the paired declared-slot enumerator's
16102        // presence bit from the typed-view composer's edge-list, a
16103        // two-consumer split at the enumerator and the view composer
16104        // far from the source `caixa.lisp`.
16105        //
16106        // Peer of the sibling
16107        // `aplicacao_view_membros_arm_routes_through_accessor`
16108        // (0f26987) composition pin on the M3 `:membros` outer-
16109        // `&[Composite]` composite-slice arm, closing the aplicacao-
16110        // view composer's routing invariant on the composite-slice
16111        // inputs at the outer altitude.
16112        let c = caixa_aplicacao_with_contratos(vec![
16113            contrato_http_for_test("cart", "catalog", "/items"),
16114            contrato_http_for_test("cart", "pricing", "/price"),
16115        ]);
16116        let view = c
16117            .aplicacao_view()
16118            .expect("Aplicacao kind must produce an aplicacao_view");
16119        assert_eq!(
16120            view.contratos(),
16121            c.contratos(),
16122            "aplicacao_view must fold Caixa::contratos verbatim into \
16123             AplicacaoSpec::contratos — the accessor and the view \
16124             composer must route through the same substrate-primitive \
16125             typed dispatch on the outer :contratos slice (got view \
16126             contratos={:?}, expected {:?})",
16127            view.contratos(),
16128            c.contratos(),
16129        );
16130    }
16131
16132    #[test]
16133    fn contratos_projects_slice_by_borrow() {
16134        // The by-borrow pin: [`Caixa::contratos`] returns `&[WitContract]`
16135        // by borrow — the returned slice borrows the underlying
16136        // `Vec<WitContract>` storage of the `:contratos` slot and the
16137        // accessor must not clone the backing `Vec` on every call.
16138        // Peer of the sibling outer top-level [`Caixa`] `&[T]`-return
16139        // by-borrow pins (`autores_projects_slice_by_borrow` b5d813f,
16140        // `etiquetas_projects_slice_by_borrow` 78c7d3c,
16141        // `bibliotecas_projects_slice_by_borrow` 8a36c23,
16142        // `exe_projects_slice_by_borrow` 65d9527,
16143        // `servicos_projects_slice_by_borrow` 611f78b,
16144        // `deps_projects_slice_by_borrow` ad34b4e,
16145        // `deps_dev_projects_slice_by_borrow` f7fd81e,
16146        // `upgrade_from_projects_slice_by_borrow` 2a1f907,
16147        // `children_projects_slice_by_borrow` c17b51e,
16148        // `membros_projects_slice_by_borrow` 0f26987) on the sibling
16149        // outer top-level [`Caixa`] scalar-element and composite-
16150        // element `&[T]` axes — closes the outer-`Caixa` M3 mesh-slot
16151        // composite-element `&[Composite]` axis on the by-borrow pin:
16152        // the accessor's returned slice must borrow from `&self` (the
16153        // returned reference's lifetime is tied to `&self`), and
16154        // calling the accessor twice on the same [`Caixa`] must yield
16155        // slices that are pointer-equal (the underlying byte-buffer is
16156        // the storage `Vec`'s allocation, not a fresh copy) as well as
16157        // value-equal (idempotent, no side effects on `&self`).
16158        //
16159        // Pins against a future silent detour that returned an owned
16160        // `Vec<WitContract>` (which would type-check but silently clone
16161        // on every call), a `&Vec<WitContract>` return (which would
16162        // leak the backing `Vec`'s grow/push/reserve surface no
16163        // downstream consumer reaches for), or a one-arm-only accessor
16164        // that returned a saturating value on some sentinel input.
16165        for contratos in [
16166            vec![],
16167            vec![contrato_http_for_test("cart", "catalog", "/items")],
16168            vec![
16169                contrato_http_for_test("cart", "catalog", "/items"),
16170                contrato_http_for_test("cart", "pricing", "/price"),
16171            ],
16172        ] {
16173            let c = caixa_aplicacao_with_contratos(contratos.clone());
16174            let first = c.contratos();
16175            let second = c.contratos();
16176            assert_eq!(
16177                first, second,
16178                "Caixa::contratos must be idempotent — two successive \
16179                 calls on the same &self must return the same \
16180                 &[WitContract]",
16181            );
16182            assert_eq!(
16183                first.as_ptr(),
16184                second.as_ptr(),
16185                "Caixa::contratos must borrow the underlying \
16186                 Vec<WitContract> storage — two successive calls must \
16187                 return slices with the same backing pointer (a fresh \
16188                 Vec<WitContract> clone would change the pointer on \
16189                 every call)",
16190            );
16191            assert_eq!(
16192                first,
16193                contratos.as_slice(),
16194                "Caixa::contratos must return :contratos verbatim by \
16195                 borrow — got {first:?}, expected {contratos:?}",
16196            );
16197        }
16198    }
16199
16200    // ── drift-detection: Caixa top-level multi-word serde-derive-to-const identity ──
16201
16202    #[test]
16203    fn caixa_multi_word_serde_keys_match_lifted_top_level_key_consts() {
16204        // Load-bearing invariant: every multi-word top-level [`Caixa`]
16205        // serde-derived JSON key routes through a lifted `&'static str`
16206        // const. The Rust field names are `snake_case`
16207        // (`deps_dev` / `upgrade_from` / `max_restarts` /
16208        // `restart_window`); [`Caixa`]'s `#[serde(rename_all =
16209        // "camelCase")]` derive attribute maps each to the camelCase
16210        // byte-string the [`Caixa::to_lisp`] round-trip's
16211        // `serde_json::to_value(self)` step lands under before
16212        // `tatara_lisp::domain::json_to_sexp` re-projects the JSON keys
16213        // to the kebab-case `:deps-dev` / `:upgrade-from` /
16214        // `:max-restarts` / `:restart-window` author surface. Serialize
16215        // a fully-populated [`Caixa`] and pin that each canonical
16216        // byte-sequence appears verbatim in the JSON — a future
16217        // accidental `rename_all = "snake_case"` / `"kebab-case"` /
16218        // verbatim-field-name flip at the derive attribute (any of
16219        // which would silently break every [`Caixa::to_lisp`]
16220        // round-trip and the future M4 operator-side manifest ingest's
16221        // `Value::get(<key>)` navigation) surfaces here as a build-time
16222        // test failure at `manifest.rs`, not as an apply-time
16223        // `.get(<stale-canonical-const>)` returning `None` far from the
16224        // derive-attr drift's commit. Same discipline the sibling
16225        // `supervisor_spec_serde_keys_match_lifted_supervisor_key_consts`
16226        // (40cc4e5), `membro_serde_keys_match_lifted_membro_key_consts`
16227        // (ce80ca0), and `upgrade_from_entry_serde_keys_match_lifted_
16228        // m2_upgrade_from_key_consts` (36ffe65) pins established on the
16229        // sibling M2 supervision-tree, M3 [`Membro`] per-entry, and M2
16230        // [`UpgradeFromEntry`] per-entry axes — extended here to the
16231        // enclosing M0 [`Caixa`] top-level axis so the last of the four
16232        // multi-word top-level [`Caixa`] serde-derived JSON keys
16233        // (`depsDev`) joins the substrate's "one canonical byte-string
16234        // per typed serialized-key axis" discipline.
16235        use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
16236        use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
16237        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16238        c.deps_dev = vec![Dep::simple("tatara-check", "^0.1")];
16239        c.upgrade_from = vec![UpgradeFromEntry {
16240            from: "0.0.1".into(),
16241            instructions: vec![UpgradeInstruction::Restart],
16242        }];
16243        c.estrategia = Some(RestartStrategy::OneForOne);
16244        c.max_restarts = Some(3);
16245        c.restart_window = Some("60s".into());
16246        c.children = vec![ChildSpec {
16247            caixa: "child".into(),
16248            versao: "^0.1".into(),
16249            restart: RestartPolicy::Permanent,
16250        }];
16251        let json = serde_json::to_string(&c).unwrap();
16252        for key in [
16253            crate::render::CAIXA_KEY_DEPS_DEV,
16254            crate::render::M2_KEY_UPGRADE_FROM,
16255            crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
16256            crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
16257        ] {
16258            let quoted = format!("\"{key}\"");
16259            assert!(
16260                json.contains(&quoted),
16261                "serialized Caixa must carry the lifted top-level \
16262                 multi-word byte-sequence {quoted} verbatim in the JSON \
16263                 emission (got: {json})",
16264            );
16265        }
16266    }
16267
16268    #[test]
16269    fn caixa_top_level_multi_word_key_consts_are_pairwise_distinct() {
16270        // Cross-axis drift-detection pin: a future collapse of the four
16271        // canonical [`Caixa`] top-level multi-word byte-strings onto the
16272        // same value (e.g. an accidental copy-paste flip of
16273        // [`crate::render::CAIXA_KEY_DEPS_DEV`] to also read
16274        // `"upgradeFrom"`) would silently reroute every downstream
16275        // `Value::get(<key>)` probe on one axis onto the sibling axis's
16276        // top-level entry and pass every propagation-probe test that
16277        // expected only the stale axis's value. Peer of the sibling
16278        // four-way distinct pin on the `SUPERVISOR_KEY_*` tetrad
16279        // (40cc4e5) and the two-way pin on `MEMBRO_KEY_*` (ce80ca0).
16280        let all = [
16281            crate::render::CAIXA_KEY_DEPS_DEV,
16282            crate::render::M2_KEY_UPGRADE_FROM,
16283            crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
16284            crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
16285        ];
16286        for (i, a) in all.iter().enumerate() {
16287            for b in all.iter().skip(i + 1) {
16288                assert_ne!(
16289                    a, b,
16290                    "Caixa top-level multi-word key consts must be \
16291                     pairwise-distinct canonical byte-sequences — got \
16292                     `{a}` == `{b}`",
16293                );
16294            }
16295        }
16296    }
16297
16298    #[test]
16299    fn caixa_top_level_multi_word_key_consts_are_lower_camel_case_shape() {
16300        // Shape-pin: every [`Caixa`] top-level multi-word key const must
16301        // be a lowerCamelCase byte-sequence (no `snake_case`
16302        // underscores, no `kebab-case` hyphens, no leading colon, no
16303        // `PascalCase` leading capital, no whitespace / dots) — the
16304        // canonical shape the `#[serde(rename_all = "camelCase")]`
16305        // derive produces on [`Caixa`]. A future flip to a
16306        // non-camelCase attribute at the derive surfaces both here
16307        // (this test fails on the stale-constant shape) and at
16308        // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
16309        // (that test fails on the mismatch between const and derive).
16310        // Peer with `membro_key_consts_are_lower_camel_case_shape`
16311        // (ce80ca0) and `supervisor_key_consts_are_lower_camel_case_shape`
16312        // (40cc4e5) on the sibling per-entry / supervisor-tree axes.
16313        for key in [
16314            crate::render::CAIXA_KEY_DEPS_DEV,
16315            crate::render::M2_KEY_UPGRADE_FROM,
16316            crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
16317            crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
16318        ] {
16319            assert!(
16320                !key.is_empty(),
16321                "Caixa top-level multi-word key const must be non-empty \
16322                 (got {key:?})"
16323            );
16324            let first = key.chars().next().unwrap();
16325            assert!(
16326                first.is_ascii_lowercase(),
16327                "Caixa top-level multi-word key const must lead with an \
16328                 ASCII-lowercase byte (got {key:?}, leads with {first:?})",
16329            );
16330            assert!(
16331                key.chars().all(|c| c.is_ascii_alphanumeric()),
16332                "Caixa top-level multi-word key const must be \
16333                 ASCII-alphanumeric only — no `_` / `-` / `:` / `.` / \
16334                 whitespace (got {key:?})",
16335            );
16336        }
16337    }
16338
16339    #[test]
16340    fn caixa_key_deps_dev_pins_canonical_camel_case_byte_string() {
16341        // Scalar-value pin: the byte-string the
16342        // [`crate::render::CAIXA_KEY_DEPS_DEV`] const resolves to,
16343        // asserted verbatim. A future rebrand (`depsDev` → `devDeps`
16344        // matching Cargo's verbatim `dev-dependencies` axis, `depsDev`
16345        // → `depsTest` matching a hypothetical per-test-target
16346        // vocabulary flip) lands as an edit to exactly one const AND
16347        // one derive attribute — the sibling
16348        // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
16349        // pin already ties the const to the derive attribute, so a
16350        // rebrand that touches only one side of the pair fails at
16351        // caixa-core build time. Same "scalar-value pin per const"
16352        // discipline the sibling
16353        // `m2_top_level_author_key_consts_pin_canonical_kebab_case_labels`
16354        // (f49c8b0) and `contrato_key_consts_pin_canonical_camel_case_labels`
16355        // (ca463a4) pins carry on the peer M2 / M3 top-level slot axes.
16356        assert_eq!(crate::render::CAIXA_KEY_DEPS_DEV, "depsDev");
16357    }
16358
16359    #[test]
16360    fn caixa_key_deps_pins_canonical_byte_string() {
16361        // Scalar-value pin: the byte-string the
16362        // [`crate::render::CAIXA_KEY_DEPS`] const resolves to, asserted
16363        // verbatim. Peer of `caixa_key_deps_dev_pins_canonical_camel_case_byte_string`
16364        // on the two-list dep-graph serialized-key axis — the sibling
16365        // pin covers the multi-word `deps_dev → depsDev` camelCase
16366        // arm, this pin covers the single-word `deps → deps` no-op arm
16367        // (the [`crate::Caixa::deps`] field name carries no `_`, so the
16368        // `#[serde(rename_all = "camelCase")]` derive is a no-op on this
16369        // axis and the emitted JSON key equals the source-side field
16370        // name byte-for-byte). A future [`crate::Caixa::deps`] field
16371        // rename (`deps` → `dependencies` matching Cargo's verbatim
16372        // `[dependencies]` axis, `deps` → `runtime_deps` matching a
16373        // hypothetical per-runtime-target vocabulary flip) OR an added
16374        // `#[serde(rename = "…")]` explicit override lands as an edit
16375        // to exactly one const AND one derive-attr / field name — the
16376        // sibling `caixa_deps_serde_key_matches_lifted_caixa_key_deps`
16377        // pin ties the const to the emitted JSON key, so a rebrand
16378        // that touches only one side of the pair fails at caixa-core
16379        // build time.
16380        assert_eq!(crate::render::CAIXA_KEY_DEPS, "deps");
16381    }
16382
16383    #[test]
16384    fn caixa_deps_serde_key_matches_lifted_caixa_key_deps() {
16385        // Load-bearing invariant on the single-word `deps` top-level
16386        // axis: the byte-string [`crate::render::CAIXA_KEY_DEPS`] pins
16387        // must appear verbatim in the JSON [`Caixa::to_lisp`]'s
16388        // `serde_json::to_value(self)` step emits. Serialize a
16389        // populated [`Caixa`] whose `:deps` slot carries at least one
16390        // entry (the `#[serde(default)]` attribute on the field emits
16391        // an empty `[]` even without members, but a non-empty vec
16392        // additionally covers the codec's per-`Dep`-entry emission
16393        // path) and pin that `"deps"` appears verbatim in the JSON
16394        // emission — a future accidental `rename_all = "snake_case"` /
16395        // `"kebab-case"` flip at the derive attribute (or an added
16396        // `#[serde(rename = "…")]` explicit override on the field, or
16397        // a Rust field rename) would break every [`Caixa::to_lisp`]
16398        // round-trip and the future M4 operator-side manifest ingest's
16399        // `Value::get(CAIXA_KEY_DEPS)` navigation — surfaces here as a
16400        // build-time test failure at `manifest.rs`, not as an
16401        // apply-time `.get(<stale-canonical-const>)` returning `None`
16402        // far from the drift's commit. Peer of the sibling
16403        // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
16404        // multi-word pin on the same M0 [`Caixa`] top-level
16405        // serialized-key axis, extended here to the single-word arm
16406        // the multi-word test's `rename_all = "camelCase"` sweep can't
16407        // reach (single-word `deps → deps` is a no-op the multi-word
16408        // pin's `\"depsDev\"` / `\"upgradeFrom\"` / `\"maxRestarts\"` /
16409        // `\"restartWindow\"` byte-scan can never observe).
16410        let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16411        c.deps = vec![Dep::simple("caixa-core", "^0.1")];
16412        let json = serde_json::to_string(&c).unwrap();
16413        let quoted = format!("\"{}\"", crate::render::CAIXA_KEY_DEPS);
16414        assert!(
16415            json.contains(&quoted),
16416            "serialized Caixa must carry the lifted top-level `deps` \
16417             byte-sequence {quoted} verbatim in the JSON emission (got: \
16418             {json})",
16419        );
16420    }
16421
16422    #[test]
16423    fn caixa_dep_graph_two_list_key_consts_are_pairwise_distinct() {
16424        // Cross-axis drift-detection pin on the two-list dep-graph
16425        // renderer-side wire-key axis: a future collapse of the
16426        // canonical [`crate::render::CAIXA_KEY_DEPS`] /
16427        // [`crate::render::CAIXA_KEY_DEPS_DEV`] byte-strings onto the
16428        // same value (e.g. an accidental copy-paste flip of
16429        // `CAIXA_KEY_DEPS_DEV` to also read `"deps"`) would silently
16430        // reroute every downstream `Value::get(<key>)` probe on one
16431        // axis onto the sibling axis's dep-list and pass every
16432        // propagation-probe test that expected only the stale axis's
16433        // value — a dev-only dep would land in the runtime closure at
16434        // publish time, or a runtime dep would be excluded from the
16435        // published lacre. Peer of the sibling four-way distinct pin
16436        // on the top-level multi-word tetrad
16437        // (`caixa_top_level_multi_word_key_consts_are_pairwise_distinct`)
16438        // and the two-way pin on the sibling
16439        // [`DEP_AUTHOR_KEY_DEPS`] / [`DEP_AUTHOR_KEY_DEPS_DEV`]
16440        // author-facing arm (4da6fba's test), extended here to the
16441        // renderer-side wire-key arm of the same two-list dep-graph
16442        // axis so both halves of the "one canonical byte-string per
16443        // typed axis per (author, wire)" grid carry the same
16444        // distinct-ness discipline.
16445        assert_ne!(
16446            crate::render::CAIXA_KEY_DEPS,
16447            crate::render::CAIXA_KEY_DEPS_DEV,
16448            "CAIXA_KEY_DEPS and CAIXA_KEY_DEPS_DEV must be distinct \
16449             canonical byte-sequences on the two-list dep-graph \
16450             renderer-side wire-key axis"
16451        );
16452    }
16453}