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
277/// Substrate-canonical universal-axis per-[`Caixa`] `:licenca` SPDX-shaped
278/// license-expression fallback for the `Option<String>` `:licenca` slot —
279/// the `"MIT"` SPDX identifier every [`caixa-helm`]-rendered
280/// `lareira-<nome>` Helm chart's `README.md` `## License` section folds an
281/// author-omitted (`None`) `:licenca` slot through, extracted as a typed
282/// `pub const` so every substrate-side consumer that resolves "what license
283/// scalar does an author-omitted `:licenca` degrade onto?" reaches for
284/// exactly one substrate-primitive `&'static str`.
285///
286/// The `:licenca` fallback axis has one production consumer today — the
287/// [`caixa-helm`] `build_readme` fold at `caixa-helm/src/lib.rs`'s
288/// `caixa.licenca().unwrap_or(CAIXA_LICENCA_DEFAULT)` `README.md`
289/// `## License` section body — with three sibling caixa-core sites that
290/// cite the `"MIT"` fallback in prose (this crate's [`Caixa::licenca`]
291/// accessor's docstring, [`Self::validate_licenca`]'s docstring, and the
292/// [`ManifestError::LicencaEmpty`] `#[error]` template's user-facing text)
293/// all quoting the exact byte-string a future substrate-side rebrand of the
294/// fallback (a tightening to `"Apache-2.0"` as the substrate absorbs the
295/// wasm-component-model conventions the `wasi:*` WIT worlds already carry,
296/// a per-cluster license-default overlay the M4 CR materializer resolves
297/// per-CR, a promotion to the plain `Option<String>` byte-string into a
298/// richer `SpdxExpression` enum once the SPDX-expression parser lands per
299/// [`Self::validate_licenca`]'s docstring roadmap) would silently split
300/// against — the caixa-helm renderer would emit the new byte, the
301/// docstrings would still cite the prior byte, and every author who reads
302/// the accessor docstring before authoring would file a fresh
303/// `:licenca "MIT"` verbatim rather than defer to the substrate default,
304/// with the drift surfacing at chart-README-audit time far from the
305/// substrate rebrand commit.
306///
307/// Prior to this lift the sole production emitter (`build_readme`) carried
308/// an inline `"MIT"` byte literal at
309/// `caixa-helm/src/lib.rs:1018`'s `.unwrap_or("MIT")` fallback arm — one
310/// occurrence of the same load-bearing per-`Caixa` universal-axis
311/// SPDX-shaped license-expression convention as the four sibling caixa-core
312/// docstring citations, drift-prone by construction ahead of the second
313/// occurrence the future M4 `mesh.pleme.io/v1alpha1/Aplicacao` CR
314/// materializer's per-Aplicacao registry-annotation synthesis (the
315/// [`Self::validate_licenca`] roadmap already names the `Chart.yaml
316/// annotations["artifacthub.io/license"]` axis every registry-facing chart
317/// carries as the second consumer) will surface.
318///
319/// The `"MIT"` value pins the canonical CAIXA-SDLC §I license scaffold
320/// every `feira init`-emitted [`Self::template`] carries verbatim
321/// (`:licenca "MIT"`) and every substrate-side renderer fixture
322/// ([`caixa-helm`]'s `sample_caixa`, [`caixa-flux`]'s renderer fixtures,
323/// [`caixa-mesh`]'s renderer fixtures) seeds by construction, matching the
324/// pleme-io repo `LICENSE` header this workspace itself ships under. The
325/// alternatives an author declares explicitly (compound SPDX expressions
326/// like `"Apache-2.0 OR MIT"`, permissive-family peers like
327/// `"Apache-2.0"` / `"BSD-3-Clause"`, license-with-exception forms like
328/// `"Apache-2.0 WITH LLVM-exception"`) express deliberate license postures
329/// an author declares explicitly, never a posture an author-omitted slot
330/// should silently assume by default.
331///
332/// Lifted as a typed `pub const` so the substrate's chosen license
333/// fallback has exactly one source of truth on the `:licenca` fallback
334/// axis, on the same substrate-primitive lift discipline the peer
335/// per-`Caixa` load-bearing-scalar constants
336/// ([`crate::version::DEFAULT_PUBLISH_TAG_PREFIX`],
337/// [`crate::version::DEFAULT_GIT_REMOTE`],
338/// [`crate::version::DEFAULT_PLEME_GIT_ORG`]) already carry on the sibling
339/// per-`Caixa` universal-axis publish-side convention surface, and the
340/// same discipline the sibling M2 per-supervisor default set carries
341/// end-to-end ([`crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT`],
342/// [`crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT`],
343/// [`crate::supervisor::SUPERVISOR_RESTART_WINDOW_DEFAULT`],
344/// [`crate::supervisor::SUPERVISOR_CHILD_RESTART_DEFAULT`]) and the M3
345/// per-`:placement` default set already carries
346/// ([`crate::aplicacao::PLACEMENT_ESTRATEGIA_DEFAULT`]) on the paired
347/// M2 / M3 typed-slot-default axes. First typed default on the outer
348/// top-level [`Caixa`] universal-axis surface to converge onto the
349/// substrate-primitive-lift discipline the M2 / M3 typed-slot families
350/// already carry.
351pub const CAIXA_LICENCA_DEFAULT: &str = "MIT";
352
353impl Caixa {
354 /// Parse a `caixa.lisp` source string to a typed `Caixa`.
355 ///
356 /// Classifies the dialect **before** parsing. A `(defcaixa …)` of another
357 /// declaration is [`LeituraError::DialetoEstrangeiro`], naming what it is
358 /// and who reads it, instead of an unknown-keyword rejection that reads as
359 /// "your manifest is broken".
360 ///
361 /// The ordering is load-bearing. Handing a foreign dialect to the derive
362 /// first and interpreting the failure afterwards would mean guessing from
363 /// an error message, and the guess would be wrong for every file whose
364 /// first unknown slot happens to be one both schemas could plausibly carry.
365 pub fn from_lisp(src: &str) -> Result<Self, LeituraError> {
366 use tatara_lisp::domain::TataraDomain;
367 let forms = tatara_lisp::read(src).map_err(LeituraError::Leitura)?;
368 let first = forms.first().ok_or(crate::dialeto::DialetoError::Vazio)?;
369
370 // Route the foreign-dialect rejection gate through the lifted
371 // [`crate::dialeto::CaixaDialeto::is_molde_family`] (e9d2315)
372 // typed predicate rather than the pre-lift hand-rolled three-arm
373 // `match { Pacote => {}, Desconhecido => {}, foreign => Err(…) }`
374 // literal — the `defmolde` declaration-family partition (the two-
375 // arity closure of [`crate::dialeto::CaixaDialeto::Molde`] and
376 // [`crate::dialeto::CaixaDialeto::MoldePosicional`], the two arms
377 // whose sibling [`crate::dialeto::CaixaDialeto::palavra_canonica`]
378 // projection already collapses onto `"defmolde"` and whose sibling
379 // [`crate::dialeto::CaixaDialeto::consumidor`] projection already
380 // collapses onto `"pleme-doc-gen"`) resolves through one dispatch
381 // on the substrate primitive. `Pacote` (the tatara-lisp package
382 // manifest this derive can parse) and `Desconhecido` (deliberately
383 // falls through to the derive rather than short-circuiting: a
384 // `(defcaixa …)` matching neither schema is most likely a genuine
385 // package manifest with a typo in `:nome`, and the derive's
386 // diagnostic — which names the offending keyword and suggests the
387 // nearest slot — is far better than anything this classifier
388 // could say) both return `false` from `is_molde_family()` and fall
389 // through to the derive. Only the typed dialect flows into the
390 // error — the three user-facing projections (canonical keyword,
391 // description, consumer) are read at Display time through
392 // [`crate::dialeto::CaixaDialeto`]'s own accessors, so the
393 // variant cannot carry a snapshot that drifts from
394 // [`crate::dialeto::CaixaDialeto::palavra_canonica`] /
395 // `descricao` / `consumidor`. A future fifth dialect the
396 // [`crate::dialeto`] module doc's "third dialect" hazard
397 // actualises that belongs to the `defmolde` family lands one
398 // match arm at [`crate::dialeto::CaixaDialeto::is_molde_family`]
399 // and this gate picks up the new arm by construction — the pre-
400 // lift wildcard `foreign =>` was compile-time-anonymous and would
401 // silently absorb any hypothetical fifth `defcaixa`-family arm as
402 // foreign; routing the partition through the typed predicate
403 // closes both drift surfaces.
404 let dialeto = crate::dialeto::classify_form(first)?;
405 if dialeto.is_molde_family() {
406 return Err(LeituraError::DialetoEstrangeiro { dialeto });
407 }
408
409 Self::compile_from_sexp(first).map_err(LeituraError::Leitura)
410 }
411
412 /// Register `Caixa` with the global tatara-lisp domain registry so
413 /// `defcaixa` is dispatchable from any tatara-lisp binary that seeds
414 /// the registry (e.g. `tatara-check`).
415 ///
416 /// Returns the typed [`tatara_lisp::KeywordCollision`] on the second
417 /// (and every subsequent) call in the same process — one keyword,
418 /// one type, per process is a hard invariant of the upstream
419 /// registry, and a caller that hits it must fix its crate graph
420 /// rather than swallowing the error. Peer of the sibling per-crate
421 /// `register()` entry points at `caixa-flake/src/flake.rs`,
422 /// `caixa-fmt/src/lisp_config.rs`, `caixa-lacre/src/lock.rs`,
423 /// `caixa-lint/src/lisp_config.rs`, `caixa-resolver/src/lisp_config.rs`
424 /// — every substrate crate that owns a tatara-lisp keyword now
425 /// propagates the same typed error verbatim, so a downstream binary
426 /// that seeds the registry (`tatara-check`, the future LSP) reaches
427 /// for one shape at every call site.
428 ///
429 /// # Errors
430 ///
431 /// [`tatara_lisp::KeywordCollision`] when a peer type has already
432 /// claimed the `defcaixa` keyword in this process.
433 pub fn register() -> Result<(), tatara_lisp::KeywordCollision> {
434 tatara_lisp::domain::register::<Self>()
435 }
436
437 /// Substrate-canonical per-`Caixa` `:licenca` SPDX-expression scalar
438 /// accessor every consumer of the top-level manifest's license axis
439 /// keys off — returns the author-declared `:licenca` byte-string
440 /// verbatim as an `Option<&str>`, borrowed from the typed slot's own
441 /// `Option<String>` storage. `None` when the slot is absent (the
442 /// canonical "omit to defer to the caixa-helm renderer's `MIT`
443 /// fallback" shape [`Self::validate_licenca`] documents at
444 /// caixa-core/src/manifest.rs:1560; the peer [`caixa-helm`]
445 /// `build_readme` fold at caixa-helm/src/lib.rs:962 reads this
446 /// predicate too, so an authored-but-unset `:licenca` round-trips to
447 /// a rendered `lareira-<nome>` chart's `README.md` `## License`
448 /// section structurally identical to one that omits the slot).
449 ///
450 /// The `:licenca` slot carries the universal-axis SPDX-expression
451 /// license identifier every kind of caixa emits under (CAIXA-SDLC
452 /// §I — the author-facing surface every `defcaixa` form supplies) —
453 /// the typed slot's `Option<String>` accept-set (empty-string
454 /// rejected through [`ManifestError::LicencaEmpty`], SPDX-alphabet-
455 /// invalid rejected through [`ManifestError::LicencaInvalid`]) maps
456 /// onto the `lareira-<nome>` Helm chart's `README.md` `## License`
457 /// section (caixa-helm/src/lib.rs:962) and (through future
458 /// tightening documented at [`Self::validate_licenca`]) the
459 /// Chart.yaml `annotations["artifacthub.io/license"]` axis every
460 /// registry-facing chart carries. Every downstream consumer that
461 /// reads the license byte-string keys off this scalar (the
462 /// [`Self::validate_licenca`] empty-arm + SPDX-shape gate that
463 /// routes through `self.licenca.as_deref()`, the caixa-helm
464 /// `build_readme` `unwrap_or_else(|| "MIT".into())` fold that keys
465 /// the fallback off the `Option::is_none()` arm, every future
466 /// per-`Caixa` registry-facing renderer the CAIXA-SDLC §I roadmap
467 /// acknowledges).
468 ///
469 /// Prior to this lift the `.licenca` field was accessed inline at
470 /// two production sites — [`Self::validate_licenca`]'s
471 /// `self.licenca.as_deref()` empty-and-shape gate binding and the
472 /// caixa-helm `build_readme` `caixa.licenca.clone().unwrap_or_else(||
473 /// "MIT".into())` `README.md` `## License` fold — two open-coded
474 /// field-accesses that expressed no compile-time link back to the
475 /// typed slot. A future extension of the `:licenca` axis to a
476 /// richer author surface — a per-`:licenca` structured SPDX
477 /// expression parser + license-id allowlist (the future tightening
478 /// [`Self::validate_licenca`]'s docstring acknowledges), a
479 /// per-cluster license-default overlay the M4 CR materializer
480 /// resolves per-CR (the "cluster policy pins `Apache-2.0` for every
481 /// unlisted caixa" arm), a promotion of the plain
482 /// `Option<String>` byte-string to a richer `SpdxExpression` enum
483 /// once the SPDX-expression parser lands — would have had to be
484 /// threaded through both open-coded copies in lockstep or the
485 /// validate gate and the caixa-helm emit path would silently
486 /// disagree on which license a given [`Caixa`] resolves to (an
487 /// author's `:licenca "MIT OR Apache-2.0"` would satisfy validate
488 /// while the emit path silently rendered a stale `MIT` fallback,
489 /// or vice versa). Lifting the resolution to a typed method on the
490 /// substrate primitive means every downstream consumer of the
491 /// caixa's per-`Caixa` license surface reaches for exactly one
492 /// typed dispatch — the resolver's accept-set migrates as a unit
493 /// on any future axis addition.
494 ///
495 /// First `Option<&str>`-return top-level [`Caixa`] scalar accessor —
496 /// opens the "outer [`Caixa`] `Option<&str>` scalar" projection
497 /// pattern the sibling per-`Caixa` `:descricao` / `:repositorio` /
498 /// `:edicao` future lifts fold on. Same "one typed dispatch on the
499 /// substrate primitive, thin projections at each consumer"
500 /// discipline the peer per-`:placement` [`crate::aplicacao::Placement::shard_key`]
501 /// (7cd2a28) / [`crate::aplicacao::Placement::affinity`] (74ec2d3)
502 /// / per-`:contratos` [`crate::aplicacao::WitContract::endpoint`]
503 /// (7020470) / [`crate::aplicacao::WitContract::subject`] (90de675)
504 /// / [`crate::aplicacao::WitContract::slot`] (ed22b66)
505 /// `Option<&str>`-return accessors carry on the sibling M2 / M3
506 /// typed-slot atom axes, extended here to the outer top-level
507 /// `Caixa` universal-axis surface. Named `licenca()` to match the
508 /// storage field's name; the accessor's identity maps onto the
509 /// canonical CAIXA-SDLC §I vocabulary the slot's docstring already
510 /// carries.
511 #[must_use]
512 pub const fn licenca(&self) -> Option<&str> {
513 match &self.licenca {
514 Some(s) => Some(s.as_str()),
515 None => None,
516 }
517 }
518
519 /// Substrate-canonical per-`Caixa` `:repositorio` git-repo-URL scalar
520 /// accessor every consumer of the top-level manifest's homepage /
521 /// source-of-truth axis keys off — returns the author-declared
522 /// `:repositorio` byte-string verbatim as an `Option<&str>`, borrowed
523 /// from the typed slot's own `Option<String>` storage. `None` when
524 /// the slot is absent (the canonical "omit to defer to the renderer's
525 /// per-target placeholder" shape — [`caixa-helm`]'s `ChartYaml.home`
526 /// carries the `Option<String>` through verbatim so an author-omitted
527 /// `:repositorio` renders a `Chart.yaml` without a `home:` field
528 /// (`skip_serializing_if = "Option::is_none"`), while [`caixa-flux`]'s
529 /// `ClusterBundleOpts::for_caixa` folds the omitted slot through a
530 /// `format!("https://github.com/{DEFAULT_PLEME_GIT_ORG}/{nome}")`
531 /// fallback derived from `caixa.nome`).
532 ///
533 /// The `:repositorio` slot carries the universal-axis git-repo-URL
534 /// homepage identifier every kind of caixa emits under (CAIXA-SDLC
535 /// §I — the author-facing surface every `defcaixa` form supplies) —
536 /// the typed slot's `Option<String>` accept-set (empty-string
537 /// rejected through [`ManifestError::RepositorioEmpty`], git-repo-URL-
538 /// shape-invalid rejected through [`ManifestError::RepositorioInvalid`]
539 /// past the shared [`crate::render::is_git_repo_url`] predicate the
540 /// peer per-`:deps :fonte :repo` axis also routes through) maps onto
541 /// four load-bearing downstream consumers:
542 ///
543 /// - [`Self::validate_repositorio`]'s empty-arm + shape-predicate
544 /// gate binding at caixa-core/src/manifest.rs:1456 — the
545 /// universal-axis identity gate wired at caixa-build time.
546 /// - [`caixa-helm`]'s `build_chart_yaml` `ChartYaml.home` fold at
547 /// caixa-helm/src/lib.rs:840 — the rendered `lareira-<nome>`
548 /// Helm chart's `Chart.yaml` `home:` field, which every registry
549 /// that ingests the chart (ArtifactHub, chartmuseum,
550 /// `helm search repo`) surfaces as the chart's canonical source-
551 /// of-truth link.
552 /// - [`caixa-helm`]'s `build_readme` `## Source` fold at
553 /// caixa-helm/src/lib.rs:957 — the rendered `lareira-<nome>`
554 /// chart's `README.md` header link back to the source repo,
555 /// which every author who inspects the rendered chart bundle
556 /// lands at.
557 /// - [`caixa-flux`]'s `ClusterBundleOpts::for_caixa`
558 /// `GitRepository.spec.url` fold at caixa-flux/src/lib.rs:2006 —
559 /// the rendered `GitRepository` CR's `spec.url` field, which
560 /// FluxCD's `source-controller` polls to reconcile the caixa's
561 /// manifest bundle from git.
562 ///
563 /// Prior to this lift the `.repositorio` field was accessed inline
564 /// at four production sites — [`Self::validate_repositorio`]'s
565 /// `self.repositorio.as_deref()` empty-and-shape gate binding, the
566 /// caixa-helm `build_chart_yaml` `caixa.repositorio.clone()`
567 /// `Chart.yaml` `home:` field fold, the caixa-helm `build_readme`
568 /// `caixa.repositorio.clone().unwrap_or_else(|| caixa.nome.clone())`
569 /// `README.md` `## Source` fold, and the caixa-flux
570 /// `ClusterBundleOpts::for_caixa`
571 /// `caixa.repositorio.clone().unwrap_or_else(|| format!(...))`
572 /// `GitRepository.spec.url` fold — four open-coded field-accesses
573 /// that expressed no compile-time link back to the typed slot. A
574 /// future extension of the `:repositorio` axis to a richer author
575 /// surface — a per-`:repositorio` structured
576 /// [`crate::render::GitRepoUrl`]-shaped scheme+host+path parse
577 /// (the future tightening [`Self::validate_repositorio`]'s
578 /// docstring anticipates alongside the peer per-`:deps :fonte
579 /// :repo` axis), a per-cluster repo-mirror overlay the M4 CR
580 /// materializer resolves per-CR (the "cluster policy rewrites
581 /// `github:pleme-io/...` to `git.internal/mirror/pleme-io/...`"
582 /// arm the private-registry story acknowledges), a promotion of
583 /// the plain `Option<String>` byte-string to a richer
584 /// `RepoUrl` enum discriminated on scheme — would have had to be
585 /// threaded through all four open-coded copies in lockstep or the
586 /// validate gate and the three emit paths would silently disagree
587 /// on which URL a given [`Caixa`] resolves to (an author's
588 /// `:repositorio "github:pleme-io/checkout"` would satisfy validate
589 /// while one of the emit paths silently rendered a stale URL, or
590 /// vice versa). Lifting the resolution to a typed method on the
591 /// substrate primitive means every downstream consumer of the
592 /// caixa's per-`Caixa` repo-URL surface reaches for exactly one
593 /// typed dispatch — the resolver's accept-set migrates as a unit on
594 /// any future axis addition.
595 ///
596 /// Second outer top-level [`Caixa`] `Option<&str>`-return scalar
597 /// accessor — sibling of [`Self::licenca`] (6d5bc28), the accessor
598 /// that opened the "outer [`Caixa`] `Option<&str>` scalar"
599 /// projection pattern this lift folds on. Same "one typed dispatch
600 /// on the substrate primitive, thin projections at each consumer"
601 /// discipline the peer per-`:placement`
602 /// [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
603 /// [`crate::aplicacao::Placement::affinity`] (74ec2d3) /
604 /// per-`:contratos` [`crate::aplicacao::WitContract::endpoint`]
605 /// (7020470) / [`crate::aplicacao::WitContract::subject`] (90de675)
606 /// / [`crate::aplicacao::WitContract::slot`] (ed22b66)
607 /// `Option<&str>`-return accessors carry on the sibling M2 / M3
608 /// typed-slot atom axes, extended here to the second outer top-level
609 /// `Caixa` universal-axis surface. Named `repositorio()` to match
610 /// the storage field's name; the accessor's identity maps onto the
611 /// canonical CAIXA-SDLC §I vocabulary the slot's docstring already
612 /// carries.
613 #[must_use]
614 pub const fn repositorio(&self) -> Option<&str> {
615 match &self.repositorio {
616 Some(s) => Some(s.as_str()),
617 None => None,
618 }
619 }
620
621 /// Substrate-canonical per-`Caixa` **resolved-git-repo-URL** composer —
622 /// returns the caixa's canonical git-source-of-truth URL as an owned
623 /// [`String`], author-declared `:repositorio` byte-string verbatim on
624 /// the `Some` arm and the substrate's canonical pleme-org github URL
625 /// fallback ([`crate::DEFAULT_PLEME_GIT_ORG`] and [`Self::nome`]
626 /// interpolated into `https://github.com/<org>/<nome>`) on the
627 /// `None` arm. Every substrate-side consumer that resolves
628 /// "which git URL does this caixa's source live at?" reaches for
629 /// exactly one typed dispatch on the substrate primitive — the raw
630 /// `caixa.repositorio().map(str::to_owned).unwrap_or_else(|| format!(
631 /// "https://github.com/{org}/{nome}", org = DEFAULT_PLEME_GIT_ORG,
632 /// nome = caixa.nome()))` open-coded composition every prior caller
633 /// re-derived collapses onto one canonical arm.
634 ///
635 /// Distinct from [`Self::repositorio`] (`Option<&str>`, exposes the
636 /// author-omitted / author-declared partition to the caller) — this
637 /// accessor is the **resolved** URL surface, folding the fallback in
638 /// at the substrate-primitive boundary. Every consumer that keys off
639 /// the `Option::is_none()` discriminator (a [`Chart.yaml`] `home:`
640 /// field emit that must omit the field entirely on an author-omitted
641 /// `:repositorio`, per the [`Self::repositorio`] docstring's
642 /// documented four-consumer list) reaches through the raw
643 /// [`Self::repositorio`] `Option<&str>` accessor by construction — the
644 /// resolved-URL composer sits alongside it as the second projection
645 /// on the same underlying `:repositorio` slot rather than replacing
646 /// the raw accessor.
647 ///
648 /// The fallback branch is the exact byte-image of the prior inline
649 /// [`caixa-flux::ClusterBundleOpts::for_caixa`] `git_url` composer at
650 /// caixa-flux/src/lib.rs:2080 — pinned by the sibling caixa-flux
651 /// byte-parity test
652 /// `cluster_bundle_opts_for_caixa_git_url_routes_through_canonical_git_url_accessor`
653 /// against a future implementation of this method that reordered the
654 /// `format!` template arguments, migrated the `<org>` segment to a
655 /// different constant (the [`crate::DEFAULT_PLEME_GIT_ORG`] axis a
656 /// future substrate-side git-org migration may split off), or
657 /// silently absorbed the empty-string arm (a hypothetical
658 /// `Some("") → fallback` collapse the raw [`Self::repositorio`]
659 /// accessor's docstring explicitly rejects on the sibling raw
660 /// accessor).
661 ///
662 /// Peer of the sibling per-`&Caixa`-axis composed helpers
663 /// [`caixa-flux::cluster_bundle_for_caixa`] (06d52d7) on the sibling
664 /// substrate-side renderer surface — same "close the composed
665 /// substrate-primitive at one canonical arm on the single-`&Caixa`
666 /// dispatch, converge every prior open-coded caller onto the arm"
667 /// discipline extended onto the resolved-git-URL projection of the
668 /// per-`Caixa` `:repositorio` axis. Owns per-call [`String`]
669 /// allocation on both arms (the `Some` arm's `str::to_owned` and the
670 /// `None` arm's `format!`) — the by-value return matches every
671 /// downstream consumer's field-fill shape (the caixa-flux
672 /// `ClusterBundleOpts::git_url: String` field, every future
673 /// `Chart.yaml` `home:` fold's `Option<String>` field-fill on the
674 /// `Some` arm).
675 #[must_use]
676 pub fn canonical_git_url(&self) -> String {
677 self.repositorio().map_or_else(
678 || {
679 format!(
680 "https://github.com/{org}/{nome}",
681 org = crate::DEFAULT_PLEME_GIT_ORG,
682 nome = self.nome(),
683 )
684 },
685 str::to_owned,
686 )
687 }
688
689 /// Substrate-canonical per-`Caixa` **resolved-publish-tag** composer —
690 /// returns the caixa's canonical Zig-style git-publish-tag as an owned
691 /// [`String`], derived by concatenating
692 /// [`crate::DEFAULT_PUBLISH_TAG_PREFIX`] with the typed
693 /// [`Self::versao`] byte-string on a single `format!` template.
694 /// Every substrate-side consumer that resolves "which git tag does this
695 /// caixa publish under?" reaches for exactly one typed dispatch on the
696 /// substrate primitive — the raw `format!("{prefix}{versao}", prefix =
697 /// caixa_core::DEFAULT_PUBLISH_TAG_PREFIX, versao = caixa.versao())`
698 /// open-coded composition every prior caller re-derived collapses onto
699 /// one canonical arm.
700 ///
701 /// Peer of the sibling [`Self::canonical_git_url`] (124f864) resolved-
702 /// git-URL composer on the paired per-`Caixa` git-remote axis — same
703 /// "close the composed substrate-primitive at one canonical arm on the
704 /// single-`&Caixa` dispatch, converge every prior open-coded caller
705 /// onto the arm" discipline extended from the resolved-URL projection
706 /// of the per-`Caixa` `:repositorio` axis onto the resolved-tag
707 /// projection of the per-`Caixa` `:versao` axis. The two accessors
708 /// jointly close the pair of scalars every `FluxCD` `GitRepository` CR
709 /// keys off (`spec.url` via [`Self::canonical_git_url`],
710 /// `spec.ref.tag` via [`Self::publish_tag`]) at the substrate primitive
711 /// — a downstream consumer that reaches through both accessors reads
712 /// the complete published-git-identity of a caixa through two typed
713 /// dispatches, not four open-coded field accesses.
714 ///
715 /// The reader-side (`caixa-flux::cluster_bundle` /
716 /// `ClusterBundleOpts::for_caixa`'s `git_ref` field, every future
717 /// per-cluster snapshot bundle emitter, the future M4
718 /// `mesh.pleme.io/v1alpha1/Aplicacao` CR materializer's tag-carrier
719 /// slot on the tatara `Process` intent) always resolves the tag under
720 /// the canonical [`crate::DEFAULT_PUBLISH_TAG_PREFIX`] prefix — this
721 /// method encodes that reader-side convention. The writer-side
722 /// (`caixa-feira`'s `feira publish` `--prefix` clap flag) allows the
723 /// operator to override the prefix at publish time; the two surfaces
724 /// intentionally sit on the "canonical default + operator override"
725 /// pair the sibling [`crate::DEFAULT_PUBLISH_TAG_PREFIX`] constant's
726 /// own docstring documents — a `feira publish --prefix release/`
727 /// override is the operator's explicit opt-out from the substrate
728 /// default, not a supported drift axis.
729 ///
730 /// The composition body is the exact byte-image of the prior inline
731 /// [`caixa-flux::ClusterBundleOpts::for_caixa`] `git_ref` composer at
732 /// caixa-flux/src/lib.rs:2105 — pinned by the sibling caixa-flux
733 /// byte-parity test
734 /// `cluster_bundle_opts_for_caixa_git_ref_routes_through_publish_tag_accessor`
735 /// against a future implementation of this method that reordered the
736 /// `format!` template arguments, migrated the `<prefix>` segment to a
737 /// different constant (the [`crate::DEFAULT_PUBLISH_TAG_PREFIX`] axis
738 /// a future Zig-style-tag rebrand may split off — the constant's own
739 /// docstring anticipates a substrate-side move to `release/<versao>`
740 /// or bare `<versao>` shapes once a sibling forge convention adopts a
741 /// slash-namespaced or bare-scalar form), interposed a canonicalization
742 /// pass on the `:versao` axis (a SemVer-2 build-metadata strip an OCI-
743 /// tag normalizer might apply once the M4 registry-alignment slot
744 /// lands), or silently absorbed an empty `:versao` arm (which cannot
745 /// occur past the [`Self::validate_versao`] gate but which a
746 /// hypothetical bypass on the accessor path must not silently paper
747 /// over).
748 ///
749 /// Owns per-call [`String`] allocation via the single `format!`
750 /// invocation — the by-value return matches every downstream
751 /// consumer's field-fill shape (the caixa-flux `GitRefSpec::Tag(String)`
752 /// variant's owned payload, every future `intent.aplicacao.tag: String`
753 /// field-fill on the M4 CR materializer's tag-carrier slot).
754 #[must_use]
755 pub fn publish_tag(&self) -> String {
756 format!(
757 "{prefix}{versao}",
758 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
759 versao = self.versao(),
760 )
761 }
762
763 /// Substrate-canonical per-`Caixa` **resolved-Helm-chart-name** composer
764 /// — returns the caixa's canonical `lareira-<nome>` per-Servico Helm
765 /// chart identity as an owned [`String`], derived by dispatching through
766 /// the substrate-canonical [`crate::lareira_chart_name`] helper against
767 /// the typed [`Self::nome`] byte-string. Every substrate-side consumer
768 /// that resolves "which Helm chart identity does this caixa render
769 /// under?" reaches for exactly one typed dispatch on the substrate
770 /// primitive — the raw `caixa_core::lareira_chart_name(caixa.nome())`
771 /// two-step compose every prior caller re-derived collapses onto one
772 /// canonical arm on the single-`&Caixa` dispatch.
773 ///
774 /// Peer of the sibling [`Self::canonical_git_url`] (124f864) resolved-
775 /// git-URL composer + [`Self::publish_tag`] (07e05b8) resolved-publish-
776 /// tag composer on the paired per-`Caixa` published-artifact-identity
777 /// axis — same "close the composed substrate-primitive at one canonical
778 /// arm on the single-`&Caixa` dispatch, converge every prior open-coded
779 /// caller onto the arm" discipline extended from the resolved-URL /
780 /// resolved-tag projections of the `:repositorio` / `:versao` axes onto
781 /// the resolved-chart-name projection of the `:nome` axis. The three
782 /// accessors jointly close the triple of scalars every per-Servico
783 /// deploy artifact keys off (git source URL via
784 /// [`Self::canonical_git_url`], git source tag via
785 /// [`Self::publish_tag`], per-Servico Helm chart identity via
786 /// [`Self::lareira_chart_name`]) at the substrate primitive — a
787 /// downstream consumer that reaches through all three reads the
788 /// complete deploy-artifact identity of a caixa through three typed
789 /// dispatches, not six open-coded compositions across three renderer
790 /// crates.
791 ///
792 /// The reader-side (three production sites at the time of the lift —
793 /// [`caixa-helm::render_chart_for_servico_with`]'s `ChartDir.name`
794 /// composer at caixa-helm/src/lib.rs:778, the peer
795 /// [`caixa-flux::cluster_bundle`]'s per-CR `chart_name` binding at
796 /// caixa-flux/src/lib.rs:2219, and
797 /// [`caixa-tatara::process_for_aplicacao`]'s `release_name`
798 /// composer at caixa-tatara/src/lib.rs:227, plus every future
799 /// per-Servico OCI publish emitter the CAIXA-SDLC §II
800 /// `caixa-publish.yml` reusable workflow's `skopeo push` step keys
801 /// off, the future per-cluster snapshot bundle emitter, the future
802 /// M4 `mesh.pleme.io/v1alpha1/Aplicacao` CR materializer's
803 /// per-member chart-carrier slot on the tatara `Process` intent) —
804 /// always resolves the chart name under the canonical
805 /// [`crate::LAREIRA_CHART_NAME_PREFIX`] prefix; this method encodes
806 /// that reader-side convention. The joint-length invariant the peer
807 /// [`Self::validate_nome_chart_name_budget`] gate enforces at
808 /// caixa-build time (author-declared `:nome` + fixed prefix ≤
809 /// [`crate::DNS_1123_LABEL_MAX_LEN`]) is verified on the input to
810 /// this composer by construction, so the produced `lareira-<nome>`
811 /// string is a valid Helm chart-name segment on every accept-set
812 /// input.
813 ///
814 /// The composition body is the exact byte-image of the prior inline
815 /// `caixa_core::lareira_chart_name(caixa.nome())` two-step form every
816 /// prior caller re-derived — pinned by the sibling caixa-helm /
817 /// caixa-flux / caixa-tatara byte-parity tests
818 /// `<crate>_lareira_chart_name_routes_through_caixa_accessor` against
819 /// a future implementation of this method that reordered the
820 /// composition arguments, migrated the `<prefix>` segment to a
821 /// different constant (the [`crate::LAREIRA_CHART_NAME_PREFIX`] axis a
822 /// future substrate-side chart-family rebrand may split off — the
823 /// constant's own docstring anticipates a substrate-side move once
824 /// the `lareira-` scoping intent outlives the family it names),
825 /// interposed a canonicalization pass on the `:nome` axis (a per-
826 /// registry namespace-qualification an M4 CR materializer might apply
827 /// per-CR — the "`pleme-io/checkout` vs `partner-org/checkout`
828 /// collision" arm the multi-tenant-registry story acknowledges), or
829 /// silently absorbed an empty `:nome` arm (which cannot occur past
830 /// the [`Self::validate_nome`] gate but which a hypothetical bypass
831 /// on the accessor path must not silently paper over).
832 ///
833 /// Owns per-call [`String`] allocation via the single
834 /// [`crate::lareira_chart_name`] `format!` invocation — the by-value
835 /// return matches every downstream consumer's field-fill shape (the
836 /// caixa-helm `ChartDir.name: String` field, the caixa-flux per-CR
837 /// `chart_name: String` binding, the caixa-tatara
838 /// `AplicacaoIntent.release_name: Option<String>` field-fill on the
839 /// `Some` arm).
840 #[must_use]
841 pub fn lareira_chart_name(&self) -> String {
842 crate::lareira_chart_name(self.nome())
843 }
844
845 /// Substrate-canonical per-`Caixa` **resolved-OCI-chart-ref** composer
846 /// — returns the caixa's canonical `oci://<registry>/lareira-<nome>`
847 /// per-Servico Helm chart OCI artifact reference as an owned
848 /// [`String`], derived by dispatching through the substrate-canonical
849 /// [`crate::oci_chart_ref`] helper (which itself composes
850 /// [`crate::OCI_SCHEME_PREFIX`] + the caller-supplied `registry` +
851 /// [`crate::lareira_chart_name`]-of-[`Self::nome`]) against the
852 /// caller-supplied `registry` and the typed [`Self::nome`] byte-string.
853 /// Every substrate-side consumer that resolves "which OCI chart
854 /// artifact does this caixa publish under, in this registry?" reaches
855 /// for exactly one typed dispatch on the substrate primitive — the raw
856 /// `caixa_core::oci_chart_ref(registry, caixa.nome())` two-step compose
857 /// every prior caller re-derived collapses onto one canonical arm on
858 /// the single-`(&Caixa, &str)` dispatch.
859 ///
860 /// Fourth member of the paired per-`Caixa` published-artifact-identity
861 /// axis alongside [`Self::canonical_git_url`] (124f864) /
862 /// [`Self::publish_tag`] (07e05b8) / [`Self::lareira_chart_name`]
863 /// (a8f0bee) — same "close the composed substrate-primitive at one
864 /// canonical arm on the single-`&Caixa` dispatch, converge every
865 /// prior open-coded caller onto the arm" discipline extended from the
866 /// resolved-URL / resolved-tag / resolved-chart-name projections of
867 /// the `:repositorio` / `:versao` / `:nome` axes onto the resolved-
868 /// OCI-ref projection over the paired `(registry, :nome)` inputs. The
869 /// four accessors jointly close the per-`Caixa` published-artifact-
870 /// identity surface every downstream consumer of a caixa's published
871 /// deploy artifacts keys off (git source URL via
872 /// [`Self::canonical_git_url`], git source tag via
873 /// [`Self::publish_tag`], per-Servico Helm chart identity via
874 /// [`Self::lareira_chart_name`], per-registry OCI chart artifact
875 /// reference via [`Self::oci_chart_ref`]) at the substrate primitive
876 /// — a downstream consumer that reaches through all four reads the
877 /// complete deploy-artifact identity of a caixa through four typed
878 /// dispatches, not eight open-coded compositions across four renderer
879 /// crates. The unique-signature dispatch (`(&Caixa, &str)` on this
880 /// method vs. `&Caixa` on the sibling three) reflects the extra input
881 /// axis this composer folds in: unlike the git-URL / git-tag / chart-
882 /// name axes (each derived purely from a `&Caixa`), the OCI-ref axis
883 /// pairs the caixa's per-`:nome` chart identity with the caller-
884 /// supplied per-registry authority segment, so the accessor threads
885 /// the registry byte-string through as a positional `&str`.
886 ///
887 /// The reader-side (one production site at the time of the lift —
888 /// [`caixa-tatara::process_for_aplicacao`]'s `derive_chart_ref` helper
889 /// at caixa-tatara/src/lib.rs:333 that composes the emitted
890 /// `AplicacaoIntent.chart_ref` scalar the tatara-reconciler feeds into
891 /// `helm install`, plus every future per-Servico OCI publish emitter
892 /// the CAIXA-SDLC §II `caixa-publish.yml` reusable workflow's
893 /// `skopeo push` step keys off, the future per-cluster snapshot bundle
894 /// emitter's per-CR `oci://…` field-fill on the M4 registry-alignment
895 /// slot, the future M4 `mesh.pleme.io/v1alpha1/Aplicacao` CR
896 /// materializer's per-member `chart_ref` slot on the tatara `Process`
897 /// intent, the `FluxCD` `HelmRelease` `spec.chart.spec.chart` field-fill
898 /// on the OCI-source path an M4 per-cluster registry-rewrite overlay
899 /// applies per-CR) — always resolves the OCI ref under the canonical
900 /// [`crate::OCI_SCHEME_PREFIX`] scheme prefix + the canonical
901 /// [`Self::lareira_chart_name`] chart-name segment; this method
902 /// encodes that reader-side convention.
903 ///
904 /// The composition body is the exact byte-image of the prior inline
905 /// `caixa_core::oci_chart_ref(registry, caixa.nome())` two-step form
906 /// every prior caller re-derived — pinned by the sibling caixa-tatara
907 /// byte-parity test
908 /// `derive_chart_ref_routes_through_caixa_oci_chart_ref_accessor`
909 /// against a future implementation of this method that reordered the
910 /// composition arguments, migrated the `<scheme>` segment to a
911 /// different constant (the [`crate::OCI_SCHEME_PREFIX`] axis a future
912 /// substrate-side registry-protocol rebrand may split off — the
913 /// constant's own docstring anticipates a substrate-side move once
914 /// Helm 3 / `FluxCD` introduce a successor scheme past `oci://`),
915 /// migrated the `<chart>` segment off the paired
916 /// [`crate::lareira_chart_name`] composer (a per-registry
917 /// namespace-qualification an M4 CR materializer might apply per-CR),
918 /// interposed a canonicalization pass on the `registry` axis (an OCI-
919 /// authority normalization once the M4 registry-alignment slot lands),
920 /// or silently absorbed an empty `:nome` arm (which cannot occur past
921 /// the [`Self::validate_nome`] gate but which a hypothetical bypass
922 /// on the accessor path must not silently paper over).
923 ///
924 /// Owns per-call [`String`] allocation via the single
925 /// [`crate::oci_chart_ref`] `format!` invocation — the by-value return
926 /// matches every downstream consumer's field-fill shape (the caixa-
927 /// tatara `AplicacaoIntent.chart_ref: String` field-fill, every
928 /// future `intent.aplicacao.chart_ref: String` field-fill on the M4
929 /// CR materializer's chart-ref-carrier slot, every future
930 /// `HelmRelease.spec.chart.spec.chart: String` field-fill on the OCI-
931 /// source path).
932 #[must_use]
933 pub fn oci_chart_ref(&self, registry: &str) -> String {
934 crate::oci_chart_ref(registry, self.nome())
935 }
936
937 /// Substrate-canonical per-`Caixa` `:descricao` free-form-prose
938 /// chart-description scalar accessor every consumer of the top-level
939 /// manifest's Chart.yaml `description:` axis keys off — returns the
940 /// author-declared `:descricao` byte-string verbatim as an
941 /// `Option<&str>`, borrowed from the typed slot's own
942 /// `Option<String>` storage. `None` when the slot is absent (the
943 /// canonical "omit to defer to the per-renderer `caixa.nome`-derived
944 /// fallback" shape — [`caixa-helm`]'s `build_chart_yaml` folds the
945 /// omitted slot through a `format!("Generated chart for caixa Servico
946 /// {}", caixa.nome)` fallback, [`caixa-helm`]'s `build_readme` folds
947 /// it through a `format!("caixa Servico {}", caixa.nome)` fallback,
948 /// and [`caixa-feira`]'s `render_flake` folds it through a
949 /// `format!("caixa {}", c.nome)` `flake.nix` `description = ""`
950 /// fallback — each derived from `caixa.nome` on the null-carrier arm).
951 ///
952 /// The `:descricao` slot carries the universal-axis free-form-prose
953 /// chart-description identifier every kind of caixa emits under
954 /// (CAIXA-SDLC §I — the author-facing surface every `defcaixa` form
955 /// supplies) — the typed slot's `Option<String>` accept-set
956 /// (empty-string rejected through [`ManifestError::DescricaoEmpty`],
957 /// chart-description-shape-invalid rejected through
958 /// [`ManifestError::DescricaoInvalid`] past the shared
959 /// [`crate::render::is_chart_description_shape`] predicate the peer
960 /// per-`Caixa` `:descricao` axis also routes through) maps onto four
961 /// load-bearing downstream consumers:
962 ///
963 /// - [`Self::validate_descricao`]'s empty-arm + shape-predicate
964 /// gate binding — the universal-axis identity gate wired at
965 /// caixa-build time.
966 /// - [`caixa-helm`]'s `build_chart_yaml` `ChartYaml.description`
967 /// `Chart.yaml` field fold — the rendered `lareira-<nome>` Helm
968 /// chart's `Chart.yaml` `description:` field, which
969 /// `apiVersion: v2` charts require non-empty (`helm lint` fires
970 /// `WARNING [chart.metadata.description]: description is required`
971 /// when absent) and which every registry that ingests the chart
972 /// (ArtifactHub, chartmuseum, `helm search repo`) surfaces as the
973 /// chart's canonical one-line prose descriptor.
974 /// - [`caixa-helm`]'s `build_readme` chart-`README.md` header fold
975 /// — the rendered `lareira-<nome>` chart's `README.md` prose
976 /// header directly beneath the `# <chart-name>` title, which
977 /// every author who inspects the rendered chart bundle lands at.
978 /// - [`caixa-feira`]'s `render_flake` `flake.nix` `description = ""`
979 /// top-level fold — the emitted `flake.nix`'s `description`
980 /// field, which every Nix consumer (`nix flake show`,
981 /// `nix flake metadata`, downstream flake-registry ingestors)
982 /// surfaces as the flake's canonical descriptor.
983 ///
984 /// Prior to this lift the `.descricao` field was accessed inline at
985 /// four production sites — [`Self::validate_descricao`]'s
986 /// `self.descricao.as_deref()` empty-and-shape gate binding, the
987 /// caixa-helm `build_chart_yaml`
988 /// `caixa.descricao.clone().unwrap_or_else(|| format!(...))`
989 /// `Chart.yaml` `description:` fold, the caixa-helm `build_readme`
990 /// `caixa.descricao.clone().unwrap_or_else(|| format!(...))`
991 /// `README.md` header fold, and the caixa-feira `render_flake`
992 /// `c.descricao.clone().unwrap_or_else(|| format!(...))` `flake.nix`
993 /// `description = ""` fold — four open-coded field-accesses that
994 /// expressed no compile-time link back to the typed slot. A future
995 /// extension of the `:descricao` axis to a richer author surface —
996 /// a per-`:descricao` locale-tagged multi-language descriptor map
997 /// (the "one caixa, N language-tagged prose descriptions" arm
998 /// author-tooling internationalization anticipates), a
999 /// per-registry-target length-and-shape overlay the M4 CR
1000 /// materializer resolves per-CR (the "ArtifactHub caps description
1001 /// at 512 bytes but the internal registry caps at 256" arm), a
1002 /// promotion of the plain `Option<String>` byte-string to a richer
1003 /// `ChartDescription` newtype guaranteeing the
1004 /// `is_chart_description_shape` predicate at the type level — would
1005 /// have had to be threaded through all four open-coded copies in
1006 /// lockstep or the validate gate and the three emit paths would
1007 /// silently disagree on which prose string a given [`Caixa`]
1008 /// resolves to (an author's
1009 /// `:descricao "Checkout flow orchestration."` would satisfy
1010 /// validate while one of the emit paths silently rendered a stale
1011 /// `caixa.nome`-derived fallback, or vice versa). Lifting the
1012 /// resolution to a typed method on the substrate primitive means
1013 /// every downstream consumer of the caixa's per-`Caixa`
1014 /// chart-description surface reaches for exactly one typed dispatch
1015 /// — the resolver's accept-set migrates as a unit on any future
1016 /// axis addition.
1017 ///
1018 /// Third outer top-level [`Caixa`] `Option<&str>`-return scalar
1019 /// accessor — sibling of [`Self::licenca`] (6d5bc28) and
1020 /// [`Self::repositorio`] (cc7332d), the accessors that opened the
1021 /// "outer [`Caixa`] `Option<&str>` scalar" projection pattern this
1022 /// lift folds on. Same "one typed dispatch on the substrate
1023 /// primitive, thin projections at each consumer" discipline the
1024 /// peer per-`:placement`
1025 /// [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
1026 /// [`crate::aplicacao::Placement::affinity`] (74ec2d3) /
1027 /// per-`:contratos` [`crate::aplicacao::WitContract::endpoint`]
1028 /// (7020470) / [`crate::aplicacao::WitContract::subject`] (90de675)
1029 /// / [`crate::aplicacao::WitContract::slot`] (ed22b66)
1030 /// `Option<&str>`-return accessors carry on the sibling M2 / M3
1031 /// typed-slot atom axes, extended here to the third outer top-level
1032 /// `Caixa` universal-axis surface. Named `descricao()` to match the
1033 /// storage field's name; the accessor's identity maps onto the
1034 /// canonical CAIXA-SDLC §I vocabulary the slot's docstring already
1035 /// carries. The one remaining universal `Option<String>` slot
1036 /// (`:edicao`) folds on this pattern next.
1037 #[must_use]
1038 pub const fn descricao(&self) -> Option<&str> {
1039 match &self.descricao {
1040 Some(s) => Some(s.as_str()),
1041 None => None,
1042 }
1043 }
1044
1045 /// Substrate-canonical per-`Caixa` `:edicao` language-edition scalar
1046 /// accessor every consumer of the top-level manifest's tatara-lisp
1047 /// edition-selector axis keys off — returns the author-declared
1048 /// `:edicao` byte-string verbatim as an `Option<&str>`, borrowed from
1049 /// the typed slot's own `Option<String>` storage. `None` when the
1050 /// slot is absent (the canonical "omit the slot to defer to the
1051 /// substrate's default edition" shape every existing
1052 /// [`caixa-resolver`] integration test fixture carries via
1053 /// `edicao: None` — see `caixa-resolver/tests/git_integration.rs`;
1054 /// the peer [`Self::validate_edicao`] gate is a no-op on the omitted
1055 /// arm by construction, so an author-omitted `:edicao` round-trips
1056 /// to a build without triggering the year-shape predicate).
1057 ///
1058 /// The `:edicao` slot carries the universal-axis 4-digit-ASCII-
1059 /// decimal-year language-edition identifier every kind of caixa
1060 /// emits under (CAIXA-SDLC §I — the author-facing surface every
1061 /// `defcaixa` form supplies) — the typed slot's `Option<String>`
1062 /// accept-set (empty-string rejected through
1063 /// [`ManifestError::EdicaoEmpty`], year-shape-invalid rejected
1064 /// through [`ManifestError::EdicaoInvalid`] past the 4-digit-ASCII-
1065 /// decimal-year predicate [`Self::validate_edicao`] enforces) maps
1066 /// onto one load-bearing downstream consumer today
1067 /// ([`Self::validate_edicao`]'s empty-arm + year-shape-predicate
1068 /// gate binding at caixa-core/src/manifest.rs:1959) plus every
1069 /// future edition-aware substrate consumer the CAIXA-SDLC §I
1070 /// roadmap anticipates (the tatara-lisp compiler's macro-surface
1071 /// selector every edition-aware build step keys off, the future
1072 /// per-edition compatibility-flag overlay the M4 CR materializer
1073 /// resolves per-CR, the peer [`Caixa::template`] canonical
1074 /// `:edicao "2026"` scaffold every `feira init` emits verbatim,
1075 /// and the renderer-side fixtures at `caixa-helm/src/lib.rs:978` /
1076 /// `caixa-flux/src/lib.rs:2319` / `caixa-mesh/src/lib.rs:3208` that
1077 /// carry `edicao: Some("2026".into())` by construction).
1078 ///
1079 /// Prior to this lift the `.edicao` field was accessed inline at
1080 /// one production site — [`Self::validate_edicao`]'s
1081 /// `self.edicao.as_deref()` empty-and-shape gate binding — one
1082 /// open-coded field-access that expressed no compile-time link
1083 /// back to the typed slot. A future extension of the `:edicao`
1084 /// axis to a richer author surface — a per-`:edicao` known-
1085 /// edition allowlist (the future tightening
1086 /// [`Self::validate_edicao`]'s docstring acknowledges past the
1087 /// structural year-shape floor, rejecting year-shaped values that
1088 /// don't name a tatara-lisp edition the substrate actually
1089 /// understands — `"1999"` is year-shaped but no `1999` edition
1090 /// exists), a per-edition compatibility-flag overlay the M4 CR
1091 /// materializer resolves per-CR (the "edition `"2026"` enables
1092 /// macro-surface features the sibling `"2018"` gates behind a
1093 /// feature flag" arm the edition-selector story anticipates), a
1094 /// promotion of the plain `Option<String>` byte-string to a
1095 /// richer `CaixaEdition` enum discriminated on year once a sibling
1096 /// edition to `"2026"` lands — would have had to be threaded
1097 /// through the open-coded copy in lockstep with every future
1098 /// edition-aware consumer, or the validate gate and the future
1099 /// edition-aware consumer path would silently disagree on which
1100 /// edition a given [`Caixa`] resolves to (an author's
1101 /// `:edicao "2026"` would satisfy validate while a future
1102 /// edition-aware consumer silently defaulted to a stale edition,
1103 /// or vice versa). Lifting the resolution to a typed method on
1104 /// the substrate primitive means every downstream consumer of the
1105 /// caixa's per-`Caixa` edition surface reaches for exactly one
1106 /// typed dispatch — the resolver's accept-set migrates as a unit
1107 /// on any future axis addition.
1108 ///
1109 /// Fourth and final outer top-level [`Caixa`] `Option<&str>`-return
1110 /// scalar accessor — sibling of [`Self::licenca`] (6d5bc28),
1111 /// [`Self::repositorio`] (cc7332d), and [`Self::descricao`]
1112 /// (3f16e2f), the accessors that opened the "outer [`Caixa`]
1113 /// `Option<&str>` scalar" projection pattern this lift folds on.
1114 /// Same "one typed dispatch on the substrate primitive, thin
1115 /// projections at each consumer" discipline the peer per-`:placement`
1116 /// [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
1117 /// [`crate::aplicacao::Placement::affinity`] (74ec2d3) /
1118 /// per-`:contratos` [`crate::aplicacao::WitContract::endpoint`]
1119 /// (7020470) / [`crate::aplicacao::WitContract::subject`] (90de675)
1120 /// / [`crate::aplicacao::WitContract::slot`] (ed22b66)
1121 /// `Option<&str>`-return accessors carry on the sibling M2 / M3
1122 /// typed-slot atom axes, extended here to close the outer top-level
1123 /// `Caixa` universal-axis surface's last unlifted `Option<String>`
1124 /// slot. Named `edicao()` to match the storage field's name; the
1125 /// accessor's identity maps onto the canonical CAIXA-SDLC §I
1126 /// vocabulary the slot's docstring already carries.
1127 #[must_use]
1128 pub const fn edicao(&self) -> Option<&str> {
1129 match &self.edicao {
1130 Some(s) => Some(s.as_str()),
1131 None => None,
1132 }
1133 }
1134
1135 /// Substrate-canonical per-`Caixa` `:nome` universal-axis DNS-1123-
1136 /// label caixa-identity scalar accessor every consumer of the top-
1137 /// level manifest's identity axis keys off — returns the author-
1138 /// declared `:nome` byte-string verbatim as an `&str`, borrowed from
1139 /// the typed slot's own `String` storage. Non-optional (`:nome` is
1140 /// a required-axis scalar every `defcaixa` form must supply; the
1141 /// [`Self::from_lisp`] derive rejects an omitted / non-string
1142 /// `:nome` at parse time, so a `Caixa` past parse definitionally
1143 /// carries a non-`None` `:nome`).
1144 ///
1145 /// The `:nome` slot carries the universal-axis DNS-1123-label
1146 /// caixa-identity every kind of caixa emits under (CAIXA-SDLC §I —
1147 /// the primary identity axis every `defcaixa` form supplies
1148 /// alongside `:versao` / `:kind`; the substrate-wide identity every
1149 /// other typed surface that names a caixa reaches through — `:deps`
1150 /// entries, `:membros` entries, `:children` entries, the
1151 /// `lareira-<nome>` Helm chart name every per-Servico renderer
1152 /// derives, the `pleme-program-<nome>` label every per-Aplicacao
1153 /// renderer emits) — the typed slot's `String` accept-set (empty
1154 /// rejected through [`ManifestError::NomeEmpty`], DNS-1123-shape-
1155 /// invalid rejected through [`ManifestError::NomeInvalid`] past
1156 /// the shared [`crate::render::require_valid_dns_1123_label`] gate
1157 /// the peer name axes each land on, joint-length-with-`lareira-`-
1158 /// prefix rejected through
1159 /// [`ManifestError::NomeChartNameBudgetExceeded`] past
1160 /// [`crate::render::is_lareira_chart_name_shape`]) maps onto every
1161 /// load-bearing downstream consumer the substrate carries — the
1162 /// two universal-axis validate gates at caixa-build time
1163 /// ([`Self::validate_nome`] + [`Self::validate_nome_chart_name_budget`]),
1164 /// [`crate::lareira_chart_name`]'s `lareira-<nome>` Helm chart-name
1165 /// derivation every per-Servico renderer keys off, the caixa-helm
1166 /// `Chart.yaml`'s `name:` axis, caixa-flux's `programs.yaml` entry
1167 /// `name:` axis, caixa-mesh's Cilium `CiliumNetworkPolicy` /
1168 /// `HTTPRoute` per-Aplicacao name axes at
1169 /// caixa-mesh/src/lib.rs:{2650, 2797, 2919, 2925},
1170 /// [`crate::pleme_program_selector`] /
1171 /// [`crate::pleme_program_in_aplicacao_selector`] label-selector
1172 /// derivations, and every future substrate renderer that emits an
1173 /// artifact keyed by the caixa's identity.
1174 ///
1175 /// Prior to this lift the `.nome` field was accessed inline at a
1176 /// dozen production sites across `caixa-core` (the two universal-
1177 /// axis validate gates + [`Dep::validate`]-adjacent duplicate
1178 /// tracking), `caixa-helm` (the `lareira_chart_name` fold, the
1179 /// `ChartYaml.name` / `ChartYaml.description` / `Chart.yaml`
1180 /// `keywords` fallback), `caixa-flux` (the `programs.yaml`
1181 /// entry `name:` fold, the `flux_kustomization_source_subtree`
1182 /// per-cluster subpath derivation), and `caixa-mesh` (the
1183 /// `pleme_program_in_aplicacao_selector` label-selector fold, the
1184 /// `cilium_network_policy_name` / `gateway_api_http_route_name`
1185 /// per-CR name derivations, the `LABEL_APLICACAO` labels-map
1186 /// insert) — a dozen open-coded field-accesses that expressed no
1187 /// compile-time link back to the typed slot. A future extension of
1188 /// the `:nome` axis to a richer author surface — a per-`:nome`
1189 /// structured `CaixaIdentity` newtype that carries the joint-
1190 /// length-with-prefix invariant [`Self::validate_nome_chart_name_budget`]
1191 /// enforces at the type level (rather than as a validate-time
1192 /// gate), a per-registry `:nome` namespacing overlay the M4 CR
1193 /// materializer resolves per-CR (the "`pleme-io/checkout` vs
1194 /// `partner-org/checkout` collision" arm the multi-tenant-registry
1195 /// story acknowledges), a promotion of the plain `String` byte-
1196 /// string to a richer `CaixaNome` newtype discriminated on
1197 /// namespace prefix — would have had to be threaded through every
1198 /// open-coded copy in lockstep or the two validate gates and the
1199 /// dozen emit paths would silently disagree on which identity a
1200 /// given [`Caixa`] resolves to (an author's `:nome "checkout"`
1201 /// would satisfy validate while one of the emit paths silently
1202 /// rendered a drifted other identity, or vice versa). Lifting the
1203 /// resolution to a typed method on the substrate primitive means
1204 /// every downstream consumer of the caixa's per-`Caixa` identity
1205 /// surface reaches for exactly one typed dispatch — the resolver's
1206 /// accept-set migrates as a unit on any future axis addition.
1207 ///
1208 /// First outer top-level [`Caixa`] `&str`-return required-scalar
1209 /// accessor — opens the "outer [`Caixa`] `&str` required-scalar"
1210 /// projection pattern the sibling per-`Caixa` `:versao` future lift
1211 /// folds on. Sibling in shape to the peer per-`:membros`
1212 /// [`crate::aplicacao::Membro::nome`] (4a32abf) / per-`:contratos`
1213 /// [`crate::aplicacao::WitContract::source`] /
1214 /// [`crate::aplicacao::WitContract::destination`] (7f0fd43),
1215 /// [`crate::aplicacao::WitContract::world_ref`] (0804823),
1216 /// [`crate::aplicacao::Membro::versao_requirement`] (a40b0e3),
1217 /// [`crate::aplicacao::Entrada::destination`] (6db982c),
1218 /// [`crate::aplicacao::CircuitBreaker::max_failures`] (3a74062),
1219 /// per-sub-struct required-axis accessors carry on the sibling M3
1220 /// mesh-slot-atom scalar-value axes, extended here to open the
1221 /// outer top-level [`Caixa`] `&str`-return required-scalar surface.
1222 /// Named `nome()` to match the storage field's name; the accessor's
1223 /// identity maps onto the canonical CAIXA-SDLC §I vocabulary the
1224 /// slot's docstring already carries.
1225 #[must_use]
1226 pub const fn nome(&self) -> &str {
1227 self.nome.as_str()
1228 }
1229
1230 /// Substrate-canonical per-`Caixa` `:versao` universal-axis SemVer-2
1231 /// pinned-version scalar accessor every consumer of the top-level
1232 /// manifest's version axis keys off — returns the author-declared
1233 /// `:versao` byte-string verbatim as an `&str`, borrowed from the
1234 /// typed slot's own `String` storage. Non-optional (`:versao` is a
1235 /// required-axis scalar every `defcaixa` form must supply alongside
1236 /// `:nome` / `:kind`; the [`Self::from_lisp`] derive rejects an
1237 /// omitted / non-string `:versao` at parse time, so a `Caixa` past
1238 /// parse definitionally carries a non-`None` `:versao`).
1239 ///
1240 /// The `:versao` slot carries the universal-axis SemVer-2
1241 /// concrete-version body every kind of caixa emits under
1242 /// (CAIXA-SDLC §I — the required-scalar every `defcaixa` form
1243 /// supplies alongside `:nome` / `:kind`; the substrate-wide
1244 /// pinned-version every downstream artifact-emitting consumer
1245 /// composes under — the `lareira-<nome>` Helm chart's `Chart.yaml`
1246 /// `version:` + `appVersion:` axes, the `feira publish` Zig-style
1247 /// `v<versao>` git tag the [`crate::DEFAULT_PUBLISH_TAG_PREFIX`]
1248 /// prefix composes on top of, the programs.yaml entry's `versao:`
1249 /// value the `lareira-fleet-programs` aggregator carries onto each
1250 /// rendered `ComputeUnit`, the OCI image's `:v<versao>` / `:latest`
1251 /// tags every substrate-side `skopeo push` writes, the lacre
1252 /// closure's pinned `concrete_versao`, and the `:upgrade-from :from`
1253 /// prior-version references peers in the exact same SemVer-2 shape).
1254 /// The typed slot's `String` accept-set (empty rejected through
1255 /// [`ManifestError::VersaoEmpty`], SemVer-2-shape-invalid rejected
1256 /// through [`ManifestError::VersaoInvalid`] past
1257 /// [`semver::Version::parse`]) maps onto every load-bearing
1258 /// downstream consumer the substrate carries — the [`Self::validate_versao`]
1259 /// universal-axis validate gate at caixa-build time, the
1260 /// [`crate::CaixaVersion::parse`] typed-wrapper resolver,
1261 /// [`caixa-helm`]'s `Chart.yaml` `version:` / `appVersion:` fold,
1262 /// [`caixa-flux`]'s `programs.yaml` entry `versao:` fold + the
1263 /// `cluster_bundle` `GitRepository` `ref: { tag: v<versao> }`
1264 /// derivation, [`caixa-mesh`]'s per-Aplicacao `programs.yaml` fan-
1265 /// out entry `versao:` fold, [`caixa-feira`]'s `feira publish` git-
1266 /// tag derivation (`format!("{prefix}{versao}")`), and every future
1267 /// substrate renderer that emits an artifact keyed by the caixa's
1268 /// pinned version.
1269 ///
1270 /// Prior to this lift the `.versao` field was accessed inline at a
1271 /// dozen production sites across `caixa-core` (the universal-axis
1272 /// [`Self::validate_versao`] gate + [`Dep::validate`]-adjacent
1273 /// version-shape gates), `caixa-helm` (the `ChartYaml.version` /
1274 /// `ChartYaml.app_version` folds), `caixa-flux` (the `programs.yaml`
1275 /// entry `versao:` fold, the `cluster_bundle` `GitRepository` `ref:
1276 /// { tag: v<versao> }` derivation), `caixa-mesh` (the per-Aplicacao
1277 /// `programs.yaml` fan-out entry `versao:` fold), and `caixa-feira`
1278 /// (the `feira publish` git-tag derivation + the `feira app graph` /
1279 /// `feira app deploy` diagnostic renderers) — a dozen open-coded
1280 /// field-accesses that expressed no compile-time link back to the
1281 /// typed slot. A future extension of the `:versao` axis to a richer
1282 /// author surface — a per-`:versao` structured `CaixaVersion` at the
1283 /// storage layer (the substrate already carries a `CaixaVersion`
1284 /// newtype at [`crate::version::CaixaVersion`], deferred until the
1285 /// serde-transparent-newtype-through-DeriveTataraDomain path lands),
1286 /// a per-registry `:versao` immutability overlay the M4 CR
1287 /// materializer enforces per-CR, a promotion of the plain `String`
1288 /// byte-string to a richer `PinnedVersao` newtype discriminated on
1289 /// SemVer-2 pre-release / build-metadata presence — would have had
1290 /// to be threaded through every open-coded copy in lockstep or the
1291 /// validate gate and the dozen emit paths would silently disagree
1292 /// on which version a given [`Caixa`] resolves to (an author's
1293 /// `:versao "0.1.0"` would satisfy validate while one of the emit
1294 /// paths silently rendered a drifted other version, or vice versa).
1295 /// Lifting the resolution to a typed method on the substrate
1296 /// primitive means every downstream consumer of the caixa's
1297 /// per-`Caixa` pinned-version surface reaches for exactly one typed
1298 /// dispatch — the resolver's accept-set migrates as a unit on any
1299 /// future axis addition.
1300 ///
1301 /// Second outer top-level [`Caixa`] `&str`-return required-scalar
1302 /// accessor — folds on the "outer [`Caixa`] `&str` required-scalar"
1303 /// projection pattern the sibling per-`Caixa` [`Self::nome`]
1304 /// (e6b7d97) opened. Sibling in shape to the peer per-`:membros`
1305 /// [`crate::aplicacao::Membro::versao_requirement`] (4127bb6) /
1306 /// per-`:children` [`crate::supervisor::ChildSpec::versao_requirement`]
1307 /// (2c053c8) / per-`:upgrade-from` [`crate::UpgradeFromEntry::prior_versao`]
1308 /// (75d27a8) per-sub-struct `:versao`-shaped `&str`-return accessors
1309 /// on the sibling per-typed-slot version-carrier axes, extended here
1310 /// to close the second outer top-level [`Caixa`] required-`&str`-
1311 /// carrying axis so the two universal-axis identity-carrying
1312 /// scalars every `defcaixa` form supplies (`:nome` + `:versao`)
1313 /// share the same "one typed dispatch per axis" discipline. Named
1314 /// `versao()` to match the storage field's name; the accessor's
1315 /// identity maps onto the canonical CAIXA-SDLC §I vocabulary the
1316 /// slot's docstring already carries.
1317 #[must_use]
1318 pub const fn versao(&self) -> &str {
1319 self.versao.as_str()
1320 }
1321
1322 /// Substrate-canonical per-`Caixa` `:kind` universal-axis
1323 /// closed-set-enum discriminant accessor every consumer of the top-
1324 /// level manifest's kind axis keys off — returns the author-declared
1325 /// `:kind` variant verbatim as a [`CaixaKind`], `Copy`-projected
1326 /// from the typed slot's own [`CaixaKind`] storage. Non-optional
1327 /// (`:kind` is a required-axis discriminant every `defcaixa` form
1328 /// must supply alongside `:nome` / `:versao`; the [`Self::from_lisp`]
1329 /// derive rejects an omitted / non-symbol `:kind` at parse time, so
1330 /// a `Caixa` past parse definitionally carries a valid [`CaixaKind`]
1331 /// variant).
1332 ///
1333 /// The `:kind` slot carries the universal-axis closed-set typed-
1334 /// discriminant every substrate-side dispatch keys off (CAIXA-SDLC
1335 /// §I — the primary shape gate every renderer / verifier /
1336 /// operator branches on; the five variants `Biblioteca` /
1337 /// `Binario` / `Servico` / `Supervisor` / `Aplicacao` partition
1338 /// the caixa surface into disjoint runtime contracts) — the typed
1339 /// slot's [`CaixaKind`] accept-set (parse-time-rejected non-symbol
1340 /// values through the derive-macro's symbol-arm gate, exhaustively
1341 /// matched at every downstream dispatch site) maps onto every
1342 /// load-bearing downstream consumer the substrate carries:
1343 ///
1344 /// - [`crate::render::require_kind`]'s per-renderer entry-gate
1345 /// predicate — the canonical two-line
1346 /// `require_kind(caixa, Servico)?` prelude every per-Servico
1347 /// renderer (`caixa-helm`, `caixa-flux`, the future `caixa-otel`
1348 /// / per-Servico OCI packager / M4 `wasm.pleme.io/v1alpha1/
1349 /// ComputeUnit` CR materializer) runs at its entry-point,
1350 /// alongside the [`crate::render::KindMismatch`] error carrier's
1351 /// `actual:` field the diagnostic surfaces to name the offending
1352 /// caixa's variant.
1353 /// - [`Self::aplicacao_view`]'s + [`Self::supervisor_view`]'s
1354 /// per-view kind-gate binding — the two `Option<TypedSpec>`
1355 /// `_view` composers that fold the flat mesh-slot / supervisor-
1356 /// slot columns into their typed sub-spec only when the kind
1357 /// matches (returns `None` otherwise); the future per-Servico
1358 /// M2-view composer (`servico_view`) will follow the same shape.
1359 /// - [`Self::declared_foreign_code_slots`]'s per-slot kind-
1360 /// coherence gate — the `!self.kind.requires_exe()` /
1361 /// `!self.kind.requires_servicos()` predicates that fence
1362 /// each code-surface slot from the wrong owning kind.
1363 /// - [`crate::LayoutInvariants::verify`]'s kind ↔ code-surface
1364 /// coherence gates — the six `caixa.kind == CaixaKind::X` /
1365 /// `caixa.kind != CaixaKind::X` predicates and the four kind-
1366 /// coherence error carriers (`SupervisorOwnsCode` /
1367 /// `AplicacaoOwnsCode` / `MeshSlotsOnNonAplicacao` /
1368 /// `SupervisorSlotsOnNonSupervisor` / `ServicoSlotsOnNonServico`
1369 /// / `ForeignCodeSlot`) which each name the offending caixa's
1370 /// variant in their `kind:` field.
1371 ///
1372 /// Prior to this lift the `.kind` field was accessed inline at
1373 /// twenty-plus production sites across `caixa-core` (the
1374 /// [`crate::render::require_kind`] entry-gate predicate + the
1375 /// [`crate::render::KindMismatch`] `actual:` field, the two `_view`
1376 /// composers, the `declared_foreign_code_slots` per-slot kind-
1377 /// coherence gate, and the six [`crate::LayoutInvariants::verify`]
1378 /// kind ↔ code-surface predicates + four error carriers) — a score
1379 /// of open-coded field-accesses that expressed no compile-time link
1380 /// back to the typed slot. A future extension of the `:kind` axis
1381 /// to a richer author surface — a per-`:kind` sub-variant discriminant
1382 /// (e.g. `Servico(ServicoRuntime)` splitting the current single
1383 /// variant across the wasm-component / legacy-container / native-
1384 /// binary runtime axes the M5 roadmap acknowledges), a per-cluster
1385 /// kind-overlay the M4 CR materializer resolves per-CR (the
1386 /// "cluster policy demotes `Aplicacao` to `Servico` on a single-
1387 /// tenant cluster" arm), a promotion of the plain [`CaixaKind`]
1388 /// enum to a richer `KindWithRuntime` discriminated on the
1389 /// component-model world axis — would have had to be threaded
1390 /// through every open-coded copy in lockstep or the entry gate,
1391 /// the view composers, and the layout invariants would silently
1392 /// disagree on which kind a given [`Caixa`] resolves to. Lifting
1393 /// the resolution to a typed method on the substrate primitive
1394 /// means every downstream consumer of the caixa's per-`Caixa`
1395 /// kind surface reaches for exactly one typed dispatch — the
1396 /// resolver's accept-set migrates as a unit on any future axis
1397 /// addition.
1398 ///
1399 /// First outer top-level [`Caixa`] `Copy`-return required-enum-
1400 /// discriminant accessor — opens the "outer [`Caixa`] `Copy`-return
1401 /// required-discriminant" projection pattern. Sibling in shape to
1402 /// the peer per-`:supervisor` [`crate::supervisor::SupervisorSpec::estrategia`]
1403 /// (eafb619), per-`:placement` [`crate::aplicacao::Placement::estrategia`]
1404 /// (921fe1b), and per-`:children` [`crate::supervisor::ChildSpec::restart`]
1405 /// (dfb4a81) `Copy`-return closed-set-enum discriminant accessors
1406 /// on the sibling nested-spec typed-slot discriminator axes,
1407 /// extended here to the outer top-level [`Caixa`] universal-axis
1408 /// surface. Named `kind()` to match the storage field's name;
1409 /// the accessor's identity maps onto the canonical CAIXA-SDLC §I
1410 /// vocabulary the slot's docstring already carries.
1411 #[must_use]
1412 pub const fn kind(&self) -> CaixaKind {
1413 self.kind
1414 }
1415
1416 /// Substrate-canonical per-`Caixa` `:autores` universal-axis
1417 /// maintainer-name-list slice-accessor every consumer of the top-
1418 /// level manifest's maintainer axis keys off — returns the author-
1419 /// declared `:autores` list verbatim as a `&[String]` slice-view over
1420 /// the same backing buffer the raw `self.autores.as_slice()` field
1421 /// access borrows from. Empty-list-carrying (`:autores` is a default-
1422 /// empty axis every `defcaixa` form supplies with an empty `()` when
1423 /// unset; the [`Self::from_lisp`] derive folds an omitted `:autores`
1424 /// through `#[serde(default)]` to `Vec::new()`, so a `Caixa` past
1425 /// parse definitionally carries a `Vec<String>` slot — possibly
1426 /// empty — and the returned `&[String]` degenerates to an empty
1427 /// slice on that arm without any silent `None` collapse).
1428 ///
1429 /// The `:autores` slot carries the universal-axis maintainer-name
1430 /// list every kind of caixa emits under (CAIXA-SDLC §I — the author-
1431 /// facing surface every `defcaixa` form supplies alongside `:nome` /
1432 /// `:versao` / `:kind`; the substrate-wide contact-carrying axis
1433 /// every downstream registry-facing artifact emits under) — the
1434 /// typed slot's `Vec<String>` accept-set (empty-per-entry rejected
1435 /// through [`ManifestError::AutorEmpty`], non-chart-maintainer-shape
1436 /// rejected through [`ManifestError::AutorInvalid`], cross-entry
1437 /// duplicate rejected through [`ManifestError::AutorDuplicate`]) maps
1438 /// onto every load-bearing downstream consumer the substrate carries
1439 /// — the [`Self::validate_autores`] universal-axis empty-per-entry +
1440 /// shape + duplicate gate at caixa-core/src/manifest.rs, the
1441 /// caixa-helm `build_chart_yaml` `maintainers:` fold at
1442 /// caixa-helm/src/lib.rs that walks each entry into a `Maintainer {
1443 /// name, email: None }` record, every future per-`Caixa` registry-
1444 /// facing renderer the CAIXA-SDLC §I roadmap acknowledges (the
1445 /// future `artifacthub.io/maintainers` `Chart.yaml` annotation the
1446 /// caixa-helm docstring alludes to at [`Self::validate_licenca`],
1447 /// the future per-cluster author-notification overlay the M4 CR
1448 /// materializer resolves per-CR).
1449 ///
1450 /// Prior to this lift the `.autores` field was accessed inline at
1451 /// two production sites — [`Self::validate_autores`]'s `for autor
1452 /// in &self.autores` walk that gates every entry through
1453 /// [`ManifestError::AutorEmpty`] / `AutorInvalid` / `AutorDuplicate`,
1454 /// and the caixa-helm `build_chart_yaml` `caixa.autores.iter().map(|a|
1455 /// Maintainer { name: a.clone(), email: None }).collect()` fold that
1456 /// materializes every entry into a `Chart.yaml` `maintainers:` row —
1457 /// two open-coded field-accesses that expressed no compile-time link
1458 /// back to the typed slot. A future extension of the `:autores` axis
1459 /// to a richer author surface — a per-`:autores` structured
1460 /// `Maintainer { name, email, url }` at the storage layer once the
1461 /// substrate absorbs `artifacthub.io/maintainers`' name+email+url
1462 /// tuple, a per-registry `:autores` allowlist the M4 CR materializer
1463 /// enforces per-CR (the "cluster policy demands every author declare
1464 /// an on-file `mailto:` contact" arm), a promotion of the plain
1465 /// `Vec<String>` byte-string list to a richer
1466 /// `Vec<ChartMaintainer>` newtype discriminated on the RFC-5322
1467 /// `<name> [<email>]` grammar the `is_chart_maintainer_name_shape`
1468 /// predicate already resolves through — would have had to be
1469 /// threaded through both open-coded copies in lockstep or the
1470 /// validate gate and the caixa-helm emit path would silently
1471 /// disagree on which authors a given [`Caixa`] resolves to (an
1472 /// author's `:autores ("alice" "bob")` would satisfy validate while
1473 /// the caixa-helm emit path silently rendered a drifted other
1474 /// maintainer list, or vice versa). Lifting the resolution to a
1475 /// typed method on the substrate primitive means every downstream
1476 /// consumer of the caixa's per-`Caixa` maintainer surface reaches
1477 /// for exactly one typed dispatch — the resolver's accept-set
1478 /// migrates as a unit on any future axis addition.
1479 ///
1480 /// First outer top-level [`Caixa`] `&[T]`-return slice-accessor —
1481 /// opens the "outer [`Caixa`] `&[T]` slice" projection pattern the
1482 /// sibling per-`Caixa` `:etiquetas` / `:deps` / `:deps-dev` / `:exe`
1483 /// / `:bibliotecas` / `:servicos` / `:upgrade-from` / `:children`
1484 /// future lifts fold on. Sibling in shape to the peer per-`:supervisor`
1485 /// [`crate::supervisor::SupervisorSpec::children`] (bc92bce), per-`:placement`
1486 /// [`crate::aplicacao::Placement::clusters`] (a6e18d7), per-`:membros`
1487 /// [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36), per-`:contratos`
1488 /// [`crate::aplicacao::AplicacaoSpec::contratos`] (0dcc926), and
1489 /// per-`:upgrade-from :instructions` [`crate::upgrade::UpgradeFromEntry::instructions`]
1490 /// (0137e5a) `&[T]`-return slice accessors on the sibling per-M2 /
1491 /// per-M3 typed-slot list axes, extended here to the outer top-level
1492 /// [`Caixa`] universal-axis surface. Returns `&[String]` (not
1493 /// `&Vec<String>`) because every downstream consumer of the author
1494 /// list treats it as a read-only sequence — the slice-view is the
1495 /// narrowest borrow that supports every present + roadmapped consumer
1496 /// (`.iter()`, `.len()`, `.is_empty()`) without leaking the backing
1497 /// `Vec`'s grow/push/reserve surface no consumer of the typed view
1498 /// reaches for (the storage-side `Vec` remains reachable through the
1499 /// `pub autores` field for the mutation-carrying serde round-trip and
1500 /// per-test fixture-mutation paths). Named `autores()` to match the
1501 /// storage field's name; the accessor's identity maps onto the
1502 /// canonical CAIXA-SDLC §I vocabulary the slot's docstring already
1503 /// carries.
1504 #[must_use]
1505 pub const fn autores(&self) -> &[String] {
1506 self.autores.as_slice()
1507 }
1508
1509 /// Substrate-canonical per-`Caixa` `:etiquetas` universal-axis
1510 /// registry-search-tag-list slice-accessor every consumer of the
1511 /// top-level manifest's topical-tag axis keys off — returns the
1512 /// author-declared `:etiquetas` list verbatim as a `&[String]`
1513 /// slice-view over the same backing buffer the raw
1514 /// `self.etiquetas.as_slice()` field access borrows from. Empty-
1515 /// list-carrying (`:etiquetas` is a default-empty axis every
1516 /// `defcaixa` form supplies with an empty `()` when unset; the
1517 /// [`Self::from_lisp`] derive folds an omitted `:etiquetas` through
1518 /// `#[serde(default)]` to `Vec::new()`, so a `Caixa` past parse
1519 /// definitionally carries a `Vec<String>` slot — possibly empty —
1520 /// and the returned `&[String]` degenerates to an empty slice on
1521 /// that arm without any silent `None` collapse).
1522 ///
1523 /// The `:etiquetas` slot carries the universal-axis topical-tag
1524 /// list every kind of caixa emits under (CAIXA-SDLC §I — the
1525 /// author-facing surface every `defcaixa` form supplies alongside
1526 /// `:nome` / `:versao` / `:kind`; the substrate-wide registry-
1527 /// search-facing axis every downstream registry-facing artifact
1528 /// emits under) — the typed slot's `Vec<String>` accept-set
1529 /// (empty-per-entry rejected through [`ManifestError::EtiquetaEmpty`],
1530 /// non-chart-keyword-shape rejected through
1531 /// [`ManifestError::EtiquetaInvalid`], cross-entry duplicate
1532 /// rejected through [`ManifestError::EtiquetaDuplicate`]) maps onto
1533 /// every load-bearing downstream consumer the substrate carries —
1534 /// the [`Self::validate_etiquetas`] universal-axis empty-per-entry
1535 /// + shape + duplicate gate at caixa-core/src/manifest.rs, the
1536 /// caixa-helm `build_chart_yaml` `keywords:` fold at
1537 /// caixa-helm/src/lib.rs that walks each entry into the rendered
1538 /// `Chart.yaml` `keywords:` array (chained with the
1539 /// [`crate::LAREIRA_CHART_KEYWORDS`] substrate-wide floor set and
1540 /// dedup'd through a `BTreeSet` at emit time), every future per-
1541 /// `Caixa` registry-facing renderer the CAIXA-SDLC §I roadmap
1542 /// acknowledges (the future `artifacthub.io/keywords` `Chart.yaml`
1543 /// annotation, the future per-cluster tag-notification overlay the
1544 /// M4 CR materializer resolves per-CR).
1545 ///
1546 /// Prior to this lift the `.etiquetas` field was accessed inline at
1547 /// two production sites — [`Self::validate_etiquetas`]'s `for
1548 /// etiqueta in &self.etiquetas` walk that gates every entry through
1549 /// [`ManifestError::EtiquetaEmpty`] / `EtiquetaInvalid` /
1550 /// `EtiquetaDuplicate`, and the caixa-helm `build_chart_yaml`
1551 /// `caixa.etiquetas.iter().cloned().chain(...)` fold that
1552 /// materializes every entry into a `Chart.yaml` `keywords:` row —
1553 /// two open-coded field-accesses that expressed no compile-time
1554 /// link back to the typed slot. A future extension of the
1555 /// `:etiquetas` axis to a richer tag surface — a per-`:etiquetas`
1556 /// structured `ChartKeyword { name, uri, category }` at the storage
1557 /// layer once the substrate absorbs `artifacthub.io/keywords`
1558 /// richer tag tuple, a per-registry `:etiquetas` allowlist the M4
1559 /// CR materializer enforces per-CR (the "cluster policy demands
1560 /// every tag come from a substrate-approved taxonomy" arm), a
1561 /// promotion of the plain `Vec<String>` byte-string list to a
1562 /// richer `Vec<ChartKeyword>` newtype discriminated on the DNS-
1563 /// 1123-label-shaped grammar the `is_chart_keyword_shape` predicate
1564 /// already resolves through — would have had to be threaded through
1565 /// both open-coded copies in lockstep or the validate gate and the
1566 /// caixa-helm emit path would silently disagree on which tags a
1567 /// given [`Caixa`] resolves to (an author's `:etiquetas ("demo"
1568 /// "aplicacao")` would satisfy validate while the caixa-helm emit
1569 /// path silently rendered a drifted other keyword list, or vice
1570 /// versa). Lifting the resolution to a typed method on the
1571 /// substrate primitive means every downstream consumer of the
1572 /// caixa's per-`Caixa` topical-tag surface reaches for exactly one
1573 /// typed dispatch — the resolver's accept-set migrates as a unit
1574 /// on any future axis addition.
1575 ///
1576 /// Second outer top-level [`Caixa`] `&[T]`-return slice-accessor —
1577 /// folds on the "outer [`Caixa`] `&[T]` slice" projection pattern
1578 /// [`Self::autores`] (b5d813f) opened, sibling in shape and
1579 /// idiom. The remaining unlifted outer-`Caixa` slice-carrying axes
1580 /// (`:deps` / `:deps-dev` / `:exe` / `:bibliotecas` / `:servicos`
1581 /// / `:upgrade-from` / `:children` / `:membros` / `:contratos`)
1582 /// fold onto the same pattern in future lifts. Sibling in shape to
1583 /// the peer per-`:supervisor`
1584 /// [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
1585 /// per-`:placement` [`crate::aplicacao::Placement::clusters`]
1586 /// (a6e18d7), per-`:membros`
1587 /// [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
1588 /// per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
1589 /// (0dcc926), and per-`:upgrade-from :instructions`
1590 /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
1591 /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
1592 /// typed-slot list axes, extended here to the outer top-level
1593 /// [`Caixa`] universal-axis surface. Returns `&[String]` (not
1594 /// `&Vec<String>`) because every downstream consumer of the tag
1595 /// list treats it as a read-only sequence — the slice-view is the
1596 /// narrowest borrow that supports every present + roadmapped
1597 /// consumer (`.iter()`, `.len()`, `.is_empty()`) without leaking
1598 /// the backing `Vec`'s grow/push/reserve surface no consumer of
1599 /// the typed view reaches for (the storage-side `Vec` remains
1600 /// reachable through the `pub etiquetas` field for the mutation-
1601 /// carrying serde round-trip and per-test fixture-mutation paths).
1602 /// Named `etiquetas()` to match the storage field's name; the
1603 /// accessor's identity maps onto the canonical CAIXA-SDLC §I
1604 /// vocabulary the slot's docstring already carries.
1605 #[must_use]
1606 pub const fn etiquetas(&self) -> &[String] {
1607 self.etiquetas.as_slice()
1608 }
1609
1610 /// Substrate-canonical per-`Caixa` `:bibliotecas` universal-axis
1611 /// library-source-path-list slice-accessor every consumer of the
1612 /// top-level manifest's Biblioteca-source axis keys off — returns
1613 /// the author-declared `:bibliotecas` list verbatim as a
1614 /// `&[String]` slice-view over the same backing buffer the raw
1615 /// `self.bibliotecas.as_slice()` field access borrows from. Empty-
1616 /// list-carrying (`:bibliotecas` is a default-empty axis every
1617 /// `defcaixa` form supplies with an empty `()` when unset; the
1618 /// [`Self::from_lisp`] derive folds an omitted `:bibliotecas`
1619 /// through `#[serde(default)]` to `Vec::new()`, so a `Caixa` past
1620 /// parse definitionally carries a `Vec<String>` slot — possibly
1621 /// empty — and the returned `&[String]` degenerates to an empty
1622 /// slice on that arm without any silent `None` collapse).
1623 ///
1624 /// The `:bibliotecas` slot carries the universal-axis lisp-library
1625 /// entry-path list every `:kind Biblioteca` caixa emits under
1626 /// (CAIXA-SDLC §I — the author-facing surface every `defcaixa`
1627 /// form supplies alongside `:nome` / `:versao` / `:kind`; the
1628 /// substrate-wide library-carrier axis every downstream
1629 /// authoring-facing consumer keys off) — the typed slot's
1630 /// `Vec<String>` accept-set (empty-per-entry rejected through
1631 /// [`ManifestError::CodePathEmpty { slot: ":bibliotecas" }`],
1632 /// non-sandboxed-relative-shape rejected through
1633 /// [`ManifestError::CodePathShape`], non-`.lisp`-extension rejected
1634 /// through [`ManifestError::CodePathNonLispExtension`], cross-entry
1635 /// duplicate rejected through [`ManifestError::CodePathDuplicate`])
1636 /// maps onto every load-bearing downstream consumer the substrate
1637 /// carries — the [`crate::LayoutInvariants`] Biblioteca-arm
1638 /// empty-check + per-entry file-exists loop at
1639 /// caixa-core/src/layout.rs that gates each entry through
1640 /// [`crate::LayoutError::MissingLib`] / `MissingEntry`, the
1641 /// [`Self::validate_code_paths`] per-slot shape gate at
1642 /// caixa-core/src/manifest.rs that walks each entry through the
1643 /// sandbox-relative / `.lisp`-extension / cross-entry duplicate
1644 /// gates, the `feira build` per-entry `tatara_lisp::read` parse
1645 /// walk at caixa-feira/src/cmd/build.rs that phase-1-checks each
1646 /// declared library file for lexical / structural errors before
1647 /// downstream `importar` resolution, every future per-`Caixa`
1648 /// library-facing renderer the CAIXA-SDLC §I roadmap acknowledges
1649 /// (the future `tatara-lispc` compilation entry the docstring at
1650 /// caixa-feira/src/cmd/build.rs alludes to, the future per-cluster
1651 /// bytecode-caching overlay the M4 CR materializer resolves per-CR,
1652 /// the future `caixa-lsp` per-library semantic-token stream the
1653 /// caixa-lsp docstring roadmaps).
1654 ///
1655 /// Prior to this lift the `.bibliotecas` field was accessed inline
1656 /// at three production sites — [`crate::LayoutInvariants`]'s
1657 /// `caixa.bibliotecas.is_empty()` `MissingLib`-arm gate + `for p
1658 /// in &caixa.bibliotecas` `MissingEntry` walk that gates each
1659 /// declared library path through the on-disk-existence check,
1660 /// the compound-code-path `has_code = !caixa.bibliotecas.is_empty()
1661 /// || !caixa.exe.is_empty() || !caixa.servicos.is_empty()` OR-fold
1662 /// on the [`crate::LayoutError::SupervisorOwnsCode`] /
1663 /// `AplicacaoOwnsCode` kind-coherence gate, and the `feira build`
1664 /// per-entry `for entry in &caixa.bibliotecas` + `caixa.bibliotecas.
1665 /// len()` phase-1 tatara-lispc-precursor parse walk — three open-
1666 /// coded field-accesses that expressed no compile-time link back
1667 /// to the typed slot. A future extension of the `:bibliotecas`
1668 /// axis to a richer library surface — a per-`:bibliotecas`
1669 /// structured `BibliotecaEntry { path, edition, exports }` at the
1670 /// storage layer once the substrate absorbs the per-library
1671 /// language-edition + explicit-exports tuple the tatara-lisp
1672 /// module-system roadmap acknowledges, a per-registry
1673 /// `:bibliotecas` allowlist the M4 CR materializer enforces
1674 /// per-CR (the "cluster policy demands every biblioteca declare
1675 /// its own :edicao" arm), a promotion of the plain `Vec<String>`
1676 /// byte-string list to a richer `Vec<LibraryPath>` newtype
1677 /// discriminated on the `lib/<nome>.lisp`-shape grammar the
1678 /// [`crate::render::is_sandboxed_relative_path`] +
1679 /// [`crate::render::is_lisp_extension`] predicates already resolve
1680 /// through — would have had to be threaded through all three
1681 /// open-coded copies in lockstep or the layout gate, the shape
1682 /// validator, and the `feira build` phase-1 parse walk would
1683 /// silently disagree on which library paths a given [`Caixa`]
1684 /// resolves to (an author's `:bibliotecas ("lib/foo.lisp"
1685 /// "lib/bar.lisp")` would satisfy layout while `feira build`
1686 /// silently parsed a drifted other list, or vice versa). Lifting
1687 /// the resolution to a typed method on the substrate primitive
1688 /// means every downstream consumer of the caixa's per-`Caixa`
1689 /// library-source surface reaches for exactly one typed dispatch
1690 /// — the resolver's accept-set migrates as a unit on any future
1691 /// axis addition.
1692 ///
1693 /// Third outer top-level [`Caixa`] `&[T]`-return slice-accessor —
1694 /// folds on the "outer [`Caixa`] `&[T]` slice" projection pattern
1695 /// [`Self::autores`] (b5d813f) opened and [`Self::etiquetas`]
1696 /// (78c7d3c) folded on, sibling in shape and idiom. The remaining
1697 /// unlifted outer-`Caixa` slice-carrying axes (`:deps` /
1698 /// `:deps-dev` / `:exe` / `:servicos` / `:upgrade-from` /
1699 /// `:children` / `:membros` / `:contratos`) fold onto the same
1700 /// pattern in future lifts. Sibling in shape to the peer
1701 /// per-`:supervisor`
1702 /// [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
1703 /// per-`:placement` [`crate::aplicacao::Placement::clusters`]
1704 /// (a6e18d7), per-`:membros`
1705 /// [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
1706 /// per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
1707 /// (0dcc926), and per-`:upgrade-from :instructions`
1708 /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
1709 /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
1710 /// typed-slot list axes, extended here to the outer top-level
1711 /// [`Caixa`] universal-axis surface. Returns `&[String]` (not
1712 /// `&Vec<String>`) because every downstream consumer of the
1713 /// library-source list treats it as a read-only sequence — the
1714 /// slice-view is the narrowest borrow that supports every
1715 /// present + roadmapped consumer (`.iter()`, `.len()`,
1716 /// `.is_empty()`) without leaking the backing `Vec`'s
1717 /// grow/push/reserve surface no consumer of the typed view
1718 /// reaches for (the storage-side `Vec` remains reachable through
1719 /// the `pub bibliotecas` field for the mutation-carrying serde
1720 /// round-trip and per-test fixture-mutation paths). Named
1721 /// `bibliotecas()` to match the storage field's name; the
1722 /// accessor's identity maps onto the canonical CAIXA-SDLC §I
1723 /// vocabulary the slot's docstring already carries.
1724 #[must_use]
1725 pub const fn bibliotecas(&self) -> &[String] {
1726 self.bibliotecas.as_slice()
1727 }
1728
1729 /// Substrate-canonical per-`Caixa` `:exe` universal-axis
1730 /// nix-built-executable-entry-path-list slice-accessor every consumer
1731 /// of the top-level manifest's Binario-executable axis keys off —
1732 /// returns the author-declared `:exe` list verbatim as a `&[String]`
1733 /// slice-view over the same backing buffer the raw
1734 /// `self.exe.as_slice()` field access borrows from. Empty-list-
1735 /// carrying (`:exe` is a default-empty axis every `defcaixa` form
1736 /// supplies with an empty `()` when unset; the [`Self::from_lisp`]
1737 /// derive folds an omitted `:exe` through `#[serde(default)]` to
1738 /// `Vec::new()`, so a `Caixa` past parse definitionally carries a
1739 /// `Vec<String>` slot — possibly empty — and the returned `&[String]`
1740 /// degenerates to an empty slice on that arm without any silent
1741 /// `None` collapse).
1742 ///
1743 /// The `:exe` slot carries the universal-axis nix-built executable
1744 /// entry-path list every `:kind Binario` caixa emits under
1745 /// (CAIXA-SDLC §I — the author-facing surface every `defcaixa`
1746 /// form supplies alongside `:nome` / `:versao` / `:kind`; the
1747 /// substrate-wide `exe/`-directory-fenced entry-carrier axis every
1748 /// downstream flake-build-facing consumer keys off) — the typed
1749 /// slot's `Vec<String>` accept-set (empty-per-entry rejected
1750 /// through [`ManifestError::CodePathEmpty { slot: ":exe" }`],
1751 /// non-sandboxed-relative-shape rejected through
1752 /// [`ManifestError::CodePathShape`], cross-entry duplicate rejected
1753 /// through [`ManifestError::CodePathDuplicate`], out-of-`exe/`-
1754 /// directory paths rejected past the layout's
1755 /// [`crate::LayoutError::ExeOutsideDir`] `starts_with` fence) maps
1756 /// onto every load-bearing downstream consumer the substrate carries
1757 /// — the [`crate::LayoutInvariants`] Binario-arm empty-check +
1758 /// per-entry file-exists + `exe/`-directory-fence loop at
1759 /// caixa-core/src/layout.rs that gates each entry through
1760 /// [`crate::LayoutError::BinarioWithoutExe`] / `MissingEntry` /
1761 /// `ExeOutsideDir`, the compound `has_code` OR-fold on the
1762 /// [`crate::LayoutError::SupervisorOwnsCode`] /
1763 /// [`crate::LayoutError::AplicacaoOwnsCode`] kind-coherence gate
1764 /// that fences code-surface slots off from the two no-code kinds,
1765 /// [`Self::declared_foreign_code_slots`]'s `!self.exe.is_empty()`
1766 /// arm on the [`crate::LayoutError::ForeignCodeSlot`] gate that
1767 /// fences the `:exe` code surface off from every non-Binario code-
1768 /// running kind, [`Self::validate_code_paths`]'s per-slot shape gate
1769 /// that walks each entry through the sandbox-relative / cross-entry
1770 /// duplicate gates, every future per-`Caixa` executable-facing
1771 /// renderer the CAIXA-SDLC §I roadmap acknowledges (the future
1772 /// `caixa-flake` per-Binario `packages.<system>.<nome>` derivation
1773 /// entry the caixa-flake docstring roadmaps, the future per-cluster
1774 /// `nix-store` overlay the M4 CR materializer resolves per-CR, the
1775 /// future `feira nix` per-executable Binario-target emit path).
1776 ///
1777 /// Prior to this lift the `.exe` field was accessed inline at three
1778 /// production sites — the compound-code-path `has_code =
1779 /// !caixa.bibliotecas().is_empty() || !caixa.exe.is_empty() ||
1780 /// !caixa.servicos.is_empty()` OR-fold on the
1781 /// [`crate::LayoutError::SupervisorOwnsCode`] /
1782 /// `AplicacaoOwnsCode` kind-coherence gate, the Binario-arm
1783 /// `caixa.exe.is_empty()` [`crate::LayoutError::BinarioWithoutExe`]
1784 /// gate, the per-entry `for p in &caixa.exe`
1785 /// `MissingEntry`/`ExeOutsideDir` walk, and the
1786 /// [`Self::declared_foreign_code_slots`]'s
1787 /// `!self.exe.is_empty()` arm on the `ForeignCodeSlot` gate — four
1788 /// open-coded field-accesses that expressed no compile-time link
1789 /// back to the typed slot. A future extension of the `:exe` axis
1790 /// to a richer executable surface — a per-`:exe` structured
1791 /// `BinarioEntry { path, wrapper, capabilities }` at the storage
1792 /// layer once the substrate absorbs the per-executable
1793 /// nix-wrapper + linux-capabilities tuple the CAIXA-SDLC §I
1794 /// executable roadmap acknowledges, a per-registry `:exe` allowlist
1795 /// the M4 CR materializer enforces per-CR (the "cluster policy
1796 /// demands every Binario declare an explicit `:wrapper`" arm), a
1797 /// promotion of the plain `Vec<String>` byte-string list to a
1798 /// richer `Vec<ExecutablePath>` newtype discriminated on the
1799 /// `exe/<nome>`-shape grammar the layout's `starts_with(exe_dir)`
1800 /// fence already resolves through — would have had to be threaded
1801 /// through all four open-coded copies in lockstep or the layout
1802 /// gate, the shape validator, and the `feira nix` emit path would
1803 /// silently disagree on which executable paths a given [`Caixa`]
1804 /// resolves to (an author's `:exe ("exe/cli" "exe/serve")` would
1805 /// satisfy layout while `feira nix` silently packaged a drifted
1806 /// other list, or vice versa). Lifting the resolution to a typed
1807 /// method on the substrate primitive means every downstream
1808 /// consumer of the caixa's per-`Caixa` executable-source surface
1809 /// reaches for exactly one typed dispatch — the resolver's accept-
1810 /// set migrates as a unit on any future axis addition.
1811 ///
1812 /// Fourth outer top-level [`Caixa`] `&[T]`-return slice-accessor —
1813 /// folds on the "outer [`Caixa`] `&[T]` slice" projection pattern
1814 /// [`Self::autores`] (b5d813f) opened, [`Self::etiquetas`]
1815 /// (78c7d3c) folded on, and [`Self::bibliotecas`] (8a36c23) closed
1816 /// the universal-axis text-tag family of. Opens the outer-`Caixa`
1817 /// foreign-code-slot `&[T]` sub-family the sibling `:servicos`
1818 /// future lift closes onto (per the trio of code-surface list slots
1819 /// the [`Self::validate_code_paths`] per-slot dispatch tuple
1820 /// already carries — `:bibliotecas` + `:exe` + `:servicos`, of which
1821 /// `:bibliotecas` landed at 8a36c23 and `:servicos` remains as the
1822 /// last unlifted code-surface slot). Sibling in shape to the peer
1823 /// per-`:supervisor` [`crate::supervisor::SupervisorSpec::children`]
1824 /// (bc92bce), per-`:placement`
1825 /// [`crate::aplicacao::Placement::clusters`] (a6e18d7),
1826 /// per-`:membros` [`crate::aplicacao::AplicacaoSpec::membros`]
1827 /// (6c77e36), per-`:contratos`
1828 /// [`crate::aplicacao::AplicacaoSpec::contratos`] (0dcc926), and
1829 /// per-`:upgrade-from :instructions`
1830 /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
1831 /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
1832 /// typed-slot list axes, extended here to the outer top-level
1833 /// [`Caixa`] universal-axis surface. Returns `&[String]` (not
1834 /// `&Vec<String>`) because every downstream consumer of the
1835 /// executable-source list treats it as a read-only sequence — the
1836 /// slice-view is the narrowest borrow that supports every
1837 /// present + roadmapped consumer (`.iter()`, `.len()`,
1838 /// `.is_empty()`) without leaking the backing `Vec`'s
1839 /// grow/push/reserve surface no consumer of the typed view
1840 /// reaches for (the storage-side `Vec` remains reachable through
1841 /// the `pub exe` field for the mutation-carrying serde
1842 /// round-trip and per-test fixture-mutation paths). Named `exe()`
1843 /// to match the storage field's name; the accessor's identity
1844 /// maps onto the canonical CAIXA-SDLC §I vocabulary the slot's
1845 /// docstring already carries.
1846 #[must_use]
1847 pub const fn exe(&self) -> &[String] {
1848 self.exe.as_slice()
1849 }
1850
1851 /// Substrate-canonical per-`Caixa` `:servicos` universal-axis
1852 /// ComputeUnit-CR-YAML-entry-path-list slice-accessor every consumer
1853 /// of the top-level manifest's Servico-component axis keys off —
1854 /// returns the author-declared `:servicos` list verbatim as a
1855 /// `&[String]` slice-view over the same backing buffer the raw
1856 /// `self.servicos.as_slice()` field access borrows from. Empty-list-
1857 /// carrying (`:servicos` is a default-empty axis every `defcaixa`
1858 /// form supplies with an empty `()` when unset; the
1859 /// [`Self::from_lisp`] derive folds an omitted `:servicos` through
1860 /// `#[serde(default)]` to `Vec::new()`, so a `Caixa` past parse
1861 /// definitionally carries a `Vec<String>` slot — possibly empty —
1862 /// and the returned `&[String]` degenerates to an empty slice on
1863 /// that arm without any silent `None` collapse).
1864 ///
1865 /// The `:servicos` slot carries the universal-axis
1866 /// `.computeunit.yaml` ComputeUnit-CR entry-path list every
1867 /// `:kind Servico` caixa emits under (CAIXA-SDLC §I — the
1868 /// author-facing surface every `defcaixa` form supplies alongside
1869 /// `:nome` / `:versao` / `:kind`; the substrate-wide
1870 /// `servicos/`-directory-fenced entry-carrier axis every downstream
1871 /// Servico-facing renderer keys off) — the typed slot's
1872 /// `Vec<String>` accept-set (empty-per-entry rejected through
1873 /// [`ManifestError::CodePathEmpty { slot: ":servicos" }`],
1874 /// non-sandboxed-relative-shape rejected through
1875 /// [`ManifestError::CodePathShape`], non-`.computeunit.yaml`
1876 /// extension rejected through
1877 /// [`ManifestError::CodePathNonComputeUnitYamlExtension`], cross-
1878 /// entry duplicate rejected through
1879 /// [`ManifestError::CodePathDuplicate`], `len != 1` rejected by the
1880 /// V0 [`crate::ServicoCountMismatch`] gate on the per-Servico
1881 /// renderer entry-points, out-of-`servicos/`-directory paths
1882 /// rejected past the layout's [`crate::LayoutError::ServicoOutsideDir`]
1883 /// `starts_with` fence) maps onto every load-bearing downstream
1884 /// consumer the substrate carries — the [`crate::LayoutInvariants`]
1885 /// Servico-arm empty-check + per-entry file-exists + `servicos/`-
1886 /// directory-fence loop at caixa-core/src/layout.rs that gates each
1887 /// entry through [`crate::LayoutError::ServicoWithoutServicos`] /
1888 /// `MissingEntry` / `ServicoOutsideDir`, the compound `has_code`
1889 /// OR-fold on the [`crate::LayoutError::SupervisorOwnsCode`] /
1890 /// [`crate::LayoutError::AplicacaoOwnsCode`] kind-coherence gate
1891 /// that fences code-surface slots off from the two no-code kinds,
1892 /// [`Self::declared_foreign_code_slots`]'s
1893 /// `!self.servicos.is_empty()` arm on the
1894 /// [`crate::LayoutError::ForeignCodeSlot`] gate that fences the
1895 /// `:servicos` code surface off from every non-Servico code-running
1896 /// kind, [`Self::validate_code_paths`]'s per-slot shape gate that
1897 /// walks each entry through the sandbox-relative / `.computeunit.
1898 /// yaml`-extension / cross-entry duplicate gates, the
1899 /// [`crate::require_single_servico`] V0 singularity gate every
1900 /// per-Servico renderer entry-point runs through
1901 /// [`crate::require_v0_servico_shape`], the `feira chart` /
1902 /// `feira deploy` per-verb `first_servico_path` walk at
1903 /// caixa-feira/src/cmd/chart.rs that resolves the singleton
1904 /// ComputeUnit-CR file, every future per-`Caixa` Servico-facing
1905 /// renderer the CAIXA-SDLC §I roadmap acknowledges (the future
1906 /// per-Servico OCI packager, the future M4
1907 /// `wasm.pleme.io/v1alpha1/ComputeUnit` CR materializer, the future
1908 /// per-Servico OTel collector-config emit).
1909 ///
1910 /// Prior to this lift the `.servicos` field was accessed inline at
1911 /// five production sites — the compound-code-path `has_code =
1912 /// !caixa.bibliotecas().is_empty() || !caixa.exe().is_empty() ||
1913 /// !caixa.servicos.is_empty()` OR-fold on the
1914 /// [`crate::LayoutError::SupervisorOwnsCode`] /
1915 /// `AplicacaoOwnsCode` kind-coherence gate, the Servico-arm
1916 /// `caixa.servicos.is_empty()`
1917 /// [`crate::LayoutError::ServicoWithoutServicos`] gate, the
1918 /// per-entry `for p in &caixa.servicos`
1919 /// `MissingEntry`/`ServicoOutsideDir` walk, the
1920 /// [`Self::declared_foreign_code_slots`]'s
1921 /// `!self.servicos.is_empty()` arm on the `ForeignCodeSlot` gate,
1922 /// and the [`crate::require_single_servico`] V0 count gate's
1923 /// `caixa.servicos.len() == 1` / `caixa.servicos.len()` count
1924 /// projection (both the accept-arm predicate and the
1925 /// diagnostic-carrying `ServicoCountMismatch { count }`
1926 /// projection) — five open-coded field-accesses across three
1927 /// crates that expressed no compile-time link back to the typed
1928 /// slot. A future extension of the `:servicos` axis to a richer
1929 /// component surface — a per-`:servicos` structured
1930 /// `ServicoEntry { path, world, capabilities }` at the storage
1931 /// layer once the substrate absorbs the per-component WIT-world +
1932 /// capability-set tuple the CAIXA-SDLC §I Servico roadmap
1933 /// acknowledges, a per-registry `:servicos` allowlist the M4 CR
1934 /// materializer enforces per-CR (the "cluster policy demands every
1935 /// Servico declare an explicit `:world`" arm), a promotion of the
1936 /// plain `Vec<String>` byte-string list to a richer
1937 /// `Vec<ComputeUnitPath>` newtype discriminated on the
1938 /// `servicos/<nome>.computeunit.yaml`-shape grammar the layout's
1939 /// `starts_with(servicos_dir)` fence and the
1940 /// [`crate::render::is_computeunit_yaml_extension`] predicate
1941 /// already resolve through, a promotion of the V0 singleton
1942 /// contract to a multi-component `Vec<ComputeUnitPath>` past the M5
1943 /// component-model multi-world boundary — would have had to be
1944 /// threaded through all five open-coded copies in lockstep or the
1945 /// layout gate, the shape validator, the V0 count gate, and the
1946 /// `feira chart` / `feira deploy` entry-point walks would silently
1947 /// disagree on which ComputeUnit-CR paths a given [`Caixa`]
1948 /// resolves to (an author's `:servicos ("servicos/foo.computeunit.
1949 /// yaml")` would satisfy layout while `feira chart` silently
1950 /// packaged a drifted other list, or vice versa). Lifting the
1951 /// resolution to a typed method on the substrate primitive means
1952 /// every downstream consumer of the caixa's per-`Caixa`
1953 /// ComputeUnit-CR-source surface reaches for exactly one typed
1954 /// dispatch — the resolver's accept-set migrates as a unit on any
1955 /// future axis addition.
1956 ///
1957 /// Fifth and final outer top-level [`Caixa`] `&[T]`-return slice-
1958 /// accessor — folds on the "outer [`Caixa`] `&[T]` slice"
1959 /// projection pattern [`Self::autores`] (b5d813f) opened,
1960 /// [`Self::etiquetas`] (78c7d3c) folded on, [`Self::bibliotecas`]
1961 /// (8a36c23) closed the universal-axis text-tag family of, and
1962 /// [`Self::exe`] (65d9527) opened the foreign-code-slot sub-family
1963 /// of. Closes the outer-`Caixa` foreign-code-slot `&[T]` sub-family
1964 /// — with `:bibliotecas`, `:exe`, and `:servicos` now each carrying
1965 /// a substrate-canonical slice accessor, the trio of code-surface
1966 /// list slots the [`Self::validate_code_paths`] per-slot dispatch
1967 /// tuple carries is complete on the typed dispatch surface (the
1968 /// internal `[(":bibliotecas", &self.bibliotecas, ..), (":exe",
1969 /// &self.exe, ..), (":servicos", &self.servicos, ..)]` per-slot
1970 /// dispatch tuple's homogeneous `&Vec<String>`-typed shape blocks a
1971 /// per-element accessor swap in isolation — a future companion lift
1972 /// promotes the tuple's element type to `&[String]` and threads the
1973 /// triple of typed dispatches through as a unit). Sibling in shape
1974 /// to the peer per-`:supervisor`
1975 /// [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
1976 /// per-`:placement` [`crate::aplicacao::Placement::clusters`]
1977 /// (a6e18d7), per-`:membros`
1978 /// [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
1979 /// per-`:contratos`
1980 /// [`crate::aplicacao::AplicacaoSpec::contratos`] (0dcc926), and
1981 /// per-`:upgrade-from :instructions`
1982 /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
1983 /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
1984 /// typed-slot list axes, extended here to the outer top-level
1985 /// [`Caixa`] universal-axis surface. Returns `&[String]` (not
1986 /// `&Vec<String>`) because every downstream consumer of the
1987 /// ComputeUnit-CR-source list treats it as a read-only sequence —
1988 /// the slice-view is the narrowest borrow that supports every
1989 /// present + roadmapped consumer (`.iter()`, `.len()`,
1990 /// `.is_empty()`, `.first()`) without leaking the backing `Vec`'s
1991 /// grow/push/reserve surface no consumer of the typed view reaches
1992 /// for (the storage-side `Vec` remains reachable through the
1993 /// `pub servicos` field for the mutation-carrying serde round-trip
1994 /// and per-test fixture-mutation paths, and for the
1995 /// [`Self::validate_code_paths`] per-slot dispatch tuple whose
1996 /// homogeneous-element-type shape carries the raw field access
1997 /// until the trio-closure lift promotes the tuple as a unit).
1998 /// Named `servicos()` to match the storage field's name; the
1999 /// accessor's identity maps onto the canonical CAIXA-SDLC §I
2000 /// vocabulary the slot's docstring already carries.
2001 #[must_use]
2002 pub const fn servicos(&self) -> &[String] {
2003 self.servicos.as_slice()
2004 }
2005
2006 /// Substrate-canonical per-`Caixa` `:deps` universal-axis
2007 /// runtime-dependency-declaration-list slice-accessor every consumer
2008 /// of the top-level manifest's runtime-dep-graph axis keys off —
2009 /// returns the author-declared `:deps` list verbatim as a `&[Dep]`
2010 /// slice-view over the same backing buffer the raw
2011 /// `self.deps.as_slice()` field access borrows from. Empty-list-
2012 /// carrying (`:deps` is a default-empty axis every `defcaixa` form
2013 /// supplies with an empty `()` when unset; the [`Self::from_lisp`]
2014 /// derive folds an omitted `:deps` through `#[serde(default)]` to
2015 /// `Vec::new()`, so a `Caixa` past parse definitionally carries a
2016 /// `Vec<Dep>` slot — possibly empty — and the returned `&[Dep]`
2017 /// degenerates to an empty slice on that arm without any silent
2018 /// `None` collapse).
2019 ///
2020 /// The `:deps` slot carries the universal-axis runtime dependency
2021 /// list every kind of caixa emits under (CAIXA-SDLC §I — the author-
2022 /// facing surface every `defcaixa` form supplies alongside `:nome` /
2023 /// `:versao` / `:kind`; the substrate-wide runtime-closure-input axis
2024 /// every downstream resolver-facing artifact emits under) — the
2025 /// typed slot's `Vec<Dep>` accept-set (empty-`:nome` rejected through
2026 /// [`DepError::NomeEmpty`], non-DNS-1123-label `:nome` rejected
2027 /// through [`DepError::NomeInvalid`], malformed `:versao` rejected
2028 /// through [`DepError::VersaoInvalid`], empty `:fonte.repo` rejected
2029 /// through [`DepError::FonteRepoEmpty`], within-list duplicate `:nome`
2030 /// rejected through [`DepError::DuplicateNome { list: ":deps" }`])
2031 /// maps onto every load-bearing downstream consumer the substrate
2032 /// carries — the [`Self::validate_deps`] per-entry
2033 /// [`Dep::validate`] + within-list dedup walk at
2034 /// caixa-core/src/manifest.rs, the [`crate::dep::validate_no_self_dep`]
2035 /// cross-list self-reference gate at caixa-core/src/layout.rs that
2036 /// checks each entry against the caixa's own `:nome`, the
2037 /// caixa-resolver `for dep in &root.deps` closure walk at
2038 /// caixa-resolver/src/resolve.rs that seeds every git-clone target
2039 /// through the resolver's [`crate::Dep`]-keyed pipeline, the
2040 /// caixa-crd `caixa.deps.iter().map(dep_into_ref).collect()` fold at
2041 /// caixa-crd/src/conversion.rs that materializes each entry into the
2042 /// K8s `Caixa` CR's `spec.deps` field, every future per-`Caixa`
2043 /// resolver-facing renderer the CAIXA-SDLC §I roadmap acknowledges
2044 /// (the future per-cluster runtime-closure-audit overlay the M4 CR
2045 /// materializer resolves per-CR, the future `lacre.lisp` BLAKE3-
2046 /// closure emit walk the caixa-resolver docstring roadmaps).
2047 ///
2048 /// First outer top-level [`Caixa`] `&[Dep]`-return slice-accessor —
2049 /// opens the outer-`Caixa` dependency-slot `&[Dep]` sub-family the
2050 /// sibling `:deps-dev` future lift closes on. Peer of the closed
2051 /// outer-`Caixa` foreign-code-slot `&[String]` sub-family
2052 /// ([`Self::bibliotecas`] 8a36c23, [`Self::exe`] 65d9527,
2053 /// [`Self::servicos`] 611f78b) and the outer-`Caixa` universal-axis
2054 /// text-tag family ([`Self::autores`] b5d813f, [`Self::etiquetas`]
2055 /// 78c7d3c) — extends the "outer [`Caixa`] `&[T]` slice" projection
2056 /// pattern onto a novel element-type axis (`Dep` composite vs the
2057 /// prior sibling family's `String` scalar). Sibling in shape to the
2058 /// peer per-`:supervisor`
2059 /// [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
2060 /// per-`:placement` [`crate::aplicacao::Placement::clusters`]
2061 /// (a6e18d7), per-`:membros`
2062 /// [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
2063 /// per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
2064 /// (0dcc926), and per-`:upgrade-from :instructions`
2065 /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
2066 /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
2067 /// typed-slot list axes, extended here to the outer top-level
2068 /// [`Caixa`] universal-axis dep-graph surface. Returns `&[Dep]`
2069 /// (not `&Vec<Dep>`) because every downstream consumer of the
2070 /// runtime-dep list treats it as a read-only sequence — the slice-
2071 /// view is the narrowest borrow that supports every present +
2072 /// roadmapped consumer (`.iter()`, `.len()`, `.is_empty()`) without
2073 /// leaking the backing `Vec`'s grow/push/reserve surface no consumer
2074 /// of the typed view reaches for (the storage-side `Vec` remains
2075 /// reachable through the `pub deps` field for the mutation-carrying
2076 /// serde round-trip and per-test fixture-mutation paths). Named
2077 /// `deps()` to match the storage field's name; the accessor's
2078 /// identity maps onto the canonical CAIXA-SDLC §I vocabulary the
2079 /// slot's docstring already carries.
2080 #[must_use]
2081 pub const fn deps(&self) -> &[Dep] {
2082 self.deps.as_slice()
2083 }
2084
2085 /// Substrate-canonical per-`Caixa` `:deps-dev` universal-axis
2086 /// development-only-dependency-declaration-list slice-accessor every
2087 /// consumer of the top-level manifest's dev-dep-graph axis keys off —
2088 /// returns the author-declared `:deps-dev` list verbatim as a `&[Dep]`
2089 /// slice-view over the same backing buffer the raw
2090 /// `self.deps_dev.as_slice()` field access borrows from. Empty-list-
2091 /// carrying (`:deps-dev` is a default-empty axis every `defcaixa`
2092 /// form supplies with an empty `()` when unset; the
2093 /// [`Self::from_lisp`] derive folds an omitted `:deps-dev` through
2094 /// `#[serde(default)]` to `Vec::new()`, so a `Caixa` past parse
2095 /// definitionally carries a `Vec<Dep>` slot — possibly empty — and
2096 /// the returned `&[Dep]` degenerates to an empty slice on that arm
2097 /// without any silent `None` collapse).
2098 ///
2099 /// The `:deps-dev` slot carries the universal-axis dev-only
2100 /// dependency list every kind of caixa emits under (CAIXA-SDLC §I —
2101 /// the author-facing sibling of `:deps` that every `defcaixa` form
2102 /// supplies to declare tests / lint / bench closures the runtime
2103 /// `:deps` axis does not carry; the substrate-wide dev-closure-input
2104 /// axis every downstream test-facing artifact emits under, matching
2105 /// Cargo's `[dev-dependencies]` table's dev-time-only visibility
2106 /// contract) — the typed slot's `Vec<Dep>` accept-set (empty-`:nome`
2107 /// rejected through [`DepError::NomeEmpty`], non-DNS-1123-label
2108 /// `:nome` rejected through [`DepError::NomeInvalid`], malformed
2109 /// `:versao` rejected through [`DepError::VersaoInvalid`], empty
2110 /// `:fonte.repo` rejected through [`DepError::FonteRepoEmpty`],
2111 /// within-list duplicate `:nome` rejected through
2112 /// [`DepError::DuplicateNome { list: ":deps-dev" }`]) maps onto every
2113 /// load-bearing downstream consumer the substrate carries — the
2114 /// [`Self::validate_deps`] per-entry [`Dep::validate`] + within-list
2115 /// dedup walk at caixa-core/src/manifest.rs, the
2116 /// [`crate::dep::validate_no_self_dep`] cross-list self-reference
2117 /// gate at caixa-core/src/layout.rs that checks each entry against
2118 /// the caixa's own `:nome`, the caixa-resolver
2119 /// `for dep in &root.deps_dev` closure walk at
2120 /// caixa-resolver/src/resolve.rs that seeds every dev-only git-clone
2121 /// target through the resolver's [`crate::Dep`]-keyed pipeline, and
2122 /// every future per-`Caixa` resolver-facing renderer the CAIXA-SDLC
2123 /// §I roadmap acknowledges (the future per-cluster dev-closure-audit
2124 /// overlay the M4 CR materializer resolves per-CR, the future
2125 /// `lacre.lisp` BLAKE3-closure emit walk the caixa-resolver docstring
2126 /// roadmaps).
2127 ///
2128 /// Second outer top-level [`Caixa`] `&[Dep]`-return slice-accessor —
2129 /// closes the outer-`Caixa` dependency-slot `&[Dep]` sub-family the
2130 /// sibling [`Self::deps`] (ad34b4e) opened on. The two accessors
2131 /// jointly close the two-list dep-graph surface every downstream
2132 /// resolver-facing consumer keys off (runtime `:deps` +
2133 /// dev-only `:deps-dev`, the canonical Cargo-shaped dependency-table
2134 /// pair the [`Self::validate_deps`] gate already walks in canonical
2135 /// order). Peer of the closed outer-`Caixa` foreign-code-slot
2136 /// `&[String]` sub-family ([`Self::bibliotecas`] 8a36c23,
2137 /// [`Self::exe`] 65d9527, [`Self::servicos`] 611f78b) and the outer-
2138 /// `Caixa` universal-axis text-tag family ([`Self::autores`]
2139 /// b5d813f, [`Self::etiquetas`] 78c7d3c) — folds the "outer
2140 /// [`Caixa`] `&[T]` slice" projection pattern onto the sibling
2141 /// dev-dep composite-element axis (`Dep` composite, matching the
2142 /// [`Self::deps`] element type). Sibling in shape to the peer
2143 /// per-`:supervisor` [`crate::supervisor::SupervisorSpec::children`]
2144 /// (bc92bce), per-`:placement`
2145 /// [`crate::aplicacao::Placement::clusters`] (a6e18d7),
2146 /// per-`:membros` [`crate::aplicacao::AplicacaoSpec::membros`]
2147 /// (6c77e36), per-`:contratos`
2148 /// [`crate::aplicacao::AplicacaoSpec::contratos`] (0dcc926), and
2149 /// per-`:upgrade-from :instructions`
2150 /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
2151 /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
2152 /// typed-slot list axes, folded here to the outer top-level
2153 /// [`Caixa`] universal-axis dev-dep-graph surface. Returns `&[Dep]`
2154 /// (not `&Vec<Dep>`) because every downstream consumer of the
2155 /// dev-dep list treats it as a read-only sequence — the slice-view
2156 /// is the narrowest borrow that supports every present +
2157 /// roadmapped consumer (`.iter()`, `.len()`, `.is_empty()`) without
2158 /// leaking the backing `Vec`'s grow/push/reserve surface no consumer
2159 /// of the typed view reaches for (the storage-side `Vec` remains
2160 /// reachable through the `pub deps_dev` field for the mutation-
2161 /// carrying serde round-trip and per-test fixture-mutation paths).
2162 /// Named `deps_dev()` to match the storage field's `snake_case` name;
2163 /// the kebab-case author-surface tag `:deps-dev` is the same axis
2164 /// after tatara-lisp's kebab↔snake fold and the accessor's identity
2165 /// maps onto the canonical CAIXA-SDLC §I vocabulary the slot's
2166 /// docstring already carries.
2167 #[must_use]
2168 pub const fn deps_dev(&self) -> &[Dep] {
2169 self.deps_dev.as_slice()
2170 }
2171
2172 /// Substrate-canonical per-[`Caixa`] typed-dispatch read accessor
2173 /// every consumer that walks one of the two dep-list axes keyed on a
2174 /// [`crate::dep::DepList`] discriminant reaches for — routes the
2175 /// `(list: DepList) -> &[Dep]` projection through one typed method on
2176 /// the substrate primitive rather than the prior open-coded
2177 /// `match list { Prod => caixa.deps(), Dev => caixa.deps_dev() }`
2178 /// inline dispatch every per-axis walker would otherwise carry.
2179 /// Returns the author-declared per-list `Vec<Dep>` verbatim as a
2180 /// `&[Dep]` slice-view over the same backing buffer the sibling
2181 /// [`Self::deps`] (`Prod`) / [`Self::deps_dev`] (`Dev`) per-slot
2182 /// accessors borrow from, preserving the empty-list-carrying invariant
2183 /// each per-slot accessor already establishes (`:deps` / `:deps-dev`
2184 /// are default-empty axes every `defcaixa` form supplies with an empty
2185 /// `()` when unset; the [`Self::from_lisp`] derive folds an omitted
2186 /// list through `#[serde(default)]` to `Vec::new()`, so both arms
2187 /// definitionally carry a `Vec<Dep>` slot — possibly empty — and the
2188 /// returned `&[Dep]` degenerates to an empty slice on either arm
2189 /// without any silent `None` collapse).
2190 ///
2191 /// The [`crate::dep::DepList`] closed-set typed enum is the
2192 /// substrate's canonical discriminator for the "runtime-closure
2193 /// `:deps` vs dev-only-closure `:deps-dev`" axis every dep-list
2194 /// consumer dispatches on — the compiler-checked exhaustiveness on
2195 /// the enum's `match` arms is the build-time guarantee that no future
2196 /// per-list read-site regresses to a bare-`bool`-flag inline dispatch
2197 /// that a future third dep-list axis (a `:deps-build` build-only
2198 /// closure once the substrate grows cross-artifact heterogeneous
2199 /// dep-graphs, per CAIXA-SDLC §I) would silently split at every
2200 /// consumer. Prior to this the read side carried two per-slot
2201 /// accessors ([`Self::deps`] ad34b4e, [`Self::deps_dev`]) and no
2202 /// typed dispatch that a per-axis walker could parametrise on, so
2203 /// every per-list walker (the [`Self::validate_deps`] per-list
2204 /// [`crate::render::insert_first_seen`] dedup walk, a future
2205 /// `feira app graph` per-list dep summary, a future M4 per-cluster
2206 /// dev-closure-audit overlay the CR materializer resolves per-CR)
2207 /// open-coded the same two-block "run over `:deps`, then run over
2208 /// `:deps-dev`" pattern — a silent duplication that a future third
2209 /// dep-list axis would have had to grow a third block at every site.
2210 ///
2211 /// Peer of the sibling [`Self::push_dep`] typed-mutation dispatch
2212 /// (359fba5) — closes the two-side dispatch symmetry on the outer
2213 /// [`Caixa`] two-list dep-graph surface: `push_dep` on the mutation
2214 /// side, `deps_of` on the read side, both keyed on the same
2215 /// [`crate::dep::DepList`] discriminator. Same "one typed dispatch on
2216 /// the substrate primitive, thin projections at each consumer"
2217 /// discipline the sibling per-slot read accessors ([`Self::nome`]
2218 /// e6b7d97, [`Self::versao`], [`Self::kind`]) carry — extended onto
2219 /// the outer-[`Caixa`] typed-dispatch read surface.
2220 #[must_use]
2221 pub fn deps_of(&self, list: crate::dep::DepList) -> &[Dep] {
2222 match list {
2223 crate::dep::DepList::Prod => self.deps(),
2224 crate::dep::DepList::Dev => self.deps_dev(),
2225 }
2226 }
2227
2228 /// Substrate-canonical per-[`Caixa`] typed-mutation dispatch every
2229 /// consumer that appends to one of the two dep-list axes keys off
2230 /// — routes the `(list: DepList, dep: Dep)` tuple through one typed
2231 /// method on the substrate primitive rather than the prior
2232 /// `feira add`-side open-coded `if self.dev { &mut caixa.deps_dev }
2233 /// else { &mut caixa.deps }` inline dispatch + open-coded
2234 /// `.iter().any(|d| d.nome == …)` dup-check cascade. Refuses the
2235 /// mutation with the canonical typed [`DepError::DuplicateNome`] on
2236 /// a within-list name collision — the same `list: &'static str`
2237 /// diagnostic shape [`Self::validate_deps`]'s per-list
2238 /// [`crate::render::insert_first_seen`] walk raises on the peer
2239 /// parse-time within-list dedup axis, so a future author reading a
2240 /// `feira add` refusal and a `feira build` refusal reaches for the
2241 /// same corrective surface without switching diagnostic idioms.
2242 ///
2243 /// The two-arm [`crate::dep::DepList`] enum is the substrate's
2244 /// closed-set typed carrier for the "runtime-closure `:deps` vs
2245 /// dev-only-closure `:deps-dev`" axis every dep-list consumer
2246 /// dispatches on — the compiler-checked exhaustiveness on the
2247 /// enum's `match` arms is the build-time guarantee that no future
2248 /// per-list mutation-site regresses to a bare-`bool`-flag
2249 /// (`is_dev: bool`) inline dispatch that a future third
2250 /// dep-list axis (a `:deps-build` build-only closure once the
2251 /// substrate grows cross-artifact heterogeneous dep-graphs, per
2252 /// CAIXA-SDLC §I) would silently split at every consumer.
2253 ///
2254 /// Same "one typed dispatch on the substrate primitive, thin
2255 /// projections at each consumer" discipline the sibling per-slot
2256 /// read accessors ([`Self::deps`] ad34b4e, [`Self::deps_dev`],
2257 /// [`Self::nome`] e6b7d97, [`Self::versao`], [`Self::kind`])
2258 /// carry — extended onto the outer-[`Caixa`] typed-mutation surface,
2259 /// the substrate's first typed-mutation dispatch on the top-level
2260 /// manifest. The prior `feira add` open-coded `&mut caixa.deps` /
2261 /// `&mut caixa.deps_dev` inline field-access + `bail!` string-
2262 /// diagnostic path routed no through-line back to the typed slot,
2263 /// so a future extension of either dep-list axis to a richer author
2264 /// surface (a per-cluster override the operator pins through a
2265 /// future `:placement`-scoped dep-list slot the CAIXA-SDLC §I
2266 /// roadmap acknowledges, an M4
2267 /// `mesh.pleme.io/v1alpha1/Caixa` CR materializer's per-CR
2268 /// admission-webhook that normalized the list at admission time)
2269 /// would have had to be threaded through the `feira add` mutation
2270 /// site in lockstep with every read consumer or one path would
2271 /// silently disagree with the other on which list a given dep lands
2272 /// in. Lifting the resolution rule to a typed method on the
2273 /// substrate primitive means every downstream dep-list-mutating
2274 /// consumer of the top-level manifest reaches for exactly one typed
2275 /// dispatch — the resolver's accept-set migrates as a unit on any
2276 /// future axis addition.
2277 ///
2278 /// # Errors
2279 ///
2280 /// Returns [`DepError::DuplicateNome`] with `list = list.as_str()`
2281 /// when another entry in the same list already carries the same
2282 /// `:nome` — the mutation is refused and the caller can surface the
2283 /// typed diagnostic to the author (the `feira add` verb routes the
2284 /// error through `anyhow::Error::from`, which preserves the
2285 /// canonical `#[error(...)]`-templated diagnostic body).
2286 pub fn push_dep(&mut self, list: crate::dep::DepList, dep: Dep) -> Result<(), DepError> {
2287 let target = match list {
2288 crate::dep::DepList::Prod => &mut self.deps,
2289 crate::dep::DepList::Dev => &mut self.deps_dev,
2290 };
2291 if target.iter().any(|d| d.nome() == dep.nome()) {
2292 return Err(DepError::DuplicateNome {
2293 nome: dep.nome().to_string(),
2294 list: list.as_str(),
2295 });
2296 }
2297 target.push(dep);
2298 Ok(())
2299 }
2300
2301 /// Substrate-canonical per-`Caixa` `:limits` M2 typed-slot outer-
2302 /// composite Lunatic-per-process wasm32-sandboxing-composite optional-
2303 /// composite-reference accessor every consumer of the top-level
2304 /// manifest's per-Servico [`LimitsSpec`] outer-composite reader keys
2305 /// off — returns the author-declared `:limits` typed composite
2306 /// verbatim as an `Option<&LimitsSpec>` reference over the same
2307 /// backing storage the raw `self.limits.as_ref()` field access
2308 /// borrows from, with `None` naming the "no `:limits` block
2309 /// authored — every per-axis Lunatic-sandbox cap defers to the
2310 /// wasm-engine-default arm named on the per-axis
2311 /// [`LimitsSpec::memory`] / [`LimitsSpec::fuel`] /
2312 /// [`LimitsSpec::wall_clock`] / [`LimitsSpec::cpu`] scalar-accessor
2313 /// docstrings" partition every downstream Servico-M2-overlay
2314 /// emitter treats as "emit nothing" and the sibling
2315 /// [`crate::StandardLayout::verify`] per-`:limits` shape gate
2316 /// treats as "skip the per-axis
2317 /// [`crate::LimitsError::MemoryZero`] / `MemoryBelowWasm32Page` /
2318 /// `FuelZero` / `WallClockZero` / `CpuZero` refusal cascade".
2319 ///
2320 /// The outer `:limits` slot carries the M2 Servico-runtime typed
2321 /// composite — the load-bearing container of every Lunatic-shaped
2322 /// per-process wasm32-sandbox cap axis every long-running wasm
2323 /// component's runtime dispatches on (INSPIRATIONS §III.1 —
2324 /// Lunatic per-process linear-memory / fuel / wall-clock /
2325 /// millicore cap primitives translated onto pleme-io's typed
2326 /// `:limits :memory` / `:limits :fuel` / `:limits :wall-clock` /
2327 /// `:limits :cpu` sub-slot axes; CAIXA-SDLC §II — the typed-M2
2328 /// slot algebra the wasm-engine + `pleme-computeunit` Helm-library
2329 /// chart both fan on). Every per-`:limits` axis threads through a
2330 /// lifted per-slot accessor on the [`LimitsSpec`] type: the
2331 /// [`LimitsSpec::memory`] wasm32 linear-memory byte-cap scalar
2332 /// accessor, the [`LimitsSpec::fuel`] wasmtime fuel-cap scalar
2333 /// accessor, the [`LimitsSpec::wall_clock`] per-call wall-clock
2334 /// deadline scalar accessor, and the [`LimitsSpec::cpu`]
2335 /// K8s-millicore soft-CPU-share scalar accessor. Every downstream
2336 /// consumer that reaches for a limits axis first passes through
2337 /// this outer accessor onto the composite and then dispatches
2338 /// onto the per-axis accessor — the two-level dispatch means
2339 /// every per-`:limits` reader now routes through a typed dispatch
2340 /// on the substrate primitive at both altitudes.
2341 ///
2342 /// Prior to this lift the `.limits` `Option<LimitsSpec>` composite
2343 /// was accessed inline at three production sites — the
2344 /// [`crate::StandardLayout::verify`] per-`:limits` shape gate's
2345 /// `if let Some(l) = &caixa.limits { … }` traversal head
2346 /// (caixa-core/src/layout.rs:882, which drives the per-axis
2347 /// refusal cascade on the composite: the `LimitsError::MemoryZero`
2348 /// / `MemoryBelowWasm32Page` / `MemoryExceedsWasm32Max` /
2349 /// `FuelZero` / `FuelExceedsMax` / `WallClockZero` /
2350 /// `WallClockExceedsMax` / `CpuZero` / `CpuExceedsMax` refusals
2351 /// [`LimitsSpec::validate`] fans onto), the
2352 /// [`crate::render::servico_m2_overlay`] per-Servico M2 overlay
2353 /// emitter's `if let Some(limits) = &caixa.limits { … }` traversal
2354 /// head (caixa-core/src/render.rs:18504, which drives the
2355 /// `M2_KEY_LIMITS`-keyed `limits.is_empty()`-gated `serde_yaml`
2356 /// projection every `caixa-helm` / `caixa-flux` Servico values-
2357 /// block emitter fans on), and the
2358 /// [`Self::declared_servico_slots`] per-Servico M2 declared-slot-
2359 /// set enumerator's `self.limits.is_some()` presence probe
2360 /// (caixa-core/src/manifest.rs:1788, which drives the
2361 /// `M2_AUTHOR_KEY_LIMITS` kebab-case author-label push every
2362 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
2363 /// gate reads) — three open-coded outer-field accesses that
2364 /// expressed no compile-time link back to the typed slot at the
2365 /// [`Caixa`] altitude. A future extension of the `:limits` outer
2366 /// axis to a richer author surface (a multi-`:limits` list the M4
2367 /// CR materializer resolves per-CR at admission time so a Servico
2368 /// can expose a compute-heavy + IO-heavy limits pair, a per-
2369 /// cluster `:limits-overrides` slot the operator pins so a
2370 /// cluster-specific policy can tighten a caixa-declared cap
2371 /// without re-authoring the `caixa.lisp`, a promotion of the
2372 /// plain `Option<LimitsSpec>` to a richer
2373 /// `{static, dynamic}` partition once the wasm-engine's runtime-
2374 /// resolved dynamic-cap surface lands) would have had to be
2375 /// threaded through all three open-coded copies in lockstep or
2376 /// one consumer would silently disagree with the peers on which
2377 /// limits composite a given Caixa resolves to — the layout gate's
2378 /// per-axis bracket-dispatch seed reading the raw slot while the
2379 /// peer `servico_m2_overlay` emitter read an operator-resolved
2380 /// slot would silently split the build-time sandbox-shape gate
2381 /// from the runtime `ComputeUnit` CR emission gate, a three-
2382 /// consumer split at the layout gate, the M2 overlay emitter, and
2383 /// the declared-slot enumerator far from the source `caixa.lisp`
2384 /// with no field naming the limits-drift root cause. Lifting the
2385 /// resolution rule to a typed method on the substrate primitive
2386 /// means every downstream consumer of the caixa's per-`Caixa`
2387 /// Lunatic-sandboxing outer-composite surface reaches for exactly
2388 /// one typed dispatch — the resolver's accept-set migrates as a
2389 /// unit on any future axis addition.
2390 ///
2391 /// First outer top-level [`Caixa`] `Option<&Composite>`-return
2392 /// composite-reference accessor — opens the outer-`Caixa`
2393 /// `Option<&Composite>` composite-reference projection pattern the
2394 /// sibling per-`Caixa` `:behavior` [`crate::BehaviorSpec`] /
2395 /// `:politicas` [`crate::aplicacao::MeshPolicy`] / `:placement`
2396 /// [`crate::aplicacao::Placement`] / `:entrada`
2397 /// [`crate::aplicacao::Entrada`] future outer-composite lifts
2398 /// fold on. Peer of the M3 mesh-slot outer-composite family the
2399 /// sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
2400 /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2401 /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2402 /// accessors already close on the outer [`crate::AplicacaoSpec`]
2403 /// altitude — extends that "one typed dispatch on the substrate
2404 /// primitive, thin projections at each consumer" discipline onto
2405 /// the outer top-level [`Caixa`] altitude, opening the M2 Servico-
2406 /// runtime slot family's outer-composite axis. Returns
2407 /// `Option<&LimitsSpec>` (not the owning composite by copy or
2408 /// clone) because every downstream consumer of the limits
2409 /// composite treats it as a read-only per-axis dispatch source —
2410 /// the reference-view is the narrowest borrow that supports every
2411 /// present + roadmapped consumer (per-axis accessor dispatch,
2412 /// `.is_empty()`-gated overlay projection, presence-probe early
2413 /// return on the "author-omitted `:limits` ⇒ engine-default
2414 /// applies" partition) without cloning the composite through
2415 /// every consumer's fast path. The `Option` half of the return-
2416 /// type preserves the load-bearing "author-omitted `:limits` ⇒
2417 /// engine-default applies" partition (not a default composite the
2418 /// downstream must reject on emptiness) — the accessor projects
2419 /// the raw `Option<LimitsSpec>` slot's presence bit through the
2420 /// reference-return unchanged. Named `limits()` to match the
2421 /// storage field's name verbatim and the tatara-lisp author-
2422 /// surface term (`:limits`) the field's own docstring already
2423 /// carries.
2424 #[must_use]
2425 pub const fn limits(&self) -> Option<&LimitsSpec> {
2426 self.limits.as_ref()
2427 }
2428
2429 /// Substrate-canonical per-`Caixa` `:behavior` M2 typed-slot outer-
2430 /// composite OTP-`gen_server`-shaped callback-table optional-
2431 /// composite-reference accessor every consumer of the top-level
2432 /// manifest's per-Servico [`BehaviorSpec`] outer-composite reader
2433 /// keys off — returns the author-declared `:behavior` typed
2434 /// composite verbatim as an `Option<&BehaviorSpec>` reference over
2435 /// the same backing storage the raw `self.behavior.as_ref()` field
2436 /// access borrows from, with `None` naming the "no `:behavior`
2437 /// block authored — every per-callback OTP-shaped hook defers to
2438 /// the wasm-engine's runtime default arm named on the per-axis
2439 /// [`BehaviorSpec::on_init`] / [`BehaviorSpec::on_call`] /
2440 /// [`BehaviorSpec::on_cast`] / [`BehaviorSpec::on_info`] /
2441 /// [`BehaviorSpec::on_state_change`] /
2442 /// [`BehaviorSpec::on_terminate`] scalar-accessor docstrings"
2443 /// partition every downstream Servico-M2-overlay emitter treats as
2444 /// "emit nothing" and the sibling [`crate::StandardLayout::verify`]
2445 /// per-`:behavior` shape gate treats as "skip the per-arm
2446 /// [`crate::behavior::BehaviorError`] refusal cascade + the
2447 /// per-callback on-disk `MissingEntry` existence check".
2448 ///
2449 /// The outer `:behavior` slot carries the M2 Servico-runtime typed
2450 /// composite — the load-bearing container of every OTP-shaped
2451 /// per-Servico lifecycle-callback path axis every long-running wasm
2452 /// component's runtime dispatches on (INSPIRATIONS §II.3 — Erlang/
2453 /// OTP `gen_server:init/1` / `handle_call/3` / `handle_cast/2` /
2454 /// `handle_info/2` / `code_change/3` / `terminate/2` primitives
2455 /// translated onto pleme-io's typed `:behavior :on-init` /
2456 /// `:on-call` / `:on-cast` / `:on-info` / `:on-state-change` /
2457 /// `:on-terminate` sub-slot axes; CAIXA-SDLC §II — the typed-M2
2458 /// slot algebra the wasm-engine + `pleme-computeunit` Helm-library
2459 /// chart both fan on). Every per-`:behavior` axis threads through a
2460 /// lifted per-callback accessor on the [`BehaviorSpec`] type
2461 /// (9b4ecde / d66c702 / 156ddbe / 99616ac / 4846cef / 701add7).
2462 /// Every downstream consumer that reaches for a behavior axis
2463 /// first passes through this outer accessor onto the composite
2464 /// and then dispatches onto the per-callback accessor — the
2465 /// two-level dispatch means every per-`:behavior` reader now
2466 /// routes through a typed dispatch on the substrate primitive at
2467 /// both altitudes.
2468 ///
2469 /// Composes cross-slot with the M2 `:upgrade-from` gate: the
2470 /// [`crate::upgrade::validate_upgrade_from_against_behavior`]
2471 /// cross-slot composition gate at [`crate::StandardLayout::verify`]
2472 /// keys the "per-version `:state-change` instruction must have a
2473 /// `:on-state-change` callback" precondition off this accessor's
2474 /// composite (the callback-side counterpart to the
2475 /// `:upgrade-from :instructions :state-change :script` refusal at
2476 /// the appup-side). Threading that gate's traversal input through
2477 /// this accessor closes the cross-slot invariant on the substrate
2478 /// primitive, not on the raw field.
2479 ///
2480 /// Prior to this lift the `.behavior` `Option<BehaviorSpec>`
2481 /// composite was accessed inline at four production sites — the
2482 /// [`crate::StandardLayout::verify`] per-`:behavior` shape gate's
2483 /// `if let Some(b) = &caixa.behavior { … }` traversal head
2484 /// (caixa-core/src/layout.rs:896, which drives the per-arm
2485 /// `BehaviorError` refusal cascade + the per-callback on-disk
2486 /// [`crate::LayoutError::MissingEntry`] existence check under
2487 /// [`crate::render::LAYOUT_MISSING_ENTRY_KIND_BEHAVIOR_CALLBACK`]),
2488 /// the [`crate::upgrade::validate_upgrade_from_against_behavior`]
2489 /// cross-slot composition gate's `caixa.behavior.as_ref()`
2490 /// traversal-input feed (caixa-core/src/layout.rs:1008, which
2491 /// drives the `:state-change` ↔ `:on-state-change` precondition
2492 /// refusal), the [`crate::render::servico_m2_overlay`] per-Servico
2493 /// M2 overlay emitter's `if let Some(behavior) = &caixa.behavior
2494 /// { … }` traversal head (caixa-core/src/render.rs:18513, which
2495 /// drives the `M2_KEY_BEHAVIOR`-keyed `behavior.is_empty()`-gated
2496 /// `serde_yaml` projection every `caixa-helm` / `caixa-flux`
2497 /// Servico values-block emitter fans on), and the
2498 /// [`Self::declared_servico_slots`] per-Servico M2 declared-slot-
2499 /// set enumerator's `self.behavior.is_some()` presence probe
2500 /// (caixa-core/src/manifest.rs:1919, which drives the
2501 /// `M2_AUTHOR_KEY_BEHAVIOR` kebab-case author-label push every
2502 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
2503 /// gate reads) — four open-coded outer-field accesses that
2504 /// expressed no compile-time link back to the typed slot at the
2505 /// [`Caixa`] altitude. A future extension of the `:behavior`
2506 /// outer axis to a richer author surface (a per-callback overlay
2507 /// resolver the operator materializes at admission time so a
2508 /// cluster-specific policy can inject a per-callback tracing
2509 /// interceptor without re-authoring the `caixa.lisp`, a promotion
2510 /// of the plain `Option<BehaviorSpec>` to a richer `{static,
2511 /// dynamic}` partition once a runtime-resolved behavior-swap
2512 /// surface lands, the M4 per-callback middleware chain the
2513 /// caixa-operator's per-Servico admission webhook keys off) would
2514 /// have had to be threaded through all four open-coded copies in
2515 /// lockstep or one consumer would silently disagree with the
2516 /// peers on which behavior composite a given Caixa resolves to —
2517 /// the layout gate's per-callback existence-check seed reading
2518 /// the raw slot while the peer `servico_m2_overlay` emitter read
2519 /// an operator-resolved slot would silently split the build-time
2520 /// callback-shape gate from the runtime `ComputeUnit` CR emission
2521 /// gate from the cross-slot `:state-change` composition gate from
2522 /// the M2 declared-slot enumerator, a four-consumer split far
2523 /// from the source `caixa.lisp` with no field naming the
2524 /// behavior-drift root cause. Lifting the resolution rule to a
2525 /// typed method on the substrate primitive means every downstream
2526 /// consumer of the caixa's per-`Caixa` OTP-callback-table outer-
2527 /// composite surface reaches for exactly one typed dispatch — the
2528 /// resolver's accept-set migrates as a unit on any future axis
2529 /// addition.
2530 ///
2531 /// Second outer top-level [`Caixa`] `Option<&Composite>`-return
2532 /// composite-reference accessor — sibling to the opening
2533 /// [`Self::limits`] (b2bd9d7) accessor on the outer-`Caixa`
2534 /// `Option<&Composite>` composite-reference sub-family, extends
2535 /// the "one typed dispatch on the substrate primitive, thin
2536 /// projections at each consumer" discipline onto the second of
2537 /// the three M2 Servico-runtime slots. The remaining
2538 /// `Option<&Composite>` axes at the outer top-level [`Caixa`]
2539 /// altitude — the M3 mesh-slot family (`:politicas`,
2540 /// `:placement`, `:entrada` — already closed on the inner
2541 /// [`crate::AplicacaoSpec`] altitude via 534dc21 / 9abb8f0 /
2542 /// d32111c) — remain the future sibling lifts on the outer
2543 /// top-level projection. Returns `Option<&BehaviorSpec>` (not
2544 /// the owning composite by copy or clone) because every
2545 /// downstream consumer of the behavior composite treats it as a
2546 /// read-only per-callback dispatch source — the reference-view is
2547 /// the narrowest borrow that supports every present + roadmapped
2548 /// consumer (per-callback accessor dispatch, `.is_empty()`-gated
2549 /// overlay projection, presence-probe early return on the
2550 /// "author-omitted `:behavior` ⇒ runtime-default applies"
2551 /// partition, cross-slot `:state-change` composition input)
2552 /// without cloning the composite through every consumer's fast
2553 /// path. The `Option` half of the return-type preserves the
2554 /// load-bearing "author-omitted `:behavior` ⇒ runtime-default
2555 /// applies" partition (not a default composite the downstream
2556 /// must reject on emptiness) — the accessor projects the raw
2557 /// `Option<BehaviorSpec>` slot's presence bit through the
2558 /// reference-return unchanged. Named `behavior()` to match the
2559 /// storage field's name verbatim and the tatara-lisp author-
2560 /// surface term (`:behavior`) the field's own docstring already
2561 /// carries.
2562 #[must_use]
2563 pub const fn behavior(&self) -> Option<&crate::BehaviorSpec> {
2564 self.behavior.as_ref()
2565 }
2566
2567 /// Substrate-canonical per-`Caixa` `:politicas` M3 mesh-slot outer-
2568 /// composite MESH-COMPOSITION-shaped mesh-policy optional-composite-
2569 /// reference accessor every consumer of the top-level manifest's
2570 /// per-Aplicacao [`crate::aplicacao::MeshPolicy`] outer-composite
2571 /// reader keys off — returns the author-declared `:politicas` typed
2572 /// composite verbatim as an `Option<&MeshPolicy>` reference over the
2573 /// same backing storage the raw `self.politicas.as_ref()` field
2574 /// access borrows from, with `None` naming the "no `:politicas`
2575 /// block authored — every per-axis mesh-policy scalar defers to the
2576 /// cluster-default arm named on the per-axis
2577 /// [`crate::aplicacao::MeshPolicy::timeout`] /
2578 /// [`crate::aplicacao::MeshPolicy::retries`] /
2579 /// [`crate::aplicacao::MeshPolicy::circuit_breaker`] /
2580 /// [`crate::aplicacao::MeshPolicy::mtls_required`] /
2581 /// [`crate::aplicacao::MeshPolicy::rate_limit`] scalar-accessor
2582 /// docstrings" partition every downstream caixa-mesh /
2583 /// caixa-flux / caixa-helm Aplicacao-artifact emitter treats as
2584 /// "emit no per-`:politicas` overlay" and the sibling
2585 /// [`Self::aplicacao_view`] Aplicacao-composition seed folds through
2586 /// the [`crate::aplicacao::MeshPolicy::default`] cluster-default
2587 /// arm.
2588 ///
2589 /// The outer `:politicas` slot carries the M3 mesh-slot per-
2590 /// Aplicacao typed composite — the load-bearing container of every
2591 /// mesh-level policy axis every Cilium NetworkPolicy / Gateway API
2592 /// v1.x HTTPRoute / future M4 per-edge policy overlay emitter fans
2593 /// on (MESH-COMPOSITION §III.2 — the Aplicacao's typed mesh-policy
2594 /// composite; §V — the "no infinite blocking" per-call deadline +
2595 /// "sandboxing-by-default" mTLS-enforcement CSE invariants; §III.3
2596 /// — the typed inter-Servico contrato-edge overlay the per-`(:de,
2597 /// :para)` mesh renderer keys off). Every per-`:politicas` axis
2598 /// threads through a lifted per-slot accessor on the
2599 /// [`crate::aplicacao::MeshPolicy`] type: the
2600 /// [`crate::aplicacao::MeshPolicy::mtls_required`] (c0110f1) Cilium
2601 /// mTLS-enforcement toggle, the
2602 /// [`crate::aplicacao::MeshPolicy::retries`] (bdfb399) transient-
2603 /// failure retry budget, the [`crate::aplicacao::MeshPolicy::timeout`]
2604 /// (7073d0f) Gateway-API per-call deadline, the
2605 /// [`crate::aplicacao::MeshPolicy::circuit_breaker`] (b0e741a)
2606 /// Envoy-outlier-detection composite. Every downstream consumer
2607 /// that reaches for a mesh-policy axis first passes through this
2608 /// outer accessor onto the composite and then dispatches onto the
2609 /// per-axis accessor — the two-level dispatch means every per-
2610 /// `:politicas` reader now routes through a typed dispatch on the
2611 /// substrate primitive at both altitudes.
2612 ///
2613 /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2614 /// seed: the Aplicacao-view builder folds the outer `Option`'s
2615 /// author-omitted arm onto the [`crate::aplicacao::MeshPolicy::default`]
2616 /// cluster-default, so the peer inner [`crate::AplicacaoSpec::politicas`]
2617 /// (534dc21) `&MeshPolicy`-return accessor observes a typed
2618 /// composite whether or not the author declared the outer slot.
2619 /// The outer accessor preserves the "author-omitted vs authored-
2620 /// empty" partition the inner accessor's `is_empty()`-gated
2621 /// renderer overlay collapses — routing the presence bit through
2622 /// this accessor keeps the [`Self::declared_mesh_slots`] M3 kind-
2623 /// coherence enumerator's `M3_AUTHOR_KEY_POLITICAS` push separate
2624 /// from the inner `MeshPolicy::is_empty()`-gated overlay elision.
2625 ///
2626 /// Prior to this lift the `.politicas` `Option<MeshPolicy>`
2627 /// composite was accessed inline at two production sites — the
2628 /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2629 /// `self.politicas.clone().unwrap_or_default()` traversal head
2630 /// (caixa-core/src/manifest.rs:1899, which drives the fold onto
2631 /// the [`crate::aplicacao::MeshPolicy::default`] cluster-default
2632 /// arm the inner [`crate::AplicacaoSpec::politicas`] accessor
2633 /// then observes), and the [`Self::declared_mesh_slots`] M3
2634 /// declared-slot-set enumerator's `self.politicas.is_some()`
2635 /// presence probe (caixa-core/src/manifest.rs:1961, which drives
2636 /// the `M3_AUTHOR_KEY_POLITICAS` kebab-case author-label push
2637 /// every [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
2638 /// coherence gate reads) — two open-coded outer-field accesses
2639 /// that expressed no compile-time link back to the typed slot at
2640 /// the [`Caixa`] altitude. A future extension of the `:politicas`
2641 /// outer axis to a richer author surface (a per-cluster
2642 /// `:politicas-overrides` slot the operator materializes at
2643 /// admission time so a cluster-specific policy can tighten the
2644 /// caixa-declared bound without re-authoring the `caixa.lisp`, a
2645 /// promotion of the plain `Option<MeshPolicy>` to a richer
2646 /// `{static, dynamic}` partition once the M4 per-edge
2647 /// contrato-scoped policy-override surface lands, the M5 traffic-
2648 /// shaping composition the caixa-operator's per-Aplicacao mesh
2649 /// admission webhook keys off) would have had to be threaded
2650 /// through both open-coded copies in lockstep or the Aplicacao-
2651 /// composition seed's default-fold arm would silently disagree
2652 /// with the M3 declared-slot enumerator on which policy composite
2653 /// a given Caixa resolves to — the seed reading an operator-
2654 /// resolved slot while the enumerator's presence probe read the
2655 /// raw slot would silently split the build-time mesh-artifact
2656 /// emission gate from the M3 declared-slot enumerator's kind-
2657 /// coherence gate, a two-consumer split far from the source
2658 /// `caixa.lisp` with no field naming the policy-drift root cause.
2659 /// Lifting the resolution rule to a typed method on the substrate
2660 /// primitive means every downstream consumer of the caixa's per-
2661 /// `Caixa` MESH-COMPOSITION mesh-policy outer-composite surface
2662 /// reaches for exactly one typed dispatch — the resolver's
2663 /// accept-set migrates as a unit on any future axis addition.
2664 ///
2665 /// Third outer top-level [`Caixa`] `Option<&Composite>`-return
2666 /// composite-reference accessor — sibling to the opening
2667 /// [`Self::limits`] (b2bd9d7) and [`Self::behavior`] (35d8b52)
2668 /// accessors on the outer-`Caixa` `Option<&Composite>` composite-
2669 /// reference sub-family, extends the "one typed dispatch on the
2670 /// substrate primitive, thin projections at each consumer"
2671 /// discipline onto the first of the three M3 mesh-slot axes.
2672 /// Peer of the closed inner mesh-slot outer-composite family the
2673 /// sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
2674 /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2675 /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2676 /// accessor pins already close on the inner [`crate::AplicacaoSpec`]
2677 /// altitude — opens the outer top-level [`Caixa`] altitude's M3
2678 /// mesh-slot arm of the composite-reference family the remaining
2679 /// two axes (`:placement`, `:entrada`) fold onto in future
2680 /// sibling lifts. Returns `Option<&MeshPolicy>` (not the owning
2681 /// composite by copy or clone) because every downstream consumer
2682 /// of the mesh-policy composite treats it as a read-only per-axis
2683 /// dispatch source — the reference-view is the narrowest borrow
2684 /// that supports every present + roadmapped consumer (per-axis
2685 /// accessor dispatch, `.is_empty()`-gated overlay projection,
2686 /// presence-probe early return on the "author-omitted `:politicas`
2687 /// ⇒ cluster-default applies" partition, `Aplicacao`-composition
2688 /// seed's default-fold arm) without cloning the composite through
2689 /// every consumer's fast path. The `Option` half of the return-
2690 /// type preserves the load-bearing "author-omitted `:politicas` ⇒
2691 /// cluster-default applies" partition (not a default composite
2692 /// the downstream must reject on emptiness) — the accessor
2693 /// projects the raw `Option<MeshPolicy>` slot's presence bit
2694 /// through the reference-return unchanged. Named `politicas()` to
2695 /// match the storage field's name verbatim and the tatara-lisp
2696 /// author-surface term (`:politicas`) the field's own docstring
2697 /// already carries.
2698 #[must_use]
2699 pub const fn politicas(&self) -> Option<&crate::aplicacao::MeshPolicy> {
2700 self.politicas.as_ref()
2701 }
2702
2703 /// Substrate-canonical per-`Caixa` `:placement` M3 mesh-slot outer-
2704 /// composite MESH-COMPOSITION-shaped distribution optional-composite-
2705 /// reference accessor every consumer of the top-level manifest's
2706 /// per-Aplicacao [`crate::aplicacao::Placement`] outer-composite
2707 /// reader keys off — returns the author-declared `:placement` typed
2708 /// composite verbatim as an `Option<&Placement>` reference over the
2709 /// same backing storage the raw `self.placement.as_ref()` field
2710 /// access borrows from, with `None` naming the "no `:placement`
2711 /// block authored — every per-axis placement scalar defers to the
2712 /// cluster-default arm named on the per-axis
2713 /// [`crate::aplicacao::Placement::estrategia`] /
2714 /// [`crate::aplicacao::Placement::clusters`] /
2715 /// [`crate::aplicacao::Placement::affinity`] /
2716 /// [`crate::aplicacao::Placement::shard_key`] scalar-accessor
2717 /// docstrings" partition every downstream caixa-mesh /
2718 /// caixa-flux / caixa-helm Aplicacao-artifact emitter treats as
2719 /// "emit no per-`:placement` overlay" and the sibling
2720 /// [`Self::aplicacao_view`] Aplicacao-composition seed folds through
2721 /// the [`crate::aplicacao::Placement::default`] cluster-default arm.
2722 ///
2723 /// The outer `:placement` slot carries the M3 mesh-slot per-
2724 /// Aplicacao typed distribution composite — the load-bearing
2725 /// container of every where-does-this-Aplicacao-run axis every
2726 /// caixa-mesh programs.yaml per-cluster distribution overlay /
2727 /// caixa-flux per-Aplicacao GitRepository/HelmRelease fan-out /
2728 /// future M4 per-Aplicacao Akka-style cluster-sharding entity-id
2729 /// resolver emitter fans on (MESH-COMPOSITION §II.4 — the
2730 /// Aplicacao's typed distribution composite; §V CSE invariants —
2731 /// "distribution is a first-class typed composite, not a runtime
2732 /// scheduler hint" the per-axis scalars enforce; §III.3 — the
2733 /// typed inter-Servico contrato-edge overlay the per-cluster
2734 /// mesh renderer keys off). Every per-`:placement` axis threads
2735 /// through a lifted per-slot accessor on the
2736 /// [`crate::aplicacao::Placement`] type: the
2737 /// [`crate::aplicacao::Placement::estrategia`] (921fe1b)
2738 /// MESH-COMPOSITION distribution-strategy scalar, the
2739 /// [`crate::aplicacao::Placement::clusters`] (a6e18d7) per-cluster
2740 /// distribution-target slice, the [`crate::aplicacao::Placement::affinity`]
2741 /// M3-Adaptive-compression-hint optional-scalar, and the
2742 /// [`crate::aplicacao::Placement::shard_key`] (7cd2a28) Akka-cluster-
2743 /// sharding extractor-expression optional-scalar. Every downstream
2744 /// consumer that reaches for a placement axis first passes through
2745 /// this outer accessor onto the composite and then dispatches onto
2746 /// the per-axis accessor — the two-level dispatch means every per-
2747 /// `:placement` reader now routes through a typed dispatch on the
2748 /// substrate primitive at both altitudes.
2749 ///
2750 /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2751 /// seed: the Aplicacao-view builder folds the outer `Option`'s
2752 /// author-omitted arm onto the [`crate::aplicacao::Placement::default`]
2753 /// cluster-default, so the peer inner [`crate::AplicacaoSpec::placement`]
2754 /// (9abb8f0) `&Placement`-return accessor observes a typed composite
2755 /// whether or not the author declared the outer slot. The outer
2756 /// accessor preserves the "author-omitted vs authored-empty" partition
2757 /// the inner accessor collapses at the cluster-default fold —
2758 /// routing the presence bit through this accessor keeps the
2759 /// [`Self::declared_mesh_slots`] M3 kind-coherence enumerator's
2760 /// `M3_AUTHOR_KEY_PLACEMENT` push separate from the inner
2761 /// [`crate::AplicacaoSpec::validate_placement`]-gated overlay
2762 /// dispatch.
2763 ///
2764 /// Prior to this lift the `.placement` `Option<Placement>`
2765 /// composite was accessed inline at two production sites — the
2766 /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2767 /// `self.placement.clone().unwrap_or_default()` traversal head
2768 /// (caixa-core/src/manifest.rs:2036, which drives the fold onto
2769 /// the [`crate::aplicacao::Placement::default`] cluster-default
2770 /// arm the inner [`crate::AplicacaoSpec::placement`] accessor
2771 /// then observes), and the [`Self::declared_mesh_slots`] M3
2772 /// declared-slot-set enumerator's `self.placement.is_some()`
2773 /// presence probe (caixa-core/src/manifest.rs:2100, which drives
2774 /// the `M3_AUTHOR_KEY_PLACEMENT` kebab-case author-label push
2775 /// every [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
2776 /// coherence gate reads) — two open-coded outer-field accesses
2777 /// that expressed no compile-time link back to the typed slot at
2778 /// the [`Caixa`] altitude. A future extension of the `:placement`
2779 /// outer axis to a richer author surface (a per-cluster
2780 /// `:placement-overrides` slot the operator materializes at
2781 /// admission time so a cluster-specific placement can tighten the
2782 /// caixa-declared bound without re-authoring the `caixa.lisp`, a
2783 /// per-tenant placement-alias table the M4
2784 /// `mesh.pleme.io/v1alpha1/Aplicacao` CR materializer resolves
2785 /// per-CR at admission time, a promotion of the plain
2786 /// `Option<Placement>` to a richer `{static, dynamic}` partition
2787 /// once Orleans-style virtual-actor dynamic placement comes into
2788 /// typed scope) would have had to be threaded through both open-
2789 /// coded copies in lockstep or the Aplicacao-composition seed's
2790 /// default-fold arm would silently disagree with the M3 declared-
2791 /// slot enumerator on which distribution composite a given Caixa
2792 /// resolves to — the seed reading an operator-resolved slot while
2793 /// the enumerator's presence probe read the raw slot would
2794 /// silently split the build-time distribution-artifact emission
2795 /// gate from the M3 declared-slot enumerator's kind-coherence
2796 /// gate, a two-consumer split far from the source `caixa.lisp`
2797 /// with no field naming the distribution-drift root cause.
2798 /// Lifting the resolution rule to a typed method on the substrate
2799 /// primitive means every downstream consumer of the caixa's per-
2800 /// `Caixa` MESH-COMPOSITION distribution outer-composite surface
2801 /// reaches for exactly one typed dispatch — the resolver's
2802 /// accept-set migrates as a unit on any future axis addition.
2803 ///
2804 /// Fourth outer top-level [`Caixa`] `Option<&Composite>`-return
2805 /// composite-reference accessor — sibling to the opening
2806 /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) M2-
2807 /// Servico-runtime pair and the peer [`Self::politicas`] (5d23d29)
2808 /// M3-mesh-slot arm on the outer-`Caixa` `Option<&Composite>`
2809 /// composite-reference sub-family, folds on the "one typed
2810 /// dispatch on the substrate primitive, thin projections at each
2811 /// consumer" discipline extended onto the second of the three M3
2812 /// mesh-slot axes. Peer of the closed inner mesh-slot outer-
2813 /// composite family the sibling
2814 /// [`crate::AplicacaoSpec::politicas`] (534dc21) /
2815 /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2816 /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2817 /// accessor pins already close on the inner
2818 /// [`crate::AplicacaoSpec`] altitude — folds on the outer top-
2819 /// level [`Caixa`] altitude's M3 mesh-slot arm the sibling
2820 /// [`Self::politicas`] opened, extending the discipline onto the
2821 /// second of the three M3 mesh-slot axes. The remaining M3
2822 /// mesh-slot axis (`:entrada`) folds onto this accessor's
2823 /// discipline in the final sibling lift, closing the outer top-
2824 /// level [`Caixa`] `Option<&Composite>` M3 mesh-slot sub-family.
2825 /// Returns `Option<&Placement>` (not the owning composite by copy
2826 /// or clone) because every downstream consumer of the placement
2827 /// composite treats it as a read-only per-axis dispatch source —
2828 /// the reference-view is the narrowest borrow that supports every
2829 /// present + roadmapped consumer (per-axis accessor dispatch,
2830 /// serde composite-serialization on the programs.yaml overlay,
2831 /// presence-probe early return on the "author-omitted `:placement`
2832 /// ⇒ cluster-default applies" partition, `Aplicacao`-composition
2833 /// seed's default-fold arm) without cloning the composite through
2834 /// every consumer's fast path. The `Option` half of the return-
2835 /// type preserves the load-bearing "author-omitted `:placement` ⇒
2836 /// cluster-default applies" partition (not a default composite
2837 /// the downstream must reject on emptiness) — the accessor
2838 /// projects the raw `Option<Placement>` slot's presence bit
2839 /// through the reference-return unchanged. Named `placement()` to
2840 /// match the storage field's name verbatim and the tatara-lisp
2841 /// author-surface term (`:placement`) the field's own docstring
2842 /// already carries.
2843 #[must_use]
2844 pub const fn placement(&self) -> Option<&crate::aplicacao::Placement> {
2845 self.placement.as_ref()
2846 }
2847
2848 /// Substrate-canonical per-`Caixa` `:entrada` M3 mesh-slot outer-
2849 /// composite MESH-COMPOSITION-shaped external-gateway optional-
2850 /// composite-reference accessor every consumer of the top-level
2851 /// manifest's per-Aplicacao [`crate::aplicacao::Entrada`] outer-
2852 /// composite reader keys off — returns the author-declared
2853 /// `:entrada` typed composite verbatim as an `Option<&Entrada>`
2854 /// reference over the same backing storage the raw
2855 /// `self.entrada.as_ref()` field access borrows from, with `None`
2856 /// naming the "no `:entrada` block authored — this Aplicacao is
2857 /// cluster-internal, no `Gateway`/`HTTPRoute` fan-out emitted"
2858 /// partition every downstream caixa-mesh Gateway-API artifact
2859 /// emitter treats as "emit no gateway-listener + no `HTTPRoute`
2860 /// backend for this Aplicacao" and the sibling
2861 /// [`Self::aplicacao_view`] Aplicacao-composition seed forwards
2862 /// verbatim (unlike the peer `:politicas` / `:placement` arms,
2863 /// `:entrada` has no cluster-default fold — an omitted `:entrada`
2864 /// stays `None` on the projected [`crate::AplicacaoSpec`] and the
2865 /// peer inner [`crate::AplicacaoSpec::entrada`] accessor observes
2866 /// the same `Option<&Entrada>` presence bit unchanged).
2867 ///
2868 /// The outer `:entrada` slot carries the M3 mesh-slot per-
2869 /// Aplicacao typed external-gateway composite — the load-bearing
2870 /// container of every how-does-the-outside-world-reach-this-
2871 /// Aplicacao axis every caixa-mesh `Gateway`/`HTTPRoute` fan-out
2872 /// emitter fans on (MESH-COMPOSITION §II.5 — the Aplicacao's typed
2873 /// external-entry composite; §V CSE invariants — "the external
2874 /// gateway is a first-class typed composite, not a per-Servico
2875 /// ingress annotation" the per-axis scalars enforce; §III.4 — the
2876 /// typed hostname + backend-Servico pair the per-cluster Gateway-
2877 /// API renderer keys off). Every per-`:entrada` axis threads
2878 /// through a lifted per-slot accessor on the
2879 /// [`crate::aplicacao::Entrada`] type: the
2880 /// [`crate::aplicacao::Entrada::host`] Gateway-API `Listener.hostname`
2881 /// scalar, the [`crate::aplicacao::Entrada::para`] backend-Servico
2882 /// caixa-name scalar, the [`crate::aplicacao::Entrada::paths`]
2883 /// per-rule `HTTPPathMatch` list, the [`crate::aplicacao::Entrada::port`]
2884 /// backend `trigger.service.port` scalar, and the
2885 /// [`crate::aplicacao::Entrada::resolved_paths`] URL-path fallback
2886 /// resolver every HTTPRoute-aware renderer consumes. Every
2887 /// downstream consumer that reaches for an entry axis first passes
2888 /// through this outer accessor onto the composite and then
2889 /// dispatches onto the per-axis accessor — the two-level dispatch
2890 /// means every per-`:entrada` reader now routes through a typed
2891 /// dispatch on the substrate primitive at both altitudes.
2892 ///
2893 /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2894 /// seed: the Aplicacao-view builder forwards the outer `Option`
2895 /// arm verbatim (no default fold — `:entrada` is inherently
2896 /// optional; a cluster-internal Aplicacao has no external gateway
2897 /// at all, not "an external gateway that defaults to nothing"), so
2898 /// the peer inner [`crate::AplicacaoSpec::entrada`] (d32111c)
2899 /// `Option<&Entrada>`-return accessor observes the same presence
2900 /// bit whether or not the author declared the outer slot. Routing
2901 /// the presence bit through this accessor keeps the
2902 /// [`Self::declared_mesh_slots`] M3 kind-coherence enumerator's
2903 /// `M3_AUTHOR_KEY_ENTRADA` push separate from the inner
2904 /// [`crate::AplicacaoSpec::validate_entrada`]-gated
2905 /// hostname/backend/path emission dispatch.
2906 ///
2907 /// Prior to this lift the `.entrada` `Option<Entrada>` composite
2908 /// was accessed inline at two production sites — the
2909 /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2910 /// `self.entrada.clone()` traversal head (caixa-core/src/manifest.rs:2182,
2911 /// which drives the forward onto the peer inner
2912 /// [`crate::AplicacaoSpec::entrada`] accessor the caixa-mesh
2913 /// Gateway-API fan-out then observes), and the
2914 /// [`Self::declared_mesh_slots`] M3 declared-slot-set enumerator's
2915 /// `self.entrada.is_some()` presence probe (caixa-core/src/manifest.rs:2248,
2916 /// which drives the `M3_AUTHOR_KEY_ENTRADA` kebab-case author-
2917 /// label push every [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
2918 /// kind-coherence gate reads) — two open-coded outer-field
2919 /// accesses that expressed no compile-time link back to the typed
2920 /// slot at the [`Caixa`] altitude. A future extension of the
2921 /// `:entrada` outer axis to a richer author surface (a per-cluster
2922 /// `:entrada-overrides` slot the operator materializes at admission
2923 /// time so a cluster-specific hostname can pin the caixa-declared
2924 /// bound without re-authoring the `caixa.lisp`, a per-tenant
2925 /// gateway-alias table the M4 `mesh.pleme.io/v1alpha1/Aplicacao`
2926 /// CR materializer resolves per-CR at admission time, a promotion
2927 /// of the plain `Option<Entrada>` to a richer
2928 /// `{public, private, internal}` partition once Cilium-identity-
2929 /// scoped internal gateways come into typed scope) would have had
2930 /// to be threaded through both open-coded copies in lockstep or the
2931 /// Aplicacao-composition seed's forward arm would silently
2932 /// disagree with the M3 declared-slot enumerator on which external-
2933 /// gateway composite a given Caixa resolves to — the seed reading
2934 /// an operator-resolved slot while the enumerator's presence probe
2935 /// read the raw slot would silently split the build-time gateway-
2936 /// artifact emission gate from the M3 declared-slot enumerator's
2937 /// kind-coherence gate, a two-consumer split far from the source
2938 /// `caixa.lisp` with no field naming the entry-drift root cause.
2939 /// Lifting the resolution rule to a typed method on the substrate
2940 /// primitive means every downstream consumer of the caixa's per-
2941 /// `Caixa` MESH-COMPOSITION external-gateway outer-composite
2942 /// surface reaches for exactly one typed dispatch — the resolver's
2943 /// accept-set migrates as a unit on any future axis addition.
2944 ///
2945 /// Fifth and final outer top-level [`Caixa`] `Option<&Composite>`-
2946 /// return composite-reference accessor — closes the outer-`Caixa`
2947 /// `Option<&Composite>` composite-reference sub-family opened by
2948 /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) on the
2949 /// M2 Servico-runtime arm and extended onto the M3 mesh-slot arm
2950 /// by [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074),
2951 /// folds on the "one typed dispatch on the substrate primitive,
2952 /// thin projections at each consumer" discipline extended onto the
2953 /// third and final M3 mesh-slot axis. Peer of the closed inner
2954 /// mesh-slot outer-composite family the sibling
2955 /// [`crate::AplicacaoSpec::politicas`] (534dc21) /
2956 /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2957 /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2958 /// accessor pins already close on the inner
2959 /// [`crate::AplicacaoSpec`] altitude — this lift closes the mirror
2960 /// sub-family on the outer top-level [`Caixa`] altitude, so both
2961 /// altitudes of the outer-composite reference-return discipline
2962 /// (per-`Caixa` outer-slot presence + per-`AplicacaoSpec` inner-
2963 /// slot presence) now carry the full five-arm accept-set behind a
2964 /// typed dispatch on the substrate primitive. Returns
2965 /// `Option<&Entrada>` (not the owning composite by copy or clone)
2966 /// because every downstream consumer of the entrada composite
2967 /// treats it as a read-only per-axis dispatch source — the
2968 /// reference-view is the narrowest borrow that supports every
2969 /// present + roadmapped consumer (per-axis accessor dispatch,
2970 /// serde composite-serialization on the programs.yaml overlay,
2971 /// presence-probe early return on the "author-omitted `:entrada`
2972 /// ⇒ cluster-internal Aplicacao" partition, `Aplicacao`-composition
2973 /// seed's forward arm) without cloning the composite through every
2974 /// consumer's fast path. The `Option` half of the return-type
2975 /// preserves the load-bearing "author-omitted `:entrada` ⇒
2976 /// cluster-internal Aplicacao" partition (not a default composite
2977 /// the downstream must reject on emptiness — a cluster-internal
2978 /// Aplicacao has no external gateway at all, not "a default gateway
2979 /// that emits nothing"); the accessor projects the raw
2980 /// `Option<Entrada>` slot's presence bit through the reference-
2981 /// return unchanged. Named `entrada()` to match the storage field's
2982 /// name verbatim and the tatara-lisp author-surface term
2983 /// (`:entrada`) the field's own docstring already carries.
2984 #[must_use]
2985 pub const fn entrada(&self) -> Option<&crate::aplicacao::Entrada> {
2986 self.entrada.as_ref()
2987 }
2988
2989 /// Substrate-canonical per-`Caixa` `:ci` slot accessor — returns the
2990 /// author-declared typed CI run (`canteiro_types::CiRun`) verbatim as
2991 /// an `Option<&CiRun>`, borrowed from the typed slot's own
2992 /// `Option<CiRun>` storage. `None` when the slot is absent (every
2993 /// non-`Acao` kind, and an `Acao` caixa that hasn't declared `:ci`
2994 /// yet — the latter is caught by [`crate::LayoutError::MissingCi`],
2995 /// not silently accepted).
2996 ///
2997 /// Named `ci()` to match the storage field's name and the
2998 /// tatara-lisp author surface (`:ci`); mirrors the sibling
2999 /// `Option<&Composite>` accessors on this same `Caixa` altitude
3000 /// ([`Self::limits`], [`Self::behavior`], [`Self::politicas`],
3001 /// [`Self::placement`], [`Self::entrada`]) — one typed dispatch on
3002 /// the substrate primitive rather than an open-coded `self.ci.as_ref()`
3003 /// at every consumer.
3004 #[must_use]
3005 pub const fn ci(&self) -> Option<&canteiro_types::CiRun> {
3006 self.ci.as_ref()
3007 }
3008
3009 /// Substrate-canonical per-`Caixa` `:estrategia` M2 supervisor-tree-
3010 /// slot flat-spread OTP-shaped sibling-restart-strategy discriminant
3011 /// accessor every consumer of the top-level manifest's per-Supervisor
3012 /// restart-strategy axis keys off — returns the author-declared
3013 /// `:estrategia` variant verbatim as an `Option<RestartStrategy>`,
3014 /// `Copy`-projected from the typed slot's own
3015 /// `Option<crate::supervisor::RestartStrategy>` storage. Optional
3016 /// (`:estrategia` is a flat-spread supervisor-only slot every
3017 /// non-`Supervisor`-kind `defcaixa` carries as `None` by
3018 /// `#[serde(default)]`, and every `Supervisor`-kind `defcaixa` may
3019 /// still omit to defer to [`RestartStrategy::default`] —
3020 /// [`RestartStrategy::OneForOne`] — through the [`Self::supervisor_view`]
3021 /// `unwrap_or_default()` fold; a returned `None` degenerates to the
3022 /// [`SupervisorSpec::default`]-inherited strategy without any silent
3023 /// promotion to a fresh explicit variant at the accessor boundary).
3024 ///
3025 /// The `:estrategia` slot carries the M2 typed OTP-shaped sibling-
3026 /// restart-strategy discriminant every substrate-side per-Supervisor
3027 /// dispatch fans on (INSPIRATIONS §II.2 — OTP `supervisor:strategy`
3028 /// closed-set `one_for_one | one_for_all | rest_for_one |
3029 /// simple_one_for_one` algebra translated onto pleme-io's typed
3030 /// [`RestartStrategy`] enum; CAIXA-SDLC §II — the M2 supervisor-tree
3031 /// slot algebra the operator's hierarchical reconciliation scheduler
3032 /// fans on). The slot is *flat-spread* on the outer top-level `Caixa`
3033 /// (per the field-shape docstring at caixa-core/src/manifest.rs — "The
3034 /// supervisor slots are flat on Caixa (vs nested under a
3035 /// `SupervisorSpec` sub-form) to keep tatara-lisp authoring at one
3036 /// level of nesting"), so the accessor's altitude is the outer
3037 /// [`Caixa`] surface rather than the composed [`SupervisorSpec`]
3038 /// altitude the sibling [`crate::supervisor::SupervisorSpec::estrategia`]
3039 /// (eafb619) accessor keys off. The two typed axes — the outer
3040 /// author-surface `Option<RestartStrategy>` on the [`Caixa`] altitude
3041 /// (author-omitted arm carried as `None`) and the inner post-
3042 /// composition `RestartStrategy` on the [`SupervisorSpec`] altitude
3043 /// (`Option` collapsed through the [`Self::supervisor_view`]
3044 /// `unwrap_or_default()` fold) — now share one accessor discipline for
3045 /// the shared substrate concept "the author-declared OTP-shaped
3046 /// sibling-restart-strategy variant that partitions the downstream
3047 /// per-Supervisor renderer's per-arm fan-out"; the outer-altitude
3048 /// `None` arm is the pre-composition presence bit every declared-slot
3049 /// enumerator ([`Self::declared_supervisor_slots`]) reads, and the
3050 /// inner-altitude non-`Option` `RestartStrategy` is the post-
3051 /// composition partition-dispatch input every strategy-arm consumer
3052 /// ([`SupervisorSpec::validate`], the future wasm-operator's per-
3053 /// Supervisor sibling-restart branch, the future M4
3054 /// `mesh.pleme.io/v1alpha1/Supervisor` CR materializer's admission
3055 /// webhook) fans on.
3056 ///
3057 /// Prior to this lift the `.estrategia` field was accessed inline at
3058 /// two production sites in `caixa-core/src/manifest.rs` — the
3059 /// [`Self::declared_supervisor_slots`] `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA`
3060 /// presence-probe arm at `if self.estrategia.is_some()` (which drives
3061 /// the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
3062 /// coherence gate's per-slot label push) and the [`Self::supervisor_view`]
3063 /// `SupervisorSpec` construction site at `estrategia:
3064 /// self.estrategia.unwrap_or_default()` (which composes the flat-
3065 /// spread outer author-surface `Option<RestartStrategy>` onto the
3066 /// inner post-composition [`SupervisorSpec`] `RestartStrategy` field
3067 /// the [`SupervisorSpec::estrategia`] accessor keys off) — two open-
3068 /// coded field-accesses that expressed no compile-time link back to
3069 /// the typed slot. A future extension of the outer `:estrategia` axis
3070 /// to a richer author surface (a per-cluster strategy override the
3071 /// operator pins through a future `:estrategia-overrides` overlay the
3072 /// MESH-COMPOSITION §III.2 supervision-canary roadmap acknowledges,
3073 /// a per-tenant strategy-alias table the M4 CR materializer resolves
3074 /// per-CR, a per-Supervisor dynamic strategy derivation the future
3075 /// adaptive-supervision engine computes from child-failure-history
3076 /// topology, a per-child-cohort strategy split the future
3077 /// `RestForCohort` extension the INSPIRATIONS.md §II.2 Erlang/OTP
3078 /// absorption roadmap acknowledges, a promotion of the plain
3079 /// `Option<RestartStrategy>` to a richer
3080 /// `AuthorDeclaredStrategy { declared, overlay }` newtype once the
3081 /// operator-resolved overlay lands) would have had to be threaded
3082 /// through both open-coded copies in lockstep or the enumerator's
3083 /// presence probe and the composition site's `unwrap_or_default()`
3084 /// fold would silently disagree on which strategy a given [`Caixa`]
3085 /// resolves to (an author's `:estrategia OneForAll` would satisfy
3086 /// the enumerator's presence probe while the composition site
3087 /// silently rendered a stale `OneForOne`, or vice versa). Lifting
3088 /// the resolution rule to a typed method on the substrate primitive
3089 /// means every downstream consumer of the caixa's per-`Caixa` outer-
3090 /// altitude sibling-restart-strategy surface reaches for exactly one
3091 /// typed dispatch — the resolver's accept-set migrates as a unit on
3092 /// any future axis addition.
3093 ///
3094 /// First outer top-level [`Caixa`] `Option<Copy>`-return supervisor-
3095 /// tree-slot flat-spread accessor for M2 supervisor-slot Copy-carry
3096 /// axes — opens the outer-`Caixa` `Option<Copy>` flat-spread
3097 /// projection pattern the sibling per-`Caixa` `:max-restarts`
3098 /// `Option<u32>` and (through the future duration-newtype landing)
3099 /// `:restart-window` `Option<Duration>` future outer-scalar lifts
3100 /// fold on. Peer of the inner-altitude [`crate::supervisor::SupervisorSpec::estrategia`]
3101 /// (eafb619) `Copy`-return sibling-restart-strategy scalar accessor on
3102 /// the post-composition [`SupervisorSpec`] altitude — same "one
3103 /// typed dispatch on the substrate primitive, thin projections at
3104 /// each consumer" discipline extended onto the pre-composition outer
3105 /// author-surface [`Caixa`] altitude for the same OTP-shaped
3106 /// sibling-restart-strategy axis. Peer of the closed outer-`Caixa`
3107 /// `Option<&Composite>` composite-reference family the sibling
3108 /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) /
3109 /// [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074) /
3110 /// [`Self::entrada`] (e4128e4) accessor pins already carry on the
3111 /// outer `Option<&Composite>` altitude — extends the outer-`Caixa`
3112 /// typed-slot accessor discipline onto the flat-spread M2 supervisor-
3113 /// tree `Option<Copy>`-discriminant sub-family the sibling M3
3114 /// [`crate::aplicacao::Placement::estrategia`] (921fe1b)
3115 /// `PlacementStrategy` `Copy`-composite-enum scalar accessor already
3116 /// pins on the inner-altitude per-`:placement` composite. Named
3117 /// `estrategia()` to match the storage field's name and the
3118 /// per-[`SupervisorSpec`] peer [`crate::supervisor::SupervisorSpec::estrategia`]
3119 /// / per-[`crate::aplicacao::Placement`] peer
3120 /// [`crate::aplicacao::Placement::estrategia`] method-name discipline
3121 /// verbatim; the accessor's identity name maps onto the canonical
3122 /// OTP-shape supervision vocabulary the [`RestartStrategy`] enum's
3123 /// docstring already carries.
3124 #[must_use]
3125 pub const fn estrategia(&self) -> Option<crate::supervisor::RestartStrategy> {
3126 self.estrategia
3127 }
3128
3129 /// Substrate-canonical per-`Caixa` `:max-restarts` M2 supervisor-tree-
3130 /// slot flat-spread OTP-`MaxIntensity`-shaped restart-budget-count
3131 /// scalar accessor every consumer of the top-level manifest's per-
3132 /// Supervisor `:max-restarts` restart-budget-count axis keys off —
3133 /// returns the author-declared `:max-restarts` typed `Option<u32>`
3134 /// verbatim, `Copy`-projected from the typed slot's own `Option<u32>`
3135 /// storage (`u32` is `Copy`, so `Option<u32>` is `Copy` and the
3136 /// accessor returns by value; no borrow of `&self` past the call).
3137 /// Optional (`:max-restarts` is a flat-spread supervisor-only slot
3138 /// every non-`Supervisor`-kind `defcaixa` carries as `None` by
3139 /// `#[serde(default)]`, and every `Supervisor`-kind `defcaixa` may
3140 /// still omit to defer to the [`Self::supervisor_view`]
3141 /// `unwrap_or(5)` fold's OTP-canonical `{intensity, 5, 60}` default).
3142 ///
3143 /// The `:max-restarts` slot carries the M2 typed Erlang/OTP-shaped
3144 /// `MaxIntensity` restart-budget count that pairs with the sibling
3145 /// `:restart-window` `Period` to form the `MaxIntensity / Period`
3146 /// restart-intensity ratio the supervisor trips its own escalation on
3147 /// (INSPIRATIONS §II.2 — Erlang/OTP `supervisor` `{intensity, 5, 60}`
3148 /// worker-supervisor default; RUNTIME-PATTERNS §II.2; CAIXA-SDLC §II
3149 /// — the M2 supervisor-tree slot algebra the operator's hierarchical
3150 /// reconciliation scheduler fans on). The slot is *flat-spread* on
3151 /// the outer top-level `Caixa` (per the field-shape docstring at
3152 /// caixa-core/src/manifest.rs — "The supervisor slots are flat on
3153 /// Caixa (vs nested under a `SupervisorSpec` sub-form)"), so the
3154 /// accessor's altitude is the outer [`Caixa`] surface rather than the
3155 /// composed [`SupervisorSpec`] altitude the sibling
3156 /// [`crate::supervisor::SupervisorSpec::max_restarts`] accessor keys
3157 /// off. The two typed axes — the outer author-surface `Option<u32>`
3158 /// on the [`Caixa`] altitude (author-omitted arm carried as `None`)
3159 /// and the inner post-composition `u32` on the [`SupervisorSpec`]
3160 /// altitude (`Option` collapsed through the [`Self::supervisor_view`]
3161 /// `unwrap_or(5)` fold) — now share one accessor discipline for the
3162 /// shared substrate concept "the author-declared OTP-shaped
3163 /// restart-budget count every downstream per-Supervisor consumer's
3164 /// restart-intensity budget-vs-count comparator fans on".
3165 ///
3166 /// Prior to this lift the `.max_restarts` field was accessed inline
3167 /// at two production sites in `caixa-core/src/manifest.rs` — the
3168 /// [`Self::declared_supervisor_slots`] `SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS`
3169 /// presence-probe arm at `if self.max_restarts.is_some()` (which
3170 /// drives the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
3171 /// kind-coherence gate's per-slot label push) and the
3172 /// [`Self::supervisor_view`] `SupervisorSpec` construction site at
3173 /// `max_restarts: self.max_restarts.unwrap_or(5)` (which composes the
3174 /// flat-spread outer author-surface `Option<u32>` onto the inner
3175 /// post-composition [`SupervisorSpec`] `u32` field the
3176 /// [`SupervisorSpec::max_restarts`] accessor keys off) — two open-
3177 /// coded field-accesses that expressed no compile-time link back to
3178 /// the typed slot. A future extension of the outer `:max-restarts`
3179 /// axis to a richer author surface (a per-cluster restart-budget
3180 /// override the operator pins through a future `:max-restarts-overrides`
3181 /// overlay the MESH-COMPOSITION §III.2 supervision-canary roadmap
3182 /// acknowledges, a per-tenant restart-budget-alias table the M4 CR
3183 /// materializer resolves per-CR, a per-Supervisor dynamic restart-
3184 /// budget derivation the future adaptive-supervision engine computes
3185 /// from child-failure-history topology, a promotion of the plain
3186 /// `Option<u32>` count to a richer `{MaxR, MaxT}` per-child-cohort
3187 /// restart-budget-partition once the INSPIRATIONS §II.2 Erlang/OTP
3188 /// per-child-cohort roadmap lands) would have had to be threaded
3189 /// through both open-coded copies in lockstep or the enumerator's
3190 /// presence probe and the composition site's `unwrap_or(5)` fold
3191 /// would silently disagree on which restart-budget a given [`Caixa`]
3192 /// resolves to (an author's `:max-restarts 10` would satisfy the
3193 /// enumerator's presence probe while the composition site silently
3194 /// composed the OTP-canonical `5`, or vice versa). Lifting the
3195 /// resolution rule to a typed method on the substrate primitive means
3196 /// every downstream consumer of the caixa's per-`Caixa` outer-altitude
3197 /// restart-budget-count surface reaches for exactly one typed dispatch
3198 /// — the resolver's accept-set migrates as a unit on any future axis
3199 /// addition.
3200 ///
3201 /// Second outer top-level [`Caixa`] `Option<Copy>`-return supervisor-
3202 /// tree-slot flat-spread accessor for M2 supervisor-slot Copy-carry
3203 /// axes — folds on the outer-`Caixa` `Option<Copy>` flat-spread
3204 /// projection pattern the sibling per-`Caixa`
3205 /// [`Self::estrategia`] (ed04d3c) accessor opened, extends the
3206 /// sub-family onto the sibling `Option<u32>` restart-budget-count arm.
3207 /// Peer of the inner-altitude
3208 /// [`crate::supervisor::SupervisorSpec::max_restarts`] `u32` accessor
3209 /// on the post-composition [`SupervisorSpec`] altitude — same "one
3210 /// typed dispatch on the substrate primitive, thin projections at
3211 /// each consumer" discipline extended onto the pre-composition outer
3212 /// author-surface [`Caixa`] altitude for the same OTP-`MaxIntensity`-
3213 /// shaped restart-budget-count axis. Named `max_restarts()` to match
3214 /// the storage field's name and the per-[`SupervisorSpec`] peer
3215 /// [`crate::supervisor::SupervisorSpec::max_restarts`] method-name
3216 /// discipline verbatim; the accessor's identity maps onto the
3217 /// canonical OTP-shape supervision vocabulary the `:max-restarts`
3218 /// field's docstring already carries.
3219 #[must_use]
3220 pub const fn max_restarts(&self) -> Option<u32> {
3221 self.max_restarts
3222 }
3223
3224 /// Substrate-canonical per-`Caixa` `:restart-window` M2 supervisor-
3225 /// tree-slot flat-spread OTP-`Period`-shaped restart-intensity-
3226 /// denominator raw-duration-string scalar accessor every consumer of
3227 /// the top-level manifest's per-Supervisor `:restart-window` sliding-
3228 /// window axis keys off — returns the author-declared `:restart-window`
3229 /// typed `Option<String>` verbatim as an `Option<&str>`, borrowed
3230 /// from the typed slot's own `Option<String>` storage. `None` when
3231 /// the slot is absent (the canonical "never reset — every restart
3232 /// across the supervisor's lifetime counts against the sibling
3233 /// `:max-restarts` budget" sentinel every non-`Supervisor`-kind
3234 /// `defcaixa` carries by `#[serde(default)]` and every
3235 /// `Supervisor`-kind `defcaixa` may still omit to defer to the
3236 /// [`Self::supervisor_view`] `restart_window: None` composition
3237 /// through the [`crate::supervisor::duration_codec::parse`] soft-
3238 /// swallow `.and_then(|s| … .ok())` fold).
3239 ///
3240 /// The `:restart-window` slot carries the raw M2 typed Erlang/OTP-
3241 /// shaped `Period` sliding-observation-interval duration string that
3242 /// pairs with the sibling `:max-restarts` `MaxIntensity` restart-
3243 /// budget count to form the `MaxIntensity / Period` restart-intensity
3244 /// ratio the supervisor trips its own escalation on (INSPIRATIONS
3245 /// §II.2 — Erlang/OTP `supervisor` `{intensity, 5, 60}` worker-
3246 /// supervisor default; RUNTIME-PATTERNS §II.2). The outer-`Caixa`
3247 /// slot stores the raw duration string (`"60s"`, `"5m"`, `"500ms"`)
3248 /// authored under `:restart-window` — the typed [`SupervisorSpec`]
3249 /// holds an `Option<Duration>` routed through the shared
3250 /// [`crate::supervisor::duration_codec`] via `with = "duration_codec"`
3251 /// — so the outer altitude's accessor returns `Option<&str>` (raw
3252 /// authoring surface) while the inner altitude's
3253 /// [`crate::supervisor::SupervisorSpec::restart_window`] returns
3254 /// `Option<Duration>` (parsed typed surface). The parse-refusal arm
3255 /// is closed by the sibling [`Self::validate_restart_window`] gate
3256 /// that surfaces [`ManifestError::RestartWindowMalformed`] naming
3257 /// the offending value; the view-construction path
3258 /// [`Self::supervisor_view`] soft-swallows the same parse error to
3259 /// `None` to keep the view best-effort.
3260 ///
3261 /// Prior to this lift the `.restart_window` field was accessed inline
3262 /// at three production sites in `caixa-core/src/manifest.rs` — the
3263 /// [`Self::declared_supervisor_slots`]
3264 /// `SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW` presence-probe arm at
3265 /// `if self.restart_window.is_some()` (which drives the
3266 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
3267 /// coherence gate's per-slot label push), the
3268 /// [`Self::validate_restart_window`] `let Some(s) =
3269 /// self.restart_window.as_deref()` empty-and-shape gate binding
3270 /// (which folds the raw string through the shared
3271 /// [`crate::supervisor::duration_codec::parse`] to surface
3272 /// [`ManifestError::RestartWindowMalformed`] naming the offending
3273 /// value), and the [`Self::supervisor_view`] `self.restart_window
3274 /// .as_deref().and_then(…)` view-construction fold (which composes
3275 /// the flat-spread outer author-surface `Option<String>` onto the
3276 /// inner post-composition [`SupervisorSpec`] `Option<Duration>`
3277 /// field the [`SupervisorSpec::restart_window`] accessor keys off) —
3278 /// three open-coded field-accesses that expressed no compile-time
3279 /// link back to the typed slot. A future extension of the outer
3280 /// `:restart-window` axis to a richer author surface (a per-cluster
3281 /// window override, a per-tenant window-alias table, a per-Supervisor
3282 /// dynamic window derivation the future adaptive-supervision engine
3283 /// computes from child-failure-history topology, a promotion of the
3284 /// plain `Option<String>` raw duration to a typed `Option<Duration>`
3285 /// once the future author-surface parser lands at the [`Caixa`]
3286 /// altitude and the raw-string form is retired) would have had to be
3287 /// threaded through every open-coded copy in lockstep or the three
3288 /// consumers would silently disagree on which raw string a given
3289 /// [`Caixa`] resolves to. Lifting the resolution rule to a typed
3290 /// method on the substrate primitive means every downstream consumer
3291 /// of the caixa's per-`Caixa` outer-altitude restart-window raw-
3292 /// string surface reaches for exactly one typed dispatch — the
3293 /// resolver's accept-set migrates as a unit on any future axis
3294 /// addition.
3295 ///
3296 /// Third outer top-level [`Caixa`] supervisor-tree-slot flat-spread
3297 /// accessor — folds on the outer-`Caixa` M2 supervisor-tree flat-
3298 /// spread projection pattern the sibling per-`Caixa`
3299 /// [`Self::estrategia`] (ed04d3c) `Option<Copy>` and
3300 /// [`Self::max_restarts`] `Option<Copy>` accessors opened, extends
3301 /// the sub-family onto the sibling `Option<&str>` raw-duration-
3302 /// string arm (the outer altitude's raw-string form; the inner
3303 /// altitude's parsed [`Duration`] form is the peer
3304 /// [`crate::supervisor::SupervisorSpec::restart_window`] accessor).
3305 /// Peer of the sibling per-`Caixa` `Option<&str>`-return scalar
3306 /// accessors ([`Self::licenca`] / [`Self::repositorio`] /
3307 /// [`Self::descricao`] / [`Self::edicao`]) on the universal-axis
3308 /// outer scalar-projection family the outer-`Caixa` `Option<&str>`
3309 /// sub-family already carries — same "one typed dispatch on the
3310 /// substrate primitive, thin projections at each consumer"
3311 /// discipline extended onto the M2 supervisor-tree flat-spread
3312 /// `Option<&str>` raw-duration-string arm. Named `restart_window()`
3313 /// to match the storage field's name and the per-[`SupervisorSpec`]
3314 /// peer [`crate::supervisor::SupervisorSpec::restart_window`]
3315 /// method-name discipline verbatim; the accessor's identity maps
3316 /// onto the canonical OTP-shape supervision vocabulary the
3317 /// `:restart-window` field's docstring already carries.
3318 #[must_use]
3319 pub const fn restart_window(&self) -> Option<&str> {
3320 match &self.restart_window {
3321 Some(s) => Some(s.as_str()),
3322 None => None,
3323 }
3324 }
3325
3326 /// Substrate-canonical per-`Caixa` `:upgrade-from` M2 typed-slot
3327 /// outer-composite OTP-appup-shaped per-prior-version migration-
3328 /// entry-list slice accessor every consumer of the top-level
3329 /// manifest's per-Servico hot-upgrade-block `&[UpgradeFromEntry]`
3330 /// slice-view keys off — returns the author-declared `:upgrade-from`
3331 /// typed `Vec<UpgradeFromEntry>` verbatim as a
3332 /// `&[UpgradeFromEntry]` slice-view over the same backing buffer
3333 /// the raw `self.upgrade_from.as_slice()` field access borrows
3334 /// from. Empty-slice-carrying (the "no hot-upgrade path declared"
3335 /// arm every `defcaixa` without an `:upgrade-from` block carries;
3336 /// the [`Self::from_lisp`] derive folds an omitted `:upgrade-from`
3337 /// through `#[serde(default)]` to `Vec::new()`, so a `Caixa` past
3338 /// parse definitionally carries a `Vec<UpgradeFromEntry>` slot —
3339 /// possibly empty — and the returned `&[UpgradeFromEntry]`
3340 /// degenerates to an empty slice on that arm without any silent
3341 /// `None` collapse).
3342 ///
3343 /// The outer `:upgrade-from` slot carries the M2 typed OTP-appup
3344 /// migration block — the load-bearing container of every per-
3345 /// prior-`:versao` migration-instruction list the wasm-operator
3346 /// dispatches on at hot-upgrade time (INSPIRATIONS §II.4 — OTP
3347 /// `.appup` per-prior-version `LoadModule | StateChange |
3348 /// SoftPurge | Purge | Restart` instruction algebra translated
3349 /// onto pleme-io's typed `:upgrade-from :from` + `:instructions`
3350 /// entry list; CAIXA-SDLC §II — the typed-M2 slot algebra the
3351 /// operator's hot-upgrade dispatch fans on). Every per-entry axis
3352 /// threads through a lifted per-entry accessor on the
3353 /// [`UpgradeFromEntry`] type: the
3354 /// [`UpgradeFromEntry::prior_versao`] SemVer-shaped previous-
3355 /// version scalar accessor and the
3356 /// [`UpgradeFromEntry::instructions`] `&[UpgradeInstruction]`-
3357 /// return per-entry instruction-list accessor (0137e5a). Every
3358 /// downstream consumer of the hot-upgrade path first passes
3359 /// through this outer accessor onto the slice and then dispatches
3360 /// per-entry through the inner accessors — the two-level dispatch
3361 /// means every per-`:upgrade-from` reader now routes through a
3362 /// typed dispatch on the substrate primitive at both altitudes.
3363 ///
3364 /// Prior to this lift the `.upgrade_from` `Vec<UpgradeFromEntry>`
3365 /// slot was accessed inline at production sites across three
3366 /// files — the [`Self::declared_servico_slots`] M2 declared-slot
3367 /// enumerator's `self.upgrade_from.is_empty()` presence probe
3368 /// (caixa-core/src/manifest.rs, which drives the
3369 /// `M2_AUTHOR_KEY_UPGRADE_FROM` kebab-case author-label push every
3370 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
3371 /// gate reads), the [`crate::StandardLayout::verify`] per-
3372 /// `:upgrade-from` three-stage validation pass (caixa-core/src/
3373 /// layout.rs, which fans onto the
3374 /// [`crate::upgrade::validate_upgrade_from`] per-entry shape +
3375 /// cross-entry duplicate gate, the
3376 /// [`crate::upgrade::validate_upgrade_from_against_versao`]
3377 /// SemVer-precedence cross-slot gate, the
3378 /// [`crate::upgrade::validate_upgrade_from_against_behavior`]
3379 /// `:state-change` ↔ `:on-state-change` cross-slot composition
3380 /// gate, and the per-instruction script-path existence-probe walk
3381 /// that reads each entry's [`UpgradeFromEntry::instructions`] to
3382 /// resolve every declared migration script against the layout
3383 /// root), and the [`crate::render::servico_m2_overlay`] per-
3384 /// Servico M2 overlay emitter's `!caixa.upgrade_from.is_empty()`
3385 /// presence gate + `serde_yaml::to_value(&caixa.upgrade_from)`
3386 /// projection (caixa-core/src/render.rs, which drives the
3387 /// `M2_KEY_UPGRADE_FROM`-keyed `serde_yaml` projection every
3388 /// `caixa-helm` / `caixa-flux` Servico values-block emitter fans
3389 /// on and lands as the ComputeUnit CR's `spec.upgradeFrom` field).
3390 /// A future extension of the outer `:upgrade-from` axis (a per-
3391 /// cluster `:upgrade-overrides` overlay the wasm-engine operator
3392 /// resolves at admission time so a cluster-specific migration
3393 /// policy can tighten a caixa-declared step without re-authoring
3394 /// the `caixa.lisp`, promotion of the plain
3395 /// `Vec<UpgradeFromEntry>` to a richer `{static, dynamic}`
3396 /// partition once runtime-resolved hot-upgrade instructions land,
3397 /// per-entry priority annotation once multi-strategy fan-out
3398 /// lands) would have had to be threaded through all six open-
3399 /// coded copies in lockstep or one consumer would silently
3400 /// disagree with the peers on which upgrade slice a given Caixa
3401 /// resolves to — a six-consumer split at the enumerator, the
3402 /// three-stage validate pass, the script-path probe walk, and the
3403 /// M2 overlay emitter, far from the source `caixa.lisp` with no
3404 /// field naming the upgrade-drift root cause. Lifting the
3405 /// resolution rule to a typed method on the substrate primitive
3406 /// means every downstream consumer of the caixa's per-`Caixa`
3407 /// OTP-appup outer-slice surface reaches for exactly one typed
3408 /// dispatch — the resolver's accept-set migrates as a unit on any
3409 /// future axis addition.
3410 ///
3411 /// First outer top-level [`Caixa`] `&[Composite]`-return slice
3412 /// accessor for M2 / M3 typed-slot vec-carry axes — opens the
3413 /// outer-`Caixa` `&[Composite]` composite-slice projection
3414 /// pattern the sibling `:children`
3415 /// [`crate::supervisor::ChildSpec`] / `:membros`
3416 /// [`crate::aplicacao::Membro`] / `:contratos`
3417 /// [`crate::aplicacao::WitContract`] future outer-composite-slice
3418 /// lifts fold on. Peer of the closed outer-`Caixa` scalar
3419 /// `Option<&Composite>` composite-reference family the sibling
3420 /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) /
3421 /// [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074) /
3422 /// [`Self::entrada`] (e4128e4) accessors closed on the outer
3423 /// `Option<&Composite>` altitude, extended here to the outer-
3424 /// `Caixa` `&[Composite]` vec-carry altitude. Peer at the inner
3425 /// altitude of [`crate::upgrade::UpgradeFromEntry::instructions`]
3426 /// (0137e5a) — same "one typed dispatch on the substrate
3427 /// primitive, thin projections at each consumer" discipline
3428 /// folded onto the outer top-level [`Caixa`] altitude, opening the
3429 /// M2 vec-carry slot family's outer-composite-slice axis. Sibling
3430 /// in shape to the peer outer-`Caixa` `&[Dep]`-return
3431 /// [`Self::deps`] (ad34b4e) / [`Self::deps_dev`] (f7fd81e) and
3432 /// `&[String]`-return [`Self::autores`] (b5d813f) /
3433 /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`]
3434 /// (8a36c23) / [`Self::exe`] (65d9527) / [`Self::servicos`]
3435 /// (611f78b) slice-accessors on the sibling outer-`Caixa` scalar-
3436 /// element vec-carry axes — folds the "outer [`Caixa`] `&[T]`
3437 /// slice" projection pattern onto the sibling M2 typed-composite-
3438 /// element axis (`UpgradeFromEntry` composite, matching the
3439 /// per-inner [`UpgradeFromEntry::instructions`] element type at a
3440 /// different altitude).
3441 ///
3442 /// Returns `&[UpgradeFromEntry]` (not `&Vec<UpgradeFromEntry>`)
3443 /// because every downstream consumer of the hot-upgrade list
3444 /// treats it as a read-only sequence — the slice-view is the
3445 /// narrowest borrow that supports every present + roadmapped
3446 /// consumer (`.iter()`, `.len()`, `.is_empty()`, `serde` slice-
3447 /// serialization through
3448 /// `serde_yaml::to_value(&[UpgradeFromEntry])`) without leaking
3449 /// the backing `Vec`'s grow/push/reserve surface no consumer of
3450 /// the typed view reaches for (the storage-side `Vec` remains
3451 /// reachable through the `pub upgrade_from` field for the
3452 /// mutation-carrying serde round-trip and per-test fixture-
3453 /// mutation paths). Named `upgrade_from()` to match the storage
3454 /// field's `snake_case` name; the kebab-case author-surface tag
3455 /// `:upgrade-from` is the same axis after tatara-lisp's
3456 /// kebab↔snake fold and the accessor's identity maps onto the
3457 /// canonical CAIXA-SDLC §II vocabulary the slot's docstring
3458 /// already carries.
3459 #[must_use]
3460 pub const fn upgrade_from(&self) -> &[UpgradeFromEntry] {
3461 self.upgrade_from.as_slice()
3462 }
3463
3464 /// Substrate-canonical per-`Caixa` `:children` M2 supervisor-tree-
3465 /// slot outer-composite OTP-shaped per-supervisor static-child-list
3466 /// slice accessor every consumer of the top-level manifest's per-
3467 /// Supervisor `&[ChildSpec]` slice-view keys off — returns the
3468 /// author-declared `:children` typed `Vec<crate::supervisor::ChildSpec>`
3469 /// verbatim as a `&[crate::supervisor::ChildSpec]` slice-view over
3470 /// the same backing buffer the raw `self.children.as_slice()` field
3471 /// access borrows from. Empty-slice-carrying (the "no static children
3472 /// declared" arm every non-`Supervisor`-kind `defcaixa` carries by
3473 /// #[serde(default)] and every `SimpleOneForOne` supervisor carries
3474 /// by [`crate::supervisor::SupervisorError::SimpleOneForOneWithStaticChildren`]
3475 /// gate; the returned `&[ChildSpec]` degenerates to an empty slice
3476 /// on those arms without any silent `None` collapse).
3477 ///
3478 /// The outer `:children` slot carries the M2 typed OTP-supervisor
3479 /// static-child list — the load-bearing container of every per-
3480 /// child `{caixa, versao, restart}` triple the wasm-operator's
3481 /// hierarchical reconciler dispatches on at supervisor-tree
3482 /// materialization time (INSPIRATIONS §II.2 — OTP `supervisor:init/1`
3483 /// static-child list translated onto pleme-io's typed
3484 /// [`crate::supervisor::ChildSpec`] entry list; CAIXA-SDLC §II —
3485 /// the typed-M2 slot algebra the operator's per-supervisor fan-out
3486 /// dispatch fans on). Every per-child axis threads through a lifted
3487 /// per-entry accessor on the [`crate::supervisor::ChildSpec`] type:
3488 /// the [`crate::supervisor::ChildSpec::nome`] DNS-1123-label
3489 /// child-caixa-identity scalar accessor, the peer versao SemVer-2
3490 /// version-requirement scalar accessor, and the
3491 /// [`crate::supervisor::ChildSpec::restart`] `Copy`-composite-enum
3492 /// per-child post-exit restart-decision-policy discriminant
3493 /// accessor (dfb4a81). Every downstream consumer of the supervisor-
3494 /// tree path first passes through this outer accessor onto the
3495 /// slice and then dispatches per-child through the inner accessors
3496 /// — the two-level dispatch means every per-`:children` reader now
3497 /// routes through a typed dispatch on the substrate primitive at
3498 /// both altitudes.
3499 ///
3500 /// Prior to this lift the `.children` `Vec<ChildSpec>` slot was
3501 /// accessed inline at three production sites across two files —
3502 /// the [`Self::declared_supervisor_slots`] supervisor-tree
3503 /// declared-slot enumerator's `!self.children.is_empty()` presence
3504 /// probe (caixa-core/src/manifest.rs, which drives the
3505 /// `SUPERVISOR_AUTHOR_KEY_CHILDREN` kebab-case author-label push
3506 /// every [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
3507 /// kind-coherence gate reads), the [`Self::supervisor_view`]
3508 /// per-supervisor typed-view composer's `self.children.clone()`
3509 /// per-child fold-in path (caixa-core/src/manifest.rs, which
3510 /// materializes the typed [`crate::supervisor::SupervisorSpec`]
3511 /// view every [`crate::StandardLayout::verify`] Supervisor-arm gate
3512 /// dispatches on), and the [`crate::StandardLayout::verify`] per-
3513 /// `:children :caixa` self-parent refusal probe's
3514 /// `&caixa.children`-borrowed
3515 /// [`crate::supervisor::validate_no_self_supervision`] input
3516 /// (caixa-core/src/layout.rs, which pins the "no child names the
3517 /// supervisor's own `:nome`" cross-slot coherence gate). A future
3518 /// extension of the outer `:children` axis (a per-cluster
3519 /// `:children-overrides` overlay the wasm-engine operator resolves
3520 /// at admission time so a cluster-specific child-set can tighten
3521 /// a caixa-declared list without re-authoring the `caixa.lisp`,
3522 /// promotion of the plain `Vec<ChildSpec>` to a richer
3523 /// `{static, dynamic}` partition once Erlang/OTP's
3524 /// `simple_one_for_one`-shaped dynamic-child slot lands as a typed
3525 /// axis, per-child priority annotation once multi-strategy fan-out
3526 /// lands) would have had to be threaded through all three open-
3527 /// coded copies in lockstep or one consumer would silently
3528 /// disagree with the peers on which child slice a given Caixa
3529 /// resolves to — the enumerator's presence probe reading the raw
3530 /// slot while the peer view-composer's fold-in path read an
3531 /// operator-resolved slot would silently split the paired
3532 /// declared-slot enumerator and typed-view composition, and the
3533 /// [`crate::supervisor::validate_no_self_supervision`] self-parent
3534 /// refusal probe reading a third borrow would silently drift the
3535 /// cross-slot coherence gate's traversal input from the two peers,
3536 /// a three-consumer split at the enumerator, the view composer,
3537 /// and the self-parent gate far from the source `caixa.lisp` with
3538 /// no field naming the child-set-drift root cause. Lifting the
3539 /// resolution rule to a typed method on the substrate primitive
3540 /// means every downstream consumer of the caixa's per-`Caixa`
3541 /// OTP-supervisor outer-slice surface reaches for exactly one
3542 /// typed dispatch — the resolver's accept-set migrates as a unit
3543 /// on any future axis addition.
3544 ///
3545 /// Second outer top-level [`Caixa`] `&[Composite]`-return slice
3546 /// accessor for M2 / M3 typed-slot vec-carry axes — folds on the
3547 /// outer-`Caixa` `&[Composite]` composite-slice sub-family the
3548 /// sibling [`Self::upgrade_from`] (2a1f907) accessor opened, peer
3549 /// at the outer altitude of the closed inner-`SupervisorSpec`
3550 /// [`crate::SupervisorSpec::children`] (bc92bce) accessor on the
3551 /// same OTP-supervisor static-child-list axis — same "byte-equal,
3552 /// borrow-shared" outer-accessor discipline extended onto the
3553 /// second outer-`Caixa` `&[Composite]` vec-carry axis. Sibling in
3554 /// shape to the peer outer-`Caixa` `&[Dep]`-return [`Self::deps`]
3555 /// (ad34b4e) / [`Self::deps_dev`] (f7fd81e) and `&[String]`-return
3556 /// [`Self::autores`] (b5d813f) / [`Self::etiquetas`] (78c7d3c) /
3557 /// [`Self::bibliotecas`] (8a36c23) / [`Self::exe`] (65d9527) /
3558 /// [`Self::servicos`] (611f78b) slice-accessors on the sibling
3559 /// outer-`Caixa` scalar-element vec-carry axes — folds the "outer
3560 /// [`Caixa`] `&[T]` slice" projection pattern onto the sibling
3561 /// M2 typed-composite-element axis
3562 /// ([`crate::supervisor::ChildSpec`] composite, matching the
3563 /// per-inner [`crate::SupervisorSpec::children`] element type at a
3564 /// different altitude).
3565 ///
3566 /// Returns `&[crate::supervisor::ChildSpec]` (not
3567 /// `&Vec<ChildSpec>`) because every downstream consumer of the
3568 /// child list treats it as a read-only sequence — the slice-view
3569 /// is the narrowest borrow that supports every present +
3570 /// roadmapped consumer (`.iter()`, `.len()`, `.is_empty()`, the
3571 /// [`crate::supervisor::validate_no_self_supervision`] `&[ChildSpec]`
3572 /// input, `serde` slice-serialization) without leaking the backing
3573 /// `Vec`'s grow/push/reserve surface no consumer of the typed view
3574 /// reaches for (the storage-side `Vec` remains reachable through
3575 /// the `pub children` field for the mutation-carrying serde round-
3576 /// trip and per-test fixture-mutation paths, including the
3577 /// [`Self::supervisor_view`] fold-in path that clones the slot
3578 /// into the typed view). Named `children()` to match the storage
3579 /// field's name verbatim and the tatara-lisp author-surface term
3580 /// (`:children`) the field's own docstring already carries; the
3581 /// accessor's identity maps onto the canonical OTP supervision
3582 /// vocabulary the [`Caixa::children`] field's docstring already
3583 /// reaches for ("Static children of a supervisor").
3584 #[must_use]
3585 pub const fn children(&self) -> &[crate::supervisor::ChildSpec] {
3586 self.children.as_slice()
3587 }
3588
3589 /// Substrate-canonical per-`Caixa` `:membros` M3 mesh-slot outer-
3590 /// composite MESH-COMPOSITION-shaped per-Aplicacao member-list slice
3591 /// accessor every consumer of the top-level manifest's per-Aplicacao
3592 /// `&[crate::aplicacao::Membro]` slice-view keys off — returns the
3593 /// author-declared `:membros` typed `Vec<crate::aplicacao::Membro>`
3594 /// verbatim as a `&[crate::aplicacao::Membro]` slice-view over the
3595 /// same backing buffer the raw `self.membros.as_slice()` field access
3596 /// borrows from. Empty-slice-carrying (the "no members declared" arm
3597 /// every non-`Aplicacao`-kind `defcaixa` carries by `#[serde(default)]`
3598 /// and every partially-authored Aplicacao carries before the
3599 /// [`crate::AplicacaoError::MembrosEmpty`] gate fires; the returned
3600 /// `&[Membro]` degenerates to an empty slice on those arms without any
3601 /// silent `None` collapse).
3602 ///
3603 /// The outer `:membros` slot carries the M3 typed MESH-COMPOSITION
3604 /// per-Aplicacao member list — the load-bearing container of every
3605 /// per-member `{caixa, versao}` pair the caixa-mesh renderer's
3606 /// per-Aplicacao program-emission dispatch fans on at mesh-artifact
3607 /// materialization time (MESH-COMPOSITION §III.1 — the typed graph's
3608 /// vertex set the `:contratos` `:de`/`:para` edges resolve against and
3609 /// the `:entrada :para` external-gateway destination validates
3610 /// against; CAIXA-SDLC §II — the typed-M3 slot algebra the operator's
3611 /// per-Aplicacao fan-out dispatch fans on). Every per-member axis
3612 /// threads through a lifted per-entry accessor on the
3613 /// [`crate::aplicacao::Membro`] type: the
3614 /// [`crate::aplicacao::Membro::nome`] DNS-1123-label member-caixa-
3615 /// identity scalar accessor (4a32abf) and the peer
3616 /// [`crate::aplicacao::Membro::versao_requirement`] SemVer-2
3617 /// version-requirement scalar accessor (a40b0e3). Every downstream
3618 /// consumer of the mesh-graph path first passes through this outer
3619 /// accessor onto the slice and then dispatches per-member through
3620 /// the inner accessors — the two-level dispatch means every per-
3621 /// `:membros` reader now routes through a typed dispatch on the
3622 /// substrate primitive at both altitudes.
3623 ///
3624 /// Prior to this lift the `.membros` `Vec<Membro>` slot was accessed
3625 /// inline at three production sites across two files — the
3626 /// [`Self::declared_mesh_slots`] mesh-slot declared-slot
3627 /// enumerator's `!self.membros.is_empty()` presence probe
3628 /// (caixa-core/src/manifest.rs, which drives the
3629 /// `M3_AUTHOR_KEY_MEMBROS` kebab-case author-label push every
3630 /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-coherence
3631 /// gate reads), the [`Self::aplicacao_view`] per-Aplicacao typed-view
3632 /// composer's `self.membros.clone()` per-member fold-in path
3633 /// (caixa-core/src/manifest.rs, which materializes the typed
3634 /// [`crate::aplicacao::AplicacaoSpec`] view every
3635 /// [`crate::StandardLayout::verify`] Aplicacao-arm gate dispatches
3636 /// on), and the [`crate::StandardLayout::verify`] per-`:membros
3637 /// :caixa` self-membership refusal probe's `&caixa.membros`-borrowed
3638 /// [`crate::aplicacao::validate_no_self_membership`] input
3639 /// (caixa-core/src/layout.rs, which pins the "no member names the
3640 /// Aplicacao's own `:nome`" cross-slot coherence gate). A future
3641 /// extension of the outer `:membros` axis (a per-cluster
3642 /// `:membros-overrides` overlay the wasm-engine operator resolves at
3643 /// admission time so a cluster-specific member-set can tighten a
3644 /// caixa-declared list without re-authoring the `caixa.lisp`,
3645 /// promotion of the plain `Vec<Membro>` to a richer
3646 /// `{static, dynamic}` partition once runtime-resolved Aplicacao
3647 /// members land as a typed axis, per-member priority annotation once
3648 /// multi-strategy fan-out lands) would have had to be threaded
3649 /// through all three open-coded copies in lockstep or one consumer
3650 /// would silently disagree with the peers on which member slice a
3651 /// given Caixa resolves to — the enumerator's presence probe reading
3652 /// the raw slot while the peer view-composer's fold-in path read an
3653 /// operator-resolved slot would silently split the paired
3654 /// declared-slot enumerator and typed-view composition, and the
3655 /// [`crate::aplicacao::validate_no_self_membership`] self-membership
3656 /// refusal probe reading a third borrow would silently drift the
3657 /// cross-slot coherence gate's traversal input from the two peers, a
3658 /// three-consumer split at the enumerator, the view composer, and
3659 /// the self-membership gate far from the source `caixa.lisp` with no
3660 /// field naming the member-set-drift root cause. Lifting the
3661 /// resolution rule to a typed method on the substrate primitive
3662 /// means every downstream consumer of the caixa's per-`Caixa`
3663 /// MESH-COMPOSITION outer-slice surface reaches for exactly one
3664 /// typed dispatch — the resolver's accept-set migrates as a unit on
3665 /// any future axis addition.
3666 ///
3667 /// Third outer top-level [`Caixa`] `&[Composite]`-return slice
3668 /// accessor for M2 / M3 typed-slot vec-carry axes — opens the outer-
3669 /// `Caixa` M3 mesh-slot arm of the `&[Composite]` composite-slice
3670 /// sub-family the sibling M2 [`Self::upgrade_from`] (2a1f907) /
3671 /// [`Self::children`] (c17b51e) accessors opened for the M2 vec-carry
3672 /// altitude. Peer at the outer altitude of the closed inner-
3673 /// [`crate::AplicacaoSpec::membros`] (6c77e36) accessor on the same
3674 /// MESH-COMPOSITION per-Aplicacao member-list axis — the two
3675 /// altitudes now share the same "byte-equal, borrow-shared" outer-
3676 /// accessor discipline. Sibling in shape to the peer outer-`Caixa`
3677 /// `&[Dep]`-return [`Self::deps`] (ad34b4e) / [`Self::deps_dev`]
3678 /// (f7fd81e) and `&[String]`-return [`Self::autores`] (b5d813f) /
3679 /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`] (8a36c23) /
3680 /// [`Self::exe`] (65d9527) / [`Self::servicos`] (611f78b) slice-
3681 /// accessors on the sibling outer-`Caixa` scalar-element vec-carry
3682 /// axes — folds the "outer [`Caixa`] `&[T]` slice" projection
3683 /// pattern onto the sibling M3 typed-composite-element axis
3684 /// ([`crate::aplicacao::Membro`] composite, matching the per-inner
3685 /// [`crate::AplicacaoSpec::membros`] element type at a different
3686 /// altitude).
3687 ///
3688 /// Returns `&[crate::aplicacao::Membro]` (not `&Vec<Membro>`)
3689 /// because every downstream consumer of the member list treats it
3690 /// as a read-only sequence — the slice-view is the narrowest borrow
3691 /// that supports every present + roadmapped consumer (`.iter()`,
3692 /// `.len()`, `.is_empty()`, the
3693 /// [`crate::aplicacao::validate_no_self_membership`] `&[Membro]`
3694 /// input, `serde` slice-serialization) without leaking the backing
3695 /// `Vec`'s grow/push/reserve surface no consumer of the typed view
3696 /// reaches for (the storage-side `Vec` remains reachable through the
3697 /// `pub membros` field for the mutation-carrying serde round-trip
3698 /// and per-test fixture-mutation paths, including the
3699 /// [`Self::aplicacao_view`] fold-in path that clones the slot into
3700 /// the typed view). Named `membros()` to match the storage field's
3701 /// name verbatim and the tatara-lisp author-surface term
3702 /// (`:membros`) the field's own docstring already carries; the
3703 /// accessor's identity maps onto the canonical MESH-COMPOSITION
3704 /// vocabulary the [`Caixa::membros`] field's docstring already
3705 /// reaches for ("Member Servicos that make up this Aplicacao").
3706 #[must_use]
3707 pub const fn membros(&self) -> &[crate::aplicacao::Membro] {
3708 self.membros.as_slice()
3709 }
3710
3711 /// Substrate-canonical per-`Caixa` `:contratos` M3 mesh-slot outer-
3712 /// composite MESH-COMPOSITION-shaped per-Aplicacao WIT-typed
3713 /// inter-Servico contract-list slice accessor every consumer of the
3714 /// top-level manifest's per-Aplicacao `&[crate::aplicacao::WitContract]`
3715 /// slice-view keys off — returns the author-declared `:contratos`
3716 /// typed `Vec<crate::aplicacao::WitContract>` verbatim as a
3717 /// `&[crate::aplicacao::WitContract]` slice-view over the same
3718 /// backing buffer the raw `self.contratos.as_slice()` field access
3719 /// borrows from. Empty-slice-carrying (the "no contracts declared"
3720 /// arm every non-`Aplicacao`-kind `defcaixa` carries by
3721 /// `#[serde(default)]` and every leaf Aplicacao carrying only a
3722 /// single member with no inter-Servico edge carries; the returned
3723 /// `&[WitContract]` degenerates to an empty slice on those arms
3724 /// without any silent `None` collapse).
3725 ///
3726 /// The outer `:contratos` slot carries the M3 typed MESH-COMPOSITION
3727 /// per-Aplicacao WIT-typed inter-Servico edge list — the load-bearing
3728 /// container of every per-edge `{de, para, wit, endpoint | subject |
3729 /// slot}` quadruple the caixa-mesh renderer's per-Aplicacao
3730 /// `CiliumNetworkPolicy` fan-out (one L7 policy per edge —
3731 /// MESH-COMPOSITION §III.2 point 2) and per-`(:de, :para)`
3732 /// adjacency-list seed dispatch on at mesh-artifact materialization
3733 /// time (MESH-COMPOSITION §III.1 — the typed graph's edge set the
3734 /// `:membros` vertex set resolves against, closed by the
3735 /// [`crate::AplicacaoError::ContractoUnknownMember`] / cycle-refusal
3736 /// gates in §III.3; CAIXA-SDLC §II — the typed-M3 slot algebra the
3737 /// operator's per-Aplicacao fan-out dispatch fans on). Every
3738 /// per-edge axis threads through a lifted per-entry accessor on the
3739 /// [`crate::aplicacao::WitContract`] type: the peer `de` / `para`
3740 /// DNS-1123-label member-caixa-name endpoint scalar accessors, the
3741 /// [`crate::aplicacao::WitContract::endpoint`] (7020470) HTTP-shape
3742 /// / [`crate::aplicacao::WitContract::subject`] (90de675)
3743 /// NATS-pub-sub-shape / [`crate::aplicacao::WitContract::slot`]
3744 /// (ed22b66) `wasi:keyvalue/store`-shape payload-carrier accessors,
3745 /// and the WIT-world discriminant. Every downstream consumer of the
3746 /// mesh-graph edge path first passes through this outer accessor
3747 /// onto the slice and then dispatches per-contract through the
3748 /// inner accessors — the two-level dispatch means every
3749 /// per-`:contratos` reader now routes through a typed dispatch on
3750 /// the substrate primitive at both altitudes.
3751 ///
3752 /// Prior to this lift the `.contratos` `Vec<WitContract>` slot was
3753 /// accessed inline at two production sites in
3754 /// caixa-core/src/manifest.rs — the [`Self::declared_mesh_slots`]
3755 /// mesh-slot declared-slot enumerator's
3756 /// `!self.contratos.is_empty()` presence probe (which drives the
3757 /// `M3_AUTHOR_KEY_CONTRATOS` kebab-case author-label push every
3758 /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-coherence
3759 /// gate reads) and the [`Self::aplicacao_view`] per-Aplicacao
3760 /// typed-view composer's `self.contratos.clone()` per-contract
3761 /// fold-in path (which materializes the typed
3762 /// [`crate::aplicacao::AplicacaoSpec`] view every
3763 /// [`crate::StandardLayout::verify`] Aplicacao-arm gate and every
3764 /// downstream `caixa-mesh` renderer dispatches on). A future
3765 /// extension of the outer `:contratos` axis (a per-cluster
3766 /// `:contratos-overrides` overlay the wasm-engine operator resolves
3767 /// at admission time so a cluster-specific edge-set can tighten a
3768 /// caixa-declared list without re-authoring the `caixa.lisp`,
3769 /// promotion of the plain `Vec<WitContract>` to a richer
3770 /// `{static, dynamic}` partition once runtime-resolved contract
3771 /// edges land, per-edge policy annotation once the M4 per-edge
3772 /// policy overlay axis lands) would have had to be threaded through
3773 /// both open-coded copies in lockstep or one consumer would
3774 /// silently disagree with the peer on which edge slice a given
3775 /// Caixa resolves to — the enumerator's presence probe reading the
3776 /// raw slot while the peer view-composer's fold-in path read an
3777 /// operator-resolved slot would silently split the paired
3778 /// declared-slot enumerator and typed-view composition, a
3779 /// two-consumer split at the enumerator and the view composer far
3780 /// from the source `caixa.lisp` with no field naming the edge-set-
3781 /// drift root cause. Lifting the resolution rule to a typed method
3782 /// on the substrate primitive means every downstream consumer of
3783 /// the caixa's per-`Caixa` MESH-COMPOSITION outer-slice surface
3784 /// reaches for exactly one typed dispatch — the resolver's
3785 /// accept-set migrates as a unit on any future axis addition.
3786 ///
3787 /// Fourth and final outer top-level [`Caixa`] `&[Composite]`-return
3788 /// slice accessor for M2 / M3 typed-slot vec-carry axes — closes
3789 /// the outer-`Caixa` `&[Composite]` composite-slice sub-family the
3790 /// sibling M2 [`Self::upgrade_from`] (2a1f907) / [`Self::children`]
3791 /// (c17b51e) accessors opened and the M3 [`Self::membros`]
3792 /// (0f26987) accessor folded on, and closes the outer-`Caixa` M3
3793 /// mesh-slot arm of the composite-slice sub-family the sibling
3794 /// [`Self::membros`] accessor opened for the M3 vec-carry altitude.
3795 /// Peer at the outer altitude of the closed inner-
3796 /// [`crate::AplicacaoSpec::contratos`] (0dcc926) accessor on the
3797 /// same MESH-COMPOSITION per-Aplicacao contract-list axis — the two
3798 /// altitudes now share the same "byte-equal, borrow-shared" outer-
3799 /// accessor discipline. Sibling in shape to the peer outer-`Caixa`
3800 /// `&[Dep]`-return [`Self::deps`] (ad34b4e) / [`Self::deps_dev`]
3801 /// (f7fd81e) and `&[String]`-return [`Self::autores`] (b5d813f) /
3802 /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`] (8a36c23) /
3803 /// [`Self::exe`] (65d9527) / [`Self::servicos`] (611f78b) slice-
3804 /// accessors on the sibling outer-`Caixa` scalar-element vec-carry
3805 /// axes — folds the "outer [`Caixa`] `&[T]` slice" projection
3806 /// pattern onto the sibling M3 typed-composite-element axis
3807 /// ([`crate::aplicacao::WitContract`] composite, matching the
3808 /// per-inner [`crate::AplicacaoSpec::contratos`] element type at a
3809 /// different altitude).
3810 ///
3811 /// Returns `&[crate::aplicacao::WitContract]` (not
3812 /// `&Vec<WitContract>`) because every downstream consumer of the
3813 /// contract list treats it as a read-only sequence — the slice-view
3814 /// is the narrowest borrow that supports every present + roadmapped
3815 /// consumer (`.iter()`, `.len()`, `.is_empty()`, per-edge WIT-world
3816 /// discriminant dispatch, `serde` slice-serialization) without
3817 /// leaking the backing `Vec`'s grow/push/reserve surface no
3818 /// consumer of the typed view reaches for (the storage-side `Vec`
3819 /// remains reachable through the `pub contratos` field for the
3820 /// mutation-carrying serde round-trip and per-test fixture-mutation
3821 /// paths, including the [`Self::aplicacao_view`] fold-in path that
3822 /// clones the slot into the typed view). Named `contratos()` to
3823 /// match the storage field's name verbatim and the tatara-lisp
3824 /// author-surface term (`:contratos`) the field's own docstring
3825 /// already carries; the accessor's identity maps onto the canonical
3826 /// MESH-COMPOSITION vocabulary the [`Caixa::contratos`] field's
3827 /// docstring already reaches for ("WIT-typed inter-Servico
3828 /// contracts").
3829 #[must_use]
3830 pub const fn contratos(&self) -> &[crate::aplicacao::WitContract] {
3831 self.contratos.as_slice()
3832 }
3833
3834 /// Compose the Aplicacao-related flat slots into a single typed
3835 /// [`crate::aplicacao::AplicacaoSpec`] for validation +
3836 /// downstream renderer consumption. Returns `None` when the
3837 /// caixa isn't a `:kind Aplicacao`.
3838 #[must_use]
3839 pub fn aplicacao_view(&self) -> Option<crate::aplicacao::AplicacaoSpec> {
3840 if !self.kind().is_aplicacao() {
3841 return None;
3842 }
3843 Some(crate::aplicacao::AplicacaoSpec {
3844 membros: self.membros().to_vec(),
3845 contratos: self.contratos().to_vec(),
3846 politicas: self.politicas().cloned().unwrap_or_default(),
3847 placement: self.placement().cloned().unwrap_or_default(),
3848 entrada: self.entrada().cloned(),
3849 })
3850 }
3851
3852 /// The kebab-case `:slot` tags of every M3 mesh slot this caixa
3853 /// *declares* a value on, in canonical declaration order
3854 /// (`:membros` → `:contratos` → `:politicas` → `:placement` →
3855 /// `:entrada`). A slot counts as declared when its backing field
3856 /// carries a value — a non-empty `Vec`, or a `Some(...)`.
3857 ///
3858 /// The M3 mesh slots compose the typed graph of a `:kind Aplicacao`
3859 /// (MESH-COMPOSITION §III.1). [`Self::aplicacao_view`] only folds
3860 /// them into a validatable [`crate::aplicacao::AplicacaoSpec`] when
3861 /// the kind matches (returns `None` otherwise), and the caixa-mesh /
3862 /// caixa-flux / caixa-helm renderers only emit them for an
3863 /// Aplicacao. On any *other* kind a declared mesh slot is the
3864 /// manifest field's documented "ignored otherwise" (see the
3865 /// `:membros` … `:entrada` field docs): it silently passes
3866 /// [`Caixa::from_lisp`] and then vanishes — never validated, never
3867 /// rendered — far from the source caixa.lisp.
3868 /// [`crate::StandardLayout::verify`] consults this to reject that
3869 /// silent-drop at caixa-build time
3870 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`]), mirroring the
3871 /// `SupervisorOwnsCode` / `AplicacaoOwnsCode` kind-coherence gates:
3872 /// a slot foreign to the kind is a build error, not a silent drop.
3873 ///
3874 /// Lifted as a typed method (rather than an inline disjunction at
3875 /// the verify call site) so the mesh-slot set lives in one place —
3876 /// a future M4 axis added to the Aplicacao surface (per-edge policy
3877 /// overlay, distributed-app takeover config) is one push here, and
3878 /// every consumer reaching for "which mesh slots are set" (the
3879 /// verify gate, a future `feira lint` kind-coherence advisory)
3880 /// inherits the canonical order without rolling its own.
3881 ///
3882 /// Each per-arm kebab-case label is routed through the peer
3883 /// [`crate::M3_AUTHOR_KEY_MEMBROS`] /
3884 /// [`crate::M3_AUTHOR_KEY_CONTRATOS`] /
3885 /// [`crate::M3_AUTHOR_KEY_POLITICAS`] /
3886 /// [`crate::M3_AUTHOR_KEY_PLACEMENT`] /
3887 /// [`crate::M3_AUTHOR_KEY_ENTRADA`] consts declared next to the
3888 /// [`crate::M3_KEY_PLACEMENT`] renderer-side wire-key peer, so both
3889 /// halves of every M3 top-level mesh slot's dual axis (author-facing
3890 /// kebab-case label + renderer-side artifact key) route through one
3891 /// canonical declaration per arm — same discipline the peer
3892 /// [`crate::M2_AUTHOR_KEY_LIMITS`] / [`crate::M2_AUTHOR_KEY_BEHAVIOR`]
3893 /// / [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] top-level M2 slot consts
3894 /// (f49c8b0) establish on the sibling per-Servico M2 top-level slot
3895 /// axis, extended here to close the M3 mesh-slot author-facing-label
3896 /// axis so both altitudes of the typed-slot algebra
3897 /// (per-Servico M2 + per-Aplicacao M3) share the same
3898 /// "one canonical byte-string per arm, next to the axis" discipline.
3899 #[must_use]
3900 pub fn declared_mesh_slots(&self) -> Vec<&'static str> {
3901 let mut slots = Vec::new();
3902 if !self.membros().is_empty() {
3903 slots.push(crate::render::M3_AUTHOR_KEY_MEMBROS);
3904 }
3905 if !self.contratos().is_empty() {
3906 slots.push(crate::render::M3_AUTHOR_KEY_CONTRATOS);
3907 }
3908 if self.politicas().is_some() {
3909 slots.push(crate::render::M3_AUTHOR_KEY_POLITICAS);
3910 }
3911 if self.placement().is_some() {
3912 slots.push(crate::render::M3_AUTHOR_KEY_PLACEMENT);
3913 }
3914 if self.entrada().is_some() {
3915 slots.push(crate::render::M3_AUTHOR_KEY_ENTRADA);
3916 }
3917 slots
3918 }
3919
3920 /// The kebab-case `:slot` tags of every supervisor-tree slot this
3921 /// caixa *declares* a value on, in canonical declaration order
3922 /// (`:estrategia` → `:max-restarts` → `:restart-window` →
3923 /// `:children`). A slot counts as declared when its backing field
3924 /// carries a value — a `Some(...)`, or a non-empty `Vec`.
3925 ///
3926 /// The supervisor-tree slots compose the typed OTP supervisor of a
3927 /// `:kind Supervisor` (INSPIRATIONS §II.2; the `:estrategia` +
3928 /// `:children` field docs above). [`Self::supervisor_view`] only
3929 /// folds them into a validatable [`SupervisorSpec`] when the kind
3930 /// matches (returns `None` otherwise), and the wasm-operator's
3931 /// hierarchical reconciler only consumes them for a Supervisor. On
3932 /// any *other* kind a declared supervisor slot is the manifest
3933 /// field's documented "ignored otherwise" (see the `:estrategia` …
3934 /// `:children` field docs): it silently passes [`Caixa::from_lisp`]
3935 /// and then vanishes — never validated, never reconciled — far from
3936 /// the source caixa.lisp. [`crate::StandardLayout::verify`] consults
3937 /// this to reject that silent-drop at caixa-build time
3938 /// ([`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]), the
3939 /// exact mirror of the [`Self::declared_mesh_slots`] /
3940 /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] gate on the
3941 /// Aplicacao-only slot set: a slot foreign to the kind is a build
3942 /// error, not a silent drop.
3943 #[must_use]
3944 pub fn declared_supervisor_slots(&self) -> Vec<&'static str> {
3945 let mut slots = Vec::new();
3946 if self.estrategia().is_some() {
3947 slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA);
3948 }
3949 if self.max_restarts().is_some() {
3950 slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS);
3951 }
3952 if self.restart_window().is_some() {
3953 slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW);
3954 }
3955 if !self.children().is_empty() {
3956 slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN);
3957 }
3958 slots
3959 }
3960
3961 /// The kebab-case `:slot` tags of every M2 Servico-runtime slot this
3962 /// caixa *declares* a value on, in canonical declaration order
3963 /// (`:limits` → `:behavior` → `:upgrade-from`). A slot counts as
3964 /// declared when its backing field carries a value — a `Some(...)`,
3965 /// or a non-empty `Vec`.
3966 ///
3967 /// The M2 slots configure the runtime of a long-running wasm
3968 /// component, i.e. a `:kind Servico`: `:limits` is Lunatic
3969 /// per-process sandboxing (INSPIRATIONS §III.1), `:behavior` is the
3970 /// OTP `gen_server` callback set (§II.3), `:upgrade-from` is the OTP
3971 /// appup hot-code-reload table (§II.4). The caixa-helm / caixa-flux
3972 /// renderers gate on [`crate::require_kind`]`(_, Servico)` and only
3973 /// emit these slots for a Servico; on any *other* kind a declared M2
3974 /// slot is the manifest field's documented "ignored otherwise": its
3975 /// well-formedness is checked by [`crate::StandardLayout::verify`]
3976 /// but the value is never rendered into a chart / programs.yaml entry
3977 /// — it silently passes [`Caixa::from_lisp`] + `feira build` and then
3978 /// vanishes, far from the source caixa.lisp.
3979 /// [`crate::StandardLayout::verify`] consults this to reject that
3980 /// silent-drop at caixa-build time
3981 /// ([`crate::LayoutError::ServicoSlotsOnNonServico`]), the exact
3982 /// mirror of the [`Self::declared_mesh_slots`] /
3983 /// [`Self::declared_supervisor_slots`] gates on the peer
3984 /// kind-exclusive slot sets: a slot foreign to the kind is a build
3985 /// error, not a silent drop.
3986 ///
3987 /// Each per-arm kebab-case label is routed through the peer
3988 /// [`crate::M2_AUTHOR_KEY_LIMITS`] / [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
3989 /// [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] consts declared next to the
3990 /// [`crate::M2_KEY_LIMITS`] / [`crate::M2_KEY_BEHAVIOR`] /
3991 /// [`crate::M2_KEY_UPGRADE_FROM`] renderer-side wire-key peers, so
3992 /// both halves of the M2 top-level slot's dual axis (author-facing
3993 /// kebab-case label + renderer-side camelCase overlay-container wire
3994 /// key) route through one canonical declaration per arm — same
3995 /// discipline the peer [`crate::M2_BEHAVIOR_AUTHOR_KEY_ON_*`] sub-slot
3996 /// author-label consts (889dc18) establish on the sibling
3997 /// per-callback axis inside the `:behavior` overlay block.
3998 #[must_use]
3999 pub fn declared_servico_slots(&self) -> Vec<&'static str> {
4000 let mut slots = Vec::new();
4001 if self.limits().is_some() {
4002 slots.push(crate::render::M2_AUTHOR_KEY_LIMITS);
4003 }
4004 if self.behavior().is_some() {
4005 slots.push(crate::render::M2_AUTHOR_KEY_BEHAVIOR);
4006 }
4007 if !self.upgrade_from().is_empty() {
4008 slots.push(crate::render::M2_AUTHOR_KEY_UPGRADE_FROM);
4009 }
4010 slots
4011 }
4012
4013 /// The kebab-case `:slot` tags of every code-surface slot this caixa
4014 /// declares a value on that its [`CaixaKind`] doesn't natively own,
4015 /// in canonical declaration order (`:exe` → `:servicos`). A
4016 /// code-surface slot is owned by exactly one kind: `:exe` by
4017 /// [`CaixaKind::Binario`] (the nix-built executable surface), and
4018 /// `:servicos` by [`CaixaKind::Servico`] (the wasm component +
4019 /// `ComputeUnit` daemon surface).
4020 ///
4021 /// Each is silently ignored when declared on the wrong kind: the
4022 /// caixa-helm / caixa-flux / caixa-flake renderers gate on
4023 /// [`crate::require_kind`]`(_, <owning-kind>)`, so on any *other*
4024 /// code-running kind a declared `:exe` / `:servicos` is the manifest
4025 /// field's documented "ignored otherwise" — its path is checked for
4026 /// existence by the layout's `bibliotecas`/`exe`/`servicos` loops
4027 /// (which run after [`Caixa::from_lisp`]), but the value is never
4028 /// rendered into a build target or programs.yaml entry. It silently
4029 /// passes [`Caixa::from_lisp`] + `feira build`, far from the source
4030 /// caixa.lisp, with no field naming which slot is foreign.
4031 ///
4032 /// [`crate::StandardLayout::verify`] consults this to reject that
4033 /// silent-drop at caixa-build time
4034 /// ([`crate::LayoutError::ForeignCodeSlot`]), beside the M2
4035 /// servico-runtime, supervisor-tree, and M3 mesh kind-coherence
4036 /// gates ([`Self::declared_servico_slots`] /
4037 /// [`Self::declared_supervisor_slots`] /
4038 /// [`Self::declared_mesh_slots`]): the fourth kind ↔ slot algebra
4039 /// axis to be closed on the typed surface. The Supervisor /
4040 /// Aplicacao "no code at all" cases ([`crate::LayoutError::SupervisorOwnsCode`]
4041 /// / [`crate::LayoutError::AplicacaoOwnsCode`]) keep their dedicated
4042 /// diagnostics — they fire ahead of this gate on the same `verify`
4043 /// pass, so for Supervisor / Aplicacao the `OwnCode` arm always wins
4044 /// and this method is moot. For Biblioteca / Binario / Servico, this
4045 /// gate fires when a code-running kind declares another code-running
4046 /// kind's exclusive code surface.
4047 ///
4048 /// `:bibliotecas` is deliberately excluded — a Binario or Servico
4049 /// may legitimately ship a `lib/` helper that the underlying
4050 /// substrate (the nix flake for Binario, the wasm component build
4051 /// for Servico) bundles into its build, so the slot's
4052 /// declared-on-wrong-kind cardinality isn't a structural error on
4053 /// either code-running kind. A Biblioteca declaring `:bibliotecas`
4054 /// is the native case (the slot's owning kind). Supervisor /
4055 /// Aplicacao declaring `:bibliotecas` is gated upstream by
4056 /// [`crate::LayoutError::SupervisorOwnsCode`] /
4057 /// [`crate::LayoutError::AplicacaoOwnsCode`].
4058 ///
4059 /// Lifted as a typed method (rather than an inline disjunction at
4060 /// the verify call site) so the foreign-code-slot set lives in one
4061 /// place — a future kind that gains its own code-surface slot is
4062 /// one push here, and every consumer reaching for "which code
4063 /// surfaces are foreign to this kind" (the verify gate, a future
4064 /// `feira lint` kind-coherence advisory, the future `app-operator`'s
4065 /// per-caixa build-target classifier) inherits the canonical order
4066 /// without rolling its own.
4067 #[must_use]
4068 pub fn declared_foreign_code_slots(&self) -> Vec<&'static str> {
4069 let mut slots = Vec::new();
4070 if !self.exe().is_empty() && !self.kind().requires_exe() {
4071 slots.push(":exe");
4072 }
4073 if !self.servicos().is_empty() && !self.kind().requires_servicos() {
4074 slots.push(":servicos");
4075 }
4076 slots
4077 }
4078
4079 /// Validate every entry of `:deps` and `:deps-dev` through
4080 /// [`Dep::validate`] — closing the parity loop with the per-axis
4081 /// `:versao` gates already wired into the typed-graph
4082 /// ([`crate::AplicacaoSpec::validate_membros`] for `:membros`,
4083 /// 9888b13) and typed supervisor tree
4084 /// ([`crate::SupervisorSpec::validate`] for `:children`, b38ff3a).
4085 ///
4086 /// Until this gate landed `:deps :versao` and `:deps-dev :versao`
4087 /// were the only `:versao` axes still untyped past
4088 /// [`Caixa::from_lisp`]: the derive macro stored the requirement
4089 /// as a String without parsing it, so a malformed-but-non-empty
4090 /// requirement (`"^bad-version"`, `"^^0.1"`, `"v0.1"`, `"not-a-req"`)
4091 /// silently passed parse and the `semver::Error` surfaced at
4092 /// lacre-resolve time, far from the source caixa.lisp, with no
4093 /// field naming which `:deps` entry carried the typo. Lifting the
4094 /// gate here makes the four `:versao` typed surfaces (`:deps`,
4095 /// `:deps-dev`, `:membros`, `:children`) structurally equivalent —
4096 /// every requirement string past `validate_deps` is round-trippable
4097 /// through [`crate::parse_requirement`] without re-checking at the
4098 /// resolver layer.
4099 ///
4100 /// Both lists run through the same per-entry validator so a typo
4101 /// in `:deps-dev` surfaces with the same diagnostic as one in
4102 /// `:deps` — neither axis is a second-class citizen of the typed
4103 /// surface.
4104 ///
4105 /// Within each list, [`DepError::DuplicateNome`] closes the
4106 /// set-not-multiset discipline on the `:nome` axis: two entries
4107 /// naming the same caixa carry two `:versao` / `:fonte` / feature
4108 /// triples that the caixa-resolver's lacre pipeline collapses to one
4109 /// via its `HashMap`-keyed-by-`:nome` consumption — the second entry
4110 /// silently overwrites the first at `concrete_versao`-resolve time
4111 /// (the same "second wins / one silently overwrites the other"
4112 /// shape the peer typed-graph duplicate gates already close on every
4113 /// other Vec-shaped authoring surface that keys by name). The
4114 /// duplicate check fires per-list and runs *after* each per-entry
4115 /// [`Dep::validate`] call so a malformed-and-duplicated entry
4116 /// surfaces its narrower per-entry diagnostic
4117 /// ([`DepError::NomeInvalid`], [`DepError::VersaoInvalid`],
4118 /// [`DepError::FonteRepoEmpty`], …) before the cross-entry duplicate
4119 /// diagnostic — the canonical "per-entry shape before cross-entry
4120 /// uniqueness" precedence the peer `:children :caixa`
4121 /// ([`crate::SupervisorSpec::validate`]), `:membros :caixa`
4122 /// ([`crate::AplicacaoSpec::validate_membros`]), `:contratos`
4123 /// ([`crate::AplicacaoSpec::validate`]), `:placement :clusters`
4124 /// ([`crate::AplicacaoSpec::validate_placement`]),
4125 /// `:entrada :paths` ([`crate::AplicacaoSpec::validate`]),
4126 /// `:upgrade-from :from` ([`crate::upgrade::validate_upgrade_from`]),
4127 /// and the within-`:upgrade-from`-entry per-instruction-class
4128 /// singularity gates ([`crate::UpgradeError::DuplicateLoadModule`],
4129 /// [`crate::UpgradeError::DuplicateStateChange`],
4130 /// [`crate::UpgradeError::DuplicateCleanup`]) all establish.
4131 ///
4132 /// Cross-list (`:deps` ↔ `:deps-dev`) coincidence is *not* gated
4133 /// here: Cargo's `[dependencies]` + `[dev-dependencies]` accept the
4134 /// same name in both tables (the dev table's pin overrides the
4135 /// runtime table's pin in test/dev contexts), and caixa's surface
4136 /// mirrors that convention until a deliberate choice retires the
4137 /// override pattern. Only within-list duplicates are structurally
4138 /// incoherent — those are what this gate closes.
4139 pub fn validate_deps(&self) -> Result<(), DepError> {
4140 for &list in crate::dep::DepList::ALL {
4141 let mut seen = std::collections::HashSet::new();
4142 for dep in self.deps_of(list) {
4143 dep.validate()?;
4144 crate::render::insert_first_seen(&mut seen, dep.nome(), || {
4145 DepError::DuplicateNome {
4146 nome: dep.nome().to_string(),
4147 list: list.as_str(),
4148 }
4149 })?;
4150 }
4151 }
4152 Ok(())
4153 }
4154
4155 /// Reject `:nome` values the K8s apiserver would refuse at admission
4156 /// time. The top-level Caixa identity flows directly into every
4157 /// substrate-side artifact's `metadata.name` axis: the
4158 /// `lareira-<nome>` Helm chart name ([`caixa-helm::lib::chart_name`]),
4159 /// the programs.yaml `name:` entry the `lareira-fleet-programs`
4160 /// aggregator keys ComputeUnit derivation off
4161 /// ([`caixa-flux::lib::programs_yaml_entry`]), the
4162 /// `LABEL_APLICACAO` label value carried on every Aplicacao-owned
4163 /// pod and the per-`:contratos` CiliumNetworkPolicy `metadata.name`
4164 /// (`<aplicacao>-<de>-to-<para>`) and the per-`:entrada`
4165 /// `<aplicacao>-<para>` HTTPRoute `metadata.name`
4166 /// ([`caixa-mesh::lib::cilium_network_policies`],
4167 /// [`caixa-mesh::lib::gateway_routes`]), and the default
4168 /// `lib/<nome>.lisp` / `exe/<nome>` layout paths
4169 /// ([`crate::StandardLayout::verify`]). Each K8s apiserver-side
4170 /// schema enforces the DNS-1123 label rule on admission; a
4171 /// structurally invalid `:nome` (`"MyApp"` — the canonical
4172 /// "I copied the display name verbatim" footgun, `"my_app"` — the
4173 /// Python-/Postgres-leak, `"team.app"` — `:nome` is a single label
4174 /// not a subdomain, `"-app"` / `"app-"` — DNS-1123 boundary
4175 /// violations, `"my app"` — the paste-from-doc footgun, `"café"` —
4176 /// IDN must be pre-encoded as Punycode, the 64-byte UUID-shaped
4177 /// over-cap slug) silently passed [`Caixa::from_lisp`] and the
4178 /// failure surfaced at `kubectl apply` time as a `metadata.name:
4179 /// Invalid value` rejection on whichever derived artifact admitted
4180 /// first, far from the source `caixa.lisp` and without any field
4181 /// naming the offending `:nome`.
4182 ///
4183 /// Thin wrapper around [`crate::render::is_dns_1123_label`] (the
4184 /// substrate-side predicate the per-axis name gates already share:
4185 /// `:membros :caixa` 3f9d7a0, `:placement :clusters` 6cbb900,
4186 /// `:children :caixa` 31bfa43) that maps the shared parser-shaped
4187 /// reason into the [`ManifestError::NomeInvalid`] variant, so the
4188 /// diagnostic is self-locating (the offending `:nome` is named
4189 /// verbatim) and the author can grep their `caixa.lisp` for
4190 /// `:nome "<value>"` and fix it in one edit. Same diagnostic shape
4191 /// every per-axis sibling gate already exposes
4192 /// ([`crate::AplicacaoError::MembroCaixaInvalid`],
4193 /// [`crate::AplicacaoError::PlacementClusterInvalid`],
4194 /// [`crate::SupervisorError::ChildCaixaInvalid`]).
4195 ///
4196 /// Empty `:nome` (which [`Caixa::from_lisp`] does not reject — the
4197 /// derive macro stores the raw String) is gated by the narrower
4198 /// [`ManifestError::NomeEmpty`] arm before the predicate is
4199 /// consulted, mirroring the empty-first cascade every per-axis
4200 /// name gate already uses (e.g. `MembroCaixaEmpty` before
4201 /// `MembroCaixaInvalid`, `EmptyChildName` before `ChildCaixaInvalid`).
4202 pub fn validate_nome(&self) -> Result<(), ManifestError> {
4203 // Routes through the shared
4204 // [`crate::render::require_valid_dns_1123_label`] gate the peer
4205 // name axes each land on so drift between the eight axes'
4206 // accepted DNS-1123-label sets is structurally impossible.
4207 let nome = self.nome();
4208 crate::render::require_valid_dns_1123_label(
4209 nome,
4210 || ManifestError::NomeEmpty,
4211 |reason| ManifestError::NomeInvalid {
4212 nome: nome.to_string(),
4213 reason,
4214 },
4215 )
4216 }
4217
4218 /// Reject `:nome` values whose joint length with the canonical
4219 /// [`crate::LAREIRA_CHART_NAME_PREFIX`] (`"lareira-"`) overflows
4220 /// the K8s DNS-1123 label cap [`crate::DNS_1123_LABEL_MAX_LEN`]
4221 /// (63 bytes). Every per-Servico / per-Aplicacao renderer the
4222 /// substrate carries materializes the caixa's `:nome` through the
4223 /// canonical [`crate::lareira_chart_name`] helper (f7320d7) into a
4224 /// `lareira-<nome>` artifact that lands as a K8s `metadata.name` /
4225 /// Helm chart name / `HelmRelease` `release_name`: `caixa-helm`'s
4226 /// `ChartDir.name` + `Chart.yaml::name`
4227 /// (caixa-helm/src/lib.rs:207), `caixa-flux`'s `cluster_bundle`
4228 /// `HelmRelease` `chart:` slot (caixa-flux/src/lib.rs:329),
4229 /// `caixa-tatara`'s `process_for_aplicacao` `release_name` +
4230 /// `oci://<registry>/lareira-<nome>` chart ref
4231 /// (caixa-tatara/src/lib.rs:124,178). Helm's own `Chart.yaml::name`
4232 /// admission rule strict-parses against DNS-1123-label, the Helm
4233 /// operator's tracking-secret name is derived from `release_name`
4234 /// and is itself DNS-1123-label-bounded, and the rendered chart's
4235 /// K8s object `metadata.name` axes embed the chart name as a
4236 /// prefix — every one fails admission on a > 63-byte chart name.
4237 ///
4238 /// The per-axis [`Self::validate_nome`] gate (6c992f8) already
4239 /// caps `:nome` itself at 63 bytes via [`is_dns_1123_label`], so a
4240 /// `:nome` of 56–63 bytes silently passed validate (the inner
4241 /// DNS-1123 check accepts the bare `:nome`) but produced a
4242 /// `lareira-<nome>` of 64–71 bytes that the apiserver / `helm lint`
4243 /// rejected at admission — far from the source `caixa.lisp`, with
4244 /// no field naming the overflow root cause. The
4245 /// [`lareira_chart_name`] helper's own doc comment
4246 /// (caixa-core/src/render.rs:3198) explicitly deferred the fix:
4247 /// "the M4 admission webhook will pin the joint-length invariant
4248 /// when it lands". This gate lands the invariant at the
4249 /// manifest-validate layer rather than waiting for the apiserver
4250 /// — the same fail-at-the-source posture every peer per-axis
4251 /// value-shape gate (DNS-1123 on `:nome`, SemVer-2 on `:versao`,
4252 /// SPDX-expression-shape on `:licenca`, 4-digit decimal year on
4253 /// `:edicao`, etc.) takes.
4254 ///
4255 /// Thin wrapper around
4256 /// [`crate::render::is_lareira_chart_name_shape`] (the
4257 /// substrate-side predicate that composes [`lareira_chart_name`] +
4258 /// [`is_dns_1123_label`] via the lifted
4259 /// [`crate::LAREIRA_CHART_NAME_NOME_MAX_LEN`] budget); maps the
4260 /// shared parser-shaped reason into the
4261 /// [`ManifestError::NomeChartNameBudgetExceeded`] variant so the
4262 /// diagnostic is self-locating (the offending `:nome` is named
4263 /// verbatim alongside the rendered chart name and the budget) and
4264 /// the author can shorten in one edit. The gate runs across every
4265 /// `:kind` — `:nome` is the substrate-wide identity axis any
4266 /// future renderer the substrate adds can derive a
4267 /// `lareira-<nome>` artifact from, and uniform enforcement closes
4268 /// the drift footgun where a future kind grows a chart-emitting
4269 /// render path while the validate cascade doesn't catch it.
4270 ///
4271 /// Runs *after* [`Self::validate_nome`] so the narrower
4272 /// `NomeEmpty` / `NomeInvalid` shape diagnostics fire first — a
4273 /// structurally-malformed `:nome` (empty, uppercase, underscore,
4274 /// dot, leading/trailing hyphen, Unicode, > 63 bytes) surfaces its
4275 /// specific shape error rather than the chart-name-budget error,
4276 /// preserving the legitimate "well-shaped `:nome` that happens to
4277 /// overflow the joint cap" arm for this gate.
4278 pub fn validate_nome_chart_name_budget(&self) -> Result<(), ManifestError> {
4279 let nome = self.nome();
4280 crate::render::is_lareira_chart_name_shape(nome).map_err(|reason| {
4281 ManifestError::NomeChartNameBudgetExceeded {
4282 nome: nome.to_string(),
4283 reason,
4284 }
4285 })
4286 }
4287
4288 /// Reject `:versao` values that don't parse as [`semver::Version`].
4289 /// The top-level Caixa version flows directly into every
4290 /// substrate-side artifact that carries a "this is which version of
4291 /// the caixa" axis: the `lareira-<nome>` Helm chart's `Chart.yaml`
4292 /// `version:` + `appVersion:` axes ([`caixa-helm::lib`] —
4293 /// SemVer-2-strict at `helm template` / `helm install` time per
4294 /// https://helm.sh/docs/topics/charts/#charts-and-versioning), the
4295 /// `feira publish` Zig-style `v<versao>` git tag
4296 /// ([`caixa-flux::lib::programs_yaml_entry`] / the
4297 /// `caixa-publish.yml` reusable workflow), the programs.yaml entry's
4298 /// `versao:` value the `lareira-fleet-programs` aggregator carries
4299 /// onto each rendered ComputeUnit, the OCI image's `:v<versao>` /
4300 /// `:latest` tags the substrate's `wasi-service-flake` builds with
4301 /// `skopeo push`, the lacre closure's pinned versions
4302 /// ([`caixa-resolver`] keys `concrete_versao`), and the
4303 /// `:upgrade-from :from` references peers in this exact `versao`
4304 /// shape (`semver::Version`, not `VersionReq`). Each consumer
4305 /// expects a strict three-part `MAJOR.MINOR.PATCH` (optionally
4306 /// `-prerelease` and/or `+build`); a structurally invalid `:versao`
4307 /// (`"0.1"` — missing patch, the canonical "I shortened it" footgun;
4308 /// `"v0.1.0"` — the git-tag-shape-leaking-into-versao typo;
4309 /// `"latest"` / `"main"` — the "I confused it with a docker tag"
4310 /// footgun; `"^0.1"` / `"~0.1.2"` — the requirement-shape leaking
4311 /// into the version field a peer `:deps :versao` accepts;
4312 /// `"0.1.0.0"` — the four-part Java/Microsoft convention DNS
4313 /// SemVer-2 forbids) silently passed [`Caixa::from_lisp`] (the
4314 /// derive macro stores the raw String) and the failure surfaced at
4315 /// the *first* downstream consumer that strict-parses it: at
4316 /// `helm install` time as a chart-version rejection, at
4317 /// `feira publish` time as a malformed git tag, at lacre-resolve
4318 /// time as a `semver::Error` not naming the offending caixa, at
4319 /// `feira upgrade --to <versao>` time as an unresolvable
4320 /// `:upgrade-from :from` match — far from the source `caixa.lisp`
4321 /// and without any field naming the offending `:versao`.
4322 ///
4323 /// Thin wrapper around [`semver::Version::parse`] — the same parser
4324 /// [`crate::CaixaVersion::parse`] (the typed `:versao` accessor)
4325 /// and [`crate::UpgradeFromEntry::validate`] (the peer
4326 /// `:upgrade-from :from` axis, 26da2c7) consume. Maps the
4327 /// `semver::Error` reason into the [`ManifestError::VersaoInvalid`]
4328 /// variant, carrying the offending `:versao` verbatim + a
4329 /// parser-shaped reason naming the specific violation, so the
4330 /// diagnostic is self-locating (the author can grep their
4331 /// `caixa.lisp` for `:versao "<value>"` and fix it in one edit).
4332 /// Same diagnostic shape as [`ManifestError::NomeInvalid`]
4333 /// (6c992f8) and [`crate::UpgradeError::FromInvalid`]
4334 /// (b0c8389) on the peer axes. With this gate, the typed `:versao`
4335 /// surfaces — top-level `:versao`, `:upgrade-from :from` — are
4336 /// now structurally equivalent (every value past validate is
4337 /// round-trippable through [`semver::Version::parse`] without
4338 /// re-checking at the renderer, resolver, or operator hot-upgrade
4339 /// layer), peer with the four `:versao` requirement axes (`:deps`,
4340 /// `:deps-dev`, `:membros`, `:children`) the prior commits
4341 /// (2420c44, 9888b13, b38ff3a) wired through `parse_requirement`.
4342 ///
4343 /// Empty `:versao` (which [`Caixa::from_lisp`] does not reject —
4344 /// the derive macro stores the raw String) is gated by the
4345 /// narrower [`ManifestError::VersaoEmpty`] arm before the parser is
4346 /// consulted, mirroring the empty-first cascade every per-axis
4347 /// version gate already uses (e.g. `MembroVersaoEmpty` before
4348 /// `MembroVersaoInvalid`, `EmptyChildVersion` before
4349 /// `ChildVersaoInvalid`, `NomeEmpty` before `NomeInvalid`).
4350 pub fn validate_versao(&self) -> Result<(), ManifestError> {
4351 let versao = self.versao();
4352 if versao.is_empty() {
4353 return Err(ManifestError::VersaoEmpty);
4354 }
4355 semver::Version::parse(versao).map_err(|e| ManifestError::VersaoInvalid {
4356 versao: versao.to_string(),
4357 reason: e.to_string(),
4358 })?;
4359 Ok(())
4360 }
4361
4362 /// Reject `:restart-window` values the shared
4363 /// [`crate::supervisor::duration_codec::parse`] refuses. The flat
4364 /// `restart_window: Option<String>` slot on [`Caixa`] is stored
4365 /// raw by the derive macro (the typed [`SupervisorSpec`] holds an
4366 /// `Option<Duration>` routed through the shared codec via `with =
4367 /// "duration_codec"`); the inline `Caixa → SupervisorSpec`
4368 /// view-construction path ([`Self::supervisor_view`]) folds the
4369 /// raw string through the same shared codec and soft-swallows the
4370 /// parse error as `None` to keep the view best-effort. Without
4371 /// this gate a malformed `:restart-window` (`"1.5s"` — the
4372 /// fractional-seconds drift class; `"1.0s"` — the decimal-shaped
4373 /// integer drift; `"0.5m"` — the unit-fraction drift; `"+30s"` /
4374 /// `"-30s"` — the leading-sign drift; `"30x"` — the unknown-unit
4375 /// footgun; `"abc"` — pure garbage; `""` — the empty-after-trim
4376 /// edge case) silently produced a `SupervisorSpec` with
4377 /// `restart_window: None`, indistinguishable from the canonical
4378 /// "omit the slot to express no reset" authoring shape — Erlang/OTP's
4379 /// `MaxIntensity / Period` invariant turns into a never-reset
4380 /// supervisor far from the source `caixa.lisp`, with no field
4381 /// naming the offending `:restart-window`. Lifting the gate to a
4382 /// Caixa-level validator mirrors the trajectory of the peer
4383 /// per-axis identity gates ([`Self::validate_nome`] 6c992f8,
4384 /// [`Self::validate_versao`] 1fdaa02, [`Self::validate_deps`]
4385 /// a7f0d8c) and the ABSORPTION-ROADMAP.md M2.2 test pin
4386 /// (line 196: "reject invalid `:restart-window` (non-duration)").
4387 ///
4388 /// Thin wrapper around [`crate::supervisor::duration_codec::parse`]
4389 /// (the shared codec backing `:supervisor :restart-window` as
4390 /// serde-routed on [`SupervisorSpec`], `:politicas :timeout`, and
4391 /// `:politicas :circuit-breaker :window` — all three covered by
4392 /// the integer-magnitude gate 1c55a2a). Maps the codec's parse
4393 /// error verbatim into the [`ManifestError::RestartWindowMalformed`]
4394 /// variant, carrying the offending raw string + a parser-shaped
4395 /// reason naming the canonical authoring form, so the diagnostic
4396 /// is self-locating (the author can grep their `caixa.lisp` for
4397 /// `:restart-window "<value>"` and fix it in one edit) and
4398 /// uniform with every other manifest-level validate diagnostic.
4399 /// With this gate the four `:restart-window`-shaped surfaces (the
4400 /// flat raw string on [`Caixa`], the typed `Option<Duration>` on
4401 /// [`SupervisorSpec`], the two `MeshPolicy` peer durations) are
4402 /// now structurally equivalent — every value past the codec is in
4403 /// one accepted set, by construction.
4404 ///
4405 /// `None` (the canonical "omit the slot to express no reset"
4406 /// shape) is accepted trivially — the gate is a no-op when the
4407 /// author didn't author a window. The empty string is rejected by
4408 /// the shared codec (its digit-only gate refuses an empty
4409 /// magnitude), surfacing the same `RestartWindowMalformed`
4410 /// diagnostic as every other rejected non-canonical shape.
4411 pub fn validate_restart_window(&self) -> Result<(), ManifestError> {
4412 let Some(s) = self.restart_window() else {
4413 return Ok(());
4414 };
4415 crate::supervisor::duration_codec::parse(s)
4416 .map(|_| ())
4417 .map_err(|reason| ManifestError::RestartWindowMalformed {
4418 restart_window: s.to_string(),
4419 reason,
4420 })
4421 }
4422
4423 /// Reject per-entry values on the three Caixa-level code-surface
4424 /// path lists (`:bibliotecas`, `:exe`, `:servicos`) that the
4425 /// layout checker's `root.join(p)` sandbox would silently subvert.
4426 /// Same three structural footguns the peer
4427 /// [`BehaviorSpec::validate`] (b0c8389) and
4428 /// [`crate::UpgradeInstruction::validate`] `StateChange` arm
4429 /// (26da2c7) already close on the M2 `:behavior :on-*` and
4430 /// `:upgrade-from :state-change :script` axes, here lifted onto
4431 /// the three top-level code-path axes through the shared
4432 /// [`is_sandboxed_relative_path`] predicate:
4433 ///
4434 /// - empty entry (`(:bibliotecas (""))` / `(:exe (""))` /
4435 /// `(:servicos (""))`): `PathBuf::new()` round-trips through
4436 /// [`Path::join`] as the base itself — `root.join("")` ==
4437 /// `root`, so the existence check (`self.exists(&root)`)
4438 /// trivially passes (the project root exists), and the layout
4439 /// silently treats the project root as a biblioteca / exe /
4440 /// servico entry. The `:bibliotecas` loop then hands the root
4441 /// to `tatara_lisp::read` at `feira build` time as if the root
4442 /// directory itself were a Lisp source file — a parse error
4443 /// far from the source `caixa.lisp` with no field naming the
4444 /// offending entry.
4445 /// - absolute path (`(:bibliotecas ("/etc/passwd"))`):
4446 /// [`Path::join`] *replaces* the base when the right-hand side
4447 /// is absolute, so `root.join("/etc/passwd")` resolves to
4448 /// `"/etc/passwd"` and escapes the project sandbox entirely.
4449 /// The existence check then silently consults whatever the
4450 /// escaped path resolves to — for `:bibliotecas`, the layout
4451 /// has no `starts_with`-fence (only `:exe` is fenced under
4452 /// `exe/` and `:servicos` under `servicos/`), so an absolute
4453 /// `:bibliotecas` entry that happens to resolve on disk
4454 /// silently passes. For `:exe` / `:servicos` the fence catches
4455 /// the absolute case downstream as `ExeOutsideDir` /
4456 /// `ServicoOutsideDir` (or `MissingEntry` if the absolute path
4457 /// doesn't exist), but with a downstream-shaped diagnostic
4458 /// that names the resolved escape path rather than the
4459 /// authoring footgun at the source.
4460 /// - parent-escape (`(:bibliotecas ("../sibling/x.lisp"))` /
4461 /// `(:exe ("exe/../../escape.lisp"))`): a [`PathBuf`] with any
4462 /// [`std::path::Component::ParentDir`] anywhere round-trips
4463 /// through [`Path::join`] as a traversal above the caixa root.
4464 /// The `:exe` / `:servicos` `starts_with(<dir>)` fence is
4465 /// *component-aware* (not canonical-path-aware), so
4466 /// `root.join("exe/../../escape.lisp")` `starts_with(exe_dir)`
4467 /// is **true** even though the canonical resolution
4468 /// `{parent of root}/escape.lisp` lives outside the caixa root
4469 /// — the fence silently lets the parent-escape through, and
4470 /// the existence check passes if that escape-target happens
4471 /// to exist. Caught regardless of where the `..` sits
4472 /// (leading, mid-path, trailing) so the gate matches the peer
4473 /// predicate's full coverage.
4474 ///
4475 /// Same `Empty` → `Absolute` → `ParentEscape` arm-ordering every peer
4476 /// `is_sandboxed_relative_path` consumer follows (b0c8389 / 26da2c7);
4477 /// same per-slot diagnostic shape every peer per-axis path-gate
4478 /// exposes (`*Empty { slot }` / `*Absolute { slot, path }` /
4479 /// `*ParentEscape { slot, path }`). Cross-slot precedence is
4480 /// `:bibliotecas` → `:exe` → `:servicos` — the same declaration
4481 /// order [`Caixa::declared_foreign_code_slots`] uses for its
4482 /// canonical foreign-code-slot diagnostic, so a manifest with
4483 /// multiple malformed slots surfaces the lexicographically-earliest
4484 /// slot's diagnostic deterministically.
4485 ///
4486 /// Lifted to the typed surface as a Caixa-level validator (peer
4487 /// of [`Self::validate_nome`] / [`Self::validate_versao`] /
4488 /// [`Self::validate_deps`] / [`Self::validate_restart_window`])
4489 /// and wired into [`crate::StandardLayout::verify`] before the
4490 /// existence-check loops so the diagnostic names the offending
4491 /// slot at the source caixa.lisp rather than reporting a
4492 /// downstream `MissingEntry` / `ExeOutsideDir` /
4493 /// `ServicoOutsideDir` against the resolved sandbox-escape path.
4494 /// The fourth typed code-path surface — every author-supplied
4495 /// path on the manifest — is now structurally accept-shaped
4496 /// past validate, peer with `:behavior :on-*` and
4497 /// `:upgrade-from :state-change :script`.
4498 pub fn validate_code_paths(&self) -> Result<(), ManifestError> {
4499 /// Per-slot file-type contract for the three Caixa-level
4500 /// code-path surfaces (`:bibliotecas`, `:exe`, `:servicos`).
4501 /// Each variant names the predicate the per-entry file-type
4502 /// gate consults; [`Self::None`] opts the slot out of any
4503 /// file-type contract. Lifted as a typed local enum so the
4504 /// per-slot dispatch is exhaustive at the `match` — adding a
4505 /// future axis to the typed-substrate `:` slot set (the
4506 /// future `:assets` resource axis the M5 roadmap names, the
4507 /// future `:nix-flake` derivation axis the caixa-flake
4508 /// emitter consults) lands as one variant + one `match` arm,
4509 /// not a coordinated rewrite of every per-slot bool flag.
4510 ///
4511 /// Peer of the typed-substrate per-slot variant disciplines
4512 /// already established on this surface
4513 /// ([`crate::supervisor::RestartStrategy`] +
4514 /// [`crate::supervisor::RestartPolicy`] on the OTP-shape
4515 /// supervision-tree axis,
4516 /// [`crate::aplicacao::PlacementStrategy`] on the §III.1
4517 /// placement axis, [`crate::aplicacao::WitTarget`] on the
4518 /// `:contratos` payload-target axis): the typed `enum` is
4519 /// the substrate's single source of truth for the per-axis
4520 /// dispatch, and every consumer (the per-arm body here, the
4521 /// future feira-lint per-slot diagnostic renderer, the M4
4522 /// per-axis admission webhook) reaches for the same typed
4523 /// surface rather than re-deriving the partition from inline
4524 /// flag combinations.
4525 enum CodePathFileType {
4526 /// `:exe` — nix-build derivation output, no terminating-
4527 /// extension contract (the canonical `"exe/<name>"`
4528 /// fixtures the layout's `ExeOutsideDir` error message
4529 /// documents carry no extension by convention).
4530 None,
4531 /// `:bibliotecas` — tatara-lisp source files the
4532 /// `feira build` loop reads through `tatara_lisp::read`
4533 /// at parse time. Routes to [`is_lisp_extension`].
4534 LispSource,
4535 /// `:servicos` — ComputeUnit-CR YAML files the
4536 /// caixa-helm / caixa-flux renderers consume through
4537 /// `serde_yaml::from_str`. Routes to
4538 /// [`is_computeunit_yaml_extension`].
4539 ComputeUnitYaml,
4540 }
4541
4542 // The per-slot [`CodePathFileType`] selects which axes carry the
4543 // lifted file-type predicate. `:bibliotecas` is the tatara-lisp
4544 // source axis (the `feira build` loop at
4545 // `caixa-feira/src/cmd/build.rs:33` reads each entry through
4546 // `tatara_lisp::read` at parse time) — the lifted
4547 // [`is_lisp_extension`] predicate gates the `.lisp` extension.
4548 // `:exe` is the nix-built executable surface (per the canonical
4549 // `"exe/<name>"`-shaped fixtures the layout's `ExeOutsideDir`
4550 // error message documents and every in-tree
4551 // `caixa_with_code_paths` positive control uses) — its file-type
4552 // contract is "nix-build derivation output", not a typed source
4553 // file, so [`CodePathFileType::None`] opts the slot out of any
4554 // file-type gate. `:servicos` is the `.computeunit.yaml`
4555 // ComputeUnit-CR axis (the peer caixa-helm / caixa-flux
4556 // renderers consume each entry through `serde_yaml::from_str` as
4557 // a typed `ComputeUnit` CR) — the lifted
4558 // [`is_computeunit_yaml_extension`] predicate gates the compound
4559 // `.computeunit.yaml` suffix. All three axes are surfaced through
4560 // the same iteration so the sandbox-shape + duplicate gates
4561 // apply uniformly; the typed file-type dispatch fires per-slot
4562 // exactly where the downstream consumer's accepted set demands
4563 // it. The third file-type variant ([`ComputeUnitYaml`]) is the
4564 // compounding lift on the peer 64772a9 `:bibliotecas`
4565 // `.lisp`-gate trajectory — the second of the three code-path
4566 // axes to land on a typed compound-suffix gate, with the same
4567 // self-locating per-slot diagnostic shape every peer per-axis
4568 // file-type lift uses (`*NonLispExtension { slot, path }` /
4569 // `*NonComputeUnitYamlExtension { slot, path }`).
4570 for (slot, list, file_type) in [
4571 (
4572 ":bibliotecas",
4573 &self.bibliotecas,
4574 CodePathFileType::LispSource,
4575 ),
4576 (":exe", &self.exe, CodePathFileType::None),
4577 (
4578 ":servicos",
4579 &self.servicos,
4580 CodePathFileType::ComputeUnitYaml,
4581 ),
4582 ] {
4583 // Per-slot set-not-multiset gate on the typed code-path axis.
4584 // Every peer Vec-shaped author-supplied list past validate is
4585 // a set, not a multiset: `:membros :caixa`
4586 // ([`crate::AplicacaoError::MembroDuplicate`]), `:placement
4587 // :clusters` ([`crate::AplicacaoError::PlacementClusterDuplicate`]),
4588 // `:entrada :paths` ([`crate::AplicacaoError::EntradaPathDuplicate`]),
4589 // `:contratos` ([`crate::AplicacaoError::ContratoDuplicate`]),
4590 // `:children :caixa` ([`crate::SupervisorError::DuplicateChild`]),
4591 // `:deps` / `:deps-dev` `:nome` ([`crate::DepError::DuplicateNome`]
4592 // per 359fba5), `:upgrade-from :from` ([`crate::UpgradeError::DuplicateFrom`]),
4593 // `:etiquetas` ([`ManifestError::EtiquetaDuplicate`] per 360a499),
4594 // `:autores` ([`ManifestError::AutorDuplicate`] per 86c769b) —
4595 // the three code-path lists are the last Vec-shaped author-
4596 // supplied slots on the typed Caixa surface still admitting a
4597 // duplicate entry silently. Scope is per-list (`:bibliotecas`
4598 // duplicates are flagged within `:bibliotecas`, not across
4599 // `:bibliotecas` ↔ `:exe`) — the same per-list scope `:deps`
4600 // ↔ `:deps-dev` use (a `:nome` present in both lists is a
4601 // legitimate dev-vs-runtime shape on the dep axis, fenced
4602 // separately by [`crate::dep::validate_no_self_dep`]). On the
4603 // code-path axis a cross-slot collision is structurally
4604 // impossible by the layout's `starts_with(<exe|servicos>_dir)`
4605 // fence — `:exe` and `:servicos` entries are confined to their
4606 // own directory trees, so the only way a string could appear
4607 // on two code-path lists is the (rare, structurally invalid)
4608 // case where `:bibliotecas` carries an `"exe/<x>"` or
4609 // `"servicos/<x>.yaml"`-shaped path.
4610 //
4611 // Without the gate three authoring footguns silently passed:
4612 //
4613 // - `:bibliotecas ("lib/foo.lisp" "lib/foo.lisp")` — the
4614 // canonical copy-paste-the-wrong-file footgun. `feira
4615 // build` (`caixa-feira/src/cmd/build.rs:33`) walks the
4616 // list and re-parses the same file twice, wasting work
4617 // and silently masking the author's intent to declare a
4618 // *second* biblioteca.
4619 // - `:exe ("exe/cli" "exe/cli")` — the same footgun on the
4620 // Binario surface. The future `caixa-flake` `nix flake`
4621 // emitter that materializes each `:exe` entry as a flake
4622 // `packages.<exe-name>` derivation would collide on the
4623 // duplicate package name and surface a flake-eval error
4624 // far from the source `caixa.lisp`.
4625 // - `:servicos ("servicos/x.computeunit.yaml"
4626 // "servicos/x.computeunit.yaml")` — the same footgun on
4627 // the Servico surface. The peer `caixa-helm` / `caixa-flux`
4628 // renderers already refuse `:servicos.len() != 1` with
4629 // the narrower [`UnsupportedServicoCount`] diagnostic, but
4630 // that diagnostic surfaces "too many servicos" without
4631 // naming "duplicate entry" — the typed self-locating
4632 // "which entry is the duplicate" framing only lands at
4633 // this gate.
4634 //
4635 // Same `seen.insert(entry.as_str())` shape every peer per-list
4636 // duplicate gate uses (`:etiquetas` 360a499, `:autores`
4637 // 86c769b, `:deps` 359fba5) and the same "structural shape
4638 // checks fire before the duplicate check on the same entry"
4639 // ordering (a `(:bibliotecas ("" "lib/x.lisp" "lib/x.lisp"))`
4640 // shape surfaces the narrower [`Self::CodePathEmpty`] for the
4641 // empty entry first, not the duplicate on the later pair).
4642 let mut seen = std::collections::HashSet::new();
4643 for entry in list {
4644 let path = Path::new(entry);
4645 match is_sandboxed_relative_path(path) {
4646 Ok(()) => {}
4647 Err(PathShapeViolation::Empty) => {
4648 return Err(ManifestError::CodePathEmpty { slot });
4649 }
4650 Err(PathShapeViolation::Absolute) => {
4651 return Err(ManifestError::CodePathAbsolute {
4652 slot,
4653 path: path.to_path_buf(),
4654 });
4655 }
4656 Err(PathShapeViolation::ParentEscape) => {
4657 return Err(ManifestError::CodePathParentEscape {
4658 slot,
4659 path: path.to_path_buf(),
4660 });
4661 }
4662 }
4663 // The per-slot file-type gate dispatched through the
4664 // typed [`CodePathFileType`] selector above. Each variant
4665 // routes to the lifted predicate the downstream consumer
4666 // demands:
4667 //
4668 // - [`LispSource`] → [`is_lisp_extension`] for
4669 // `:bibliotecas` (the `feira build` loop's
4670 // `tatara_lisp::read` consumer);
4671 // - [`ComputeUnitYaml`] → [`is_computeunit_yaml_extension`]
4672 // for `:servicos` (the caixa-helm / caixa-flux
4673 // `serde_yaml::from_str` consumer's `ComputeUnit` CR
4674 // accepted set);
4675 // - [`None`] for `:exe` — the nix-build derivation-
4676 // output axis has no terminating-extension contract.
4677 //
4678 // Fires after the sandbox-shape arms so a path that is
4679 // *both* sandbox-escaping and wrong-extension surfaces
4680 // the more fundamental sandbox-shape diagnostic first
4681 // (mirrors the peer `EmptyPath` → `AbsolutePath` →
4682 // `ParentEscape` → `NonLispExtension` arm-ordering on
4683 // `:behavior :on-*` c97815a, and `EmptyScript` →
4684 // `AbsoluteScript` → `ParentEscapeScript` →
4685 // `NonLispExtensionScript` on
4686 // `:upgrade-from :state-change :script` 33cc830), and
4687 // before the duplicate gate so the narrower per-entry
4688 // file-type shape dominates the cross-entry uniqueness
4689 // diagnostic (a
4690 // `("servicos/x.yaml" "servicos/x.yaml")` shape on
4691 // `:servicos` surfaces
4692 // `CodePathNonComputeUnitYamlExtension` on the first
4693 // entry rather than `CodePathDuplicate` on the pair —
4694 // peer with the 64772a9 `:bibliotecas`
4695 // `("lib/x.txt" "lib/x.txt")` ordering).
4696 match file_type {
4697 CodePathFileType::None => {}
4698 CodePathFileType::LispSource => {
4699 if !is_lisp_extension(path) {
4700 return Err(ManifestError::CodePathNonLispExtension {
4701 slot,
4702 path: path.to_path_buf(),
4703 });
4704 }
4705 }
4706 CodePathFileType::ComputeUnitYaml => {
4707 if !is_computeunit_yaml_extension(path) {
4708 return Err(ManifestError::CodePathNonComputeUnitYamlExtension {
4709 slot,
4710 path: path.to_path_buf(),
4711 });
4712 }
4713 }
4714 }
4715 crate::render::insert_first_seen(&mut seen, entry.as_str(), || {
4716 ManifestError::CodePathDuplicate {
4717 slot,
4718 path: path.to_path_buf(),
4719 }
4720 })?;
4721 }
4722 }
4723 Ok(())
4724 }
4725
4726 /// Reject `:etiquetas` lists with an empty entry or with two entries
4727 /// agreeing on the same string. `:etiquetas` is the universal
4728 /// registry-search-tag axis on [`Caixa`] (every kind carries the
4729 /// `Vec<String>` slot) and lands verbatim as the Helm chart
4730 /// `Chart.yaml` `keywords:` array on every Servico (caixa-helm's
4731 /// `build_chart_yaml` at `caixa-helm/src/lib.rs:236` folds it through
4732 /// a [`std::collections::BTreeSet`] alongside the four substrate-
4733 /// fixed tags `lareira` / `wasm` / `tatara-lisp` / `caixa-servico`).
4734 /// Two authoring footguns silently passed validate without this gate:
4735 ///
4736 /// - Empty entry (`(:etiquetas (""))` — the canonical paste-from-
4737 /// blank-doc footgun) rendered as `keywords: ["", "caixa-servico",
4738 /// "lareira", "tatara-lisp", "wasm"]` in `Chart.yaml`. Helm's
4739 /// `chart.metadata.keywords` admits the value without a strict
4740 /// parser-side gate, but the empty keyword has no operational
4741 /// meaning — it indexes nothing in the future caixa-registry
4742 /// search axis and clutters the rendered chart with a no-op tag.
4743 /// - Duplicate entries (`(:etiquetas ("demo" "demo"))` — the
4744 /// copy-paste-the-wrong-tag footgun) silently passed validate
4745 /// and were silently dedup'd by caixa-helm's `BTreeSet` collect
4746 /// at chart render — a "second wins / one silently disappears"
4747 /// shape divergent from every peer typed-graph set gate
4748 /// ([`crate::AplicacaoError::MembroDuplicate`] on `:membros`,
4749 /// [`crate::AplicacaoError::PlacementClusterDuplicate`] on
4750 /// `:placement :clusters`, [`crate::AplicacaoError::EntradaPathDuplicate`]
4751 /// on `:entrada :paths`, [`crate::AplicacaoError::ContratoDuplicate`]
4752 /// on `:contratos`, [`crate::DepError::DuplicateNome`] on
4753 /// `:deps` / `:deps-dev` per 359fba5, [`crate::UpgradeError::DuplicateFrom`]
4754 /// on `:upgrade-from`, the per-instruction-class singularity
4755 /// gates [`crate::UpgradeError::DuplicateLoadModule`] /
4756 /// [`crate::UpgradeError::DuplicateStateChange`] /
4757 /// [`crate::UpgradeError::DuplicateCleanup`]). The typed-graph
4758 /// discipline is uniform: every Vec-shaped author-supplied list
4759 /// past validate is set-not-multiset, by construction.
4760 ///
4761 /// Past the empty arm the gate enforces the chart-keyword shape
4762 /// predicate via [`crate::render::is_chart_keyword_shape`]: Cargo's
4763 /// crates.io `[package] keywords` grammar — 1..=20 bytes, starts
4764 /// with an ASCII letter, ASCII alphanumeric / `_` / `-`
4765 /// continuation. Closes the canonical paste-from-doc footguns the
4766 /// bare empty + duplicate arms left open: paste-from-aligned-doc
4767 /// whitespace (`" mesh"`, `"mesh "`), paste-from-multiline-doc
4768 /// newline (`"mesh\nhttp"` — the author pasted a multi-tag block
4769 /// into one entry instead of splitting), paste-from-Windows-CRLF-doc
4770 /// carriage return, CSV-list-separator confusion (`"mesh,http,grpc"`
4771 /// — the author meant three separate list entries), path-separator
4772 /// confusion (`"caixa/servico"`), namespace-suffix (`"http.1"`),
4773 /// leading-digit (`"1foo"`), kebab-leak (`"-foo"`), snake-leak
4774 /// (`"_foo"`), non-ASCII (`"café"`), and paste-from-binary-blob
4775 /// control bytes that would silently land as malformed search tags
4776 /// in the rendered Chart.yaml `keywords:` array and break the
4777 /// Artifact Hub keyword index lookup far from the source caixa.lisp.
4778 /// Mirrors the [`Self::validate_autores`] shape-predicate cascade
4779 /// established on the sibling universal-axis `Vec<String>` surface
4780 /// — the second universal-axis Vec<String> surface to land the
4781 /// empty-first-then-shape-then-duplicate per-entry cascade.
4782 ///
4783 /// Same empty-first cascade discipline every peer per-axis gate
4784 /// uses: the per-entry empty arm fires before the per-entry shape
4785 /// arm fires before the cross-entry duplicate arm, so an
4786 /// `("" "mesh" "mesh")` authoring shape surfaces the narrower
4787 /// [`ManifestError::EtiquetaEmpty`] (the structural "this entry
4788 /// has no value" defect) before either the shape or the duplicate
4789 /// diagnostic. Walks the list in declaration order so the
4790 /// first-collision diagnostic surfaces the lexicographically-
4791 /// earliest offending position, peer with every other duplicate
4792 /// gate on this surface.
4793 ///
4794 /// Universal-axis (every kind carries `:etiquetas`), so wired at the
4795 /// caixa-build gate alongside the peer universal gates
4796 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4797 /// [`Self::validate_deps`] / [`Self::validate_code_paths`] — before
4798 /// the kind-coherence gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`]
4799 /// / [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4800 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4801 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-specific
4802 /// slot sets. The future caixa-registry search axis can reach for
4803 /// `caixa.etiquetas` knowing every entry is a non-empty distinct
4804 /// chart-keyword-shaped string without re-deriving the precondition.
4805 pub fn validate_etiquetas(&self) -> Result<(), ManifestError> {
4806 let mut seen = std::collections::HashSet::new();
4807 for etiqueta in self.etiquetas() {
4808 if etiqueta.is_empty() {
4809 return Err(ManifestError::EtiquetaEmpty);
4810 }
4811 crate::render::is_chart_keyword_shape(etiqueta).map_err(|reason| {
4812 ManifestError::EtiquetaInvalid {
4813 etiqueta: etiqueta.clone(),
4814 reason,
4815 }
4816 })?;
4817 crate::render::insert_first_seen(&mut seen, etiqueta.as_str(), || {
4818 ManifestError::EtiquetaDuplicate {
4819 etiqueta: etiqueta.clone(),
4820 }
4821 })?;
4822 }
4823 Ok(())
4824 }
4825
4826 /// Reject `:autores` lists with an empty entry or with two entries
4827 /// agreeing on the same string. `:autores` is the universal
4828 /// maintainer-axis on [`Caixa`] (every kind carries the
4829 /// `Vec<String>` slot) and lands verbatim as the Helm chart
4830 /// `Chart.yaml` `maintainers:` array on every Servico (caixa-helm's
4831 /// `build_chart_yaml` at `caixa-helm/src/lib.rs:251` maps each entry
4832 /// to a `Maintainer { name, email: None }` without dedup). Two
4833 /// authoring footguns silently passed validate without this gate:
4834 ///
4835 /// - Empty entry (`(:autores (""))` — the canonical paste-from-
4836 /// blank-doc footgun) rendered as
4837 /// `maintainers: [{name: "", email: null}]` in `Chart.yaml`. The
4838 /// empty maintainer name has no operational meaning — it
4839 /// identifies no one in the substrate's authorship index and
4840 /// clutters the rendered chart with a no-op maintainer.
4841 /// - Duplicate entries (`(:autores ("pleme-io" "pleme-io"))` —
4842 /// the copy-paste-the-wrong-author footgun) silently passed
4843 /// validate and rendered as two identical maintainer entries.
4844 /// Unlike the [`Self::validate_etiquetas`] peer (caixa-helm's
4845 /// `BTreeSet`-collect on `:etiquetas` silently dedups the
4846 /// rendered `keywords:` array at chart-render time), the
4847 /// `maintainers:` rendering has *no* dedup — duplicate `:autores`
4848 /// entries stack verbatim in the chart, divergent from every
4849 /// peer typed-graph set gate ([`crate::AplicacaoError::MembroDuplicate`]
4850 /// on `:membros`, [`crate::AplicacaoError::PlacementClusterDuplicate`]
4851 /// on `:placement :clusters`, [`crate::AplicacaoError::EntradaPathDuplicate`]
4852 /// on `:entrada :paths`, [`crate::AplicacaoError::ContratoDuplicate`]
4853 /// on `:contratos`, [`crate::DepError::DuplicateNome`] on
4854 /// `:deps` / `:deps-dev`, [`crate::UpgradeError::DuplicateFrom`]
4855 /// on `:upgrade-from`, [`ManifestError::EtiquetaDuplicate`] on
4856 /// `:etiquetas`).
4857 ///
4858 /// Past the empty arm the gate enforces the chart-maintainer-name
4859 /// shape predicate via [`crate::render::is_chart_maintainer_name_shape`]:
4860 /// the structural single-line printable-UTF-8 floor every realistic
4861 /// Helm chart maintainer name carries — 1..=128 bytes, no leading
4862 /// or trailing whitespace, no ASCII control characters anywhere,
4863 /// Unicode bytes accepted. Closes the canonical paste-from-doc
4864 /// footguns the bare empty + duplicate arms left open:
4865 /// paste-from-aligned-doc whitespace (`" pleme-io"`, `"pleme-io "`),
4866 /// paste-from-multiline-doc newline (`"alice\nbob"` — the author
4867 /// pasted a multi-line block of author records into one `:autores`
4868 /// entry instead of splitting into one entry per author),
4869 /// paste-from-Windows-CRLF-doc carriage return, tab-from-aligned-doc,
4870 /// and the paste-from-binary-blob control bytes that would silently
4871 /// land as YAML-illegal byte sequences in the rendered Chart.yaml
4872 /// `maintainers:` array. Mirrors the shape-predicate cascade
4873 /// [`Self::validate_descricao`] / [`Self::validate_licenca`] /
4874 /// [`Self::validate_edicao`] / [`Self::validate_repositorio`]
4875 /// establish past their own empty arms on the sibling universal-axis
4876 /// `Option<String>` surfaces — the first universal-axis Vec<String>
4877 /// surface to land the empty-first-then-shape-then-duplicate per-entry
4878 /// cascade.
4879 ///
4880 /// Same empty-first cascade discipline every peer per-axis gate
4881 /// uses: the per-entry empty arm fires before the per-entry shape
4882 /// arm before the cross-entry duplicate arm. Walks the list in
4883 /// declaration order so the first-collision diagnostic surfaces the
4884 /// lexicographically-earliest offending position, peer with every
4885 /// other duplicate gate on this surface.
4886 ///
4887 /// Universal-axis (every kind carries `:autores`), so wired at the
4888 /// caixa-build gate alongside the peer universal gates
4889 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4890 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
4891 /// [`Self::validate_code_paths`] — before the kind-coherence gates
4892 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
4893 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4894 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4895 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-specific
4896 /// slot sets.
4897 pub fn validate_autores(&self) -> Result<(), ManifestError> {
4898 let mut seen = std::collections::HashSet::new();
4899 for autor in self.autores() {
4900 if autor.is_empty() {
4901 return Err(ManifestError::AutorEmpty);
4902 }
4903 crate::render::is_chart_maintainer_name_shape(autor).map_err(|reason| {
4904 ManifestError::AutorInvalid {
4905 autor: autor.clone(),
4906 reason,
4907 }
4908 })?;
4909 crate::render::insert_first_seen(&mut seen, autor.as_str(), || {
4910 ManifestError::AutorDuplicate {
4911 autor: autor.clone(),
4912 }
4913 })?;
4914 }
4915 Ok(())
4916 }
4917
4918 /// Reject `:repositorio` values whose shape the shared
4919 /// [`crate::render::is_git_repo_url`] predicate refuses. The flat
4920 /// `repositorio: Option<String>` slot on [`Caixa`] is the
4921 /// universal git-shaped homepage axis every kind carries — the
4922 /// substrate routes the same string through two load-bearing
4923 /// consumers:
4924 ///
4925 /// - [`caixa-helm`] folds it verbatim into the rendered
4926 /// `lareira-<nome>` Helm chart's `Chart.yaml` `home:` field
4927 /// (`build_chart_yaml` at `caixa-helm/src/lib.rs:268`) and into
4928 /// the chart `README.md` `repo = …` interpolation
4929 /// (`caixa-helm/src/lib.rs:359`).
4930 /// - [`caixa-flux`] folds it verbatim into the standalone
4931 /// `ClusterBundleOpts::for_caixa` `git_url:` field
4932 /// (`caixa-flux/src/lib.rs:293`), which becomes the `FluxCD`
4933 /// `GitRepository.spec.url` the cluster's source-controller
4934 /// polls — the load-bearing deploy-time axis.
4935 ///
4936 /// Both consumers use `Option::unwrap_or_else(|| <fallback>)` to
4937 /// substitute a placeholder when the slot is absent (`None` → the
4938 /// fallback fires); a `Some("")` *skips the fallback* and silently
4939 /// passes the empty string through to `Chart.yaml home: ""` /
4940 /// `GitRepository url: ""` — Helm's chart lint and `FluxCD`'s source
4941 /// controller both reject the empty URL far from the source
4942 /// `caixa.lisp`, with no field naming the offending `:repositorio`.
4943 /// Similarly a malformed `:repositorio` (whitespace, control char,
4944 /// missing `:` separator, leading `-`) silently lands in the
4945 /// rendered artifacts and breaks at `git clone` / `helm template`
4946 /// / `flux reconcile` time.
4947 ///
4948 /// Thin wrapper around [`crate::render::is_git_repo_url`] — the
4949 /// same shared predicate the peer [`crate::DepSource::validate`]
4950 /// routes the `:fonte (:tipo git :repo …)` axis through. With this
4951 /// gate the two `git URL`-shaped surfaces on the typed Caixa
4952 /// (`:repositorio` here, `:deps :fonte :repo` peer) are
4953 /// structurally equivalent: every value past validate is
4954 /// guaranteed-acceptable by the predicate's union of constraints
4955 /// (non-empty, length-bounded, no leading `-`, no whitespace, no
4956 /// control chars, ASCII only, no leading `:`, contains a `:`
4957 /// separator). The predicate accepts every documented authoring
4958 /// shape — `github:org/repo` shorthand, `https://host/path`,
4959 /// `ssh://[user@]host/path`, `git://host/path`, `git@host:path`
4960 /// scp-style SSH, `file:///path` — and refuses the canonical
4961 /// paste-from-blank-doc / paste-from-multiline-doc / CLI-arg-
4962 /// injection footguns at validate time. Maps the predicate's
4963 /// `String` reason verbatim into the
4964 /// [`ManifestError::RepositorioInvalid`] variant, carrying the
4965 /// offending value + parser-shaped reason so the diagnostic is
4966 /// self-locating (the author can grep their `caixa.lisp` for
4967 /// `:repositorio "<value>"` and fix it in one edit).
4968 ///
4969 /// `None` (the canonical "omit the slot to express no published
4970 /// homepage" shape) is accepted trivially — the gate is a no-op
4971 /// when the author didn't declare a value. `Some("")` is gated by
4972 /// the narrower [`ManifestError::RepositorioEmpty`] arm before the
4973 /// shape predicate is consulted, mirroring the empty-first cascade
4974 /// every peer per-axis identity gate uses
4975 /// ([`ManifestError::NomeEmpty`] → [`ManifestError::NomeInvalid`],
4976 /// [`ManifestError::VersaoEmpty`] → [`ManifestError::VersaoInvalid`],
4977 /// [`crate::DepError::FonteRepoEmpty`] →
4978 /// [`crate::DepError::FonteRepoInvalid`]).
4979 ///
4980 /// Universal-axis (every kind carries `:repositorio`), so wired at
4981 /// the caixa-build gate alongside the peer universal gates
4982 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4983 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
4984 /// [`Self::validate_autores`] / [`Self::validate_code_paths`] —
4985 /// before the kind-coherence gates
4986 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
4987 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4988 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4989 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
4990 /// specific slot sets.
4991 pub fn validate_repositorio(&self) -> Result<(), ManifestError> {
4992 let Some(s) = self.repositorio() else {
4993 return Ok(());
4994 };
4995 if s.is_empty() {
4996 return Err(ManifestError::RepositorioEmpty);
4997 }
4998 is_git_repo_url(s).map_err(|reason| ManifestError::RepositorioInvalid {
4999 repositorio: s.to_string(),
5000 reason,
5001 })
5002 }
5003
5004 /// Reject `:descricao` values that are the empty string. The flat
5005 /// `descricao: Option<String>` slot on [`Caixa`] is the universal
5006 /// free-form-prose homepage axis every kind carries — the
5007 /// substrate routes the same string through two load-bearing
5008 /// consumers in the [`caixa-helm`] renderer:
5009 ///
5010 /// - `build_chart_yaml` folds it verbatim into the rendered
5011 /// `lareira-<nome>` Helm chart's `Chart.yaml` `description:`
5012 /// field (`caixa-helm/src/lib.rs:232-235`).
5013 /// - `build_readme` folds it verbatim into the rendered chart
5014 /// `README.md` header (`caixa-helm/src/lib.rs:333-336`).
5015 ///
5016 /// Both consumers use `Option::unwrap_or_else(|| <fallback>)` to
5017 /// substitute a `caixa.nome`-derived placeholder when the slot is
5018 /// absent (`None` → the fallback fires); a `Some("")` *skips the
5019 /// fallback* and silently passes the empty string through to
5020 /// `Chart.yaml description: ""` / a blank chart `README.md`
5021 /// header. Helm's chart spec requires a non-empty `description:`
5022 /// field on `apiVersion: v2` charts (`helm lint` surfaces it as
5023 /// `WARNING [chart.metadata.description]: description is required`),
5024 /// so the empty `Some("")` silently lands in the rendered
5025 /// artifacts and breaks at `helm lint` / `helm install` time far
5026 /// from the source `caixa.lisp`, with no field naming the
5027 /// offending `:descricao`.
5028 ///
5029 /// `None` (the canonical "omit the slot to defer to the renderer's
5030 /// `caixa.nome`-derived fallback" shape) is accepted trivially —
5031 /// the gate is a no-op when the author didn't declare a value.
5032 /// `Some("")` is gated by the narrower
5033 /// [`ManifestError::DescricaoEmpty`] arm, mirroring the empty-arm
5034 /// shape every peer per-axis empty gate uses
5035 /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
5036 /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
5037 /// [`ManifestError::RepositorioEmpty`]).
5038 ///
5039 /// Universal-axis (every kind carries `:descricao`), so wired at
5040 /// the caixa-build gate alongside the peer universal gates
5041 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
5042 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
5043 /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
5044 /// [`Self::validate_code_paths`] — before the kind-coherence
5045 /// gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
5046 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
5047 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
5048 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
5049 /// specific slot sets.
5050 ///
5051 /// Past the empty arm the gate enforces the chart-description
5052 /// shape predicate via [`crate::render::is_chart_description_shape`]:
5053 /// the structural single-line UTF-8 floor every realistic chart
5054 /// description in the wild matches — 1..=512 bytes, no leading
5055 /// or trailing whitespace, no ASCII control characters anywhere
5056 /// (`0x00..=0x1F` plus `0x7F` DEL — banning tab, newline,
5057 /// carriage return, and every other control byte), Unicode
5058 /// continuation bytes accepted (the canonical fixtures carry
5059 /// `→` and `—`). Closes the canonical paste-from-doc footguns
5060 /// the bare empty-arm gate left open: paste-from-aligned-doc
5061 /// leading / trailing whitespace (`" Checkout flow."`,
5062 /// `"Checkout flow. "`), paste-from-multiline-doc newline
5063 /// (`"Checkout\nflow."`), paste-from-Windows-CRLF-doc CR
5064 /// (`"Checkout\rflow."`), tab-from-aligned-doc
5065 /// (`"Checkout\tflow."`), and paste-from-binary-blob NUL / BEL /
5066 /// ESC / DEL bytes. Mirrors the shape-predicate cascade
5067 /// [`Self::validate_repositorio`] / [`Self::validate_licenca`] /
5068 /// [`Self::validate_edicao`] establish past their own empty arms
5069 /// on the sibling universal-axis `Option<String>` Caixa-level
5070 /// value-shape surfaces.
5071 ///
5072 /// The empty-first cascade discipline mirrors every peer per-axis
5073 /// identity gate: [`ManifestError::DescricaoEmpty`] runs before
5074 /// [`ManifestError::DescricaoInvalid`], so the narrower empty
5075 /// diagnostic surfaces on `Some("")` rather than the broader
5076 /// shape-predicate diagnostic — peer with how
5077 /// [`ManifestError::LicencaEmpty`] runs before
5078 /// [`ManifestError::LicencaInvalid`],
5079 /// [`ManifestError::EdicaoEmpty`] runs before
5080 /// [`ManifestError::EdicaoInvalid`],
5081 /// [`ManifestError::RepositorioEmpty`] runs before
5082 /// [`ManifestError::RepositorioInvalid`].
5083 pub fn validate_descricao(&self) -> Result<(), ManifestError> {
5084 let Some(s) = self.descricao() else {
5085 return Ok(());
5086 };
5087 if s.is_empty() {
5088 return Err(ManifestError::DescricaoEmpty);
5089 }
5090 crate::render::is_chart_description_shape(s).map_err(|reason| {
5091 ManifestError::DescricaoInvalid {
5092 descricao: s.to_string(),
5093 reason,
5094 }
5095 })?;
5096 Ok(())
5097 }
5098
5099 /// Reject `:licenca` values that are the empty string. The flat
5100 /// `licenca: Option<String>` slot on [`Caixa`] is the universal
5101 /// SPDX-shaped license-expression axis every kind carries — the
5102 /// substrate routes the same string through the [`caixa-helm`]
5103 /// renderer's `build_readme` which folds it verbatim into the
5104 /// rendered `lareira-<nome>` Helm chart's `README.md` `## License`
5105 /// section (`caixa-helm/src/lib.rs:361`) via
5106 /// `caixa.licenca.clone().unwrap_or_else(|| "MIT".into())`. The
5107 /// fallback only fires on `None`; a `Some("")` *skips the
5108 /// fallback* and silently passes the empty string through to a
5109 /// chart `README.md` whose `License` section renders as the bare
5110 /// trailing period (`.\n`) — peer footgun with the
5111 /// `Some("")`-skips-`unwrap_or_else` shape the
5112 /// [`Self::validate_descricao`] and [`Self::validate_repositorio`]
5113 /// gates close on the sibling free-form-prose and git-URL axes.
5114 ///
5115 /// `None` (the canonical "omit the slot to defer to the
5116 /// renderer's `MIT` fallback" shape every existing fixture
5117 /// carries) is accepted trivially — the gate is a no-op when the
5118 /// author didn't declare a value. `Some("")` is gated by the
5119 /// narrower [`ManifestError::LicencaEmpty`] arm, mirroring the
5120 /// empty-arm shape every peer per-axis empty gate uses
5121 /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
5122 /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
5123 /// [`ManifestError::RepositorioEmpty`],
5124 /// [`ManifestError::DescricaoEmpty`]).
5125 ///
5126 /// Universal-axis (every kind carries `:licenca`), so wired at
5127 /// the caixa-build gate alongside the peer universal gates
5128 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
5129 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
5130 /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
5131 /// [`Self::validate_descricao`] / [`Self::validate_code_paths`]
5132 /// — before the kind-coherence gates
5133 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
5134 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
5135 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
5136 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
5137 /// specific slot sets.
5138 ///
5139 /// Past the empty arm the gate enforces the SPDX-expression shape
5140 /// predicate via [`crate::render::is_spdx_expression_shape`]: the
5141 /// structural alphabet floor every realistic SPDX expression in
5142 /// the wild uses — ASCII alphanumeric plus `.`, `-`, `+`, `(`,
5143 /// `)`, `:` (the `DocumentRef-…:LicenseRef-…` separator), and a
5144 /// single ASCII space (token separator). Closes the canonical
5145 /// paste-from-doc footguns the bare empty-arm gate left open:
5146 /// paste-from-doc whitespace (`"MIT "`, `" MIT"`), paste-from-
5147 /// multiline-doc CRLF (`"MIT\n"`), tab-from-aligned-doc
5148 /// (`"MIT\tOR Apache-2.0"`), non-ASCII smart-quote paste,
5149 /// underscore-instead-of-hyphen typo (`"Apache_2.0"`),
5150 /// comma-instead-of-`OR`-keyword colloquial idiom (`"MIT,
5151 /// Apache-2.0"`), slash-dual-license colloquial idiom (`"MIT/
5152 /// Apache-2.0"`), and semicolon-list-separator confusion
5153 /// (`"MIT; Apache-2.0"`). Mirrors the shape-predicate cascade
5154 /// [`Self::validate_repositorio`] / [`Self::validate_edicao`]
5155 /// establish past their own empty arms.
5156 ///
5157 /// The empty-first cascade discipline mirrors every peer per-axis
5158 /// identity gate: [`ManifestError::LicencaEmpty`] runs before
5159 /// [`ManifestError::LicencaInvalid`], so the narrower empty
5160 /// diagnostic surfaces on `Some("")` rather than the broader
5161 /// shape-predicate diagnostic — peer with how
5162 /// [`ManifestError::EdicaoEmpty`] runs before
5163 /// [`ManifestError::EdicaoInvalid`],
5164 /// [`ManifestError::RepositorioEmpty`] runs before
5165 /// [`ManifestError::RepositorioInvalid`].
5166 ///
5167 /// A future tightening on this axis can extend the alphabet
5168 /// floor into a full SPDX expression parser + license-id
5169 /// allowlist (rejecting alphabet-valid values that don't name a
5170 /// real SPDX license identifier — e.g., `"NotAReal"` is
5171 /// alphabet-valid but no `NotAReal` license-id exists). That
5172 /// parser only becomes meaningful past a real SPDX-spec
5173 /// dependency; this gate establishes the structural floor by
5174 /// refusing every non-SPDX-alphabet value at validate time.
5175 pub fn validate_licenca(&self) -> Result<(), ManifestError> {
5176 let Some(s) = self.licenca() else {
5177 return Ok(());
5178 };
5179 if s.is_empty() {
5180 return Err(ManifestError::LicencaEmpty);
5181 }
5182 crate::render::is_spdx_expression_shape(s).map_err(|reason| {
5183 ManifestError::LicencaInvalid {
5184 licenca: s.to_string(),
5185 reason,
5186 }
5187 })?;
5188 Ok(())
5189 }
5190
5191 /// Reject `:edicao` values that are the empty string. The flat
5192 /// `edicao: Option<String>` slot on [`Caixa`] is the universal
5193 /// language-edition axis every kind carries — it determines the
5194 /// tatara-lisp macro surface + compatibility flags the substrate
5195 /// applies when building a caixa, and lands verbatim in the
5196 /// `Caixa::template` author-time scaffold (the canonical
5197 /// `:edicao "2026"` line every `feira init` emits via
5198 /// [`Caixa::template`] at `caixa-core/src/manifest.rs:1193`) and
5199 /// in every renderer-side fixture (`caixa-helm/src/lib.rs:375`,
5200 /// `caixa-flux/src/lib.rs:445`, `caixa-mesh/src/lib.rs:629`,
5201 /// `caixa-core/src/render.rs:2510`) via
5202 /// `edicao: Some("2026".into())`.
5203 ///
5204 /// `None` (the canonical "omit the slot to defer to the
5205 /// substrate's default edition" shape every existing
5206 /// [`caixa-resolver`] integration test fixture carries via
5207 /// `edicao: None` — see `caixa-resolver/tests/git_integration.rs`)
5208 /// is accepted trivially — the gate is a no-op when the author
5209 /// didn't declare a value. `Some("")` is gated by the narrower
5210 /// [`ManifestError::EdicaoEmpty`] arm, mirroring the empty-arm
5211 /// shape every peer per-axis empty gate uses
5212 /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
5213 /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
5214 /// [`ManifestError::RepositorioEmpty`],
5215 /// [`ManifestError::DescricaoEmpty`], [`ManifestError::LicencaEmpty`]).
5216 ///
5217 /// Universal-axis (every kind carries `:edicao`), so wired at
5218 /// the caixa-build gate alongside the peer universal gates
5219 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
5220 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
5221 /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
5222 /// [`Self::validate_descricao`] / [`Self::validate_licenca`] /
5223 /// [`Self::validate_code_paths`] — before the kind-coherence
5224 /// gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
5225 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
5226 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
5227 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
5228 /// specific slot sets.
5229 ///
5230 /// Past the empty arm the gate enforces the canonical year-shape
5231 /// predicate: every documented tatara-lisp edition is a 4-digit
5232 /// ASCII decimal year (`"2026"` is the only edition currently
5233 /// minted; future-introduced siblings will follow the same
5234 /// shape, peer with Cargo's `[package] edition` grammar which
5235 /// every value Cargo has ever accepted matches — `"2015"`,
5236 /// `"2018"`, `"2021"`, `"2024"`). Any value that's not exactly
5237 /// 4 ASCII decimal bytes is rejected with the narrower
5238 /// [`ManifestError::EdicaoInvalid`] arm, mirroring the
5239 /// shape-predicate cascade [`Self::validate_repositorio`]
5240 /// establishes past its own empty arm
5241 /// ([`ManifestError::RepositorioEmpty`] →
5242 /// [`ManifestError::RepositorioInvalid`]). Closes the canonical
5243 /// paste-from-doc footguns the bare empty-arm gate left open:
5244 ///
5245 /// - leading / trailing whitespace from a paste-from-doc
5246 /// (`"2026 "`, `" 2026"`)
5247 /// - control characters / CRLF from a paste-from-multiline-doc
5248 /// (`"2026\n"`)
5249 /// - non-ASCII look-alikes from a fullwidth keyboard
5250 /// (`"2026"`) which would silently land as a non-ASCII
5251 /// string in the rendered caixa.lisp
5252 /// - free-form non-year values (`"x"`, `"latest"`,
5253 /// `"nightly"`) that have no operational meaning on the
5254 /// substrate's build-time edition selector
5255 /// - leading non-digit prefixes (`"v2026"`, `"e2026"`,
5256 /// `"r2026"`) — common version-tag idioms that don't apply
5257 /// to the year-shaped edition axis
5258 /// - decimal-shaped values (`"2026.1"`, `"2026.0"`) — every
5259 /// edition is a year, not a fractional version
5260 /// - wrong-length numeric values (`"26"`, `"202"`, `"20260"`,
5261 /// `"00026"`) that don't name a year
5262 ///
5263 /// `None` (the canonical "omit the slot to defer to the
5264 /// substrate's default edition" shape every existing
5265 /// [`caixa-resolver`] integration test fixture carries via
5266 /// `edicao: None` — see `caixa-resolver/tests/git_integration.rs`)
5267 /// is accepted trivially — the gate is a no-op when the author
5268 /// didn't declare a value. The empty-first cascade discipline
5269 /// mirrors every peer per-axis identity gate:
5270 /// [`ManifestError::EdicaoEmpty`] runs before
5271 /// [`ManifestError::EdicaoInvalid`], so the narrower empty
5272 /// diagnostic surfaces on `Some("")` rather than the broader
5273 /// shape-predicate diagnostic — peer with how
5274 /// [`ManifestError::NomeEmpty`] runs before
5275 /// [`ManifestError::NomeInvalid`],
5276 /// [`ManifestError::VersaoEmpty`] runs before
5277 /// [`ManifestError::VersaoInvalid`],
5278 /// [`ManifestError::RepositorioEmpty`] runs before
5279 /// [`ManifestError::RepositorioInvalid`].
5280 ///
5281 /// A future tightening on this axis can extend the shape
5282 /// predicate into a known-edition allowlist (rejecting
5283 /// year-shaped values that don't name a tatara-lisp edition
5284 /// the substrate actually understands — e.g., `"1999"` is
5285 /// year-shaped but no `1999` edition exists). That allowlist
5286 /// only becomes meaningful past the introduction of a sibling
5287 /// edition to `"2026"`; this gate establishes the structural
5288 /// floor by refusing every non-year-shaped value at validate
5289 /// time.
5290 pub fn validate_edicao(&self) -> Result<(), ManifestError> {
5291 let Some(s) = self.edicao() else {
5292 return Ok(());
5293 };
5294 if s.is_empty() {
5295 return Err(ManifestError::EdicaoEmpty);
5296 }
5297 if s.len() != 4 || !s.bytes().all(|b| b.is_ascii_digit()) {
5298 return Err(ManifestError::EdicaoInvalid {
5299 edicao: s.to_string(),
5300 reason: "must be a 4-digit ASCII decimal year (canonical \"2026\")".to_string(),
5301 });
5302 }
5303 Ok(())
5304 }
5305
5306 /// Compose the supervisor-related flat slots into a single
5307 /// [`SupervisorSpec`] for validation. Returns `None` when the
5308 /// caixa isn't a `:kind Supervisor`.
5309 ///
5310 /// The flat representation in [`Caixa`] keeps tatara-lisp authoring
5311 /// simple (one form, no nested `:supervisor (…)` block); this view
5312 /// is the "typed shape" the operator + supervisor reconciler
5313 /// consume.
5314 #[must_use]
5315 pub fn supervisor_view(&self) -> Option<SupervisorSpec> {
5316 if !self.kind().is_supervisor() {
5317 return None;
5318 }
5319 // Fold through the shared `supervisor::duration_codec::parse`
5320 // — the same parser the serde-routed `with = "duration_codec"`
5321 // on `SupervisorSpec::restart_window`, the `:politicas
5322 // :timeout` codec, and the `:politicas :circuit-breaker
5323 // :window` codec all consume. The prior inline f64-shaped
5324 // duplicate (`parse_window_inline`) admitted every magnitude
5325 // the integer-magnitude gate (1c55a2a) rejects on the three
5326 // serde-routed siblings — `"1.5s"`, `"1.0s"`, `"0.5m"`,
5327 // `"+30s"`, `"-30s"` — and silently dropped malformed input as
5328 // `None` (i.e. "no reset"), divergent from the shared codec's
5329 // integer-magnitude discipline by construction. The fold
5330 // closes the divergence: every value the typed
5331 // `SupervisorSpec` carries past `supervisor_view` is in the
5332 // shared codec's accepted set. The `.ok()` here preserves the
5333 // existing soft-swallow shape on this view-construction path;
5334 // the new [`Caixa::validate_restart_window`] (sibling of
5335 // [`Self::validate_nome`] / [`Self::validate_versao`]) names
5336 // the offending raw string at build time so authoring tools
5337 // (`feira lint`, the future layout-side wire-up) surface a
5338 // self-locating diagnostic instead of a silently dropped
5339 // window.
5340 let restart_window = self
5341 .restart_window()
5342 .and_then(|s| crate::supervisor::duration_codec::parse(s).ok());
5343 Some(SupervisorSpec {
5344 // Route the author-omitted `:estrategia` arm through the
5345 // substrate-canonical
5346 // [`crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT`] typed
5347 // `pub const` rather than the transitively-derived
5348 // [`RestartStrategy::default`] route the prior
5349 // `.unwrap_or_default()` fold reached for — one source of
5350 // truth for the Erlang/OTP `one_for_one` half of Learn You
5351 // Some Erlang's `{one_for_one, intensity, 5, 60}` worker-
5352 // supervisor canonical default that also backs the
5353 // [`crate::supervisor::Default for RestartStrategy`] impl
5354 // and the [`crate::supervisor::Default for SupervisorSpec`]
5355 // impl's struct-literal `estrategia` field, all now routed
5356 // through the same lifted constant. Prior to the lift the
5357 // composition site carried `.unwrap_or_default()` with no
5358 // compile-time link back to the shared OTP-canonical
5359 // default that the peer paired
5360 // `.unwrap_or(SUPERVISOR_MAX_RESTARTS_DEFAULT)` (b698ec0)
5361 // arm on the sibling `:max-restarts` axis routes through —
5362 // so a future rebrand of the OTP-canonical strategy default
5363 // (a widening to `rest_for_one` once the substrate
5364 // discovers startup-order-coupled child cohorts as the more
5365 // common shape, a per-cluster overlay the operator pins
5366 // through the MESH-COMPOSITION §III.2 supervision-canary
5367 // `:estrategia-overrides` roadmap slot) would have had to
5368 // migrate the paired `MaxIntensity` + `Period` halves
5369 // through the lifted constants and the `one_for_one` half
5370 // through a `RestartStrategy::default()` route in lockstep
5371 // or the three halves of the same OTP-canonical default
5372 // would silently drift out of pairing. Byte-parity against
5373 // the lifted constant closes the split. Pinned by
5374 // [`supervisor_view_estrategia_fallback_routes_through_lifted_default`]
5375 // in the tests module.
5376 estrategia: self
5377 .estrategia()
5378 .unwrap_or(crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT),
5379 // Route the author-omitted `:max-restarts` arm through the
5380 // substrate-canonical [`crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT`]
5381 // typed `pub const` rather than the raw `5` literal — one
5382 // source of truth for the Erlang/OTP-canonical
5383 // `{intensity, 5, 60}` `MaxIntensity` default that also
5384 // backs the serde-side wire-format author-omitted arm on
5385 // [`crate::supervisor::SupervisorSpec::max_restarts`] via
5386 // `#[serde(default = "default_max_restarts")]` and the
5387 // [`Default for SupervisorSpec`] impl's struct-literal
5388 // default field. Prior to the lift the composition site
5389 // carried a raw `5` with no compile-time link back to the
5390 // serde-side default, so a future rebrand of the OTP-
5391 // canonical default (a tightening to Elixir's `3`, a
5392 // widening to a per-cluster overlay the operator pins
5393 // through the MESH-COMPOSITION §III.2 supervision-canary
5394 // `:supervisor :max-restarts-overrides` roadmap slot)
5395 // would have had to be threaded through both open-coded
5396 // copies in lockstep or the wire-format author-omitted arm
5397 // and this view-construction author-omitted arm would
5398 // silently disagree on which restart-budget an omitted
5399 // `:max-restarts` resolves to. Pinned by
5400 // [`supervisor_view_max_restarts_fallback_routes_through_lifted_default`]
5401 // in the tests module.
5402 max_restarts: self
5403 .max_restarts()
5404 .unwrap_or(crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT),
5405 restart_window,
5406 children: self.children().to_vec(),
5407 })
5408 }
5409
5410 /// A minimal starter manifest emitted by `feira init`.
5411 #[must_use]
5412 pub fn template(nome: &str) -> String {
5413 format!(
5414 "(defcaixa\n \
5415 :nome {nome:?}\n \
5416 :versao \"0.1.0\"\n \
5417 :kind Biblioteca\n \
5418 :edicao \"2026\"\n \
5419 :descricao \"FIXME — describe this caixa\"\n \
5420 :autores ()\n \
5421 :etiquetas ()\n \
5422 :deps ()\n \
5423 :deps-dev ()\n \
5424 :bibliotecas (\"lib/{nome}.lisp\"))\n"
5425 )
5426 }
5427
5428 /// Serialize to a canonical `caixa.lisp` source — suitable for writing
5429 /// back after mutation (e.g. `feira add`).
5430 ///
5431 /// Goes through serde JSON → canonical Sexp → per-field pretty print.
5432 /// The derive-macro `compile_from_sexp` path is the inverse, so any
5433 /// `Caixa` round-trips through `to_lisp` + `from_lisp`.
5434 #[must_use]
5435 pub fn to_lisp(&self) -> String {
5436 let json = serde_json::to_value(self).expect("Caixa serialize");
5437 let sexp = tatara_lisp::domain::json_to_sexp(&json);
5438 let tatara_lisp::Sexp::List(items) = sexp else {
5439 return format!("(defcaixa {sexp})\n");
5440 };
5441 let mut out = String::from("(defcaixa");
5442 let mut i = 0;
5443 while i + 1 < items.len() {
5444 out.push_str("\n ");
5445 out.push_str(&items[i].to_string());
5446 out.push(' ');
5447 out.push_str(&items[i + 1].to_string());
5448 i += 2;
5449 }
5450 out.push_str(")\n");
5451 out
5452 }
5453}
5454
5455/// Errors raised by top-level [`Caixa`] validators that don't fit
5456/// the per-axis [`DepError`] / [`crate::AplicacaoError`] /
5457/// [`crate::SupervisorError`] / [`crate::LayoutError`] families —
5458/// the Caixa's own identity axes (`:nome`, `:versao`) that flow
5459/// through every substrate-side artifact's `metadata.name` /
5460/// version derivation.
5461///
5462/// A future top-level sum (the M4 `CaixaError` the [`DepError`]
5463/// doc-comment anticipates) can hold one of each per-axis error
5464/// family without reshaping individual diagnostics; this enum is
5465/// the first such per-Caixa-identity family.
5466#[derive(Debug, Error, PartialEq, Eq)]
5467pub enum ManifestError {
5468 #[error(
5469 ":nome is empty (every caixa must name itself; the value flows \
5470 into every K8s artifact's `metadata.name` derivation and into \
5471 the default `lib/<nome>.lisp` / `exe/<nome>` layout paths)"
5472 )]
5473 NomeEmpty,
5474 #[error(
5475 ":nome {nome:?} is not a valid DNS-1123 label: {reason} (the K8s \
5476 apiserver enforces this rule on every `metadata.name` the \
5477 caixa's substrate-side renderers derive from `:nome` — the \
5478 `lareira-<nome>` Helm chart name, the programs.yaml entry \
5479 name, the `LABEL_APLICACAO` label value, the `<aplicacao>-<de>-to-<para>` \
5480 CiliumNetworkPolicy name, the `<aplicacao>-<para>` HTTPRoute \
5481 name; use a lowercase alphanumeric + hyphen identifier like \
5482 `\"checkout\"` or `\"cart-v2\"`)"
5483 )]
5484 NomeInvalid { nome: String, reason: String },
5485 #[error(
5486 ":nome {nome:?} overflows the joint-length budget on the canonical \
5487 `lareira-<nome>` chart-name shape: {reason} (every per-Servico / \
5488 per-Aplicacao renderer the substrate carries — `caixa-helm`'s \
5489 `Chart.yaml::name`, `caixa-flux`'s `cluster_bundle` HelmRelease \
5490 `chart:` slot, `caixa-tatara`'s `release_name` + \
5491 `oci://<registry>/lareira-<nome>` chart ref — derives the same \
5492 joint name through the canonical `lareira_chart_name` helper, and \
5493 Helm's `Chart.yaml::name` admission rule + the K8s apiserver's \
5494 DNS-1123 label cap on every chart-name-derived `metadata.name` \
5495 reject any joint name exceeding 63 bytes; the narrower \
5496 `:nome` shape (`NomeInvalid`) gates the bare-`:nome` budget, this \
5497 arm gates the chart-name budget downstream renderers inherit)"
5498 )]
5499 NomeChartNameBudgetExceeded { nome: String, reason: String },
5500 #[error(
5501 ":versao is empty (every caixa must pin its own version; the value flows \
5502 into the `lareira-<nome>` Helm chart's `Chart.yaml` version + appVersion, \
5503 the `feira publish` `v<versao>` git tag, the OCI image's `:v<versao>` / \
5504 `:latest` tags, the lacre closure's `concrete_versao`, and the \
5505 `:upgrade-from :from` peers — use a SemVer-2 literal like `\"0.1.0\"`)"
5506 )]
5507 VersaoEmpty,
5508 #[error(
5509 ":versao {versao:?} is not a valid SemVer-2 version: {reason} (the substrate \
5510 consumes this string as `semver::Version` — three-part `MAJOR.MINOR.PATCH` \
5511 with optional `-prerelease` and `+build` — across every artifact derived \
5512 from `:versao`: the `lareira-<nome>` Helm chart's `Chart.yaml` version + \
5513 appVersion (Helm SemVer-2-strict), the `feira publish` `v<versao>` git tag, \
5514 the OCI image's `:v<versao>` tag, the lacre closure's `concrete_versao`, \
5515 and the `:upgrade-from :from` peers that match against this exact shape; \
5516 use a literal like `\"0.1.0\"`, `\"0.2.0-rc.1\"`, or `\"1.0.0+build.42\"` — \
5517 not a git-tag-shape like `\"v0.1.0\"`, a docker-tag-shape like `\"latest\"`, \
5518 a requirement-shape like `\"^0.1\"`, or a four-part `\"0.1.0.0\"`)"
5519 )]
5520 VersaoInvalid { versao: String, reason: String },
5521 #[error(
5522 ":restart-window {restart_window:?} is not a valid duration: {reason} (the \
5523 substrate consumes this string through the shared \
5524 `supervisor::duration_codec` — the same parser routed via `with = \
5525 \"duration_codec\"` onto the typed `SupervisorSpec::restart_window`, \
5526 `:politicas :timeout`, and `:politicas :circuit-breaker :window` slots; \
5527 the canonical authoring form is `<integer><unit>` where the unit is one \
5528 of `ms` / `s` / `m` / `h` and the magnitude has no decimal point and no \
5529 leading `+` / `-` sign — e.g. `\"60s\"`, `\"5m\"`, `\"1h\"`, `\"500ms\"`. \
5530 Without this gate a malformed `:restart-window` silently produced a \
5531 supervisor with `restart_window: None` (\"never reset\"), turning OTP's \
5532 `MaxIntensity / Period` invariant into a never-reset supervisor far from \
5533 the source `caixa.lisp`; the gate moves the diagnostic to the manifest \
5534 layer with the offending value named verbatim. Omit the slot entirely to \
5535 express \"no reset\"; carry a positive integer duration to express the \
5536 sliding window)"
5537 )]
5538 RestartWindowMalformed {
5539 restart_window: String,
5540 reason: String,
5541 },
5542 #[error(
5543 "{slot} entry is an empty path string — every {slot} entry must name \
5544 a file relative to the caixa root; omit the entry to omit the file \
5545 (the layout checker's `root.join(\"\")` resolves to the caixa root \
5546 itself, so an empty entry silently aliases the project root as a \
5547 declared {slot} file, then fails downstream at parse / existence \
5548 time with a diagnostic that names the root rather than the offending \
5549 entry)"
5550 )]
5551 CodePathEmpty { slot: &'static str },
5552 #[error(
5553 "{slot} entry {} is an absolute path — entries must be relative to \
5554 the caixa root, since `Path::join` replaces the base with an absolute \
5555 right-hand side and `root.join(\"/abs/...\")` resolves to \"/abs/...\" \
5556 outside the caixa root sandbox; rewrite the entry as a relative path \
5557 under the caixa root (e.g. `\"lib/<name>.lisp\"`, `\"exe/<name>\"`, \
5558 `\"servicos/<name>.computeunit.yaml\"`)",
5559 path.display()
5560 )]
5561 CodePathAbsolute { slot: &'static str, path: PathBuf },
5562 #[error(
5563 "{slot} entry {} contains a `..` component — entries must not traverse \
5564 above the caixa root (the layout's `starts_with(<dir>)` fence on \
5565 `:exe` / `:servicos` is component-aware, not canonical-path-aware, \
5566 so a mid-path `..` silently traverses the sandbox; `:bibliotecas` \
5567 has no such fence, so a leading `..` escapes unconditionally if the \
5568 resolved target happens to exist)",
5569 path.display()
5570 )]
5571 CodePathParentEscape { slot: &'static str, path: PathBuf },
5572 #[error(
5573 "{slot} entry {} does not terminate in the `.lisp` extension — every \
5574 `:bibliotecas` entry is a tatara-lisp source file the `feira build` \
5575 loop reads through `tatara_lisp::read` at parse time, so any other \
5576 extension (`.rs`, `.txt`, `.lisp.bak`) or no-extension shape is \
5577 structurally a parser error far from the source caixa.lisp, with \
5578 no field naming the offending `:bibliotecas` entry. Pin a relative \
5579 path under the caixa root whose terminating extension is \
5580 lowercase-`.lisp` (e.g. `\"lib/<name>.lisp\"`, \
5581 `\"lib/handlers.lisp\"`) — the same file-type contract the peer \
5582 `:behavior :on-*` (c97815a) and `:upgrade-from :state-change :script` \
5583 (33cc830) axes already carry through the same lifted \
5584 `is_lisp_extension` predicate",
5585 path.display()
5586 )]
5587 CodePathNonLispExtension { slot: &'static str, path: PathBuf },
5588 #[error(
5589 "{slot} entry {} does not terminate in the `.computeunit.yaml` \
5590 compound suffix — every `:servicos` entry is a typed `ComputeUnit` \
5591 CR YAML file the peer caixa-helm / caixa-flux renderers consume \
5592 through `serde_yaml::from_str` at chart / FluxCD bundle render \
5593 time, so any other extension (`.yaml`, `.yml`, `.json`, the \
5594 off-by-one-segment `.computeunit-yaml`, the editor-backup \
5595 `.computeunit.yaml.bak`) or no-extension shape is structurally a \
5596 YAML-parser error / `ComputeUnit` schema-mismatch far from the \
5597 source caixa.lisp, with no field naming the offending `:servicos` \
5598 entry. Pin a relative path under the caixa root whose terminating \
5599 compound suffix is lowercase-`.computeunit.yaml` (e.g. \
5600 `\"servicos/<name>.computeunit.yaml\"`, \
5601 `\"servicos/hello-rio.computeunit.yaml\"`) — the same file-type \
5602 contract the sibling `:bibliotecas` axis (64772a9) already carries \
5603 on the tatara-lisp-source axis through the peer lifted \
5604 `is_lisp_extension` predicate, here on the compound-suffix axis \
5605 `Path::extension` can't express on its own through the lifted \
5606 `is_computeunit_yaml_extension` predicate",
5607 path.display()
5608 )]
5609 CodePathNonComputeUnitYamlExtension { slot: &'static str, path: PathBuf },
5610 #[error(
5611 "{slot} entry {} appears more than once (the code-path list is \
5612 a set, not a multiset; every peer Vec-shaped author-supplied \
5613 list past validate is set-not-multiset — `:membros :caixa`, \
5614 `:placement :clusters`, `:entrada :paths`, `:contratos`, \
5615 `:children :caixa`, `:deps` / `:deps-dev` `:nome`, \
5616 `:upgrade-from :from`, `:etiquetas`, `:autores` — and the three \
5617 code-path lists are the last Vec-shaped author-supplied slots on \
5618 the typed Caixa surface still admitting a duplicate entry. \
5619 `:bibliotecas` duplicates re-parse the same file at \
5620 `feira build` time and silently mask the author's intent to \
5621 declare a *second* biblioteca; `:exe` duplicates collide on the \
5622 flake `packages.<name>` derivation key at the future \
5623 `caixa-flake` materializer; `:servicos` duplicates surface as the \
5624 narrower [`caixa-helm`] / [`caixa-flux`] `UnsupportedServicoCount` \
5625 rejection far from the source `caixa.lisp`. Drop the duplicate \
5626 or rename it to the actual second file intended)",
5627 path.display()
5628 )]
5629 CodePathDuplicate { slot: &'static str, path: PathBuf },
5630 #[error(
5631 ":etiquetas entry is empty (every tag must carry a non-empty \
5632 registry-search identifier; the empty entry has no operational \
5633 meaning — it indexes nothing in the future caixa-registry search \
5634 axis and clutters the rendered Helm `Chart.yaml` `keywords:` array \
5635 with a no-op tag; omit the entry to express \"no tag on this \
5636 position\")"
5637 )]
5638 EtiquetaEmpty,
5639 #[error(
5640 ":etiquetas entry {etiqueta:?} appears more than once (the \
5641 registry-search tag set is a set, not a multiset; duplicate \
5642 entries are silently dedup'd by caixa-helm's `BTreeSet` collect \
5643 at chart render — a \"second wins / one silently disappears\" \
5644 shape divergent from every peer typed-graph set gate \
5645 (`:membros :caixa`, `:placement :clusters`, `:entrada :paths`, \
5646 `:contratos`, `:deps :nome`, `:upgrade-from :from`); drop the \
5647 duplicate or rename it to the actual tag intended)"
5648 )]
5649 EtiquetaDuplicate { etiqueta: String },
5650 #[error(
5651 ":etiquetas entry {etiqueta:?} is not a valid chart-keyword shape: \
5652 {reason} (the substrate consumes this string through the shared \
5653 `crate::render::is_chart_keyword_shape` predicate — the same \
5654 Cargo crates.io `[package] keywords` grammar entry shape: 1..=20 \
5655 bytes, starts with an ASCII letter, ASCII alphanumeric / `_` / `-` \
5656 continuation. The canonical authoring shapes are short kebab-case \
5657 identifiers like `\"mesh\"`, `\"wasm\"`, `\"tatara-lisp\"`, \
5658 `\"hello-world\"`, `\"caixa-servico\"`, `\"infrastructure\"`. \
5659 Without this gate a malformed `:etiquetas` entry (paste-from-doc \
5660 leading / trailing whitespace `\" mesh\"` / `\"mesh \"`; \
5661 paste-from-multiline-doc newline `\"mesh\\nhttp\"`; \
5662 paste-from-Windows-CRLF-doc CR; CSV-list-separator confusion \
5663 `\"mesh,http,grpc\"` — the author meant to author three separate \
5664 list entries; path-separator confusion `\"caixa/servico\"`; \
5665 namespace-suffix `\"http.1\"`; leading-digit `\"1foo\"`; \
5666 kebab-leak `\"-foo\"`; snake-leak `\"_foo\"`; non-ASCII \
5667 `\"café\"` — every legitimate search tag is strict ASCII; \
5668 paste-from-binary-blob NUL / BEL / ESC / DEL byte) silently \
5669 passed `from_lisp` + `validate_etiquetas` + \
5670 `StandardLayout::verify` and landed in the rendered \
5671 `lareira-<nome>` Helm chart's `Chart.yaml keywords:` array as a \
5672 malformed search tag — Artifact Hub's keyword index + the future \
5673 caixa-registry's keyword index would either silently drop the \
5674 tag or fail to index it far from the source caixa.lisp; the gate \
5675 moves the diagnostic to the manifest layer with the offending \
5676 value named verbatim)"
5677 )]
5678 EtiquetaInvalid { etiqueta: String, reason: String },
5679 #[error(
5680 ":autores entry is empty (every maintainer must carry a non-empty \
5681 identifier; the empty entry has no operational meaning — it \
5682 identifies no one in the substrate's authorship index and renders \
5683 as `maintainers: [{{name: \"\", email: null}}]` in the Helm chart's \
5684 `Chart.yaml`, a no-op maintainer the substrate cannot route to; \
5685 omit the entry to express \"no maintainer on this position\")"
5686 )]
5687 AutorEmpty,
5688 #[error(
5689 ":autores entry {autor:?} appears more than once (the maintainer \
5690 set is a set, not a multiset; unlike `:etiquetas`, caixa-helm's \
5691 `maintainers:` rendering does *no* dedup — duplicate entries \
5692 stack verbatim in `Chart.yaml` as two identical \
5693 `Maintainer {{ name, email: None }}` records, divergent from every \
5694 peer typed-graph set gate (`:etiquetas`, `:membros :caixa`, \
5695 `:placement :clusters`, `:entrada :paths`, `:contratos`, \
5696 `:deps :nome`, `:upgrade-from :from`); drop the duplicate or \
5697 rename it to the actual author intended)"
5698 )]
5699 AutorDuplicate { autor: String },
5700 #[error(
5701 ":autores entry {autor:?} is not a valid chart-maintainer-name shape: \
5702 {reason} (the substrate consumes this string through the shared \
5703 `crate::render::is_chart_maintainer_name_shape` predicate — the same \
5704 single-line-UTF-8 floor every realistic chart maintainer name carries: \
5705 1..=128 bytes, no leading or trailing whitespace, no ASCII control \
5706 characters anywhere, Unicode bytes accepted. The canonical authoring \
5707 shapes are short single-line identifiers like `\"pleme-io\"`, \
5708 `\"Pleme Contributors\"`, `\"alice <alice@example.com>\"`, \
5709 `\"François Dupont\"`. Without this gate a malformed `:autores` entry \
5710 (paste-from-aligned-doc leading whitespace `\" pleme-io\"` / trailing \
5711 whitespace `\"pleme-io \"`; paste-from-multiline-doc newline \
5712 `\"alice\\nbob\"` — the author pasted a multi-line block of author \
5713 records into one entry instead of splitting into one entry per author; \
5714 paste-from-Windows-CRLF-doc carriage return `\"alice\\rbob\"`; \
5715 tab-from-aligned-doc `\"Pleme\\tContributors\"`; paste-from-binary-blob \
5716 NUL / BEL / ESC / DEL byte) silently passed `from_lisp` + \
5717 `validate_autores` + `StandardLayout::verify` and landed in the \
5718 rendered `lareira-<nome>` Helm chart's `Chart.yaml maintainers:` array \
5719 as a YAML-illegal multi-line scalar or a silently-trimmed whitespace \
5720 round-trip — every chart-aware UI (`helm list`, `helm search`, \
5721 Artifact Hub maintainer index) would render the maintainer name in a \
5722 single-line column far from the source caixa.lisp; the gate moves the \
5723 diagnostic to the manifest layer with the offending value named \
5724 verbatim)"
5725 )]
5726 AutorInvalid { autor: String, reason: String },
5727 #[error(
5728 ":repositorio is the empty string (every published caixa names its \
5729 git source via a non-empty `:repositorio` locator — the value \
5730 flows verbatim into the rendered `lareira-<nome>` Helm chart's \
5731 `Chart.yaml` `home:` field via `caixa-helm` and into the FluxCD \
5732 `GitRepository.spec.url` via `caixa-flux`'s \
5733 `ClusterBundleOpts::for_caixa`; both consumers' \
5734 `Option::unwrap_or_else` fallbacks only fire when the slot is \
5735 `None`, so an empty `Some(\"\")` silently lands as `home: \"\"` / \
5736 `url: \"\"` in the rendered artifacts and breaks at `helm \
5737 template` / FluxCD source-controller reconcile time far from the \
5738 source caixa.lisp; omit the slot entirely to defer to the \
5739 renderer's `https://github.com/pleme-io/<nome>` / \
5740 `caixa.nome`-derived fallback, or carry a canonical authoring \
5741 shape like `\"github:org/repo\"`, `\"https://host/path\"`, \
5742 `\"ssh://[user@]host/path\"`, `\"git@host:path\"`, or \
5743 `\"file:///path\"`)"
5744 )]
5745 RepositorioEmpty,
5746 #[error(
5747 ":repositorio {repositorio:?} is not a valid git repo URL: {reason} \
5748 (the substrate consumes this string through the shared \
5749 `crate::render::is_git_repo_url` predicate — the same parser the \
5750 peer `:deps :fonte (:tipo git :repo …)` axis routes its `:repo` \
5751 value through via `DepSource::validate`; the canonical authoring \
5752 shapes are `\"github:org/repo\"` shorthand, `\"https://host/path\"` \
5753 / `\"ssh://[user@]host/path\"` / `\"git://host/path\"` / \
5754 `\"file:///path\"` URL schemes, or the `\"git@host:path\"` \
5755 scp-style SSH form. Without this gate a malformed `:repositorio` \
5756 (whitespace from a paste-from-doc; control characters / CRLF \
5757 from a paste-from-multiline-doc; a leading `-` from a \
5758 CLI-argument-injection footgun; a missing `:` separator from a \
5759 bare `org/repo` shape git treats as a relative filesystem path) \
5760 silently landed in the rendered `Chart.yaml home:` and the \
5761 FluxCD `GitRepository.spec.url` and broke at `git clone` / \
5762 FluxCD reconcile time far from the source caixa.lisp; the gate \
5763 moves the diagnostic to the manifest layer with the offending \
5764 value named verbatim)"
5765 )]
5766 RepositorioInvalid { repositorio: String, reason: String },
5767 #[error(
5768 ":descricao is the empty string (every published caixa names \
5769 its purpose via a non-empty `:descricao` summary — the value \
5770 flows verbatim into the rendered `lareira-<nome>` Helm \
5771 chart's `Chart.yaml` `description:` field via `caixa-helm`'s \
5772 `build_chart_yaml` and into the chart `README.md` header via \
5773 `build_readme`; both consumers' `Option::unwrap_or_else` \
5774 `caixa.nome`-derived fallbacks only fire when the slot is \
5775 `None`, so an empty `Some(\"\")` silently lands as \
5776 `description: \"\"` / a blank `README.md` header in the \
5777 rendered artifacts and breaks at `helm lint` time \
5778 (`WARNING [chart.metadata.description]: description is \
5779 required` on `apiVersion: v2` charts) far from the source \
5780 caixa.lisp; omit the slot entirely to defer to the \
5781 renderer's `\"Generated chart for caixa Servico <nome>\"` / \
5782 `\"caixa Servico <nome>\"` fallbacks, or carry a non-empty \
5783 summary like `\"Canonical Rust→wasm32-wasip2 caixa \
5784 Servico.\"`)"
5785 )]
5786 DescricaoEmpty,
5787 #[error(
5788 ":descricao {descricao:?} is not a valid chart-description shape: \
5789 {reason} (the substrate consumes this string through the shared \
5790 `crate::render::is_chart_description_shape` predicate — the same \
5791 single-line-UTF-8 floor every realistic chart description carries: \
5792 1..=512 bytes, no leading or trailing whitespace, no ASCII control \
5793 characters anywhere, Unicode prose bytes accepted. The canonical \
5794 authoring shapes are short single-line summaries like `\"Canonical \
5795 Rust→wasm32-wasip2 caixa Servico.\"`, `\"Checkout flow.\"`, \
5796 `\"AWS provider caixa for tatara-lisp\"`. Without this gate a \
5797 malformed `:descricao` (paste-from-aligned-doc leading whitespace \
5798 `\" Checkout flow.\"` / trailing whitespace `\"Checkout flow. \"`; \
5799 paste-from-multiline-doc newline `\"Checkout\\nflow.\"`; \
5800 paste-from-Windows-CRLF-doc carriage return `\"Checkout\\rflow.\"`; \
5801 tab-from-aligned-doc `\"Checkout\\tflow.\"`; paste-from-binary-blob \
5802 NUL / BEL / ESC / DEL byte) silently passed `from_lisp` + \
5803 `validate_descricao` + `StandardLayout::verify` and landed in the \
5804 rendered `lareira-<nome>` Helm chart's `Chart.yaml description:` \
5805 field + `README.md` header paragraph as a YAML-illegal multi-line \
5806 scalar or a silently-trimmed whitespace round-trip — every \
5807 chart-aware UI (`helm list`, `helm search`, Artifact Hub) would \
5808 render the description in a single-line column far from the source \
5809 caixa.lisp; the gate moves the diagnostic to the manifest layer \
5810 with the offending value named verbatim)"
5811 )]
5812 DescricaoInvalid { descricao: String, reason: String },
5813 #[error(
5814 ":licenca is the empty string (every published caixa names \
5815 its license via a non-empty `:licenca` SPDX expression — the \
5816 value flows verbatim into the rendered `lareira-<nome>` Helm \
5817 chart's `README.md` `## License` section via `caixa-helm`'s \
5818 `build_readme` at `caixa-helm/src/lib.rs:361`; the consumer's \
5819 `Option::unwrap_or_else(|| \"MIT\".into())` `MIT` fallback \
5820 only fires when the slot is `None`, so an empty `Some(\"\")` \
5821 silently lands as a bare trailing period in the rendered \
5822 chart `README.md` `License` section far from the source \
5823 caixa.lisp; omit the slot entirely to defer to the \
5824 renderer's `MIT` fallback, or carry a canonical SPDX \
5825 expression like `\"MIT\"`, `\"Apache-2.0\"`, \
5826 `\"Apache-2.0 OR MIT\"`)"
5827 )]
5828 LicencaEmpty,
5829 #[error(
5830 ":licenca {licenca:?} is not a valid SPDX expression shape: {reason} \
5831 (the substrate consumes this string through the shared \
5832 `crate::render::is_spdx_expression_shape` predicate — the same \
5833 alphabet-floor parser every peer per-axis value-shape gate routes \
5834 its value through; the canonical authoring shapes are single \
5835 license identifiers like `\"MIT\"`, `\"Apache-2.0\"`, `\"BSD-3-Clause\"`, \
5836 compound expressions like `\"Apache-2.0 OR MIT\"`, \
5837 `\"MIT AND BSD-3-Clause\"`, `\"(MIT OR Apache-2.0) AND ISC\"`, \
5838 license-with-exception forms like `\"Apache-2.0 WITH LLVM-exception\"`, \
5839 `+`-suffix variants like `\"GPL-2.0+\"`, and user-defined references \
5840 like `\"LicenseRef-MyLicense\"` / \
5841 `\"DocumentRef-doc:LicenseRef-MyLicense\"`. Without this gate a \
5842 malformed `:licenca` (paste-from-doc whitespace `\"MIT \"` / \
5843 `\" MIT\"`; paste-from-multiline-doc CRLF `\"MIT\\n\"`; \
5844 tab-from-aligned-doc `\"MIT\\tOR Apache-2.0\"`; non-ASCII byte from \
5845 a smart-quote paste; underscore-instead-of-hyphen typo \
5846 `\"Apache_2.0\"`; comma-instead-of-`OR`-keyword colloquial idiom \
5847 `\"MIT, Apache-2.0\"`; slash-dual-license colloquial idiom \
5848 `\"MIT/Apache-2.0\"`; semicolon-list-separator confusion \
5849 `\"MIT; Apache-2.0\"`) silently landed in the rendered chart \
5850 `README.md` `## License` section + a future SPDX-aware \
5851 `Chart.yaml license:` emitter would refuse the value at \
5852 `helm lint` time far from the source caixa.lisp; the gate moves \
5853 the diagnostic to the manifest layer with the offending value \
5854 named verbatim)"
5855 )]
5856 LicencaInvalid { licenca: String, reason: String },
5857 #[error(
5858 ":edicao is the empty string (every published caixa names \
5859 its language edition via a non-empty `:edicao` value — the \
5860 edition determines the tatara-lisp macro surface + \
5861 compatibility flags the substrate applies when building \
5862 the caixa; the canonical `Caixa::template` scaffold every \
5863 `feira init` emits carries `:edicao \"2026\"` verbatim and \
5864 every renderer-side fixture (`caixa-helm`, `caixa-flux`, \
5865 `caixa-mesh`) carries `edicao: Some(\"2026\".into())` by \
5866 construction, so an empty `Some(\"\")` silently lands as a \
5867 bare `(:edicao \"\")` line in the rendered `caixa.lisp` and \
5868 a future renderer-side consumer that folds it through \
5869 `Option::unwrap_or_else` will skip the fallback and pass the \
5870 empty edition through to the substrate's build-time edition \
5871 selector far from the source caixa.lisp; omit the slot \
5872 entirely to defer to the substrate's default edition, or \
5873 carry a canonical edition like `\"2026\"`)"
5874 )]
5875 EdicaoEmpty,
5876 #[error(
5877 ":edicao {edicao:?} is not a valid edition: {reason} (every \
5878 documented tatara-lisp edition is a 4-digit ASCII decimal \
5879 year — `\"2026\"` is the only edition currently minted; \
5880 future-introduced siblings will follow the same shape, peer \
5881 with Cargo's `[package] edition` grammar which every value \
5882 Cargo has ever accepted matches: `\"2015\"`, `\"2018\"`, \
5883 `\"2021\"`, `\"2024\"`. Without this gate the canonical \
5884 paste-from-doc footguns silently passed: a trailing space \
5885 (`\"2026 \"`) from a paste-from-doc, a CRLF (`\"2026\\n\"`) \
5886 from a paste-from-multiline-doc, a fullwidth-keyboard \
5887 look-alike (`\"2026\"`), a free-form non-year value \
5888 (`\"x\"`, `\"latest\"`, `\"nightly\"`), a leading non-digit \
5889 version-tag prefix (`\"v2026\"`, `\"e2026\"`), a \
5890 decimal-shaped pseudo-version (`\"2026.1\"`), or a \
5891 wrong-length numeric value (`\"26\"`, `\"202\"`, \
5892 `\"20260\"`) all landed as `(:edicao \"<garbage>\")` in the \
5893 rendered caixa.lisp and broke at the substrate's \
5894 build-time edition selector far from the source caixa.lisp; \
5895 omit the slot entirely to defer to the substrate's default \
5896 edition, or carry a canonical 4-digit ASCII decimal year \
5897 like `\"2026\"`)"
5898 )]
5899 EdicaoInvalid { edicao: String, reason: String },
5900}
5901
5902#[cfg(test)]
5903mod tests {
5904 use super::*;
5905
5906 #[test]
5907 fn template_round_trips() {
5908 let src = Caixa::template("demo");
5909 let c = Caixa::from_lisp(&src).expect("template must parse");
5910 assert_eq!(c.nome, "demo");
5911 assert_eq!(c.versao, "0.1.0");
5912 assert_eq!(c.kind, CaixaKind::Biblioteca);
5913 assert_eq!(c.bibliotecas, vec!["lib/demo.lisp".to_string()]);
5914 assert!(c.deps.is_empty());
5915 assert!(c.deps_dev.is_empty());
5916 }
5917
5918 #[test]
5919 fn caixa_universal_axis_scalar_accessor_pair_is_const_fn() {
5920 // Fail-before-pass-after pin on [`Caixa::nome`] +
5921 // [`Caixa::versao`]'s `const`-eval-surface posture. Each
5922 // accessor projects the top-level manifest's per-`:nome` /
5923 // per-`:versao` [`String`] storage through the `pub const fn`
5924 // [`String::as_str`] (const-stable since Rust 1.87, well within
5925 // the workspace MSRV) — any future accidental downgrade to
5926 // non-`const` fails the corresponding `<name>_via_const_fn`
5927 // wrapper at caixa-core build time with E0015 (`cannot call
5928 // non-const method`), strictly stronger than a runtime
5929 // `assert!`. Sibling of the peer per-M2/M3-slot `String → &str`
5930 // scalar-accessor family pins on the sibling `const`-eval-
5931 // surface passes ([`crate::CaixaVersion::as_str`] at the
5932 // typed-newtype wrapper, [`crate::aplicacao::Membro::nome`] /
5933 // [`crate::aplicacao::Membro::versao_requirement`] at the M3
5934 // membership axis, [`crate::aplicacao::Entrada::hostname`] /
5935 // [`crate::aplicacao::Entrada::destination`] at the M3 ingress
5936 // axis, [`crate::supervisor::ChildSpec::nome`] /
5937 // [`crate::supervisor::ChildSpec::versao_requirement`] at the
5938 // M2 supervisor-tree axis,
5939 // [`crate::upgrade::UpgradeFromEntry::prior_versao`] at the M2
5940 // upgrade axis, [`crate::dep::Dep::nome`] /
5941 // [`crate::dep::Dep::versao_requirement`] at the dep-graph
5942 // axis, and the per-`:contratos`
5943 // [`crate::aplicacao::WitContract::source`] /
5944 // [`crate::aplicacao::WitContract::destination`] /
5945 // [`crate::aplicacao::WitContract::world_ref`] trio the
5946 // sibling pin at 279823b already anchors).
5947 const fn nome_via_const_fn(c: &Caixa) -> &str {
5948 c.nome()
5949 }
5950 const fn versao_via_const_fn(c: &Caixa) -> &str {
5951 c.versao()
5952 }
5953 let src = Caixa::template("demo");
5954 let c = Caixa::from_lisp(&src).expect("template must parse");
5955 assert_eq!(nome_via_const_fn(&c), c.nome());
5956 assert_eq!(versao_via_const_fn(&c), c.versao());
5957 assert_eq!(c.nome(), "demo");
5958 assert_eq!(c.versao(), "0.1.0");
5959 }
5960
5961 #[test]
5962 fn caixa_option_string_scalar_accessor_family_is_const_fn() {
5963 // Fail-before-pass-after pin on the five per-`Caixa`
5964 // `Option<String> → Option<&str>` scalar accessors
5965 // ([`Caixa::licenca`] / [`Caixa::repositorio`] /
5966 // [`Caixa::descricao`] / [`Caixa::edicao`] on the top-level
5967 // manifest's optional universal-axis surface, plus
5968 // [`Caixa::restart_window`] on the M2 supervisor-tree
5969 // per-`SupervisorSpec` peer raw-window-string projection axis).
5970 // Each accessor destructures the typed slot's `Option<String>`
5971 // storage through the `match &self.<field> { Some(s) =>
5972 // Some(s.as_str()), None => None }` shape — routing through
5973 // [`String::as_str`] (const-stable since Rust 1.87, well within
5974 // the workspace MSRV) rather than the non-const
5975 // [`Option::as_deref`] the pre-lift bodies carried — and any
5976 // future accidental downgrade to non-`const` fails the
5977 // corresponding `<name>_via_const_fn` wrapper at caixa-core
5978 // build time with E0015 (`cannot call non-const method`),
5979 // strictly stronger than a runtime `assert!` and strictly
5980 // stronger than a module-scope `const _: () = assert!(…)` pin
5981 // (which cannot be formed on a `&Caixa` fixture because the
5982 // type's `String` / `Option<String>` carriers rule out
5983 // `const`-context value construction; the `const fn` wrapper
5984 // is the load-bearing shape that side-steps the destructor-in-
5985 // const restriction on the value axis while still pinning the
5986 // `const`-fn posture on the callee — mirror of the sibling
5987 // [`caixa_universal_axis_scalar_accessor_pair_is_const_fn`]
5988 // pin's discipline verbatim on the peer non-`Option`
5989 // `String → &str` axis at the same struct).
5990 //
5991 // Peer of the sibling per-M2/M3-slot `Option<String> →
5992 // Option<&str>` accessor family pin
5993 // [`m3_option_string_scalar_accessor_family_is_const_fn`] on
5994 // the M3 mesh-slot atom axes ([`WitContract::endpoint`] /
5995 // [`WitContract::subject`] / [`WitContract::slot`] on the
5996 // per-`:contratos` payload-carrier trio,
5997 // [`Placement::shard_key`] / [`Placement::affinity`] on the
5998 // per-`:placement` optional-scalar pair).
5999 const fn licenca_via_const_fn(c: &Caixa) -> Option<&str> {
6000 c.licenca()
6001 }
6002 const fn repositorio_via_const_fn(c: &Caixa) -> Option<&str> {
6003 c.repositorio()
6004 }
6005 const fn descricao_via_const_fn(c: &Caixa) -> Option<&str> {
6006 c.descricao()
6007 }
6008 const fn edicao_via_const_fn(c: &Caixa) -> Option<&str> {
6009 c.edicao()
6010 }
6011 const fn restart_window_via_const_fn(c: &Caixa) -> Option<&str> {
6012 c.restart_window()
6013 }
6014 // Sweep both the `Some`-carrying arm (author-declared slot,
6015 // the byte-string projection payload) and the `None`-carrying
6016 // arm (author-omitted slot, the default-path projection) on
6017 // every accessor so the `const fn` wrapper family pins each
6018 // axis's canonical two-arm partition through the same const
6019 // dispatch as the runtime path.
6020 let mut c1 = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6021 c1.licenca = Some("MIT".to_string());
6022 c1.repositorio = Some("https://github.com/pleme-io/demo".to_string());
6023 c1.descricao = Some("demo caixa".to_string());
6024 c1.edicao = Some("2024".to_string());
6025 c1.restart_window = Some("60s".to_string());
6026 assert_eq!(licenca_via_const_fn(&c1), c1.licenca());
6027 assert_eq!(repositorio_via_const_fn(&c1), c1.repositorio());
6028 assert_eq!(descricao_via_const_fn(&c1), c1.descricao());
6029 assert_eq!(edicao_via_const_fn(&c1), c1.edicao());
6030 assert_eq!(restart_window_via_const_fn(&c1), c1.restart_window());
6031 assert_eq!(c1.licenca(), Some("MIT"));
6032 assert_eq!(c1.repositorio(), Some("https://github.com/pleme-io/demo"));
6033 assert_eq!(c1.descricao(), Some("demo caixa"));
6034 assert_eq!(c1.edicao(), Some("2024"));
6035 assert_eq!(c1.restart_window(), Some("60s"));
6036 let mut c2 = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6037 c2.licenca = None;
6038 c2.repositorio = None;
6039 c2.descricao = None;
6040 c2.edicao = None;
6041 c2.restart_window = None;
6042 assert_eq!(licenca_via_const_fn(&c2), None);
6043 assert_eq!(repositorio_via_const_fn(&c2), None);
6044 assert_eq!(descricao_via_const_fn(&c2), None);
6045 assert_eq!(edicao_via_const_fn(&c2), None);
6046 assert_eq!(restart_window_via_const_fn(&c2), None);
6047 }
6048
6049 #[test]
6050 fn caixa_outer_copy_return_accessor_pair_is_const_fn() {
6051 // Fail-before-pass-after pin on the two outer-[`Caixa`]
6052 // `Copy`-return accessors — [`Caixa::kind`] on the required
6053 // [`CaixaKind`] enum-discriminant axis and [`Caixa::estrategia`]
6054 // on the M2 supervisor-tree flat-spread `Option<RestartStrategy>`
6055 // axis. Both accessors project a `Copy`-carrier field
6056 // (`CaixaKind: Copy` at caixa-core/src/kind.rs:17,
6057 // `RestartStrategy: Copy` at caixa-core/src/supervisor.rs:33 →
6058 // `Option<RestartStrategy>: Copy`) by value through a bare
6059 // `self.<field>` field-access — no dispatch, no destructor, no
6060 // heap. Any future accidental downgrade to non-`const` fails
6061 // the corresponding `<name>_via_const_fn` wrapper at caixa-core
6062 // build time with E0015 (`cannot call non-const method`),
6063 // strictly stronger than a runtime `assert!` and strictly
6064 // stronger than a module-scope `const _: () = assert!(…)` pin
6065 // (which cannot be formed on a `&Caixa` fixture because the
6066 // type's `String` / `Vec` / `Option<Composite>` carriers rule
6067 // out `const`-context value construction; the `const fn`
6068 // wrapper is the load-bearing shape that side-steps the
6069 // destructor-in-const restriction on the value axis while still
6070 // pinning the `const`-fn posture on the callee — mirror of the
6071 // sibling [`caixa_universal_axis_scalar_accessor_pair_is_const_fn`]
6072 // + [`caixa_option_string_scalar_accessor_family_is_const_fn`]
6073 // pins' discipline verbatim on the peer outer-`Caixa`
6074 // `String → &str` + `Option<String> → Option<&str>` axes at the
6075 // same struct).
6076 //
6077 // Peer of the sibling per-M2/M3-slot `Copy`-return accessor pin
6078 // family on the inner-altitude nested-spec typed-slot
6079 // discriminator axes: [`crate::supervisor::SupervisorSpec::estrategia`]
6080 // + [`crate::supervisor::ChildSpec::restart`] on the M2
6081 // supervisor-tree axis (pinned at 152c868), and
6082 // [`crate::aplicacao::Placement::estrategia`] +
6083 // [`crate::aplicacao::Entrada::port`] on the M3 mesh-slot axis
6084 // (pinned at bafa004) — the outer-`Caixa` altitude is the last
6085 // unlifted altitude for the `Copy`-return-accessor family.
6086 const fn kind_via_const_fn(c: &Caixa) -> CaixaKind {
6087 c.kind()
6088 }
6089 const fn estrategia_via_const_fn(c: &Caixa) -> Option<crate::supervisor::RestartStrategy> {
6090 c.estrategia()
6091 }
6092 // Sweep every arm of both discriminant partitions the accessors
6093 // fan on — every [`CaixaKind`] variant the six-arm required
6094 // discriminant carries (Biblioteca / Binario / Servico /
6095 // Supervisor / Aplicacao / Acao) and both arms of the
6096 // [`Option<RestartStrategy>`] flat-spread supervisor-tree slot
6097 // (`Some(<strategy>)` on an author-declared supervisor and
6098 // `None` on the author-omitted default arm every non-Supervisor
6099 // caixa carries by `#[serde(default)]`) — so the `const fn`
6100 // wrapper family pins the closed-set partition through the
6101 // same const dispatch as the runtime path.
6102 let mut c1 = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6103 c1.kind = CaixaKind::Servico;
6104 c1.estrategia = Some(crate::supervisor::RestartStrategy::OneForAll);
6105 assert_eq!(kind_via_const_fn(&c1), c1.kind());
6106 assert_eq!(estrategia_via_const_fn(&c1), c1.estrategia());
6107 assert_eq!(c1.kind(), CaixaKind::Servico);
6108 assert_eq!(
6109 c1.estrategia(),
6110 Some(crate::supervisor::RestartStrategy::OneForAll)
6111 );
6112 let mut c2 = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6113 c2.kind = CaixaKind::Aplicacao;
6114 c2.estrategia = None;
6115 assert_eq!(kind_via_const_fn(&c2), CaixaKind::Aplicacao);
6116 assert_eq!(estrategia_via_const_fn(&c2), None);
6117 // Anchor the remaining discriminant arms so any future
6118 // reordering of [`CaixaKind`]'s six-variant enum surfaces
6119 // through the wrapper dispatch, not just through the direct
6120 // method call.
6121 for kind in [
6122 CaixaKind::Biblioteca,
6123 CaixaKind::Binario,
6124 CaixaKind::Servico,
6125 CaixaKind::Supervisor,
6126 CaixaKind::Aplicacao,
6127 CaixaKind::Acao,
6128 ] {
6129 let mut c = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6130 c.kind = kind;
6131 assert_eq!(kind_via_const_fn(&c), kind);
6132 }
6133 }
6134
6135 #[test]
6136 fn caixa_outer_string_slice_return_accessor_family_is_const_fn() {
6137 // Fail-before-pass-after pin on the five outer-[`Caixa`]
6138 // `Vec<String> → &[String]` slice-return accessors on the
6139 // universal-axis surface — [`Caixa::autores`] / [`Caixa::etiquetas`]
6140 // / [`Caixa::bibliotecas`] / [`Caixa::exe`] / [`Caixa::servicos`].
6141 // Each body is a bare `self.<field>.as_slice()` dispatch through
6142 // [`Vec::as_slice`] (const-stable since Rust 1.87, well within
6143 // the workspace MSRV). Any future accidental downgrade to
6144 // non-`const` fails the corresponding `<name>_via_const_fn`
6145 // wrapper at caixa-core build time with E0015 (`cannot call
6146 // non-const method`) — mirror of the sibling
6147 // [`caixa_outer_copy_return_accessor_pair_is_const_fn`] pin's
6148 // discipline on the peer outer-`Caixa` `Copy`-return accessor
6149 // axis, and peer of the sibling composite-carrier slice-return
6150 // pin below on the peer outer-`Caixa` composite-slice axis.
6151 const fn autores_via_const_fn(c: &Caixa) -> &[String] {
6152 c.autores()
6153 }
6154 const fn etiquetas_via_const_fn(c: &Caixa) -> &[String] {
6155 c.etiquetas()
6156 }
6157 const fn bibliotecas_via_const_fn(c: &Caixa) -> &[String] {
6158 c.bibliotecas()
6159 }
6160 const fn exe_via_const_fn(c: &Caixa) -> &[String] {
6161 c.exe()
6162 }
6163 const fn servicos_via_const_fn(c: &Caixa) -> &[String] {
6164 c.servicos()
6165 }
6166 // Sweep the empty arm (`autores` / `etiquetas` / `exe` /
6167 // `servicos` — the template's `Vec::new()` default) and the
6168 // populated arm (mutated below) on every accessor so the
6169 // `const fn` wrapper family pins each axis's two-arm partition
6170 // through the same const dispatch as the runtime path.
6171 // [`Caixa::template`] seeds `lib/demo.lisp` into `:bibliotecas`,
6172 // so that arm's "empty" fixture is the populated arm the
6173 // mutation sweep covers.
6174 let c_empty = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6175 assert!(autores_via_const_fn(&c_empty).is_empty());
6176 assert!(etiquetas_via_const_fn(&c_empty).is_empty());
6177 assert!(exe_via_const_fn(&c_empty).is_empty());
6178 assert!(servicos_via_const_fn(&c_empty).is_empty());
6179 let mut c_full = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6180 c_full.autores = vec!["ada".to_string(), "erlang".to_string()];
6181 c_full.etiquetas = vec!["compounding".to_string()];
6182 c_full.bibliotecas = vec!["lib/one.lisp".to_string(), "lib/two.lisp".to_string()];
6183 c_full.exe = vec!["exe/cli.lisp".to_string()];
6184 c_full.servicos = vec!["servicos/one.computeunit.yaml".to_string()];
6185 assert_eq!(autores_via_const_fn(&c_full), c_full.autores());
6186 assert_eq!(autores_via_const_fn(&c_full), &["ada", "erlang"]);
6187 assert_eq!(etiquetas_via_const_fn(&c_full), c_full.etiquetas());
6188 assert_eq!(etiquetas_via_const_fn(&c_full), &["compounding"]);
6189 assert_eq!(bibliotecas_via_const_fn(&c_full), c_full.bibliotecas());
6190 assert_eq!(
6191 bibliotecas_via_const_fn(&c_full),
6192 &["lib/one.lisp", "lib/two.lisp"]
6193 );
6194 assert_eq!(exe_via_const_fn(&c_full), c_full.exe());
6195 assert_eq!(exe_via_const_fn(&c_full), &["exe/cli.lisp"]);
6196 assert_eq!(servicos_via_const_fn(&c_full), c_full.servicos());
6197 assert_eq!(
6198 servicos_via_const_fn(&c_full),
6199 &["servicos/one.computeunit.yaml"]
6200 );
6201 }
6202
6203 #[test]
6204 fn caixa_outer_composite_slice_return_accessor_family_is_const_fn() {
6205 // Fail-before-pass-after pin on the six outer-[`Caixa`] composite-
6206 // carrier `Vec<T> → &[T]` slice-return accessors — [`Caixa::deps`]
6207 // / [`Caixa::deps_dev`] on the dep-graph axis,
6208 // [`Caixa::upgrade_from`] on the M2 appup axis, [`Caixa::children`]
6209 // on the M2 supervisor-tree axis, and [`Caixa::membros`] /
6210 // [`Caixa::contratos`] on the M3 mesh-slot axis. Each body is a
6211 // bare `self.<field>.as_slice()` dispatch through
6212 // [`Vec::as_slice`] (const-stable since Rust 1.87, well within
6213 // the workspace MSRV) — peer of the sibling `String`-payload
6214 // slice-return pin above on the peer outer-`Caixa` universal-
6215 // axis surface, and peer of the sibling inner-composite-
6216 // altitude reference-return pin family
6217 // [`crate::aplicacao::tests::m3_aplicacao_spec_reference_return_accessor_family_is_const_fn`]
6218 // + [`crate::supervisor::tests::supervisor_children_slice_return_accessor_is_const_fn`]
6219 // + [`crate::upgrade::tests::upgrade_from_entry_instructions_slice_return_accessor_is_const_fn`]
6220 // (all pinned at 0b23e0f).
6221 const fn deps_via_const_fn(c: &Caixa) -> &[Dep] {
6222 c.deps()
6223 }
6224 const fn deps_dev_via_const_fn(c: &Caixa) -> &[Dep] {
6225 c.deps_dev()
6226 }
6227 const fn upgrade_from_via_const_fn(c: &Caixa) -> &[UpgradeFromEntry] {
6228 c.upgrade_from()
6229 }
6230 const fn children_via_const_fn(c: &Caixa) -> &[crate::supervisor::ChildSpec] {
6231 c.children()
6232 }
6233 const fn membros_via_const_fn(c: &Caixa) -> &[crate::aplicacao::Membro] {
6234 c.membros()
6235 }
6236 const fn contratos_via_const_fn(c: &Caixa) -> &[crate::aplicacao::WitContract] {
6237 c.contratos()
6238 }
6239 // Empty-arm sweep on all six composite-carrier axes — every
6240 // `Caixa::template` starts with `Vec::new()` on each.
6241 let c_empty = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6242 assert!(deps_via_const_fn(&c_empty).is_empty());
6243 assert!(deps_dev_via_const_fn(&c_empty).is_empty());
6244 assert!(upgrade_from_via_const_fn(&c_empty).is_empty());
6245 assert!(children_via_const_fn(&c_empty).is_empty());
6246 assert!(membros_via_const_fn(&c_empty).is_empty());
6247 assert!(contratos_via_const_fn(&c_empty).is_empty());
6248 // Populate `:membros` / `:contratos` directly via struct literals
6249 // — the parser-side validation path fans on `:kind`-gated cross-
6250 // slot invariants irrelevant to the accessor dispatch under test.
6251 let mut c_full = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6252 c_full.membros = vec![
6253 crate::aplicacao::Membro {
6254 caixa: "demo-a".to_string(),
6255 versao: "^0.1.0".to_string(),
6256 },
6257 crate::aplicacao::Membro {
6258 caixa: "demo-b".to_string(),
6259 versao: "^0.2.0".to_string(),
6260 },
6261 ];
6262 c_full.contratos = vec![crate::aplicacao::WitContract {
6263 de: "demo-a".to_string(),
6264 para: "demo-b".to_string(),
6265 wit: "wasi:http/proxy".to_string(),
6266 endpoint: Some("/edge".to_string()),
6267 subject: None,
6268 slot: None,
6269 }];
6270 assert_eq!(membros_via_const_fn(&c_full), c_full.membros());
6271 assert_eq!(contratos_via_const_fn(&c_full), c_full.contratos());
6272 assert_eq!(membros_via_const_fn(&c_full).len(), 2);
6273 assert_eq!(contratos_via_const_fn(&c_full).len(), 1);
6274 // Alias-borrow check on the four remaining composite-carrier
6275 // slice-return arms — the wrapper's return borrow must alias the
6276 // caller's borrow so any future accessor re-routing that skips
6277 // the storage field surfaces through the assertion.
6278 assert!(std::ptr::eq(deps_via_const_fn(&c_full), c_full.deps()));
6279 assert!(std::ptr::eq(
6280 deps_dev_via_const_fn(&c_full),
6281 c_full.deps_dev()
6282 ));
6283 assert!(std::ptr::eq(
6284 upgrade_from_via_const_fn(&c_full),
6285 c_full.upgrade_from()
6286 ));
6287 assert!(std::ptr::eq(
6288 children_via_const_fn(&c_full),
6289 c_full.children()
6290 ));
6291 }
6292
6293 #[test]
6294 fn caixa_outer_option_composite_reference_return_accessor_family_is_const_fn() {
6295 // Fail-before-pass-after pin on the six outer-[`Caixa`]
6296 // `Option<Composite> → Option<&Composite>` reference-return
6297 // accessors — [`Caixa::limits`] / [`Caixa::behavior`] on the M2
6298 // Servico-runtime typed-slot axis, [`Caixa::politicas`] /
6299 // [`Caixa::placement`] / [`Caixa::entrada`] on the M3 mesh-slot
6300 // axis, and [`Caixa::ci`] on the Acao-kind typed-CI-run axis.
6301 // Each body is a bare `self.<field>.as_ref()` dispatch through
6302 // [`Option::as_ref`] (const-stable since Rust 1.83, well within
6303 // the workspace MSRV of 1.89). Any future accidental downgrade
6304 // to non-`const` fails the corresponding `<name>_via_const_fn`
6305 // wrapper at caixa-core build time with E0015 (`cannot call
6306 // non-const method`), strictly stronger than a runtime `assert!`
6307 // and strictly stronger than a module-scope `const _: () =
6308 // assert!(…)` pin (which cannot be formed on a `&Caixa` fixture
6309 // because the type's `String` / `Vec` / `Option<Composite>`
6310 // carriers rule out `const`-context value construction; the
6311 // `const fn` wrapper is the load-bearing shape that side-steps
6312 // the destructor-in-const restriction on the value axis while
6313 // still pinning the `const`-fn posture on the callee — mirror
6314 // of the sibling
6315 // [`caixa_outer_copy_return_accessor_pair_is_const_fn`] +
6316 // [`caixa_outer_string_slice_return_accessor_family_is_const_fn`] +
6317 // [`caixa_outer_composite_slice_return_accessor_family_is_const_fn`]
6318 // pins' discipline verbatim on the peer outer-`Caixa` axes at
6319 // the same struct).
6320 //
6321 // Closes the outer-`Caixa` `Option<&Composite>` composite-
6322 // reference-return sub-family — the last unlifted altitude on
6323 // the outer-`Caixa` accessor-family const-eval surface after
6324 // the sibling `Copy`-return / universal-axis-`&str` /
6325 // `Option<&str>` / `&[String]` / composite-`&[T]` pins already
6326 // closed the sibling arms at 866d1d5 / 29c5d7e / 0650f64 /
6327 // 231a968 (the last of these pins the `Vec<T> → &[T]`
6328 // composite-slice arm the six accessors here close as their
6329 // `Option<Composite> → Option<&Composite>` peer). Peer of the
6330 // sibling inner-altitude nested-spec composite-reference-return
6331 // pin family — [`crate::AplicacaoSpec::politicas`] /
6332 // [`crate::AplicacaoSpec::placement`] /
6333 // [`crate::AplicacaoSpec::entrada`] on the inner
6334 // [`crate::AplicacaoSpec`] altitude (already `pub const fn`
6335 // per 0b23e0f), and the outer-`Caixa` altitude here now carries
6336 // the same shape so both altitudes of the reference-return
6337 // discipline (per-`Caixa` outer-slot presence + per-
6338 // `AplicacaoSpec` inner-slot presence) route through one typed
6339 // const dispatch on the substrate primitive.
6340 const fn limits_via_const_fn(c: &Caixa) -> Option<&LimitsSpec> {
6341 c.limits()
6342 }
6343 const fn behavior_via_const_fn(c: &Caixa) -> Option<&crate::BehaviorSpec> {
6344 c.behavior()
6345 }
6346 const fn politicas_via_const_fn(c: &Caixa) -> Option<&crate::aplicacao::MeshPolicy> {
6347 c.politicas()
6348 }
6349 const fn placement_via_const_fn(c: &Caixa) -> Option<&crate::aplicacao::Placement> {
6350 c.placement()
6351 }
6352 const fn entrada_via_const_fn(c: &Caixa) -> Option<&crate::aplicacao::Entrada> {
6353 c.entrada()
6354 }
6355 const fn ci_via_const_fn(c: &Caixa) -> Option<&canteiro_types::CiRun> {
6356 c.ci()
6357 }
6358 // Both-arm sweep on every accessor: the `None` author-omitted
6359 // arm (template default — no M2/M3/CI slot declared) and the
6360 // `Some(<composite>)` authored arm (mutated below via struct-
6361 // literal seeds, side-stepping the parser-side `:kind`-gated
6362 // cross-slot invariants irrelevant to the accessor dispatch
6363 // under test). Both arms route through the `const fn` wrapper
6364 // family so the two-arm `Option` partition is pinned through
6365 // the same const dispatch as the runtime path.
6366 let c_empty = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6367 assert!(limits_via_const_fn(&c_empty).is_none());
6368 assert!(behavior_via_const_fn(&c_empty).is_none());
6369 assert!(politicas_via_const_fn(&c_empty).is_none());
6370 assert!(placement_via_const_fn(&c_empty).is_none());
6371 assert!(entrada_via_const_fn(&c_empty).is_none());
6372 assert!(ci_via_const_fn(&c_empty).is_none());
6373 let mut c_full = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6374 c_full.limits = Some(LimitsSpec::default());
6375 c_full.behavior = Some(crate::BehaviorSpec::default());
6376 c_full.politicas = Some(crate::aplicacao::MeshPolicy::default());
6377 c_full.placement = Some(crate::aplicacao::Placement::default());
6378 c_full.entrada = Some(crate::aplicacao::Entrada {
6379 host: "demo.quero.cloud".to_string(),
6380 para: "demo".to_string(),
6381 paths: Vec::new(),
6382 port: crate::aplicacao::DEFAULT_SERVICO_PORT,
6383 });
6384 c_full.ci = Some(canteiro_types::CiRun {
6385 workspace: "pleme-io".into(),
6386 repo: "caixa".into(),
6387 nodes: vec![],
6388 });
6389 assert!(limits_via_const_fn(&c_full).is_some());
6390 assert!(behavior_via_const_fn(&c_full).is_some());
6391 assert!(politicas_via_const_fn(&c_full).is_some());
6392 assert!(placement_via_const_fn(&c_full).is_some());
6393 assert!(entrada_via_const_fn(&c_full).is_some());
6394 assert!(ci_via_const_fn(&c_full).is_some());
6395 // Alias-borrow check on every arm: the wrapper's inner-`Option`
6396 // reference must alias the caller's borrow so any future accessor
6397 // re-routing that skips the storage field surfaces through the
6398 // assertion.
6399 assert!(std::ptr::eq(
6400 limits_via_const_fn(&c_full).unwrap(),
6401 c_full.limits().unwrap()
6402 ));
6403 assert!(std::ptr::eq(
6404 behavior_via_const_fn(&c_full).unwrap(),
6405 c_full.behavior().unwrap()
6406 ));
6407 assert!(std::ptr::eq(
6408 politicas_via_const_fn(&c_full).unwrap(),
6409 c_full.politicas().unwrap()
6410 ));
6411 assert!(std::ptr::eq(
6412 placement_via_const_fn(&c_full).unwrap(),
6413 c_full.placement().unwrap()
6414 ));
6415 assert!(std::ptr::eq(
6416 entrada_via_const_fn(&c_full).unwrap(),
6417 c_full.entrada().unwrap()
6418 ));
6419 assert!(std::ptr::eq(
6420 ci_via_const_fn(&c_full).unwrap(),
6421 c_full.ci().unwrap()
6422 ));
6423 }
6424
6425 #[test]
6426 fn register_populates_registry() {
6427 Caixa::register().expect("first register call in this test process must succeed");
6428 let kws = tatara_lisp::domain::registered_keywords();
6429 assert!(kws.contains(&"defcaixa"));
6430 }
6431
6432 #[test]
6433 fn to_lisp_round_trips() {
6434 let src = Caixa::template("demo");
6435 let c1 = Caixa::from_lisp(&src).unwrap();
6436 let emitted = c1.to_lisp();
6437 let c2 = Caixa::from_lisp(&emitted).expect("emitted lisp parses back");
6438 assert_eq!(c1, c2);
6439 }
6440
6441 // ── DialetoEstrangeiro carries a single typed axis ────────────────────
6442 //
6443 // The compounding pin: the variant stores only the typed
6444 // [`crate::dialeto::CaixaDialeto`], and every user-facing byte-string
6445 // (canonical keyword, description, consumer) routes through the enum's
6446 // own accessors at Display time. Prior to that closure the variant
6447 // carried each accessor's return value as a stored `&'static str`
6448 // snapshot alongside `dialeto`; a caller could construct the variant
6449 // with a snapshot that drifted from what `dialeto`'s accessors would
6450 // return, and every downstream user-facing projection would silently
6451 // disagree with the classification. Storing only the axis makes the
6452 // drift structurally impossible.
6453
6454 #[test]
6455 fn dialeto_estrangeiro_variant_carries_only_the_typed_dialeto_axis() {
6456 // Single-field construction is the whole compounding shape — a
6457 // future re-introduction of a snapshot field (a `palavra_canonica:
6458 // &'static str`, a stored `descricao:`, a stored `consumidor:`)
6459 // would re-open the drift surface and this construction would fail
6460 // to compile with "missing field" until every snapshot was seeded
6461 // at the call site again. The compile-time guarantee is the
6462 // invariant; the assertion below only witnesses that the
6463 // construction is well-formed after the closure.
6464 let err = LeituraError::DialetoEstrangeiro {
6465 dialeto: crate::dialeto::CaixaDialeto::Molde,
6466 };
6467 assert!(matches!(
6468 err,
6469 LeituraError::DialetoEstrangeiro {
6470 dialeto: crate::dialeto::CaixaDialeto::Molde,
6471 }
6472 ));
6473 }
6474
6475 #[test]
6476 fn dialeto_estrangeiro_display_routes_through_typed_dialeto_accessors() {
6477 // For every foreign-dialect classification the variant surfaces —
6478 // [`crate::dialeto::CaixaDialeto::Molde`] and
6479 // [`crate::dialeto::CaixaDialeto::MoldePosicional`], the two
6480 // variants [`Caixa::from_lisp`] raises this error for — the
6481 // rendered [`std::fmt::Display`] byte-string must interpolate each
6482 // typed accessor's return verbatim. A future re-introduction of a
6483 // stored `&'static str` snapshot alongside `dialeto` that Display
6484 // read instead of the accessor would fail this pin as soon as the
6485 // two disagreed; a future accessor rebrand (a per-dialect
6486 // consumer rename, a canonical-keyword shift once the substrate
6487 // migration named in [`crate::dialeto`] completes) reaches every
6488 // consumer through one typed dispatch and this pin verifies the
6489 // display path is one of them.
6490 for d in [
6491 crate::dialeto::CaixaDialeto::Molde,
6492 crate::dialeto::CaixaDialeto::MoldePosicional,
6493 ] {
6494 let rendered = LeituraError::DialetoEstrangeiro { dialeto: d }.to_string();
6495 assert!(
6496 rendered.contains(d.palavra_canonica()),
6497 "Display must interpolate `dialeto.palavra_canonica()` \
6498 verbatim — a stored snapshot would silently drift from \
6499 the typed accessor. dialect: {d}, rendered: {rendered:?}"
6500 );
6501 assert!(
6502 rendered.contains(d.descricao()),
6503 "Display must interpolate `dialeto.descricao()` verbatim. \
6504 dialect: {d}, rendered: {rendered:?}"
6505 );
6506 assert!(
6507 rendered.contains(d.consumidor()),
6508 "Display must interpolate `dialeto.consumidor()` verbatim. \
6509 dialect: {d}, rendered: {rendered:?}"
6510 );
6511 }
6512 }
6513
6514 #[test]
6515 fn from_lisp_rejects_molde_dialect_via_typed_variant() {
6516 // The end-to-end pin the compounding closure defends: a
6517 // Molde-dialect source lands as [`LeituraError::DialetoEstrangeiro`]
6518 // carrying [`crate::dialeto::CaixaDialeto::Molde`], and the
6519 // rendered Display byte-string names the Molde accessors'
6520 // returns verbatim. Any future path that constructed the variant
6521 // with a mismatched snapshot (a stored `palavra_canonica:
6522 // "defcaixa"` on a `Molde` classification) would land Display
6523 // pointing at `defcaixa` while the typed axis said `Molde` — the
6524 // exact drift the closure removes.
6525 let src = r#"
6526 (defcaixa
6527 :name "x"
6528 :kind :Biblioteca
6529 :ecosystem :rust-single-crate
6530 :package {:name "x" :version "0.1.0"})
6531 "#;
6532 let err = Caixa::from_lisp(src).expect_err("Molde dialect must not parse as Pacote");
6533 match err {
6534 LeituraError::DialetoEstrangeiro { dialeto } => {
6535 assert_eq!(dialeto, crate::dialeto::CaixaDialeto::Molde);
6536 let rendered = LeituraError::DialetoEstrangeiro { dialeto }.to_string();
6537 assert!(rendered.contains(dialeto.palavra_canonica()));
6538 assert!(rendered.contains(dialeto.consumidor()));
6539 assert!(rendered.contains(dialeto.descricao()));
6540 }
6541 other => panic!("expected DialetoEstrangeiro, got {other:?}"),
6542 }
6543 }
6544
6545 #[test]
6546 fn from_lisp_rejects_molde_posicional_dialect_via_typed_variant() {
6547 // Coverage pin for the [`crate::dialeto::CaixaDialeto::MoldePosicional`]
6548 // arm of the [`Caixa::from_lisp`] foreign-dialect gate — the
6549 // positional-arity `defmolde` form written under a `(defcaixa …)`
6550 // head (`(defcaixa todoku-go :kind :Biblioteca :ecosystem :go
6551 // …)`). Pre-lift this arm rode the same `foreign =>` wildcard
6552 // the [`crate::dialeto::CaixaDialeto::Molde`] sibling arm rode,
6553 // so no test exercised the positional-arity path through
6554 // `Caixa::from_lisp` specifically; the sibling
6555 // [`from_lisp_rejects_molde_dialect_via_typed_variant`] only
6556 // covered [`crate::dialeto::CaixaDialeto::Molde`]. Post-lift the
6557 // two arms route through the lifted
6558 // [`crate::dialeto::CaixaDialeto::is_molde_family`] (e9d2315)
6559 // typed predicate — the same predicate the pre-lift `foreign =>`
6560 // wildcard resolved to today — and this pin makes the
6561 // positional-arity arm's byte-shape at the gate explicit rather
6562 // than implied by wildcard-absorption. A future regression that
6563 // silently reordered [`crate::dialeto::CaixaDialeto::is_molde_family`]'s
6564 // arm-set (dropped [`crate::dialeto::CaixaDialeto::MoldePosicional`]
6565 // from the two-arity closure) would fail this pin at caixa-core
6566 // test time rather than surfacing far from the change as a
6567 // `caixa.lisp` carrying a `(defcaixa todoku-go :ecosystem :go
6568 // …)` silently parsing past the derive.
6569 let src = r#"
6570 (defcaixa todoku-go
6571 :kind :Biblioteca
6572 :ecosystem :go
6573 :package {:name "todoku-go" :version "0.3.0"})
6574 "#;
6575 let err =
6576 Caixa::from_lisp(src).expect_err("MoldePosicional dialect must not parse as Pacote");
6577 match err {
6578 LeituraError::DialetoEstrangeiro { dialeto } => {
6579 assert_eq!(
6580 dialeto,
6581 crate::dialeto::CaixaDialeto::MoldePosicional,
6582 "DialetoEstrangeiro must carry the MoldePosicional \
6583 variant verbatim — the positional-arity `defmolde` \
6584 form under a `(defcaixa …)` head is the \
6585 `MoldePosicional` arm's canonical byte-shape"
6586 );
6587 let rendered = LeituraError::DialetoEstrangeiro { dialeto }.to_string();
6588 assert!(
6589 rendered.contains(dialeto.palavra_canonica()),
6590 "Display must interpolate `dialeto.palavra_canonica()` \
6591 verbatim on the MoldePosicional arm; rendered: \
6592 {rendered:?}"
6593 );
6594 assert!(
6595 rendered.contains(dialeto.consumidor()),
6596 "Display must interpolate `dialeto.consumidor()` \
6597 verbatim on the MoldePosicional arm; rendered: \
6598 {rendered:?}"
6599 );
6600 assert!(
6601 rendered.contains(dialeto.descricao()),
6602 "Display must interpolate `dialeto.descricao()` \
6603 verbatim on the MoldePosicional arm; rendered: \
6604 {rendered:?}"
6605 );
6606 }
6607 other => panic!("expected DialetoEstrangeiro, got {other:?}"),
6608 }
6609 }
6610
6611 #[test]
6612 fn from_lisp_dialect_gate_dispatches_through_caixa_dialeto_is_molde_family_predicate() {
6613 // Load-bearing byte-parity pin: for every arm in
6614 // [`crate::dialeto::CaixaDialeto::ALL`], the
6615 // [`Caixa::from_lisp`] foreign-dialect gate's DialetoEstrangeiro
6616 // partition must agree with the lifted
6617 // [`crate::dialeto::CaixaDialeto::is_molde_family`] (e9d2315)
6618 // typed predicate — i.e. from_lisp raises
6619 // [`LeituraError::DialetoEstrangeiro`] carrying `d` iff
6620 // `d.is_molde_family()` returns `true`, and does NOT raise
6621 // [`LeituraError::DialetoEstrangeiro`] on any arm where the
6622 // predicate returns `false` (the arm's source falls through to
6623 // the derive — parses cleanly on
6624 // [`crate::dialeto::CaixaDialeto::Pacote`], surfaces a
6625 // [`LeituraError::Leitura`] on
6626 // [`crate::dialeto::CaixaDialeto::Desconhecido`]).
6627 //
6628 // Pre-lift the gate hand-rolled a three-arm match
6629 // (`Pacote => {}`, `Desconhecido => {}`, `foreign => Err(…)`)
6630 // whose `foreign =>` wildcard expressed no compile-time link
6631 // back to the substrate primitive's arm-family; a future fifth
6632 // dialect the [`crate::dialeto`] module doc's "third dialect"
6633 // hazard actualises would fall silently onto the wildcard
6634 // regardless of whether it belonged to the `defmolde` family or
6635 // to a distinct `defcaixa`-family. Post-lift the partition
6636 // resolves through
6637 // [`crate::dialeto::CaixaDialeto::is_molde_family`]'s single
6638 // typed dispatch, and this pin refuses any future regression
6639 // that silently split the from_lisp partition from the typed
6640 // predicate — the two paths now migrate as one on any future
6641 // arm addition.
6642 //
6643 // Sibling in shape to the peer
6644 // [`crate::dialeto::tests::caixa_dialeto_is_molde_family_agrees_with_palavra_canonica_defmolde_projection`]
6645 // (e9d2315) that pins the same byte-parity between
6646 // [`crate::dialeto::CaixaDialeto::is_molde_family`] and the
6647 // sibling [`crate::dialeto::CaixaDialeto::palavra_canonica`]
6648 // `== "defmolde"` classifier — extends the discipline from the
6649 // two paths within the [`crate::dialeto`] primitive onto the
6650 // third external consumer of the `defmolde`-family partition
6651 // (the [`Caixa::from_lisp`] gate that raises
6652 // [`LeituraError::DialetoEstrangeiro`]).
6653 let fixtures: &[(crate::dialeto::CaixaDialeto, &str)] = &[
6654 (
6655 crate::dialeto::CaixaDialeto::Pacote,
6656 r#"
6657 (defcaixa
6658 :nome "checkout"
6659 :versao "0.1.0"
6660 :kind Biblioteca
6661 :edicao "2026"
6662 :descricao "canonical Pacote source"
6663 :autores ()
6664 :etiquetas ()
6665 :deps ()
6666 :deps-dev ()
6667 :bibliotecas ("lib/checkout.lisp"))
6668 "#,
6669 ),
6670 (
6671 crate::dialeto::CaixaDialeto::Molde,
6672 r#"
6673 (defcaixa
6674 :name "base64"
6675 :kind :Biblioteca
6676 :ecosystem :rust-single-crate
6677 :package {:name "base64" :version "0.22.1"}
6678 :workflows [:auto-release])
6679 "#,
6680 ),
6681 (
6682 crate::dialeto::CaixaDialeto::MoldePosicional,
6683 r#"
6684 (defcaixa todoku-go
6685 :kind :Biblioteca
6686 :ecosystem :go
6687 :package {:name "todoku-go" :version "0.3.0"})
6688 "#,
6689 ),
6690 (
6691 crate::dialeto::CaixaDialeto::Desconhecido,
6692 r#"(defcaixa :licenca "MIT")"#,
6693 ),
6694 ];
6695
6696 // Coverage: every arm in [`crate::dialeto::CaixaDialeto::ALL`]
6697 // must appear in the fixture table so the pin's arm-set stays
6698 // synchronised with the enum's arm-set. Fails at test time if a
6699 // future fifth arm added to [`crate::dialeto::CaixaDialeto`]
6700 // (with a corresponding `is_molde_family` return) forgot to
6701 // extend this fixture table with a canonical source for the new
6702 // arm — the pin cannot cover an arm it has no source for.
6703 for &expected in crate::dialeto::CaixaDialeto::ALL {
6704 assert!(
6705 fixtures.iter().any(|(d, _)| *d == expected),
6706 "fixture table must carry a canonical source for every \
6707 CaixaDialeto arm; missing: {expected:?}"
6708 );
6709 }
6710
6711 for &(expected_dialect, src) in fixtures {
6712 let classified = crate::dialeto::classify(src.trim()).unwrap_or_else(|err| {
6713 panic!(
6714 "fixture source for {expected_dialect:?} must classify \
6715 cleanly, got err: {err:?}"
6716 )
6717 });
6718 assert_eq!(
6719 classified, expected_dialect,
6720 "fixture source for {expected_dialect:?} must classify as \
6721 {expected_dialect:?} (drift here defeats the byte-parity \
6722 pin below — a source labelled for one arm but classifying \
6723 as another would silently satisfy or violate the pin for \
6724 the wrong reason)"
6725 );
6726
6727 let outcome = Caixa::from_lisp(src);
6728 match (expected_dialect.is_molde_family(), &outcome) {
6729 (true, Err(LeituraError::DialetoEstrangeiro { dialeto })) => {
6730 assert_eq!(
6731 *dialeto, expected_dialect,
6732 "DialetoEstrangeiro must carry the same typed arm \
6733 the classifier returned — a drift here would let \
6734 from_lisp raise the error while pointing at the \
6735 wrong dialect (e.g. rejecting a \
6736 MoldePosicional source as Molde). arm: \
6737 {expected_dialect:?}"
6738 );
6739 }
6740 (true, other) => panic!(
6741 "arm {expected_dialect:?} has is_molde_family() = true \
6742 so from_lisp must raise DialetoEstrangeiro carrying \
6743 {expected_dialect:?}; got: {other:?}"
6744 ),
6745 (false, Err(LeituraError::DialetoEstrangeiro { dialeto })) => panic!(
6746 "arm {expected_dialect:?} has is_molde_family() = false \
6747 so from_lisp must NOT raise DialetoEstrangeiro; got \
6748 one carrying: {dialeto:?}. This means the typed \
6749 predicate and the from_lisp partition disagree on \
6750 this arm — exactly the drift this pin refuses."
6751 ),
6752 (false, _) => {
6753 // A non-molde arm's source falls through to the
6754 // derive: Pacote sources parse to Ok(_); Desconhecido
6755 // sources surface as LeituraError::Leitura from the
6756 // derive's own unknown-keyword rejection. Either
6757 // shape is acceptable here — the pin's promise is
6758 // narrower: "no DialetoEstrangeiro on
6759 // is_molde_family() == false".
6760 }
6761 }
6762 }
6763 }
6764
6765 // ── M2 typed-substrate slot tests (limits, behavior, upgrade-from, supervisor) ──
6766
6767 #[test]
6768 fn limits_round_trip_via_json() {
6769 use crate::LimitsSpec;
6770 use std::time::Duration;
6771 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6772 c.limits = Some(LimitsSpec {
6773 memory: Some(64 * 1024 * 1024),
6774 fuel: Some(1_000_000),
6775 wall_clock: Some(Duration::from_secs(30)),
6776 cpu: Some(500),
6777 });
6778 let json = serde_json::to_string(&c).unwrap();
6779 assert!(json.contains("\"limits\""));
6780 assert!(json.contains("\"64MiB\""));
6781 assert!(json.contains("\"30s\""));
6782 assert!(json.contains("\"500m\""));
6783 let back: Caixa = serde_json::from_str(&json).unwrap();
6784 assert_eq!(c.limits, back.limits);
6785 }
6786
6787 #[test]
6788 fn behavior_round_trip_via_json() {
6789 use crate::BehaviorSpec;
6790 use std::path::PathBuf;
6791 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6792 c.behavior = Some(BehaviorSpec {
6793 on_init: Some(PathBuf::from("lib/init.lisp")),
6794 on_call: Some(PathBuf::from("lib/handlers.lisp")),
6795 ..Default::default()
6796 });
6797 let json = serde_json::to_string(&c).unwrap();
6798 let back: Caixa = serde_json::from_str(&json).unwrap();
6799 assert_eq!(c.behavior, back.behavior);
6800 }
6801
6802 #[test]
6803 fn upgrade_from_round_trip_via_json() {
6804 use crate::{UpgradeFromEntry, UpgradeInstruction};
6805 use std::path::PathBuf;
6806 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6807 c.upgrade_from = vec![UpgradeFromEntry {
6808 from: "0.1.0".into(),
6809 instructions: vec![
6810 UpgradeInstruction::LoadModule {
6811 module: "demo".into(),
6812 },
6813 UpgradeInstruction::StateChange {
6814 script: PathBuf::from("lib/migrations/v01-to-v02.lisp"),
6815 },
6816 UpgradeInstruction::SoftPurge {
6817 module: "demo-old".into(),
6818 },
6819 ],
6820 }];
6821 let json = serde_json::to_string(&c).unwrap();
6822 let back: Caixa = serde_json::from_str(&json).unwrap();
6823 assert_eq!(c.upgrade_from, back.upgrade_from);
6824 }
6825
6826 #[test]
6827 fn supervisor_view_returns_typed_shape() {
6828 use crate::{ChildSpec, RestartPolicy, RestartStrategy};
6829 let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
6830 c.kind = CaixaKind::Supervisor;
6831 c.bibliotecas.clear();
6832 c.estrategia = Some(RestartStrategy::OneForOne);
6833 c.max_restarts = Some(5);
6834 c.restart_window = Some("60s".into());
6835 c.children = vec![ChildSpec {
6836 caixa: "worker".into(),
6837 versao: "^0.1".into(),
6838 restart: RestartPolicy::Permanent,
6839 }];
6840 let view = c.supervisor_view().expect("Supervisor kind has a view");
6841 assert_eq!(view.estrategia, RestartStrategy::OneForOne);
6842 assert_eq!(view.max_restarts, 5);
6843 assert_eq!(
6844 view.restart_window,
6845 Some(std::time::Duration::from_secs(60))
6846 );
6847 assert_eq!(view.children.len(), 1);
6848 view.validate().unwrap();
6849 }
6850
6851 #[test]
6852 fn supervisor_view_none_for_non_supervisor_kinds() {
6853 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6854 assert!(c.supervisor_view().is_none());
6855 }
6856
6857 #[test]
6858 fn declared_mesh_slots_empty_for_bare_caixa() {
6859 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6860 assert!(c.declared_mesh_slots().is_empty());
6861 }
6862
6863 #[test]
6864 fn declared_mesh_slots_reports_only_set_slots_in_canonical_order() {
6865 use crate::{Entrada, Membro};
6866 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6867 // Set a non-adjacent pair (:membros + :entrada) to pin that the
6868 // canonical declaration order is preserved regardless of which
6869 // subset is populated.
6870 c.membros = vec![Membro {
6871 caixa: "a".into(),
6872 versao: "^0.1".into(),
6873 }];
6874 c.entrada = Some(Entrada {
6875 host: "x.example.com".into(),
6876 para: "a".into(),
6877 paths: vec![],
6878 port: 8080,
6879 });
6880 assert_eq!(
6881 c.declared_mesh_slots(),
6882 vec![
6883 crate::render::M3_AUTHOR_KEY_MEMBROS,
6884 crate::render::M3_AUTHOR_KEY_ENTRADA,
6885 ]
6886 );
6887 }
6888
6889 #[test]
6890 fn m3_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
6891 // Scalar-value pin: the five author-facing kebab-case labels the
6892 // `(defcaixa … :<slot> (…))` surface admits on the M3 top-level
6893 // mesh slot axis, one arm per typed slot. Mirrors the peer
6894 // scalar-value pin the sibling
6895 // [`crate::M2_AUTHOR_KEY_LIMITS`] /
6896 // [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
6897 // [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] M2 top-level slot consts
6898 // carry (f49c8b0), so both altitudes of the typed-slot algebra
6899 // (per-Servico M2 + per-Aplicacao M3) share the same
6900 // "one canonical byte-string per arm" discipline. A future
6901 // rebrand (`:membros` → `:members`, `:contratos` → `:contracts`,
6902 // `:politicas` → `:policies`, `:placement` → `:distribution`,
6903 // `:entrada` → `:ingress`) lands as an edit to exactly one const,
6904 // and every consumer that reaches for the label picks it up at
6905 // build time rather than at runtime as a downstream mismatch.
6906 assert_eq!(crate::render::M3_AUTHOR_KEY_MEMBROS, ":membros");
6907 assert_eq!(crate::render::M3_AUTHOR_KEY_CONTRATOS, ":contratos");
6908 assert_eq!(crate::render::M3_AUTHOR_KEY_POLITICAS, ":politicas");
6909 assert_eq!(crate::render::M3_AUTHOR_KEY_PLACEMENT, ":placement");
6910 assert_eq!(crate::render::M3_AUTHOR_KEY_ENTRADA, ":entrada");
6911 }
6912
6913 #[test]
6914 fn declared_mesh_slots_route_through_lifted_m3_author_key_consts() {
6915 // Production-through-const pin: the five per-arm labels the
6916 // [`Caixa::declared_mesh_slots`] tagger pushes onto its return
6917 // `Vec` route through the lifted
6918 // [`crate::M3_AUTHOR_KEY_MEMBROS`] /
6919 // [`crate::M3_AUTHOR_KEY_CONTRATOS`] /
6920 // [`crate::M3_AUTHOR_KEY_POLITICAS`] /
6921 // [`crate::M3_AUTHOR_KEY_PLACEMENT`] /
6922 // [`crate::M3_AUTHOR_KEY_ENTRADA`] consts, in canonical
6923 // declaration order. A future re-order or drift at the tagger
6924 // (a rename that reaches the tagger but not the const, or vice
6925 // versa) surfaces here at build time rather than at runtime as
6926 // a [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
6927 // `slots: <stale-kebab-case>` diagnostic far from the rename's
6928 // commit. Mirror of the peer
6929 // [`declared_servico_slots_route_through_lifted_m2_author_key_consts`]
6930 // pin (f49c8b0) on the sibling per-Servico M2 top-level slot
6931 // axis.
6932 use crate::{Entrada, Membro, MeshPolicy, Placement, PlacementStrategy, WitContract};
6933 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6934 c.membros = vec![Membro {
6935 caixa: "a".into(),
6936 versao: "^0.1".into(),
6937 }];
6938 c.contratos = vec![WitContract {
6939 de: "a".into(),
6940 para: "a".into(),
6941 wit: "wasi:http/proxy".into(),
6942 endpoint: Some("/x".into()),
6943 subject: None,
6944 slot: None,
6945 }];
6946 c.politicas = Some(MeshPolicy::default());
6947 c.placement = Some(Placement {
6948 estrategia: PlacementStrategy::Replicated,
6949 clusters: vec!["rio".into()],
6950 affinity: None,
6951 shard_key: None,
6952 });
6953 c.entrada = Some(Entrada {
6954 host: "x.example.com".into(),
6955 para: "a".into(),
6956 paths: vec![],
6957 port: 8080,
6958 });
6959 assert_eq!(
6960 c.declared_mesh_slots(),
6961 vec![
6962 crate::render::M3_AUTHOR_KEY_MEMBROS,
6963 crate::render::M3_AUTHOR_KEY_CONTRATOS,
6964 crate::render::M3_AUTHOR_KEY_POLITICAS,
6965 crate::render::M3_AUTHOR_KEY_PLACEMENT,
6966 crate::render::M3_AUTHOR_KEY_ENTRADA,
6967 ]
6968 );
6969 }
6970
6971 #[test]
6972 fn declared_supervisor_slots_empty_for_bare_caixa() {
6973 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6974 assert!(c.declared_supervisor_slots().is_empty());
6975 }
6976
6977 #[test]
6978 fn declared_supervisor_slots_reports_only_set_slots_in_canonical_order() {
6979 use crate::RestartStrategy;
6980 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6981 // Set a non-adjacent pair (:estrategia + :restart-window) to pin
6982 // that the canonical declaration order is preserved regardless
6983 // of which subset is populated.
6984 c.estrategia = Some(RestartStrategy::OneForOne);
6985 c.restart_window = Some("60s".into());
6986 assert_eq!(
6987 c.declared_supervisor_slots(),
6988 vec![
6989 crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
6990 crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
6991 ]
6992 );
6993 }
6994
6995 #[test]
6996 fn supervisor_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
6997 // Scalar-value pin: the four author-facing kebab-case labels the
6998 // `(defcaixa … :<slot> (…))` surface admits on the Supervisor
6999 // supervision-tree slot axis, one arm per typed slot. Mirrors the
7000 // peer scalar-value pins the sibling
7001 // [`crate::render::M2_AUTHOR_KEY_LIMITS`] /
7002 // [`crate::render::M2_AUTHOR_KEY_BEHAVIOR`] /
7003 // [`crate::render::M2_AUTHOR_KEY_UPGRADE_FROM`] top-level M2 slot
7004 // consts and [`crate::render::M3_AUTHOR_KEY_MEMBROS`] etc.
7005 // top-level M3 slot consts carry, so all three kind-scoped
7006 // typed-slot-family author-facing-label axes route through one
7007 // canonical per-arm declaration. A future rebrand
7008 // (`:estrategia` → `:strategy` for English uniformity,
7009 // `:max-restarts` → `:max-intensity` matching Erlang/OTP's
7010 // `MaxIntensity` name, `:restart-window` → `:period` matching
7011 // OTP's `Period` name, `:children` → `:workers` matching Elixir
7012 // idiom) lands as an edit to exactly one const, and every
7013 // consumer that reaches for the label picks it up at build time
7014 // rather than at runtime as a downstream mismatch.
7015 assert_eq!(
7016 crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
7017 ":estrategia"
7018 );
7019 assert_eq!(
7020 crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS,
7021 ":max-restarts"
7022 );
7023 assert_eq!(
7024 crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
7025 ":restart-window"
7026 );
7027 assert_eq!(crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN, ":children");
7028 }
7029
7030 #[test]
7031 fn declared_supervisor_slots_route_through_lifted_supervisor_author_key_consts() {
7032 // Production-through-const pin: the four per-arm labels the
7033 // [`Caixa::declared_supervisor_slots`] tagger pushes onto its
7034 // return `Vec` route through the lifted
7035 // [`crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA`] /
7036 // [`crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS`] /
7037 // [`crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW`] /
7038 // [`crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN`] consts, in
7039 // canonical declaration order. A future re-order or drift at the
7040 // tagger (a rename that reaches the tagger but not the const, or
7041 // vice versa) surfaces here at build time rather than at runtime
7042 // as a [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
7043 // `slots: <stale-kebab-case>` diagnostic far from the rename's
7044 // commit. Mirror of the peer
7045 // [`declared_servico_slots_route_through_lifted_m2_author_key_consts`]
7046 // (f49c8b0) and
7047 // [`declared_mesh_slots_route_through_lifted_m3_author_key_consts`]
7048 // (882f498) pins on the sibling M2 / M3 top-level slot axes.
7049 use crate::{ChildSpec, RestartPolicy, RestartStrategy};
7050 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7051 c.estrategia = Some(RestartStrategy::OneForOne);
7052 c.max_restarts = Some(5);
7053 c.restart_window = Some("60s".into());
7054 c.children = vec![ChildSpec {
7055 caixa: "worker".into(),
7056 versao: "^0.1".into(),
7057 restart: RestartPolicy::Permanent,
7058 }];
7059 assert_eq!(
7060 c.declared_supervisor_slots(),
7061 vec![
7062 crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
7063 crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS,
7064 crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
7065 crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN,
7066 ]
7067 );
7068 }
7069
7070 #[test]
7071 fn declared_servico_slots_empty_for_bare_caixa() {
7072 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7073 assert!(c.declared_servico_slots().is_empty());
7074 }
7075
7076 #[test]
7077 fn declared_servico_slots_reports_only_set_slots_in_canonical_order() {
7078 use crate::{UpgradeFromEntry, UpgradeInstruction};
7079 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7080 // Set a non-adjacent pair (:limits + :upgrade-from) to pin that
7081 // the canonical declaration order is preserved regardless of
7082 // which subset is populated.
7083 c.limits = Some(crate::LimitsSpec {
7084 fuel: Some(1_000_000),
7085 ..Default::default()
7086 });
7087 c.upgrade_from = vec![UpgradeFromEntry {
7088 from: "0.1.0".into(),
7089 instructions: vec![UpgradeInstruction::Restart],
7090 }];
7091 assert_eq!(
7092 c.declared_servico_slots(),
7093 vec![
7094 crate::render::M2_AUTHOR_KEY_LIMITS,
7095 crate::render::M2_AUTHOR_KEY_UPGRADE_FROM,
7096 ]
7097 );
7098 }
7099
7100 #[test]
7101 fn m2_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
7102 // Scalar-value pin: the three author-facing kebab-case labels
7103 // the `(defcaixa … :<slot> (…))` surface admits on the M2
7104 // top-level slot axis, one arm per typed slot. Mirrors the peer
7105 // scalar-value pin the sibling renderer-side
7106 // [`crate::M2_KEY_LIMITS`] / [`crate::M2_KEY_BEHAVIOR`] /
7107 // [`crate::M2_KEY_UPGRADE_FROM`] camelCase overlay-container
7108 // consts carry, so both halves of the M2 top-level slot dual
7109 // axis (author-facing kebab-case label + renderer-side
7110 // camelCase overlay-container wire key) route through one
7111 // canonical per-arm declaration. A future rebrand
7112 // (`:limits` → `:sandbox` matching Lunatic per-process
7113 // terminology INSPIRATIONS §III.1, `:behavior` → `:gen-server`
7114 // matching Erlang's verbatim name, `:upgrade-from` → `:appup`
7115 // matching Erlang's verbatim appup name) lands as an edit to
7116 // exactly one const, and every consumer that reaches for the
7117 // label picks it up at build time rather than at runtime as a
7118 // downstream mismatch.
7119 assert_eq!(crate::render::M2_AUTHOR_KEY_LIMITS, ":limits");
7120 assert_eq!(crate::render::M2_AUTHOR_KEY_BEHAVIOR, ":behavior");
7121 assert_eq!(crate::render::M2_AUTHOR_KEY_UPGRADE_FROM, ":upgrade-from");
7122 }
7123
7124 #[test]
7125 fn declared_servico_slots_route_through_lifted_m2_author_key_consts() {
7126 // Production-through-const pin: the three per-arm labels the
7127 // [`Caixa::declared_servico_slots`] tagger pushes onto its
7128 // return `Vec` route through the lifted
7129 // [`crate::M2_AUTHOR_KEY_LIMITS`] /
7130 // [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
7131 // [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] consts, in canonical
7132 // declaration order. A future re-order or drift at the tagger
7133 // (a rename that reaches the tagger but not the const, or vice
7134 // versa) surfaces here at build time rather than at runtime as
7135 // a [`crate::LayoutError::ServicoSlotsOnNonServico`]
7136 // `slots: <stale-kebab-case>` diagnostic far from the rename's
7137 // commit. Mirror of the peer
7138 // [`crate::behavior::BehaviorSpec::declared_slots`] production
7139 // tagger pin (889dc18) on the sibling per-callback axis.
7140 use crate::{BehaviorSpec, UpgradeFromEntry, UpgradeInstruction};
7141 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7142 c.limits = Some(crate::LimitsSpec {
7143 fuel: Some(1_000_000),
7144 ..Default::default()
7145 });
7146 c.behavior = Some(BehaviorSpec {
7147 on_init: Some(PathBuf::from("lib/init.lisp")),
7148 ..Default::default()
7149 });
7150 c.upgrade_from = vec![UpgradeFromEntry {
7151 from: "0.1.0".into(),
7152 instructions: vec![UpgradeInstruction::Restart],
7153 }];
7154 assert_eq!(
7155 c.declared_servico_slots(),
7156 vec![
7157 crate::render::M2_AUTHOR_KEY_LIMITS,
7158 crate::render::M2_AUTHOR_KEY_BEHAVIOR,
7159 crate::render::M2_AUTHOR_KEY_UPGRADE_FROM,
7160 ]
7161 );
7162 }
7163
7164 #[test]
7165 fn existing_manifests_unaffected_by_new_optional_slots() {
7166 // Regression test: a caixa.lisp authored before M2 typed slots
7167 // should still parse + serialize cleanly. The bare `defcaixa`
7168 // emitted by `Caixa::template` has none of the new fields.
7169 let src = Caixa::template("legacy");
7170 let c = Caixa::from_lisp(&src).unwrap();
7171 assert!(c.limits.is_none());
7172 assert!(c.behavior.is_none());
7173 assert!(c.upgrade_from.is_empty());
7174 assert!(c.estrategia.is_none());
7175 assert!(c.children.is_empty());
7176
7177 // And to_lisp emits a manifest with the new slots in the
7178 // empty/default state — round-trippable.
7179 let emitted = c.to_lisp();
7180 let back = Caixa::from_lisp(&emitted).unwrap();
7181 assert_eq!(c, back);
7182 }
7183
7184 #[test]
7185 fn validate_deps_accepts_canonical_caixa() {
7186 // Positive control: the bare template — zero deps, zero
7187 // deps_dev — passes the gate trivially. A future axis added to
7188 // `Dep::validate` mustn't regress an empty-deps caixa to a
7189 // build error.
7190 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7191 c.validate_deps().unwrap();
7192 }
7193
7194 #[test]
7195 fn validate_deps_rejects_invalid_versao_in_deps() {
7196 // Fail-before-pass-after pin: a malformed `:deps :versao`
7197 // surfaces at validate_deps() time, not at lacre-resolve time.
7198 // Mirrors `rejects_invalid_membro_versao_requirement` and
7199 // `validate_rejects_invalid_child_versao_requirement` on the
7200 // other two `:versao` axes.
7201 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7202 c.deps = vec![Dep::simple("caixa-teia", "^bad-version")];
7203 let err = c.validate_deps().unwrap_err();
7204 assert!(
7205 matches!(
7206 err,
7207 crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
7208 if nome == "caixa-teia" && versao == "^bad-version"
7209 ),
7210 "got {err:?}"
7211 );
7212 }
7213
7214 #[test]
7215 fn validate_deps_rejects_invalid_versao_in_deps_dev() {
7216 // Parity pin: `:deps-dev` must run through the same per-entry
7217 // validator as `:deps` — a typo in either axis surfaces the
7218 // same diagnostic. Without this leg, `:deps-dev` would be a
7219 // second-class citizen of the typed surface and an author
7220 // could land a build that passes validate_deps but fails at
7221 // `feira lock`-time when the dev-dep is resolved for a test
7222 // build.
7223 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7224 c.deps_dev = vec![Dep::simple("tatara-check", "^^0.1")];
7225 let err = c.validate_deps().unwrap_err();
7226 assert!(
7227 matches!(
7228 err,
7229 crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
7230 if nome == "tatara-check" && versao == "^^0.1"
7231 ),
7232 "got {err:?}"
7233 );
7234 }
7235
7236 #[test]
7237 fn validate_deps_runs_deps_before_deps_dev() {
7238 // Order pin: when both lists carry typos, the `:deps`
7239 // diagnostic surfaces first. The author's mental model is
7240 // "runtime deps are load-bearing; dev deps are scaffolding";
7241 // surfacing the runtime axis first matches that hierarchy.
7242 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7243 c.deps = vec![Dep::simple("runtime-dep", "^bad-runtime")];
7244 c.deps_dev = vec![Dep::simple("dev-dep", "^bad-dev")];
7245 let err = c.validate_deps().unwrap_err();
7246 assert!(
7247 matches!(
7248 err,
7249 crate::dep::DepError::VersaoInvalid { ref nome, .. }
7250 if nome == "runtime-dep"
7251 ),
7252 "expected `:deps` typo to surface first, got {err:?}"
7253 );
7254 }
7255
7256 #[test]
7257 fn validate_deps_accepts_canonical_versao_forms_in_both_lists() {
7258 // Positive control sweep across both lists. Pin every
7259 // canonical Cargo-shaped form so a future tightening of the
7260 // accepted set surfaces here as a test failure (parity with
7261 // `accepts_canonical_membro_versao_forms` and
7262 // `validate_accepts_canonical_child_versao_forms`).
7263 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7264 c.deps = vec![
7265 Dep::simple("caret", "^0.1"),
7266 Dep::simple("tilde", "~0.1.2"),
7267 Dep::simple("exact", "0.1.0"),
7268 Dep::simple("wildcard", "*"),
7269 Dep::simple("multi-range", ">=0.1, <2"),
7270 ];
7271 c.deps_dev = vec![
7272 Dep::simple("dev-caret", "^0.1"),
7273 Dep::simple("dev-wildcard", "*"),
7274 ];
7275 c.validate_deps().unwrap();
7276 }
7277
7278 #[test]
7279 fn validate_deps_diagnostic_carries_offending_dep() {
7280 // Diagnostic-shape pin: the error names the offending entry's
7281 // `:nome` + `:versao` verbatim and carries a non-empty
7282 // `reason` from `semver::VersionReq::parse`, so a `feira lint`
7283 // run can render the diagnostic without re-parsing.
7284 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7285 c.deps = vec![Dep::simple("caixa-teia", "not-a-req")];
7286 let err = c.validate_deps().unwrap_err();
7287 let crate::dep::DepError::VersaoInvalid {
7288 nome,
7289 versao,
7290 reason,
7291 } = err
7292 else {
7293 panic!("expected VersaoInvalid, got other variant");
7294 };
7295 assert_eq!(nome, "caixa-teia");
7296 assert_eq!(versao, "not-a-req");
7297 assert!(
7298 !reason.is_empty(),
7299 "VersaoInvalid `reason` must carry the parser's wording verbatim"
7300 );
7301 }
7302
7303 #[test]
7304 fn validate_deps_rejects_ambiguous_fonte_in_deps_dev() {
7305 // Cross-axis pin: `validate_deps` walks both :deps and
7306 // :deps-dev through `Dep::validate`, and the new fonte gate
7307 // (`:tag` + `:branch` both set — the canonical "pin drift"
7308 // footgun) must surface from the :deps-dev arm with the
7309 // offending entry's :nome named. Pin the :deps-dev arm
7310 // explicitly so a future shortcut that only walks :deps
7311 // surfaces here as a regression.
7312 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7313 c.deps_dev = vec![Dep {
7314 nome: "dev-only".into(),
7315 versao: "^0.1".into(),
7316 fonte: Some(crate::DepSource::Git {
7317 repo: "github:p/x".into(),
7318 tag: Some("v1".into()),
7319 rev: None,
7320 branch: Some("main".into()),
7321 }),
7322 opcional: false,
7323 caracteristicas: vec![],
7324 }];
7325 let err = c.validate_deps().unwrap_err();
7326 let crate::dep::DepError::FontePinAmbiguous { nome, pins } = err else {
7327 panic!("expected FontePinAmbiguous from :deps-dev walk");
7328 };
7329 assert_eq!(nome, "dev-only");
7330 assert!(pins.contains(":tag") && pins.contains(":branch"));
7331 }
7332
7333 #[test]
7334 fn validate_deps_rejects_empty_repo_in_deps() {
7335 // Parity pin on the :deps arm: an empty :repo on the runtime
7336 // deps list surfaces the same FonteRepoEmpty diagnostic the
7337 // dep.rs per-entry tests pin, naming the offending entry.
7338 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7339 c.deps = vec![Dep {
7340 nome: "runtime".into(),
7341 versao: "^0.1".into(),
7342 fonte: Some(crate::DepSource::Git {
7343 repo: String::new(),
7344 tag: Some("v1".into()),
7345 rev: None,
7346 branch: None,
7347 }),
7348 opcional: false,
7349 caracteristicas: vec![],
7350 }];
7351 let err = c.validate_deps().unwrap_err();
7352 assert!(
7353 matches!(
7354 err,
7355 crate::dep::DepError::FonteRepoEmpty { ref nome }
7356 if nome == "runtime"
7357 ),
7358 "got {err:?}"
7359 );
7360 }
7361
7362 // ── validate_deps: within-list :nome set-not-multiset gate ─────────
7363
7364 #[test]
7365 fn validate_deps_rejects_duplicate_nome_in_deps() {
7366 // Fail-before-pass-after pin: two `:deps` entries naming the same
7367 // caixa carry two `:versao` / `:fonte` / feature triples that the
7368 // caixa-resolver's lacre pipeline collapses (the second silently
7369 // overwrites the first at `concrete_versao`-resolve time). The
7370 // gate surfaces the duplicate at validate-time, naming the
7371 // offending caixa + the list, before the resolver-side silent
7372 // drop. Mirrors the peer typed-graph duplicate gates
7373 // (`DuplicateChildCaixa`, `MembroDuplicate`, `DuplicateFrom`, …).
7374 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7375 c.deps = vec![
7376 Dep::simple("caixa-teia", "^0.1"),
7377 Dep::simple("caixa-teia", "^0.2"),
7378 ];
7379 let err = c.validate_deps().unwrap_err();
7380 assert!(
7381 matches!(
7382 err,
7383 crate::dep::DepError::DuplicateNome { ref nome, list }
7384 if nome == "caixa-teia" && list == crate::render::DEP_AUTHOR_KEY_DEPS
7385 ),
7386 "got {err:?}"
7387 );
7388 }
7389
7390 #[test]
7391 fn validate_deps_rejects_duplicate_nome_in_deps_dev() {
7392 // Parity pin: `:deps-dev` runs through the same per-list
7393 // duplicate check as `:deps` — neither axis is a second-class
7394 // citizen of the set-not-multiset discipline.
7395 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7396 c.deps_dev = vec![
7397 Dep::simple("tatara-check", "*"),
7398 Dep::simple("tatara-check", "^0.1"),
7399 ];
7400 let err = c.validate_deps().unwrap_err();
7401 assert!(
7402 matches!(
7403 err,
7404 crate::dep::DepError::DuplicateNome { ref nome, list }
7405 if nome == "tatara-check" && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
7406 ),
7407 "got {err:?}"
7408 );
7409 }
7410
7411 #[test]
7412 fn validate_deps_accepts_cross_list_same_nome() {
7413 // The Cargo `[dependencies]` + `[dev-dependencies]` override
7414 // convention is preserved: a name appearing in *both* lists is
7415 // valid (the dev-pin overrides at test/dev time). Only
7416 // within-list duplicates are structurally incoherent — pin the
7417 // permissive cross-list semantics so a future shortcut that
7418 // collapses the two seen-sets into one surfaces here as a test
7419 // failure.
7420 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7421 c.deps = vec![Dep::simple("caixa-teia", "^0.1")];
7422 c.deps_dev = vec![Dep::simple("caixa-teia", "^0.2")];
7423 c.validate_deps().unwrap();
7424 }
7425
7426 #[test]
7427 fn validate_deps_accepts_distinct_nome_in_both_lists() {
7428 // Positive control: distinct names within each list pass — the
7429 // gate's identity element on the canonical authoring shape.
7430 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7431 c.deps = vec![
7432 Dep::simple("caixa-teia", "^0.1"),
7433 Dep::simple("pleme-mesh", "*"),
7434 ];
7435 c.deps_dev = vec![
7436 Dep::simple("tatara-check", "*"),
7437 Dep::simple("dev-shim", "^0.1"),
7438 ];
7439 c.validate_deps().unwrap();
7440 }
7441
7442 #[test]
7443 fn validate_deps_per_entry_validate_fires_before_duplicate_in_deps() {
7444 // Diagnostic-precedence pin: a malformed `:versao` on the
7445 // duplicating entry surfaces its narrower `VersaoInvalid`
7446 // diagnostic first, before the cross-entry duplicate gate fires
7447 // — the canonical "per-entry shape before cross-entry uniqueness"
7448 // precedence every peer set-not-multiset gate establishes
7449 // (`*_invalid_fires_before_duplicate_check` pins on
7450 // `SupervisorSpec::validate`, `AplicacaoSpec::validate_membros`,
7451 // `validate_upgrade_from`).
7452 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7453 c.deps = vec![
7454 Dep::simple("caixa-teia", "^0.1"),
7455 Dep::simple("caixa-teia", "^bad-version"),
7456 ];
7457 let err = c.validate_deps().unwrap_err();
7458 assert!(
7459 matches!(
7460 err,
7461 crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
7462 if nome == "caixa-teia" && versao == "^bad-version"
7463 ),
7464 "expected VersaoInvalid to surface before DuplicateNome, got {err:?}"
7465 );
7466 }
7467
7468 #[test]
7469 fn validate_deps_duplicate_diagnostic_names_first_collision() {
7470 // First-collision determinism pin: with three entries naming the
7471 // same caixa, the first colliding pair surfaces — not the last.
7472 // Mirrors the peer first-collision posture on every
7473 // duplicate-target gate
7474 // (`validate_upgrade_from_duplicate_diagnostic_names_second_collision`
7475 // — the second entry is the first collision; this gate uses the
7476 // same shape: the second entry's `:nome` lands in the diagnostic
7477 // because `seen.insert(first.nome)` already populated the set).
7478 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7479 c.deps = vec![
7480 Dep::simple("caixa-teia", "^0.1"),
7481 Dep::simple("caixa-teia", "^0.2"),
7482 Dep::simple("caixa-teia", "^0.3"),
7483 ];
7484 let err = c.validate_deps().unwrap_err();
7485 // The diagnostic carries the offending caixa name; the
7486 // implementation surfaces on the *second* entry (the first
7487 // collision), so the test pins the `:nome` value.
7488 assert!(
7489 matches!(
7490 err,
7491 crate::dep::DepError::DuplicateNome { ref nome, list }
7492 if nome == "caixa-teia" && list == crate::render::DEP_AUTHOR_KEY_DEPS
7493 ),
7494 "got {err:?}"
7495 );
7496 }
7497
7498 #[test]
7499 fn validate_deps_duplicate_in_deps_fires_before_duplicate_in_deps_dev() {
7500 // Cross-list precedence pin: when both lists carry duplicates,
7501 // the `:deps` diagnostic surfaces first — same author-mental-
7502 // model ordering the `validate_deps_runs_deps_before_deps_dev`
7503 // pin establishes for malformed `:versao` (runtime axis before
7504 // dev axis).
7505 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7506 c.deps = vec![
7507 Dep::simple("runtime-dep", "^0.1"),
7508 Dep::simple("runtime-dep", "^0.2"),
7509 ];
7510 c.deps_dev = vec![Dep::simple("dev-dep", "*"), Dep::simple("dev-dep", "^0.1")];
7511 let err = c.validate_deps().unwrap_err();
7512 assert!(
7513 matches!(
7514 err,
7515 crate::dep::DepError::DuplicateNome { ref nome, list }
7516 if nome == "runtime-dep" && list == crate::render::DEP_AUTHOR_KEY_DEPS
7517 ),
7518 "expected :deps duplicate to surface before :deps-dev duplicate, got {err:?}"
7519 );
7520 }
7521
7522 #[test]
7523 fn validate_deps_empty_lists_pass_duplicate_gate() {
7524 // Empty-set identity pin: the bare template (zero deps, zero
7525 // deps_dev) passes the duplicate gate as the gate's identity
7526 // element. A future tighten that conflates "empty" with
7527 // "missing" would regress this baseline.
7528 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7529 c.validate_deps().unwrap();
7530 }
7531
7532 #[test]
7533 fn validate_deps_duplicate_diagnostic_carries_list_tag() {
7534 // Diagnostic-shape pin: the `list:` field tags which list the
7535 // duplicate landed in (`:deps` vs `:deps-dev`) verbatim, so a
7536 // `feira lint` run can route the author to the right block in
7537 // their caixa.lisp without re-deriving the list from context.
7538 // Same self-locating shape every peer per-axis diagnostic
7539 // already exposes.
7540 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7541 c.deps_dev = vec![
7542 Dep::simple("dev-thing", "*"),
7543 Dep::simple("dev-thing", "^0.1"),
7544 ];
7545 let err = c.validate_deps().unwrap_err();
7546 let crate::dep::DepError::DuplicateNome { nome, list } = err else {
7547 panic!("expected DuplicateNome from :deps-dev walk");
7548 };
7549 assert_eq!(nome, "dev-thing");
7550 assert_eq!(list, crate::render::DEP_AUTHOR_KEY_DEPS_DEV);
7551 }
7552
7553 // ── validate_deps: per-entry :caracteristicas set-discipline gate ──
7554
7555 #[test]
7556 fn validate_deps_surfaces_caracteristicas_duplicate_in_deps_list() {
7557 // Thread-through pin on `:deps`: the per-entry
7558 // `Dep::validate_caracteristicas` gate fires inside
7559 // `Caixa::validate_deps`'s linear walk, so a malformed feature
7560 // list on any `:deps` entry surfaces as a `DepError` from
7561 // `validate_deps` — the same reachability shape every per-entry
7562 // `Dep::validate` arm threads through. Without this pin a future
7563 // shortcut that skips the per-entry `Dep::validate` call on the
7564 // cross-entry-uniqueness path would mask the within-entry
7565 // `:caracteristicas` gates.
7566 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7567 c.deps = vec![Dep {
7568 nome: "caixa-teia".into(),
7569 versao: "^0.1".into(),
7570 fonte: None,
7571 opcional: false,
7572 caracteristicas: vec!["http".into(), "http".into()],
7573 }];
7574 let err = c.validate_deps().unwrap_err();
7575 let crate::dep::DepError::CaracteristicaDuplicate {
7576 nome,
7577 caracteristica,
7578 } = err
7579 else {
7580 panic!("expected CaracteristicaDuplicate from :deps walk, got {err:?}");
7581 };
7582 assert_eq!(nome, "caixa-teia");
7583 assert_eq!(caracteristica, "http");
7584 }
7585
7586 #[test]
7587 fn validate_deps_surfaces_caracteristicas_empty_in_deps_dev_list() {
7588 // Peer thread-through pin on `:deps-dev`: same reachability as
7589 // the `:deps` arm above, on the dev-only authoring axis. Pins
7590 // that the `validate_deps` walk visits both lists' per-entry
7591 // gates uniformly. The empty-feature arm carries here so both
7592 // new `:caracteristicas` arms are surfaced via at least one
7593 // `validate_deps` thread-through.
7594 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7595 c.deps_dev = vec![Dep {
7596 nome: "caixa-teia".into(),
7597 versao: "^0.1".into(),
7598 fonte: None,
7599 opcional: false,
7600 caracteristicas: vec![String::new()],
7601 }];
7602 let err = c.validate_deps().unwrap_err();
7603 let crate::dep::DepError::CaracteristicaEmpty { nome } = err else {
7604 panic!("expected CaracteristicaEmpty from :deps-dev walk, got {err:?}");
7605 };
7606 assert_eq!(nome, "caixa-teia");
7607 }
7608
7609 #[test]
7610 fn validate_deps_surfaces_caracteristicas_invalid_in_deps_list() {
7611 // Thread-through pin on `:deps`: the per-entry
7612 // `Dep::validate_caracteristicas` value-shape gate (lifted via
7613 // `crate::render::is_cargo_feature_name`) fires inside
7614 // `Caixa::validate_deps`'s linear walk on the `:deps` list, so
7615 // a structurally invalid feature name on any `:deps` entry
7616 // surfaces as `DepError::CaracteristicaInvalid` from
7617 // `validate_deps` — the same reachability shape every per-entry
7618 // `Dep::validate` arm threads through. Without this pin a
7619 // future shortcut that skips the per-entry `Dep::validate` call
7620 // on the cross-entry-uniqueness path would mask the within-
7621 // entry `:caracteristicas` value-shape gate.
7622 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7623 c.deps = vec![Dep {
7624 nome: "caixa-teia".into(),
7625 versao: "^0.1".into(),
7626 fonte: None,
7627 opcional: false,
7628 caracteristicas: vec!["+http".into()],
7629 }];
7630 let err = c.validate_deps().unwrap_err();
7631 let crate::dep::DepError::CaracteristicaInvalid {
7632 nome,
7633 caracteristica,
7634 ..
7635 } = err
7636 else {
7637 panic!("expected CaracteristicaInvalid from :deps walk, got {err:?}");
7638 };
7639 assert_eq!(nome, "caixa-teia");
7640 assert_eq!(caracteristica, "+http");
7641 }
7642
7643 #[test]
7644 fn validate_deps_surfaces_caracteristicas_invalid_in_deps_dev_list() {
7645 // Peer thread-through pin on `:deps-dev`: same reachability as
7646 // the `:deps` arm above, on the dev-only authoring axis. The
7647 // `http/json` shape carries here so the segment-separator
7648 // diagnostic (the canonical Cargo `dep/feat` namespaced-dep
7649 // confusion footgun) is surfaced via the cross-entry walk too —
7650 // pinning that the `:deps-dev` list visits the same per-entry
7651 // value-shape gate as the `:deps` list.
7652 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7653 c.deps_dev = vec![Dep {
7654 nome: "caixa-teia".into(),
7655 versao: "^0.1".into(),
7656 fonte: None,
7657 opcional: false,
7658 caracteristicas: vec!["http/json".into()],
7659 }];
7660 let err = c.validate_deps().unwrap_err();
7661 let crate::dep::DepError::CaracteristicaInvalid {
7662 nome,
7663 caracteristica,
7664 ..
7665 } = err
7666 else {
7667 panic!("expected CaracteristicaInvalid from :deps-dev walk, got {err:?}");
7668 };
7669 assert_eq!(nome, "caixa-teia");
7670 assert_eq!(caracteristica, "http/json");
7671 }
7672
7673 #[test]
7674 fn to_lisp_preserves_deps() {
7675 let src = r#"
7676(defcaixa
7677 :nome "x"
7678 :versao "0.1.0"
7679 :kind Biblioteca
7680 :deps ((:nome "a" :versao "^0.1")
7681 (:nome "b" :versao "*" :fonte (:tipo git :repo "github:o/b" :tag "v1"))))
7682"#;
7683 let c1 = Caixa::from_lisp(src).unwrap();
7684 let emitted = c1.to_lisp();
7685 let c2 = Caixa::from_lisp(&emitted).expect("round trip");
7686 assert_eq!(c1.deps, c2.deps);
7687 }
7688
7689 // ── Caixa::validate_nome — top-level :nome value-shape gate ─────────
7690
7691 fn caixa_with_nome(nome: &str) -> Caixa {
7692 let mut c = Caixa::from_lisp(&Caixa::template("placeholder")).unwrap();
7693 c.nome = nome.to_string();
7694 c
7695 }
7696
7697 #[test]
7698 fn validate_nome_accepts_canonical_template() {
7699 // Positive control: the bare `feira init`-style template's
7700 // `:nome` ("demo") is a canonical DNS-1123 label; the gate must
7701 // not regress this baseline shape. A future tightening of the
7702 // accepted set surfaces here as a test failure first.
7703 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7704 c.validate_nome().unwrap();
7705 }
7706
7707 #[test]
7708 fn validate_nome_accepts_canonical_forms() {
7709 // Positive-set sweep: each realistic caixa-name shape the K8s
7710 // apiserver accepts as a `metadata.name` label must pass —
7711 // single-word, hyphen-joined, version-suffixed, single-char,
7712 // two-char, digit-start (DNS-1123 allows this; the stricter
7713 // DNS-1035 Service-name rule doesn't), version-suffix-bearing.
7714 // Mirrors `accepts_canonical_membro_caixa_forms` (3f9d7a0) on
7715 // the peer member-name axis.
7716 for nome in [
7717 "checkout",
7718 "cart-v2",
7719 "a",
7720 "db",
7721 "3rd-party-shim",
7722 "payment-retry",
7723 "0",
7724 ] {
7725 caixa_with_nome(nome)
7726 .validate_nome()
7727 .unwrap_or_else(|e| panic!("canonical :nome {nome:?} must validate, got {e:?}"));
7728 }
7729 }
7730
7731 #[test]
7732 fn validate_nome_rejects_empty() {
7733 // Fail-before-pass-after pin: `Caixa::from_lisp` does not refuse
7734 // an empty `:nome` (the derive macro stores the raw String);
7735 // the gate's empty arm names the offending axis with a narrower
7736 // diagnostic than the `NomeInvalid` parse arm would emit.
7737 let c = caixa_with_nome("");
7738 let err = c.validate_nome().unwrap_err();
7739 assert_eq!(err, ManifestError::NomeEmpty);
7740 }
7741
7742 #[test]
7743 fn validate_nome_rejects_uppercase() {
7744 // The canonical "I copied the TitleCase display name verbatim"
7745 // footgun. The K8s apiserver rejects `metadata.name: MyApp` at
7746 // admission on every derived artifact (Helm chart, ComputeUnit,
7747 // CNP, HTTPRoute, label values); the gate moves the diagnostic
7748 // to the source `caixa.lisp` and the reason suggests the
7749 // lowercased fix verbatim.
7750 let c = caixa_with_nome("MyApp");
7751 let err = c.validate_nome().unwrap_err();
7752 let ManifestError::NomeInvalid { nome, reason } = err else {
7753 panic!("expected NomeInvalid for uppercase :nome");
7754 };
7755 assert_eq!(nome, "MyApp");
7756 assert!(
7757 reason.contains("uppercase") && reason.contains("myapp"),
7758 "diagnostic must name the violation + the lowercased fix, got {reason:?}"
7759 );
7760 }
7761
7762 #[test]
7763 fn validate_nome_rejects_underscore() {
7764 // The Python-/Postgres-style `snake_case` leak. DNS-1123 forbids
7765 // `_`; the apiserver rejects on admission across every derived
7766 // artifact. Same fixture pinned for `:membros :caixa` (3f9d7a0)
7767 // and `:children :caixa` (31bfa43).
7768 let c = caixa_with_nome("my_app");
7769 let err = c.validate_nome().unwrap_err();
7770 assert!(
7771 matches!(
7772 err,
7773 ManifestError::NomeInvalid { ref nome, ref reason }
7774 if nome == "my_app" && reason.contains('_')
7775 ),
7776 "got {err:?}"
7777 );
7778 }
7779
7780 #[test]
7781 fn validate_nome_rejects_dot() {
7782 // A `:nome` is a single DNS-1123 label, not a subdomain. The
7783 // "I want to namespace with `.`" footgun the gate redirects to
7784 // `-` via the shared predicate's reason wording.
7785 let c = caixa_with_nome("team.app");
7786 let err = c.validate_nome().unwrap_err();
7787 assert!(
7788 matches!(
7789 err,
7790 ManifestError::NomeInvalid { ref nome, ref reason }
7791 if nome == "team.app" && reason.contains('.')
7792 ),
7793 "got {err:?}"
7794 );
7795 }
7796
7797 #[test]
7798 fn validate_nome_rejects_leading_hyphen() {
7799 // DNS-1123 boundary rule: the label must start with an ASCII
7800 // alphanumeric. Pin the leading-`-` arm explicitly.
7801 let c = caixa_with_nome("-app");
7802 let err = c.validate_nome().unwrap_err();
7803 assert!(
7804 matches!(
7805 err,
7806 ManifestError::NomeInvalid { ref nome, .. } if nome == "-app"
7807 ),
7808 "got {err:?}"
7809 );
7810 }
7811
7812 #[test]
7813 fn validate_nome_rejects_trailing_hyphen() {
7814 // Symmetric arm of the boundary rule, pinned separately so a
7815 // future relaxation that only checks the leading position
7816 // surfaces here. Mirrors `rejects_membro_caixa_with_trailing_hyphen`
7817 // and `_with_trailing_hyphen` on the supervisor / aplicacao
7818 // axes.
7819 let c = caixa_with_nome("app-");
7820 let err = c.validate_nome().unwrap_err();
7821 assert!(
7822 matches!(
7823 err,
7824 ManifestError::NomeInvalid { ref nome, .. } if nome == "app-"
7825 ),
7826 "got {err:?}"
7827 );
7828 }
7829
7830 #[test]
7831 fn validate_nome_rejects_unicode() {
7832 // IDN must be pre-encoded as Punycode (`xn--…`); raw Unicode
7833 // bytes are rejected by the K8s apiserver on every name axis.
7834 let c = caixa_with_nome("café");
7835 let err = c.validate_nome().unwrap_err();
7836 assert!(
7837 matches!(
7838 err,
7839 ManifestError::NomeInvalid { ref nome, .. } if nome == "café"
7840 ),
7841 "got {err:?}"
7842 );
7843 }
7844
7845 #[test]
7846 fn validate_nome_rejects_whitespace() {
7847 // The paste-from-sketch / paste-from-spec footgun. Internal
7848 // whitespace is rejected by every K8s name axis.
7849 let c = caixa_with_nome("my app");
7850 let err = c.validate_nome().unwrap_err();
7851 assert!(
7852 matches!(
7853 err,
7854 ManifestError::NomeInvalid { ref nome, .. } if nome == "my app"
7855 ),
7856 "got {err:?}"
7857 );
7858 }
7859
7860 #[test]
7861 fn validate_nome_rejects_too_long() {
7862 // 64-byte boundary pin: the K8s apiserver rejects any
7863 // `metadata.name` over 63 bytes at admission; the diagnostic
7864 // names both the 63-byte cap and the actual length so the
7865 // author can shorten in one edit. Mirrors `_too_long` on the
7866 // peer member-/cluster-/child-name axes.
7867 let over = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN + 1);
7868 let c = caixa_with_nome(&over);
7869 let err = c.validate_nome().unwrap_err();
7870 let ManifestError::NomeInvalid { nome, reason } = err else {
7871 panic!("expected NomeInvalid for over-cap :nome");
7872 };
7873 assert_eq!(nome.len(), crate::DNS_1123_LABEL_MAX_LEN + 1);
7874 assert!(
7875 reason.contains("63") && reason.contains("64"),
7876 "diagnostic must name the cap + actual length, got {reason:?}"
7877 );
7878 }
7879
7880 #[test]
7881 fn nome_max_length_validates() {
7882 // The 63-byte cap exactly — the boundary-accepting case pinned
7883 // alongside `validate_nome_rejects_too_long` so a future cap
7884 // shift surfaces both arms simultaneously. Mirrors
7885 // `membro_caixa_max_length_validates`,
7886 // `placement_cluster_max_length_validates`,
7887 // `child_caixa_max_length_validates`.
7888 let at_cap = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN);
7889 caixa_with_nome(&at_cap).validate_nome().unwrap();
7890 }
7891
7892 #[test]
7893 fn nome_empty_takes_precedence_over_invalid() {
7894 // Order pin: the empty arm fires before the predicate is
7895 // consulted. Empty < invalid in self-locating-ness — the
7896 // narrower `NomeEmpty` diagnostic doesn't carry a useless
7897 // `nome: ""` reference into the parser-shaped reason. Mirrors
7898 // `membro_caixa_empty_takes_precedence_over_invalid` on the
7899 // peer axis (3f9d7a0).
7900 let c = caixa_with_nome("");
7901 assert_eq!(c.validate_nome().unwrap_err(), ManifestError::NomeEmpty);
7902 }
7903
7904 #[test]
7905 fn nome_invalid_diagnostic_carries_offending_nome() {
7906 // Diagnostic-shape pin: the error names the offending `:nome`
7907 // verbatim with a non-empty parser-shaped reason, so a `feira
7908 // lint` run can render the diagnostic without re-parsing.
7909 // Mirrors `membro_caixa_invalid_diagnostic_carries_offending_caixa`.
7910 let c = caixa_with_nome("MyApp");
7911 let err = c.validate_nome().unwrap_err();
7912 let ManifestError::NomeInvalid { nome, reason } = err else {
7913 panic!("expected NomeInvalid variant");
7914 };
7915 assert_eq!(nome, "MyApp");
7916 assert!(
7917 !reason.is_empty(),
7918 "NomeInvalid `reason` must carry the predicate's wording verbatim"
7919 );
7920 }
7921
7922 // ── Caixa::validate_nome_chart_name_budget — joint-length on `:nome` ──
7923 //
7924 // The bare-`:nome` axis [`Caixa::validate_nome`] caps at 63 bytes
7925 // via DNS-1123; this second-axis gate caps the joint
7926 // `lareira-<nome>` chart name at the same 63-byte ceiling. The
7927 // canonical [`crate::lareira_chart_name`] helper's doc comment
7928 // (f7320d7, caixa-core/src/render.rs:3198) explicitly deferred:
7929 // "the M4 admission webhook will pin the joint-length invariant
7930 // when it lands". These tests pin it at the manifest-validate
7931 // layer instead, fail-before-pass-after on the 56-byte boundary.
7932
7933 #[test]
7934 fn validate_nome_chart_name_budget_accepts_canonical_template() {
7935 // Positive control: the bare `feira init`-style template's
7936 // `:nome` ("demo") sits far below the cap; the gate must not
7937 // regress this baseline. Same shape every peer
7938 // value-shape-gate baseline pin uses.
7939 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7940 c.validate_nome_chart_name_budget().unwrap();
7941 }
7942
7943 #[test]
7944 fn validate_nome_chart_name_budget_accepts_canonical_fixtures() {
7945 // Positive-set sweep across the canonical author surface every
7946 // in-tree fixture uses (`hello-rio`, `cart`, `checkout`,
7947 // `worker`, the `checkout-aplicacao` example members, the
7948 // `example-attest` caixa-tatara fixture). Every value sits
7949 // far below the 55-byte per-`:nome` budget. Same shape every
7950 // peer per-axis baseline pin uses.
7951 for nome in [
7952 "hello-rio",
7953 "cart",
7954 "checkout",
7955 "worker",
7956 "example-attest",
7957 "demo",
7958 "a",
7959 ] {
7960 caixa_with_nome(nome)
7961 .validate_nome_chart_name_budget()
7962 .unwrap_or_else(|e| {
7963 panic!("canonical :nome {nome:?} must pass chart-name budget, got {e:?}")
7964 });
7965 }
7966 }
7967
7968 #[test]
7969 fn validate_nome_chart_name_budget_accepts_nome_at_cap() {
7970 // Boundary-accepting case at the 55-byte per-`:nome` budget —
7971 // the joint chart name is exactly 63 bytes, the DNS-1123 label
7972 // cap. Pinned alongside the rejecting-arm test so a future cap
7973 // shift surfaces both arms simultaneously. Mirrors
7974 // `nome_max_length_validates` on the peer bare-`:nome` axis.
7975 let at_cap = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN);
7976 caixa_with_nome(&at_cap)
7977 .validate_nome_chart_name_budget()
7978 .unwrap();
7979 }
7980
7981 #[test]
7982 fn validate_nome_chart_name_budget_rejects_nome_one_over_cap() {
7983 // Fail-before-pass-after pin on the 56-byte boundary: the
7984 // smallest `:nome` length that overflows the joint chart-name
7985 // cap. The inner [`is_dns_1123_label`] gate
7986 // (`Caixa::validate_nome`) accepts it (56 ≤ 63), so prior to
7987 // this gate it silently passed the manifest-validate cascade
7988 // and surfaced as a `helm lint` / apiserver rejection on the
7989 // rendered chart name far from the source `caixa.lisp`, with
7990 // no field naming the overflow. With this gate the diagnostic
7991 // names the offending `:nome` verbatim alongside the rendered
7992 // chart name and the budget, so the author can shorten in one
7993 // edit. Mirrors `validate_nome_rejects_too_long` on the peer
7994 // bare-`:nome` axis.
7995 let over = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
7996 let c = caixa_with_nome(&over);
7997 let err = c.validate_nome_chart_name_budget().unwrap_err();
7998 let ManifestError::NomeChartNameBudgetExceeded { nome, reason } = err else {
7999 panic!("expected NomeChartNameBudgetExceeded for over-budget :nome");
8000 };
8001 assert_eq!(nome.len(), crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
8002 assert_eq!(nome, over);
8003 assert!(
8004 reason.contains("63") && reason.contains("64") && reason.contains("55"),
8005 "diagnostic must name the DNS-1123 cap (63), the actual chart-name length (64), \
8006 and the per-`:nome` budget (55), got {reason:?}"
8007 );
8008 }
8009
8010 #[test]
8011 fn validate_nome_chart_name_budget_rejects_nome_at_bare_dns_cap() {
8012 // The 63-byte `:nome` boundary — passes the bare-`:nome`
8013 // [`is_dns_1123_label`] cap exactly, but produces a 71-byte
8014 // joint chart name that overflows the DNS-1123 label cap
8015 // structurally. The most stringent fail-before-pass-after
8016 // surface: every `:nome` in the 56..=63-byte range passed the
8017 // prior cascade and broke at admission.
8018 let bare_max = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN);
8019 let c = caixa_with_nome(&bare_max);
8020 // The bare-`:nome` gate accepts the 63-byte length.
8021 c.validate_nome().unwrap();
8022 // The new joint-length gate rejects it.
8023 let err = c.validate_nome_chart_name_budget().unwrap_err();
8024 assert!(
8025 matches!(
8026 err,
8027 ManifestError::NomeChartNameBudgetExceeded { ref nome, .. }
8028 if nome.len() == crate::DNS_1123_LABEL_MAX_LEN
8029 ),
8030 "got {err:?}"
8031 );
8032 }
8033
8034 #[test]
8035 fn validate_nome_chart_name_budget_diagnostic_carries_offending_chart_name() {
8036 // Diagnostic-shape pin: the rendered `lareira-<nome>` chart
8037 // name appears verbatim in the diagnostic so the author sees
8038 // exactly the string the apiserver / `helm lint` would have
8039 // rejected — no re-derivation required to grep the source.
8040 // Peer with `nome_invalid_diagnostic_carries_offending_nome`
8041 // on the bare-`:nome` axis.
8042 let over = "x".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 5);
8043 let c = caixa_with_nome(&over);
8044 let err = c.validate_nome_chart_name_budget().unwrap_err();
8045 let ManifestError::NomeChartNameBudgetExceeded { nome, reason } = err else {
8046 panic!("expected NomeChartNameBudgetExceeded variant");
8047 };
8048 assert_eq!(nome, over);
8049 let expected_chart = crate::lareira_chart_name(&over);
8050 assert!(
8051 reason.contains(&expected_chart),
8052 "diagnostic must carry the rendered chart name {expected_chart:?} verbatim, \
8053 got {reason:?}"
8054 );
8055 assert!(
8056 reason.contains("lareira-"),
8057 "diagnostic must name the canonical chart-name prefix verbatim, got {reason:?}"
8058 );
8059 }
8060
8061 #[test]
8062 fn validate_nome_chart_name_budget_runs_after_nome_shape_via_layout_verify() {
8063 // Order pin on the layout cascade: the narrower
8064 // `NomeInvalid` (bare-DNS-1123 shape) fires before the
8065 // joint-length budget. A structurally-malformed `:nome` (here:
8066 // uppercase) surfaces its specific shape error rather than
8067 // the chart-name-budget error, even when the joint length
8068 // would also overflow — the narrower diagnostic is more
8069 // self-locating. Mirrors the cascade-precedence pins peer
8070 // gates already use (e.g. `EntradaParaEmpty` before
8071 // `EntradaParaInvalid`).
8072 let over = "A".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
8073 let c = caixa_with_nome(&over);
8074 // The bare-shape gate fires first.
8075 let err = c.validate_nome().unwrap_err();
8076 assert!(
8077 matches!(err, ManifestError::NomeInvalid { .. }),
8078 "bare-shape gate must fire before chart-name-budget gate; got {err:?}"
8079 );
8080 // And the layout verify cascade surfaces that diagnostic, not
8081 // the budget arm. Inject a path-exists oracle so the cascade
8082 // gets past the manifest-presence check and into the
8083 // value-shape gates.
8084 let layout = crate::StandardLayout::new().with_path_exists(|_| true);
8085 let err = crate::LayoutInvariants::verify(
8086 &layout,
8087 &c,
8088 std::path::Path::new("/tmp/caixa-test-fake-root"),
8089 )
8090 .unwrap_err();
8091 let issue = err.to_string();
8092 assert!(
8093 issue.contains("DNS-1123") || issue.contains("uppercase"),
8094 "layout cascade must surface the bare-DNS-1123 diagnostic on a \
8095 structurally-malformed :nome, not the chart-name-budget diagnostic; got {issue:?}"
8096 );
8097 }
8098
8099 #[test]
8100 fn layout_verify_routes_chart_name_budget_through_nome_violation() {
8101 // Cross-axis envelope pin: the layout cascade wraps both
8102 // bare-`:nome` and joint-length-`:nome` failures through the
8103 // same [`LayoutError::NomeViolation`] envelope, since both
8104 // arms are on the `:nome` axis. The user's diagnostic stays
8105 // self-locating ("which axis"), and a future consumer that
8106 // dispatches on the layout-error variant (e.g. a `feira lint`
8107 // exit-code mapping) sees a single per-axis envelope. The
8108 // wrapped `issue:` carries the full inner diagnostic.
8109 let over = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
8110 let c = caixa_with_nome(&over);
8111 // The bare-shape gate accepts.
8112 c.validate_nome().unwrap();
8113 let layout = crate::StandardLayout::new().with_path_exists(|_| true);
8114 let err = crate::LayoutInvariants::verify(
8115 &layout,
8116 &c,
8117 std::path::Path::new("/tmp/caixa-test-fake-root"),
8118 )
8119 .unwrap_err();
8120 let crate::LayoutError::NomeViolation { caixa, issue } = err else {
8121 panic!("expected LayoutError::NomeViolation, got {err:?}");
8122 };
8123 assert_eq!(caixa, over);
8124 assert!(
8125 issue.contains("lareira-") && issue.contains("63") && issue.contains("55"),
8126 "wrapped issue must carry the joint-length diagnostic verbatim, got {issue:?}"
8127 );
8128 }
8129
8130 // ── Caixa::validate_versao — top-level :versao value-shape gate ─────
8131
8132 fn caixa_with_versao(versao: &str) -> Caixa {
8133 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8134 c.versao = versao.to_string();
8135 c
8136 }
8137
8138 #[test]
8139 fn validate_versao_accepts_canonical_template() {
8140 // Positive control: the bare `feira init`-style template's
8141 // `:versao` ("0.1.0") is a canonical SemVer-2 literal; the gate
8142 // must not regress this baseline shape. A future tightening of
8143 // the accepted set surfaces here as a test failure first.
8144 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8145 c.validate_versao().unwrap();
8146 }
8147
8148 #[test]
8149 fn validate_versao_accepts_canonical_forms() {
8150 // Positive-set sweep: each realistic SemVer-2 shape the
8151 // substrate's downstream consumers accept must pass — bare
8152 // MAJOR.MINOR.PATCH, pre-release tags (`-rc.1`, `-alpha.0`),
8153 // build metadata (`+build.42`), the combined form, and the
8154 // `0.0.0` boundary case. Mirrors `accepts_canonical_forms` on
8155 // the peer `:nome` axis (6c992f8).
8156 for versao in [
8157 "0.1.0",
8158 "0.0.0",
8159 "1.0.0",
8160 "0.2.0-rc.1",
8161 "1.0.0-alpha.0",
8162 "1.0.0+build.42",
8163 "1.0.0-rc.1+build.42",
8164 "10.20.30",
8165 ] {
8166 caixa_with_versao(versao)
8167 .validate_versao()
8168 .unwrap_or_else(|e| {
8169 panic!("canonical :versao {versao:?} must validate, got {e:?}")
8170 });
8171 }
8172 }
8173
8174 #[test]
8175 fn validate_versao_rejects_empty() {
8176 // Fail-before-pass-after pin: `Caixa::from_lisp` does not refuse
8177 // an empty `:versao` (the derive macro stores the raw String);
8178 // the gate's empty arm names the offending axis with a narrower
8179 // diagnostic than the `VersaoInvalid` parse arm would emit.
8180 // Mirrors `validate_nome_rejects_empty` (6c992f8).
8181 let c = caixa_with_versao("");
8182 let err = c.validate_versao().unwrap_err();
8183 assert_eq!(err, ManifestError::VersaoEmpty);
8184 }
8185
8186 #[test]
8187 fn validate_versao_rejects_git_tag_shape() {
8188 // The canonical "I copied the git tag verbatim" footgun —
8189 // `feira publish` *emits* `v<versao>` git tags, so a leaked
8190 // `v0.1.0` in `:versao` would render as `vv0.1.0` and silently
8191 // shift every downstream consumer's version axis. `semver`
8192 // rejects the leading `v` at parse time; the gate moves the
8193 // diagnostic to the source `caixa.lisp`.
8194 let c = caixa_with_versao("v0.1.0");
8195 let err = c.validate_versao().unwrap_err();
8196 let ManifestError::VersaoInvalid { versao, reason } = err else {
8197 panic!("expected VersaoInvalid for git-tag-shape :versao");
8198 };
8199 assert_eq!(versao, "v0.1.0");
8200 assert!(
8201 !reason.is_empty(),
8202 "VersaoInvalid `reason` must carry the parser's wording, got {reason:?}"
8203 );
8204 }
8205
8206 #[test]
8207 fn validate_versao_rejects_missing_patch() {
8208 // The canonical "I shortened it" footgun — SemVer-2 requires
8209 // three parts. Cargo's `version =` field accepts the shortened
8210 // form as a requirement, conflating the two leaks across the
8211 // typed `:deps :versao` vs top-level `:versao` axes; the gate
8212 // pins the top-level axis to the strict three-part shape.
8213 let c = caixa_with_versao("0.1");
8214 let err = c.validate_versao().unwrap_err();
8215 assert!(
8216 matches!(
8217 err,
8218 ManifestError::VersaoInvalid { ref versao, .. } if versao == "0.1"
8219 ),
8220 "got {err:?}"
8221 );
8222 }
8223
8224 #[test]
8225 fn validate_versao_rejects_requirement_shape() {
8226 // The canonical "I leaked a requirement into a version" footgun —
8227 // the typed `:deps :versao` / `:membros :versao` axes accept
8228 // `^0.1` (a `VersionReq`); the top-level `:versao` requires a
8229 // concrete `Version`. Without this gate the two typed surfaces
8230 // would silently overlap, and a top-level `^0.1` would surface
8231 // at `helm install` time as a Chart.yaml version rejection far
8232 // from the source `caixa.lisp`.
8233 let c = caixa_with_versao("^0.1");
8234 let err = c.validate_versao().unwrap_err();
8235 assert!(
8236 matches!(
8237 err,
8238 ManifestError::VersaoInvalid { ref versao, .. } if versao == "^0.1"
8239 ),
8240 "got {err:?}"
8241 );
8242 }
8243
8244 #[test]
8245 fn validate_versao_rejects_docker_tag_shape() {
8246 // The "I confused it with a docker tag" footgun — `latest`,
8247 // `main`, `stable` parse as identifiers, not SemVer-2 versions.
8248 // SemVer rejects at parse time; the gate moves the diagnostic
8249 // to the source `caixa.lisp`.
8250 for bad in ["latest", "main", "stable"] {
8251 let c = caixa_with_versao(bad);
8252 let err = c.validate_versao().unwrap_err();
8253 assert!(
8254 matches!(
8255 err,
8256 ManifestError::VersaoInvalid { ref versao, .. } if versao == bad
8257 ),
8258 "got {err:?} for {bad:?}"
8259 );
8260 }
8261 }
8262
8263 #[test]
8264 fn validate_versao_rejects_four_part_form() {
8265 // The Java/Microsoft "MAJOR.MINOR.PATCH.BUILD" convention
8266 // SemVer-2 forbids. A leak from a non-SemVer ecosystem; the
8267 // semver crate rejects the extra `.0` at parse time.
8268 let c = caixa_with_versao("0.1.0.0");
8269 let err = c.validate_versao().unwrap_err();
8270 assert!(
8271 matches!(
8272 err,
8273 ManifestError::VersaoInvalid { ref versao, .. } if versao == "0.1.0.0"
8274 ),
8275 "got {err:?}"
8276 );
8277 }
8278
8279 #[test]
8280 fn versao_empty_takes_precedence_over_invalid() {
8281 // Order pin: the empty arm fires before the parser is consulted.
8282 // Empty < invalid in self-locating-ness — the narrower
8283 // `VersaoEmpty` diagnostic doesn't carry a useless `versao: ""`
8284 // reference into the parser-shaped reason. Mirrors
8285 // `nome_empty_takes_precedence_over_invalid` (6c992f8) on the
8286 // peer axis.
8287 let c = caixa_with_versao("");
8288 assert_eq!(c.validate_versao().unwrap_err(), ManifestError::VersaoEmpty);
8289 }
8290
8291 #[test]
8292 fn versao_invalid_diagnostic_carries_offending_versao() {
8293 // Diagnostic-shape pin: the error names the offending `:versao`
8294 // verbatim with a non-empty parser-shaped reason, so a `feira
8295 // lint` run can render the diagnostic without re-parsing.
8296 // Mirrors `nome_invalid_diagnostic_carries_offending_nome`.
8297 let c = caixa_with_versao("v0.1.0");
8298 let err = c.validate_versao().unwrap_err();
8299 let ManifestError::VersaoInvalid { versao, reason } = err else {
8300 panic!("expected VersaoInvalid variant");
8301 };
8302 assert_eq!(versao, "v0.1.0");
8303 assert!(
8304 !reason.is_empty(),
8305 "VersaoInvalid `reason` must carry the parser's wording verbatim"
8306 );
8307 }
8308
8309 #[test]
8310 fn validate_versao_accepts_what_upgrade_from_from_accepts() {
8311 // Parity pin: every shape `UpgradeFromEntry::validate` accepts
8312 // for `:upgrade-from :from` must also pass `validate_versao` —
8313 // the two `:versao`-typed surfaces (top-level `:versao`,
8314 // `:upgrade-from :from`) consume the *same* `semver::Version`
8315 // parser, so they must agree on the accepted set. Without this
8316 // pin, a future tightening of one axis could silently diverge
8317 // from the other. Mirrors the `:versao` requirement-axis
8318 // parity (`:deps`/`:deps-dev`/`:membros`/`:children`) the prior
8319 // commits established.
8320 for versao in ["0.1.0", "0.2.0-rc.1", "1.0.0+build.42"] {
8321 // From the canonical UpgradeFromEntry round-trip fixture
8322 // (`upgrade::tests::round_trip_load_module` peers).
8323 let entry = crate::UpgradeFromEntry {
8324 from: versao.to_string(),
8325 instructions: Vec::new(),
8326 };
8327 entry
8328 .validate()
8329 .unwrap_or_else(|e| panic!(":from {versao:?} must validate, got {e:?}"));
8330 caixa_with_versao(versao)
8331 .validate_versao()
8332 .unwrap_or_else(|e| {
8333 panic!(":versao {versao:?} must validate, got {e:?} — peer axis diverges")
8334 });
8335 }
8336 }
8337
8338 // ── Caixa::validate_restart_window — supervisor restart-window
8339 // folds through the shared `supervisor::duration_codec` ────────
8340
8341 fn caixa_with_restart_window(window: Option<&str>) -> Caixa {
8342 let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
8343 c.kind = CaixaKind::Supervisor;
8344 c.restart_window = window.map(str::to_string);
8345 c
8346 }
8347
8348 #[test]
8349 fn validate_restart_window_accepts_none() {
8350 // The canonical "omit the slot to express no reset" shape — a
8351 // `None` raw string is the absence of the typed
8352 // `:restart-window` slot, which is exactly the SupervisorSpec
8353 // "never reset" semantics. The gate must be a no-op here; a
8354 // future tightening that rejected `None` would force every
8355 // supervisor caixa to authoring-time pin a window even when
8356 // the OTP semantics call for none.
8357 caixa_with_restart_window(None)
8358 .validate_restart_window()
8359 .unwrap();
8360 }
8361
8362 #[test]
8363 fn validate_restart_window_accepts_canonical_forms() {
8364 // Positive-set sweep across the canonical authoring units the
8365 // shared `supervisor::duration_codec::parse` accepts —
8366 // matches the codec-side `parse_accepts_integer_canonical_units`
8367 // pin in supervisor::tests so a future codec-side tightening
8368 // surfaces simultaneously on both axes.
8369 for window in ["60s", "5m", "1h", "500ms", "30", "0s"] {
8370 caixa_with_restart_window(Some(window))
8371 .validate_restart_window()
8372 .unwrap_or_else(|e| {
8373 panic!("canonical :restart-window {window:?} must validate, got {e:?}")
8374 });
8375 }
8376 }
8377
8378 #[test]
8379 fn validate_restart_window_rejects_fractional_seconds() {
8380 // Fail-before-pass-after pin: the `"1.5s"` drift class (parses
8381 // as f64 to 1.5 → renders back as `"1500ms"` on first
8382 // serialize). Prior to the fold + this gate, the inline
8383 // `parse_window_inline` accepted f64 magnitudes and silently
8384 // produced a `Duration::from_secs_f64(1.5)`, divergent from
8385 // the shared codec's integer-magnitude discipline on the
8386 // serde-routed siblings. The gate now surfaces a self-locating
8387 // diagnostic at the manifest layer.
8388 let err = caixa_with_restart_window(Some("1.5s"))
8389 .validate_restart_window()
8390 .unwrap_err();
8391 let ManifestError::RestartWindowMalformed {
8392 restart_window,
8393 reason,
8394 } = err
8395 else {
8396 panic!("expected RestartWindowMalformed for fractional seconds");
8397 };
8398 assert_eq!(restart_window, "1.5s");
8399 assert!(
8400 reason.contains("\"1.5\"") && reason.contains("not a non-negative integer"),
8401 "diagnostic must carry shared-codec wording, got {reason:?}"
8402 );
8403 }
8404
8405 #[test]
8406 fn validate_restart_window_rejects_decimal_shaped_integer() {
8407 // The `"1.0s"` class — numerically `1s` exactly, but the
8408 // canonical form is `"1s"` not `"1.0s"`. Decimal-shape leak
8409 // gets the same canonical-form diagnostic.
8410 let err = caixa_with_restart_window(Some("1.0s"))
8411 .validate_restart_window()
8412 .unwrap_err();
8413 assert!(
8414 matches!(
8415 err,
8416 ManifestError::RestartWindowMalformed { ref restart_window, .. }
8417 if restart_window == "1.0s"
8418 ),
8419 "got {err:?}"
8420 );
8421 }
8422
8423 #[test]
8424 fn validate_restart_window_rejects_half_unit_minute() {
8425 // `"0.5m"` is the unit-fraction footgun — author writes a
8426 // human-readable half-minute, the prior inline parser silently
8427 // produced `Duration::from_secs_f64(30.0)` and serde
8428 // re-emitted as `"30s"`, rewriting author intent. The gate
8429 // closes the loop at the manifest layer.
8430 let err = caixa_with_restart_window(Some("0.5m"))
8431 .validate_restart_window()
8432 .unwrap_err();
8433 let ManifestError::RestartWindowMalformed {
8434 restart_window,
8435 reason,
8436 } = err
8437 else {
8438 panic!("expected RestartWindowMalformed");
8439 };
8440 assert_eq!(restart_window, "0.5m");
8441 assert!(
8442 reason.contains("\"30s\""),
8443 "diagnostic must point at the canonical-form remediation, got {reason:?}"
8444 );
8445 }
8446
8447 #[test]
8448 fn validate_restart_window_rejects_leading_sign() {
8449 // `"+30s"` and `"-30s"` both round-tripped through f64 cleanly
8450 // on the prior parser (`+30` parses as `30.0`; `-30` parsed
8451 // and was caught by the `num < 0.0` arm which silently
8452 // returned `None`, dropping the author-supplied window). The
8453 // shared codec's digit-only gate rejects both with a unified
8454 // canonical-form diagnostic; the manifest-layer wrapper names
8455 // the offending value.
8456 for bad in ["+30s", "-30s"] {
8457 let err = caixa_with_restart_window(Some(bad))
8458 .validate_restart_window()
8459 .unwrap_err();
8460 assert!(
8461 matches!(
8462 err,
8463 ManifestError::RestartWindowMalformed { ref restart_window, .. }
8464 if restart_window == bad
8465 ),
8466 "got {err:?} for {bad:?}"
8467 );
8468 }
8469 }
8470
8471 #[test]
8472 fn validate_restart_window_rejects_unknown_unit() {
8473 // `"30x"` — the typo / wrong-unit footgun. The shared codec's
8474 // unit dispatch surfaces an `unknown duration unit` reason;
8475 // the manifest-layer wrapper names the offending value.
8476 let err = caixa_with_restart_window(Some("30x"))
8477 .validate_restart_window()
8478 .unwrap_err();
8479 let ManifestError::RestartWindowMalformed {
8480 restart_window,
8481 reason,
8482 } = err
8483 else {
8484 panic!("expected RestartWindowMalformed for unknown unit");
8485 };
8486 assert_eq!(restart_window, "30x");
8487 assert!(
8488 reason.contains("unknown duration unit"),
8489 "diagnostic must carry shared-codec unit-rejection wording, got {reason:?}"
8490 );
8491 }
8492
8493 #[test]
8494 fn validate_restart_window_rejects_garbage() {
8495 // Pure non-numeric magnitude (`"abc"`) falls through to the
8496 // shared codec's narrower `"bad duration magnitude"` arm. Same
8497 // diagnostic shape as the codec-side
8498 // `parse_garbage_still_falls_through_to_bad_magnitude` pin.
8499 let err = caixa_with_restart_window(Some("abc"))
8500 .validate_restart_window()
8501 .unwrap_err();
8502 let ManifestError::RestartWindowMalformed {
8503 restart_window,
8504 reason,
8505 } = err
8506 else {
8507 panic!("expected RestartWindowMalformed for garbage");
8508 };
8509 assert_eq!(restart_window, "abc");
8510 assert!(
8511 reason.contains("bad duration magnitude"),
8512 "diagnostic must carry shared-codec garbage-rejection wording, got {reason:?}"
8513 );
8514 }
8515
8516 #[test]
8517 fn validate_restart_window_rejects_empty_string() {
8518 // The empty-after-trim edge case — distinct from the `None`
8519 // canonical "omit the slot" shape. The shared codec's
8520 // digit-only gate refuses an empty magnitude; the manifest
8521 // layer names the offending `""` so the author can grep for
8522 // the literal empty value in their `caixa.lisp` and either
8523 // remove the slot (the canonical "no reset" shape) or pin a
8524 // positive duration.
8525 let err = caixa_with_restart_window(Some(""))
8526 .validate_restart_window()
8527 .unwrap_err();
8528 assert!(
8529 matches!(
8530 err,
8531 ManifestError::RestartWindowMalformed { ref restart_window, .. }
8532 if restart_window.is_empty()
8533 ),
8534 "got {err:?}"
8535 );
8536 }
8537
8538 #[test]
8539 fn validate_restart_window_diagnostic_carries_offending_value() {
8540 // Diagnostic-shape pin (peer with
8541 // `nome_invalid_diagnostic_carries_offending_nome` /
8542 // `versao_invalid_diagnostic_carries_offending_versao`): the
8543 // error names the offending raw `:restart-window` verbatim
8544 // with a non-empty shared-codec-shaped reason, so a `feira
8545 // lint` run can render the diagnostic without re-parsing.
8546 let err = caixa_with_restart_window(Some("1.5s"))
8547 .validate_restart_window()
8548 .unwrap_err();
8549 let ManifestError::RestartWindowMalformed {
8550 restart_window,
8551 reason,
8552 } = err
8553 else {
8554 panic!("expected RestartWindowMalformed variant");
8555 };
8556 assert_eq!(restart_window, "1.5s");
8557 assert!(
8558 !reason.is_empty(),
8559 "RestartWindowMalformed `reason` must carry the codec's wording verbatim"
8560 );
8561 }
8562
8563 #[test]
8564 fn supervisor_view_folds_through_shared_codec_on_canonical_form() {
8565 // Behavioral parity pin after the fold (`parse_window_inline`
8566 // deletion): the canonical `"60s"` still produces
8567 // `Duration::from_secs(60)` on the typed view — the fold is
8568 // semantically equivalent to the prior inline parser on the
8569 // accepted set. Mirrors the pre-fold `supervisor_view_returns_typed_shape`
8570 // pin, narrowed to the parser-side contract.
8571 let c = caixa_with_restart_window(Some("60s"));
8572 let view = c.supervisor_view().expect("Supervisor kind has a view");
8573 assert_eq!(
8574 view.restart_window,
8575 Some(std::time::Duration::from_secs(60))
8576 );
8577 }
8578
8579 #[test]
8580 fn supervisor_view_soft_swallows_what_validate_rejects() {
8581 // Parity pin between the view-construction path and the
8582 // manifest-level validator: the same `"1.5s"` that surfaces
8583 // `RestartWindowMalformed` at `validate_restart_window` time
8584 // becomes `restart_window: None` on the typed view (the fold
8585 // preserves the existing best-effort shape of `supervisor_view`).
8586 // The contract is: a layout-verifier / `feira lint` flow that
8587 // cares about the malformed-window axis MUST consult
8588 // `validate_restart_window` — relying solely on the view's
8589 // `None` swallows the diagnostic silently. This pin makes the
8590 // expectation a typed invariant.
8591 let c = caixa_with_restart_window(Some("1.5s"));
8592 let view = c.supervisor_view().expect("Supervisor kind has a view");
8593 assert_eq!(
8594 view.restart_window, None,
8595 "view-construction path soft-swallows the parse error to None"
8596 );
8597 // And the manifest-level validator does NOT soft-swallow:
8598 assert!(
8599 matches!(
8600 c.validate_restart_window().unwrap_err(),
8601 ManifestError::RestartWindowMalformed { ref restart_window, .. }
8602 if restart_window == "1.5s"
8603 ),
8604 "validator must surface the offending value",
8605 );
8606 }
8607
8608 // ── validate_code_paths — per-entry shape on :bibliotecas / :exe / :servicos ──
8609
8610 fn caixa_with_code_paths(bibliotecas: Vec<&str>, exe: Vec<&str>, servicos: Vec<&str>) -> Caixa {
8611 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8612 c.bibliotecas = bibliotecas.into_iter().map(String::from).collect();
8613 c.exe = exe.into_iter().map(String::from).collect();
8614 c.servicos = servicos.into_iter().map(String::from).collect();
8615 c
8616 }
8617
8618 #[test]
8619 fn validate_code_paths_accepts_canonical_template() {
8620 // The bare `Caixa::template` shape is the gate's identity element
8621 // on the canonical authoring shape — `:bibliotecas
8622 // ("lib/demo.lisp")` + empty `:exe` + empty `:servicos`. Pins
8623 // that the gate is non-disruptive against every existing caixa.
8624 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8625 c.validate_code_paths().unwrap();
8626 }
8627
8628 #[test]
8629 fn validate_code_paths_accepts_explicit_relative_paths_on_every_slot() {
8630 // Positive control sweep: a canonical-shaped path on every slot
8631 // passes. Mirrors the peer
8632 // `behavior::validate_every_slot_relative_is_ok` pin.
8633 let c = caixa_with_code_paths(
8634 vec!["lib/demo.lisp", "lib/helpers.lisp"],
8635 vec!["exe/demo", "exe/tool"],
8636 vec!["servicos/demo.computeunit.yaml"],
8637 );
8638 c.validate_code_paths().unwrap();
8639 }
8640
8641 #[test]
8642 fn validate_code_paths_accepts_all_empty_lists() {
8643 // The empty-list identity element: every Caixa with no declared
8644 // code paths trivially passes (Supervisor / Aplicacao kinds rely
8645 // on this — the OwnCode gate already rejected them before the
8646 // path-shape gate runs in the layout, but the validator itself
8647 // must accept the empty shape).
8648 let c = caixa_with_code_paths(vec![], vec![], vec![]);
8649 c.validate_code_paths().unwrap();
8650 }
8651
8652 #[test]
8653 fn validate_code_paths_rejects_empty_bibliotecas_entry() {
8654 let c = caixa_with_code_paths(vec![""], vec![], vec![]);
8655 let err = c.validate_code_paths().unwrap_err();
8656 assert!(
8657 matches!(
8658 err,
8659 ManifestError::CodePathEmpty {
8660 slot: ":bibliotecas"
8661 }
8662 ),
8663 "got {err:?}",
8664 );
8665 }
8666
8667 #[test]
8668 fn validate_code_paths_rejects_empty_exe_entry() {
8669 let c = caixa_with_code_paths(vec![], vec![""], vec![]);
8670 let err = c.validate_code_paths().unwrap_err();
8671 assert!(
8672 matches!(err, ManifestError::CodePathEmpty { slot: ":exe" }),
8673 "got {err:?}",
8674 );
8675 }
8676
8677 #[test]
8678 fn validate_code_paths_rejects_empty_servicos_entry() {
8679 let c = caixa_with_code_paths(vec![], vec![], vec![""]);
8680 let err = c.validate_code_paths().unwrap_err();
8681 assert!(
8682 matches!(err, ManifestError::CodePathEmpty { slot: ":servicos" }),
8683 "got {err:?}",
8684 );
8685 }
8686
8687 #[test]
8688 fn validate_code_paths_rejects_absolute_bibliotecas_entry() {
8689 // `:bibliotecas` has no `starts_with(<dir>)` fence downstream,
8690 // so an absolute path that resolves on disk silently passes the
8691 // layout's existence check — the canonical sandbox-escape on
8692 // the biblioteca axis.
8693 let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
8694 let err = c.validate_code_paths().unwrap_err();
8695 let ManifestError::CodePathAbsolute { slot, path } = err else {
8696 panic!("expected CodePathAbsolute, got {err:?}");
8697 };
8698 assert_eq!(slot, ":bibliotecas");
8699 assert_eq!(path, PathBuf::from("/etc/passwd"));
8700 }
8701
8702 #[test]
8703 fn validate_code_paths_rejects_absolute_exe_entry() {
8704 let c = caixa_with_code_paths(vec![], vec!["/usr/bin/env"], vec![]);
8705 let err = c.validate_code_paths().unwrap_err();
8706 let ManifestError::CodePathAbsolute { slot, path } = err else {
8707 panic!("expected CodePathAbsolute, got {err:?}");
8708 };
8709 assert_eq!(slot, ":exe");
8710 assert_eq!(path, PathBuf::from("/usr/bin/env"));
8711 }
8712
8713 #[test]
8714 fn validate_code_paths_rejects_absolute_servicos_entry() {
8715 let c = caixa_with_code_paths(vec![], vec![], vec!["/var/servicos/x.yaml"]);
8716 let err = c.validate_code_paths().unwrap_err();
8717 let ManifestError::CodePathAbsolute { slot, path } = err else {
8718 panic!("expected CodePathAbsolute, got {err:?}");
8719 };
8720 assert_eq!(slot, ":servicos");
8721 assert_eq!(path, PathBuf::from("/var/servicos/x.yaml"));
8722 }
8723
8724 #[test]
8725 fn validate_code_paths_rejects_parent_escape_bibliotecas_leading() {
8726 // Canonical "I want a lib from a sibling caixa" footgun on the
8727 // biblioteca axis. `:bibliotecas` has no `starts_with` fence
8728 // downstream, so a leading `..` traverses to the parent of the
8729 // caixa root with no diagnostic at layout time if the resolved
8730 // target exists.
8731 let c = caixa_with_code_paths(vec!["../sibling/x.lisp"], vec![], vec![]);
8732 let err = c.validate_code_paths().unwrap_err();
8733 let ManifestError::CodePathParentEscape { slot, path } = err else {
8734 panic!("expected CodePathParentEscape, got {err:?}");
8735 };
8736 assert_eq!(slot, ":bibliotecas");
8737 assert_eq!(path, PathBuf::from("../sibling/x.lisp"));
8738 }
8739
8740 #[test]
8741 fn validate_code_paths_rejects_parent_escape_exe_mid_path() {
8742 // Mid-path `..` defeats the layout's component-aware
8743 // `starts_with(exe_dir)` fence — `root.join("exe/../../escape")`
8744 // `starts_with(<root>/exe)` is true, but the canonical resolution
8745 // lives outside the caixa root. Caught regardless of where the
8746 // `..` sits — mirrors the peer
8747 // `behavior::validate_rejects_parent_escape_mid_path` pin.
8748 let c = caixa_with_code_paths(vec![], vec!["exe/../../escape"], vec![]);
8749 let err = c.validate_code_paths().unwrap_err();
8750 let ManifestError::CodePathParentEscape { slot, path } = err else {
8751 panic!("expected CodePathParentEscape, got {err:?}");
8752 };
8753 assert_eq!(slot, ":exe");
8754 assert_eq!(path, PathBuf::from("exe/../../escape"));
8755 }
8756
8757 #[test]
8758 fn validate_code_paths_rejects_parent_escape_servicos_trailing() {
8759 let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/foo/../../escape.yaml"]);
8760 let err = c.validate_code_paths().unwrap_err();
8761 let ManifestError::CodePathParentEscape { slot, path } = err else {
8762 panic!("expected CodePathParentEscape, got {err:?}");
8763 };
8764 assert_eq!(slot, ":servicos");
8765 assert_eq!(path, PathBuf::from("servicos/foo/../../escape.yaml"));
8766 }
8767
8768 #[test]
8769 fn validate_code_paths_cross_slot_precedence_bibliotecas_before_exe_before_servicos() {
8770 // Cross-slot precedence pin: `:bibliotecas` → `:exe` →
8771 // `:servicos`. A manifest with malformed entries on all three
8772 // surfaces surfaces the `:bibliotecas` defect first, mirroring
8773 // the canonical declaration order
8774 // `Caixa::declared_foreign_code_slots` already establishes for
8775 // the foreign-code-slot diagnostic.
8776 let c = caixa_with_code_paths(vec![""], vec![""], vec![""]);
8777 let err = c.validate_code_paths().unwrap_err();
8778 assert!(
8779 matches!(
8780 err,
8781 ManifestError::CodePathEmpty {
8782 slot: ":bibliotecas"
8783 }
8784 ),
8785 "got {err:?}",
8786 );
8787 }
8788
8789 #[test]
8790 fn validate_code_paths_within_slot_precedence_empty_before_absolute_before_parent_escape() {
8791 // Within-slot precedence pin: empty → absolute → parent-escape,
8792 // matching the [`PathShapeViolation`] arm-ordering every peer
8793 // `is_sandboxed_relative_path` caller follows (b0c8389
8794 // BehaviorSpec, 26da2c7 UpgradeInstruction::StateChange). A
8795 // `:bibliotecas` list whose first entry is empty *and* whose
8796 // later entries are absolute/parent-escape surfaces the empty
8797 // arm first, on the lexicographically-earliest offending entry.
8798 let c = caixa_with_code_paths(vec!["", "/etc/passwd", "../escape.lisp"], vec![], vec![]);
8799 let err = c.validate_code_paths().unwrap_err();
8800 assert!(
8801 matches!(
8802 err,
8803 ManifestError::CodePathEmpty {
8804 slot: ":bibliotecas"
8805 }
8806 ),
8807 "got {err:?}",
8808 );
8809 }
8810
8811 #[test]
8812 fn validate_code_paths_first_offender_per_slot_wins() {
8813 // Within a single slot, the first declaration-order offender
8814 // surfaces — pins that the gate is left-to-right deterministic
8815 // (peer of every `*_first_collision_*` pin on duplicate gates).
8816 let c = caixa_with_code_paths(
8817 vec!["lib/ok.lisp", "/etc/escape", "../also-escape"],
8818 vec![],
8819 vec![],
8820 );
8821 let err = c.validate_code_paths().unwrap_err();
8822 let ManifestError::CodePathAbsolute { slot, path } = err else {
8823 panic!("expected CodePathAbsolute, got {err:?}");
8824 };
8825 assert_eq!(slot, ":bibliotecas");
8826 assert_eq!(path, PathBuf::from("/etc/escape"));
8827 }
8828
8829 #[test]
8830 fn validate_code_paths_diagnostic_carries_offending_slot_and_path() {
8831 // Diagnostic-shape pin (peer with
8832 // `nome_invalid_diagnostic_carries_offending_nome` /
8833 // `versao_invalid_diagnostic_carries_offending_versao`): the
8834 // error's Display surfaces both the offending `:slot` tag and
8835 // the offending path verbatim, so a `feira lint` run can render
8836 // the diagnostic without re-parsing.
8837 let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
8838 let rendered = c.validate_code_paths().unwrap_err().to_string();
8839 assert!(
8840 rendered.contains(":bibliotecas"),
8841 "diagnostic must name the offending slot: {rendered}",
8842 );
8843 assert!(
8844 rendered.contains("/etc/passwd"),
8845 "diagnostic must quote the offending path: {rendered}",
8846 );
8847 }
8848
8849 #[test]
8850 fn validate_code_paths_rejects_duplicate_bibliotecas_entry() {
8851 // Canonical copy-paste-the-wrong-file footgun on the biblioteca
8852 // axis. Without the gate `feira build` re-parses the same lib
8853 // twice, wasting work and silently masking the author's intent
8854 // to declare a *second* biblioteca.
8855 let c = caixa_with_code_paths(vec!["lib/demo.lisp", "lib/demo.lisp"], vec![], vec![]);
8856 let err = c.validate_code_paths().unwrap_err();
8857 let ManifestError::CodePathDuplicate { slot, path } = err else {
8858 panic!("expected CodePathDuplicate, got {err:?}");
8859 };
8860 assert_eq!(slot, ":bibliotecas");
8861 assert_eq!(path, PathBuf::from("lib/demo.lisp"));
8862 }
8863
8864 #[test]
8865 fn validate_code_paths_rejects_duplicate_exe_entry() {
8866 // Same footgun on the Binario surface. The future `caixa-flake`
8867 // emitter that materializes each `:exe` entry as a flake
8868 // `packages.<name>` derivation would collide on the duplicate
8869 // package key — surfaced here at the typed-validate layer with a
8870 // self-locating diagnostic instead.
8871 let c = caixa_with_code_paths(vec![], vec!["exe/cli", "exe/cli"], vec![]);
8872 let err = c.validate_code_paths().unwrap_err();
8873 let ManifestError::CodePathDuplicate { slot, path } = err else {
8874 panic!("expected CodePathDuplicate, got {err:?}");
8875 };
8876 assert_eq!(slot, ":exe");
8877 assert_eq!(path, PathBuf::from("exe/cli"));
8878 }
8879
8880 #[test]
8881 fn validate_code_paths_rejects_duplicate_servicos_entry() {
8882 // Same footgun on the Servico surface. The peer caixa-helm /
8883 // caixa-flux renderers refuse `:servicos.len() != 1` with the
8884 // narrower `UnsupportedServicoCount` diagnostic, but that
8885 // diagnostic surfaces "too many servicos" without naming
8886 // "duplicate entry" — the typed self-locating framing only lands
8887 // at this gate.
8888 let c = caixa_with_code_paths(
8889 vec![],
8890 vec![],
8891 vec![
8892 "servicos/demo.computeunit.yaml",
8893 "servicos/demo.computeunit.yaml",
8894 ],
8895 );
8896 let err = c.validate_code_paths().unwrap_err();
8897 let ManifestError::CodePathDuplicate { slot, path } = err else {
8898 panic!("expected CodePathDuplicate, got {err:?}");
8899 };
8900 assert_eq!(slot, ":servicos");
8901 assert_eq!(path, PathBuf::from("servicos/demo.computeunit.yaml"));
8902 }
8903
8904 #[test]
8905 fn validate_code_paths_accepts_same_path_across_slots() {
8906 // Per-list scope pin: a `:bibliotecas` entry that happens to
8907 // collide with an `:exe` or `:servicos` entry as a *string* is
8908 // not a duplicate by this gate (each list gets its own HashSet),
8909 // mirroring the peer `:deps` ↔ `:deps-dev` per-list scope
8910 // (a `:nome` present in both lists is a legitimate dev-vs-runtime
8911 // shape on the dep axis). The structural `starts_with(<exe |
8912 // servicos>_dir)` fence at layout time prevents the realistic
8913 // cross-slot collision case from existing on disk, but the gate's
8914 // per-list scope is correct independent of that downstream fence.
8915 let c = caixa_with_code_paths(
8916 vec!["lib/x.lisp"],
8917 vec!["exe/x"],
8918 vec!["servicos/x.computeunit.yaml"],
8919 );
8920 c.validate_code_paths().unwrap();
8921 }
8922
8923 #[test]
8924 fn validate_code_paths_duplicate_fires_after_structural_checks_on_same_slot() {
8925 // Within-slot ordering pin: structural defects (empty / absolute
8926 // / parent-escape) fire before the duplicate gate on the same
8927 // slot. A `:bibliotecas ("" "lib/x.lisp" "lib/x.lisp")` shape
8928 // surfaces the narrower `CodePathEmpty` for the empty entry
8929 // first, not the duplicate on the later pair — same arm-ordering
8930 // every peer per-list duplicate gate uses (`:etiquetas` 360a499,
8931 // `:autores` 86c769b, `:deps` 359fba5).
8932 let c = caixa_with_code_paths(vec!["", "lib/x.lisp", "lib/x.lisp"], vec![], vec![]);
8933 let err = c.validate_code_paths().unwrap_err();
8934 assert!(
8935 matches!(
8936 err,
8937 ManifestError::CodePathEmpty {
8938 slot: ":bibliotecas"
8939 }
8940 ),
8941 "got {err:?}",
8942 );
8943 }
8944
8945 #[test]
8946 fn validate_code_paths_duplicate_in_bibliotecas_fires_before_duplicate_in_exe() {
8947 // Cross-slot ordering pin on the duplicate arm: `:bibliotecas`
8948 // duplicates surface before `:exe` duplicates, matching the
8949 // canonical `:bibliotecas` → `:exe` → `:servicos` declaration
8950 // order every peer per-slot diagnostic on this surface follows.
8951 let c = caixa_with_code_paths(
8952 vec!["lib/x.lisp", "lib/x.lisp"],
8953 vec!["exe/y", "exe/y"],
8954 vec![],
8955 );
8956 let err = c.validate_code_paths().unwrap_err();
8957 let ManifestError::CodePathDuplicate { slot, path } = err else {
8958 panic!("expected CodePathDuplicate, got {err:?}");
8959 };
8960 assert_eq!(slot, ":bibliotecas");
8961 assert_eq!(path, PathBuf::from("lib/x.lisp"));
8962 }
8963
8964 #[test]
8965 fn validate_code_paths_duplicate_diagnostic_carries_offending_slot_and_path() {
8966 // Diagnostic-shape pin (peer with
8967 // `validate_code_paths_diagnostic_carries_offending_slot_and_path`
8968 // on the structural arm): the duplicate-arm Display surfaces both
8969 // the offending `:slot` tag and the offending path verbatim, so a
8970 // `feira lint` run can render the diagnostic without re-parsing.
8971 let c = caixa_with_code_paths(
8972 vec![],
8973 vec![],
8974 vec![
8975 "servicos/demo.computeunit.yaml",
8976 "servicos/demo.computeunit.yaml",
8977 ],
8978 );
8979 let rendered = c.validate_code_paths().unwrap_err().to_string();
8980 assert!(
8981 rendered.contains(":servicos"),
8982 "diagnostic must name the offending slot: {rendered}",
8983 );
8984 assert!(
8985 rendered.contains("servicos/demo.computeunit.yaml"),
8986 "diagnostic must quote the offending path: {rendered}",
8987 );
8988 }
8989
8990 // ── validate_code_paths — `.lisp` extension gate on :bibliotecas ──
8991 //
8992 // The lifted [`crate::render::is_lisp_extension`] predicate (33cc830)
8993 // now gates `:bibliotecas` entries on the tatara-lisp-source file-type
8994 // contract. The `feira build` loop (`caixa-feira/src/cmd/build.rs:33`)
8995 // reads every declared `:bibliotecas` entry through `tatara_lisp::read`
8996 // at parse time — the same downstream consumer the peer `:behavior
8997 // :on-*` (c97815a, [`crate::BehaviorError::NonLispExtension`]) and
8998 // `:upgrade-from :state-change :script` (33cc830,
8999 // [`crate::UpgradeError::NonLispExtensionScript`]) axes route through.
9000 // `:exe` and `:servicos` are deliberately excluded — `:exe` is the
9001 // nix-built executable surface (`"exe/<name>"` shape per the canonical
9002 // [`crate::LayoutError::ExeOutsideDir`] error message and every
9003 // in-tree `caixa_with_code_paths` positive control), and `:servicos`
9004 // is the `.computeunit.yaml` ComputeUnit-CR axis.
9005
9006 #[test]
9007 fn validate_code_paths_rejects_no_extension_bibliotecas_entry() {
9008 // Canonical "I dragged the wrong file from the workspace tree"
9009 // footgun on the biblioteca axis. Without the gate `feira build`
9010 // hands the extensionless path to `tatara_lisp::read` and fails
9011 // with a parser-shaped diagnostic far from the source caixa.lisp,
9012 // with no field naming the offending `:bibliotecas` entry.
9013 for relpath in ["lib/demo", "demo", "lib/handlers/inner"] {
9014 let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
9015 let err = c.validate_code_paths().unwrap_err();
9016 let ManifestError::CodePathNonLispExtension { slot, path } = err else {
9017 panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
9018 };
9019 assert_eq!(slot, ":bibliotecas");
9020 assert_eq!(path, PathBuf::from(relpath));
9021 }
9022 }
9023
9024 #[test]
9025 fn validate_code_paths_rejects_wrong_extension_bibliotecas_entry() {
9026 // Wrong-extension sweep across common authoring footguns. Same
9027 // sweep posture as the peer
9028 // `behavior::validate_rejects_wrong_extension` (c97815a) and
9029 // `upgrade::tests::state_change_rejects_wrong_extension_script`
9030 // (33cc830) cases.
9031 for relpath in [
9032 "lib/demo.rs",
9033 "lib/demo.txt",
9034 "lib/demo.md",
9035 "lib/demo.json",
9036 "lib/demo.yaml",
9037 "lib/demo.toml",
9038 "lib/demo.lisp.bak",
9039 "lib/demo.lispx",
9040 "lib/demo.lis",
9041 ] {
9042 let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
9043 let err = c.validate_code_paths().unwrap_err();
9044 let ManifestError::CodePathNonLispExtension { slot, path } = err else {
9045 panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
9046 };
9047 assert_eq!(slot, ":bibliotecas");
9048 assert_eq!(path, PathBuf::from(relpath));
9049 }
9050 }
9051
9052 #[test]
9053 fn validate_code_paths_rejects_case_folded_extension_bibliotecas_entry() {
9054 // Case-sensitivity sweep — pins the strict lowercase `.lisp`
9055 // contract. An uppercase `.LISP` shape that the layout's existence
9056 // check would (case-insensitively, on case-insensitive volumes)
9057 // match the on-disk file still mismatches the canonical form the
9058 // codec emits, breaking the THEORY.md §V.2.7 render-determinism
9059 // contract. Mirrors the peer
9060 // `behavior::validate_rejects_case_folded_extension` (c97815a) and
9061 // `upgrade::tests::state_change_rejects_case_folded_extension_script`
9062 // (33cc830) sweeps.
9063 for relpath in [
9064 "lib/demo.LISP",
9065 "lib/demo.Lisp",
9066 "lib/demo.LiSp",
9067 "lib/demo.lISP",
9068 ] {
9069 let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
9070 let err = c.validate_code_paths().unwrap_err();
9071 let ManifestError::CodePathNonLispExtension { slot, path } = err else {
9072 panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
9073 };
9074 assert_eq!(slot, ":bibliotecas");
9075 assert_eq!(path, PathBuf::from(relpath));
9076 }
9077 }
9078
9079 #[test]
9080 fn validate_code_paths_accepts_canonical_lisp_shapes() {
9081 // Positive-control sweep through every canonical authoring shape
9082 // every in-tree fixture and the `Caixa::template` scaffold use.
9083 // Mirrors the peer `behavior::validate_accepts_canonical_lisp_paths`
9084 // (c97815a) and the lifted predicate's own
9085 // `is_lisp_extension_accepts_canonical_shapes` sweep in render.rs
9086 // (33cc830).
9087 for relpath in [
9088 "lib/demo.lisp",
9089 "lib/handlers.lisp",
9090 "lib/migrations/v01-to-v02.lisp",
9091 "demo.lisp",
9092 "a.lisp",
9093 "./lib/demo.lisp",
9094 "lib/./handlers.lisp",
9095 "lib/migrations/v.0.1.lisp",
9096 ] {
9097 let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
9098 c.validate_code_paths()
9099 .unwrap_or_else(|e| panic!("canonical shape {relpath:?} must pass, got {e:?}"));
9100 }
9101 }
9102
9103 #[test]
9104 fn validate_code_paths_non_lisp_extension_does_not_fire_on_exe_or_servicos() {
9105 // The file-type gate is per-slot — only `:bibliotecas` carries the
9106 // tatara-lisp-source contract. An extensionless `:exe` entry
9107 // (`exe/demo`) and a `.computeunit.yaml` `:servicos` entry are the
9108 // canonical shapes every in-tree fixture uses, and must continue
9109 // to pass validate. Pins that a future tightening that broadens
9110 // the `.lisp` gate to either axis surfaces as a test failure
9111 // rather than as a silent breaking change to existing valid
9112 // manifests.
9113 let c = caixa_with_code_paths(
9114 vec![],
9115 vec!["exe/demo", "exe/tool"],
9116 vec!["servicos/demo.computeunit.yaml"],
9117 );
9118 c.validate_code_paths().unwrap();
9119 }
9120
9121 #[test]
9122 fn validate_code_paths_sandbox_shape_arms_precede_non_lisp_extension() {
9123 // Cross-arm precedence pin: a `:bibliotecas` entry that is *both*
9124 // sandbox-escaping and non-`.lisp` surfaces the more fundamental
9125 // sandbox-shape diagnostic first (the `.lisp` remediation would
9126 // be misleading when the offending path can never resolve under
9127 // the caixa root anyway). Mirrors the peer
9128 // `EmptyPath` → `AbsolutePath` → `ParentEscape` → `NonLispExtension`
9129 // ordering on `:behavior :on-*` (c97815a) and `EmptyScript` →
9130 // `AbsoluteScript` → `ParentEscapeScript` → `NonLispExtensionScript`
9131 // on `:upgrade-from :state-change :script` (33cc830).
9132 //
9133 // Empty wins (the strictly-smaller-scope structural arm).
9134 let c = caixa_with_code_paths(vec![""], vec![], vec![]);
9135 assert!(
9136 matches!(
9137 c.validate_code_paths().unwrap_err(),
9138 ManifestError::CodePathEmpty {
9139 slot: ":bibliotecas"
9140 }
9141 ),
9142 "empty must win over non-lisp-extension",
9143 );
9144 // Absolute wins (the path can't resolve under the caixa root).
9145 let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
9146 let err = c.validate_code_paths().unwrap_err();
9147 let ManifestError::CodePathAbsolute { slot, .. } = err else {
9148 panic!("absolute must win over non-lisp-extension, got {err:?}");
9149 };
9150 assert_eq!(slot, ":bibliotecas");
9151 // ParentEscape wins (the path escapes the caixa root).
9152 let c = caixa_with_code_paths(vec!["../sibling/x.txt"], vec![], vec![]);
9153 let err = c.validate_code_paths().unwrap_err();
9154 let ManifestError::CodePathParentEscape { slot, .. } = err else {
9155 panic!("parent-escape must win over non-lisp-extension, got {err:?}");
9156 };
9157 assert_eq!(slot, ":bibliotecas");
9158 }
9159
9160 #[test]
9161 fn validate_code_paths_non_lisp_extension_precedes_duplicate() {
9162 // Within-slot precedence pin: the per-entry file-type shape gate
9163 // fires before the cross-entry duplicate gate, so the narrower
9164 // structural defect dominates the uniqueness diagnostic. A
9165 // `("lib/x.txt" "lib/x.txt")` shape surfaces
9166 // `CodePathNonLispExtension` on the first entry rather than
9167 // `CodePathDuplicate` on the pair — same posture every per-entry
9168 // shape-gate-precedes-duplicate cascade follows on this surface
9169 // (the empty / absolute / parent-escape arms already precede the
9170 // duplicate arm; the lifted file-type arm joins that set).
9171 let c = caixa_with_code_paths(vec!["lib/x.txt", "lib/x.txt"], vec![], vec![]);
9172 let err = c.validate_code_paths().unwrap_err();
9173 let ManifestError::CodePathNonLispExtension { slot, path } = err else {
9174 panic!("expected CodePathNonLispExtension, got {err:?}");
9175 };
9176 assert_eq!(slot, ":bibliotecas");
9177 assert_eq!(path, PathBuf::from("lib/x.txt"));
9178 }
9179
9180 #[test]
9181 fn validate_code_paths_non_lisp_extension_diagnostic_carries_offending_slot_and_path() {
9182 // Diagnostic-shape pin (peer with
9183 // `validate_code_paths_diagnostic_carries_offending_slot_and_path`
9184 // on the sandbox-shape arms and
9185 // `validate_code_paths_duplicate_diagnostic_carries_offending_slot_and_path`
9186 // on the duplicate arm): the file-type-arm Display surfaces both
9187 // the offending `:slot` tag, the offending path verbatim, and the
9188 // expected `.lisp` extension named in the remediation text, so a
9189 // `feira lint` run can render the diagnostic without re-parsing.
9190 let c = caixa_with_code_paths(vec!["lib/demo.rs"], vec![], vec![]);
9191 let rendered = c.validate_code_paths().unwrap_err().to_string();
9192 assert!(
9193 rendered.contains(":bibliotecas"),
9194 "diagnostic must name the offending slot: {rendered}",
9195 );
9196 assert!(
9197 rendered.contains("lib/demo.rs"),
9198 "diagnostic must quote the offending path: {rendered}",
9199 );
9200 assert!(
9201 rendered.contains(".lisp"),
9202 "diagnostic must name the expected extension: {rendered}",
9203 );
9204 }
9205
9206 // ── validate_code_paths — `.computeunit.yaml` compound-suffix gate on :servicos ──
9207 //
9208 // The lifted [`crate::render::is_computeunit_yaml_extension`] predicate
9209 // now gates `:servicos` entries on the ComputeUnit-CR YAML file-type
9210 // contract. The peer caixa-helm / caixa-flux renderers consume each
9211 // `:servicos` entry through `serde_yaml::from_str` as a typed
9212 // `ComputeUnit` CR — same downstream-consumer-shape lift as the peer
9213 // `:bibliotecas` `.lisp` gate (64772a9), here on the compound-suffix
9214 // axis `Path::extension` can't express on its own.
9215
9216 #[test]
9217 fn validate_code_paths_rejects_no_extension_servicos_entry() {
9218 // Canonical "I dragged the wrong file from the workspace tree"
9219 // footgun on the Servico axis. Without the gate the peer
9220 // caixa-helm / caixa-flux renderers hand the extensionless path
9221 // to `serde_yaml::from_str` and fail with a parser-shaped
9222 // diagnostic far from the source caixa.lisp, with no field
9223 // naming the offending `:servicos` entry.
9224 for relpath in ["servicos/demo", "demo", "servicos/sub/nested"] {
9225 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
9226 let err = c.validate_code_paths().unwrap_err();
9227 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
9228 panic!(
9229 "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
9230 got {err:?}"
9231 );
9232 };
9233 assert_eq!(slot, ":servicos");
9234 assert_eq!(path, PathBuf::from(relpath));
9235 }
9236 }
9237
9238 #[test]
9239 fn validate_code_paths_rejects_wrong_extension_servicos_entry() {
9240 // Wrong-extension sweep across common authoring footguns on the
9241 // Servico axis. Bare `.yaml` is the canonical "I forgot the
9242 // `.computeunit` segment" typo; the off-by-one-segment shapes
9243 // (`.computeunit-yaml` / `.computeunit_yaml`) silently pass the
9244 // bare `Path::extension` view but mismatch the typed compound
9245 // suffix the renderers' `serde_yaml::from_str` consumer demands.
9246 // Same sweep-posture as the peer
9247 // `validate_code_paths_rejects_wrong_extension_bibliotecas_entry`
9248 // (64772a9) on the sibling tatara-lisp-source axis.
9249 for relpath in [
9250 "servicos/demo.yaml",
9251 "servicos/demo.yml",
9252 "servicos/demo.json",
9253 "servicos/demo.toml",
9254 "servicos/demo.txt",
9255 "servicos/demo.computeunit.yaml.bak",
9256 "servicos/demo.computeunit.yam",
9257 "servicos/demo.computeunit",
9258 "servicos/demo-computeunit.yaml",
9259 "servicos/demo_computeunit.yaml",
9260 ] {
9261 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
9262 let err = c.validate_code_paths().unwrap_err();
9263 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
9264 panic!(
9265 "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
9266 got {err:?}"
9267 );
9268 };
9269 assert_eq!(slot, ":servicos");
9270 assert_eq!(path, PathBuf::from(relpath));
9271 }
9272 }
9273
9274 #[test]
9275 fn validate_code_paths_rejects_case_folded_extension_servicos_entry() {
9276 // Case-sensitivity sweep — pins the strict lowercase
9277 // `.computeunit.yaml` contract. A case-folded shape that the
9278 // layout's existence check would (case-insensitively, on
9279 // case-insensitive volumes) match the on-disk file still
9280 // mismatches the canonical form the codec emits, breaking the
9281 // THEORY.md §V.2.7 render-determinism contract. Mirrors the peer
9282 // `validate_code_paths_rejects_case_folded_extension_bibliotecas_entry`
9283 // (64772a9) sweep on the sibling tatara-lisp-source axis.
9284 for relpath in [
9285 "servicos/demo.ComputeUnit.yaml",
9286 "servicos/demo.COMPUTEUNIT.yaml",
9287 "servicos/demo.computeunit.YAML",
9288 "servicos/demo.computeunit.Yaml",
9289 "servicos/demo.COMPUTEUNIT.YAML",
9290 ] {
9291 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
9292 let err = c.validate_code_paths().unwrap_err();
9293 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
9294 panic!(
9295 "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
9296 got {err:?}"
9297 );
9298 };
9299 assert_eq!(slot, ":servicos");
9300 assert_eq!(path, PathBuf::from(relpath));
9301 }
9302 }
9303
9304 #[test]
9305 fn validate_code_paths_rejects_empty_stem_servicos_entry() {
9306 // Degenerate hidden-file shape: a file name exactly equal to the
9307 // suffix (`.computeunit.yaml` — no stem preceding the suffix) is
9308 // the structural "Servico declared with no identity" footgun.
9309 // The substrate identifies each ComputeUnit by the file-stem
9310 // segment that precedes `.computeunit.yaml` (the rendered
9311 // `lareira-<stem>` Helm chart, the per-Servico `metadata.name`,
9312 // the M3 `:contratos` membership lookup), so an empty stem
9313 // leaves the Servico unidentifiable. Pinned at the typed-axis
9314 // level so a future regression that drops the `name.len() >
9315 // SUFFIX.len()` bound at the predicate surfaces here, not
9316 // piecemeal as a `lareira-` chart-name collision at render time.
9317 for relpath in ["servicos/.computeunit.yaml"] {
9318 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
9319 let err = c.validate_code_paths().unwrap_err();
9320 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
9321 panic!(
9322 "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
9323 got {err:?}"
9324 );
9325 };
9326 assert_eq!(slot, ":servicos");
9327 assert_eq!(path, PathBuf::from(relpath));
9328 }
9329 }
9330
9331 #[test]
9332 fn validate_code_paths_accepts_canonical_computeunit_yaml_shapes() {
9333 // Positive-control sweep through every canonical authoring shape
9334 // every in-tree fixture and the `Caixa::template` scaffold use.
9335 // Mirrors the peer
9336 // `validate_code_paths_accepts_canonical_lisp_shapes` (64772a9)
9337 // and the lifted predicate's own
9338 // `computeunit_yaml_extension_accepts_canonical_shapes` sweep in
9339 // render.rs.
9340 for relpath in [
9341 "servicos/demo.computeunit.yaml",
9342 "servicos/hello-rio.computeunit.yaml",
9343 "servicos/my-service.computeunit.yaml",
9344 "servicos/a.computeunit.yaml",
9345 "./servicos/demo.computeunit.yaml",
9346 "servicos/./demo.computeunit.yaml",
9347 "servicos/sub/nested.computeunit.yaml",
9348 "servicos/v0.1.computeunit.yaml",
9349 ] {
9350 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
9351 c.validate_code_paths()
9352 .unwrap_or_else(|e| panic!("canonical shape {relpath:?} must pass, got {e:?}"));
9353 }
9354 }
9355
9356 #[test]
9357 fn validate_code_paths_non_computeunit_yaml_extension_does_not_fire_on_bibliotecas_or_exe() {
9358 // The file-type gate is per-slot — only `:servicos` carries the
9359 // ComputeUnit-CR YAML contract. A canonical `.lisp` `:bibliotecas`
9360 // entry and an extensionless `:exe` entry are the canonical
9361 // shapes every in-tree fixture uses, and must continue to pass
9362 // validate. Peer of
9363 // `validate_code_paths_non_lisp_extension_does_not_fire_on_exe_or_servicos`
9364 // (64772a9) — together pin that the typed
9365 // [`CodePathFileType`] dispatch is exhaustively per-slot, with no
9366 // cross-axis leakage in either direction.
9367 let c = caixa_with_code_paths(
9368 vec!["lib/demo.lisp"],
9369 vec!["exe/demo", "exe/tool"],
9370 vec!["servicos/demo.computeunit.yaml"],
9371 );
9372 c.validate_code_paths().unwrap();
9373 }
9374
9375 #[test]
9376 fn validate_code_paths_sandbox_shape_arms_precede_non_computeunit_yaml_extension() {
9377 // Cross-arm precedence pin: a `:servicos` entry that is *both*
9378 // sandbox-escaping and wrong-extension surfaces the more
9379 // fundamental sandbox-shape diagnostic first (the
9380 // `.computeunit.yaml` remediation would be misleading when the
9381 // offending path can never resolve under the caixa root
9382 // anyway). Mirrors the peer
9383 // `validate_code_paths_sandbox_shape_arms_precede_non_lisp_extension`
9384 // (64772a9) ordering on the sibling `:bibliotecas` axis and the
9385 // peer `EmptyPath` → `AbsolutePath` → `ParentEscape` →
9386 // `NonComputeUnitYamlExtension` arm-ordering the dispatch
9387 // table establishes.
9388 //
9389 // Empty wins (the strictly-smaller-scope structural arm).
9390 let c = caixa_with_code_paths(vec![], vec![], vec![""]);
9391 assert!(
9392 matches!(
9393 c.validate_code_paths().unwrap_err(),
9394 ManifestError::CodePathEmpty { slot: ":servicos" }
9395 ),
9396 "empty must win over non-computeunit-yaml-extension",
9397 );
9398 // Absolute wins (the path can't resolve under the caixa root).
9399 let c = caixa_with_code_paths(vec![], vec![], vec!["/etc/foo.yaml"]);
9400 let err = c.validate_code_paths().unwrap_err();
9401 let ManifestError::CodePathAbsolute { slot, .. } = err else {
9402 panic!("absolute must win over non-computeunit-yaml-extension, got {err:?}");
9403 };
9404 assert_eq!(slot, ":servicos");
9405 // ParentEscape wins (the path escapes the caixa root).
9406 let c = caixa_with_code_paths(vec![], vec![], vec!["../sibling/x.yaml"]);
9407 let err = c.validate_code_paths().unwrap_err();
9408 let ManifestError::CodePathParentEscape { slot, .. } = err else {
9409 panic!("parent-escape must win over non-computeunit-yaml-extension, got {err:?}");
9410 };
9411 assert_eq!(slot, ":servicos");
9412 }
9413
9414 #[test]
9415 fn validate_code_paths_non_computeunit_yaml_extension_precedes_duplicate() {
9416 // Within-slot precedence pin: the per-entry file-type shape gate
9417 // fires before the cross-entry duplicate gate, so the narrower
9418 // structural defect dominates the uniqueness diagnostic. A
9419 // `("servicos/x.yaml" "servicos/x.yaml")` shape surfaces
9420 // `CodePathNonComputeUnitYamlExtension` on the first entry
9421 // rather than `CodePathDuplicate` on the pair — same posture
9422 // every per-entry shape-gate-precedes-duplicate cascade follows
9423 // on this surface, peer of the 64772a9 `:bibliotecas`
9424 // `("lib/x.txt" "lib/x.txt")` ordering.
9425 let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/x.yaml", "servicos/x.yaml"]);
9426 let err = c.validate_code_paths().unwrap_err();
9427 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
9428 panic!("expected CodePathNonComputeUnitYamlExtension, got {err:?}");
9429 };
9430 assert_eq!(slot, ":servicos");
9431 assert_eq!(path, PathBuf::from("servicos/x.yaml"));
9432 }
9433
9434 #[test]
9435 fn validate_code_paths_non_computeunit_yaml_extension_diagnostic_carries_offending_slot_and_path()
9436 {
9437 // Diagnostic-shape pin (peer with
9438 // `validate_code_paths_non_lisp_extension_diagnostic_carries_offending_slot_and_path`
9439 // on the sibling tatara-lisp-source axis): the file-type-arm
9440 // Display surfaces both the offending `:slot` tag, the
9441 // offending path verbatim, and the expected
9442 // `.computeunit.yaml` compound suffix named in the remediation
9443 // text, so a `feira lint` run can render the diagnostic without
9444 // re-parsing.
9445 let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/demo.yaml"]);
9446 let rendered = c.validate_code_paths().unwrap_err().to_string();
9447 assert!(
9448 rendered.contains(":servicos"),
9449 "diagnostic must name the offending slot: {rendered}",
9450 );
9451 assert!(
9452 rendered.contains("servicos/demo.yaml"),
9453 "diagnostic must quote the offending path: {rendered}",
9454 );
9455 assert!(
9456 rendered.contains(".computeunit.yaml"),
9457 "diagnostic must name the expected compound suffix: {rendered}",
9458 );
9459 }
9460
9461 // ── validate_etiquetas — universal-axis registry-search-tag shape ──
9462
9463 fn caixa_with_etiquetas(etiquetas: Vec<&str>) -> Caixa {
9464 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9465 c.etiquetas = etiquetas.into_iter().map(String::from).collect();
9466 c
9467 }
9468
9469 #[test]
9470 fn validate_etiquetas_accepts_empty_list() {
9471 // The empty-list identity: every caixa with no declared tags
9472 // trivially passes — `Caixa::template` emits `:etiquetas ()`,
9473 // so the gate is non-disruptive against every existing manifest.
9474 let c = caixa_with_etiquetas(vec![]);
9475 c.validate_etiquetas().unwrap();
9476 }
9477
9478 #[test]
9479 fn validate_etiquetas_accepts_canonical_forms() {
9480 // Positive control sweep: a canonical-shaped non-empty distinct
9481 // tag list passes, mirroring the example checkout-aplicacao
9482 // (`:etiquetas ("example" "aplicacao" "mesh" "ecommerce" "demo")`)
9483 // and the hello-rio fixture (`("hello-world" "wasm" "rust")`).
9484 let c = caixa_with_etiquetas(vec!["example", "aplicacao", "mesh", "ecommerce", "demo"]);
9485 c.validate_etiquetas().unwrap();
9486 }
9487
9488 #[test]
9489 fn validate_etiquetas_rejects_empty_entry() {
9490 // Canonical paste-from-blank-doc footgun. Without the gate the
9491 // empty entry rendered as `keywords: [""]` in `Chart.yaml`, a
9492 // no-op tag indexing nothing in the future caixa-registry.
9493 let c = caixa_with_etiquetas(vec![""]);
9494 let err = c.validate_etiquetas().unwrap_err();
9495 assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
9496 }
9497
9498 #[test]
9499 fn validate_etiquetas_rejects_duplicate_entry() {
9500 // Canonical copy-paste-the-wrong-tag footgun. Without the gate
9501 // the duplicate was silently dedup'd by caixa-helm's BTreeSet
9502 // collect at chart render — a "second wins / one silently
9503 // disappears" shape divergent from every peer typed-graph set
9504 // gate. The duplicate-arm names the offending tag verbatim.
9505 let c = caixa_with_etiquetas(vec!["demo", "demo"]);
9506 let err = c.validate_etiquetas().unwrap_err();
9507 let ManifestError::EtiquetaDuplicate { etiqueta } = err else {
9508 panic!("expected EtiquetaDuplicate, got {err:?}");
9509 };
9510 assert_eq!(etiqueta, "demo");
9511 }
9512
9513 #[test]
9514 fn validate_etiquetas_empty_takes_precedence_over_duplicate() {
9515 // Empty-first cascade pin: `("" "demo" "demo")` surfaces
9516 // `EtiquetaEmpty` not `EtiquetaDuplicate` — the narrower
9517 // structural "this entry has no value" defect dominates the
9518 // cross-entry uniqueness diagnostic. Mirrors the peer
9519 // empty-before-duplicate cascades on `:caracteristicas`
9520 // (`CaracteristicaEmpty` before `CaracteristicaDuplicate`,
9521 // fc3b4d5) and `:membros :caixa` (`MembroCaixaEmpty` before
9522 // `MembroDuplicate`).
9523 let c = caixa_with_etiquetas(vec!["", "demo", "demo"]);
9524 let err = c.validate_etiquetas().unwrap_err();
9525 assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
9526 }
9527
9528 #[test]
9529 fn validate_etiquetas_duplicate_reports_first_collision() {
9530 // First-collision pin: `("a" "b" "a" "b")` surfaces the `"a"`
9531 // duplicate (the lexicographically-earliest offending position
9532 // — the second `"a"` at index 2 collides with the first `"a"`
9533 // at index 0), not the later `"b"` collision at index 3,
9534 // peer with every other first-collision diagnostic posture on
9535 // this surface (`validate_load_singularity_reports_first_collision`,
9536 // `validate_cleanup_singularity_reports_first_collision`).
9537 let c = caixa_with_etiquetas(vec!["a", "b", "a", "b"]);
9538 let err = c.validate_etiquetas().unwrap_err();
9539 let ManifestError::EtiquetaDuplicate { etiqueta } = err else {
9540 panic!("expected EtiquetaDuplicate, got {err:?}");
9541 };
9542 assert_eq!(etiqueta, "a");
9543 }
9544
9545 #[test]
9546 fn validate_etiquetas_case_sensitive() {
9547 // Case-sensitivity pin: `("Foo" "foo")` is two distinct entries,
9548 // mirroring the peer `:membros :caixa` / `:children :caixa`
9549 // exact-string-match discipline. The shape gate this routine
9550 // landed (`is_chart_keyword_shape`, Cargo crates.io keyword
9551 // grammar) accepts mixed case — crates.io's keyword rule is
9552 // "case-insensitive" at the index layer but admits mixed case
9553 // at the entry layer (the canonical Helm chart `keywords:`
9554 // shape is lowercase by convention, but the grammar admits
9555 // uppercase). Case-sensitivity at the duplicate-set layer
9556 // remains structural — two distinct strings are two distinct
9557 // entries.
9558 let c = caixa_with_etiquetas(vec!["Foo", "foo"]);
9559 c.validate_etiquetas().unwrap();
9560 }
9561
9562 #[test]
9563 fn validate_etiquetas_diagnostic_carries_offending_tag() {
9564 // Diagnostic-shape pin (peer with
9565 // `validate_code_paths_diagnostic_carries_offending_slot_and_path`):
9566 // the error's Display surfaces the offending tag verbatim, so a
9567 // `feira lint` run can render the diagnostic without re-parsing
9568 // and the author can grep their caixa.lisp for the offending
9569 // value.
9570 let c = caixa_with_etiquetas(vec!["demo", "demo"]);
9571 let rendered = c.validate_etiquetas().unwrap_err().to_string();
9572 assert!(
9573 rendered.contains(":etiquetas"),
9574 "diagnostic must name the offending slot: {rendered}",
9575 );
9576 assert!(
9577 rendered.contains("demo"),
9578 "diagnostic must quote the offending tag: {rendered}",
9579 );
9580 }
9581
9582 #[test]
9583 fn validate_etiquetas_rejects_leading_whitespace_entry() {
9584 // Canonical paste-from-aligned-doc footgun. Without the shape
9585 // gate `" mesh"` silently passed validate and landed as a
9586 // YAML plain-style scalar with leading whitespace in the
9587 // rendered Chart.yaml `keywords:` array — every YAML 1.2
9588 // dumper trims leading whitespace from plain-style scalars,
9589 // so the authored space round-tripped inconsistently back
9590 // through `caixa.lisp`. Mirrors the peer
9591 // `validate_autores_rejects_leading_whitespace_entry`.
9592 let c = caixa_with_etiquetas(vec![" mesh"]);
9593 let err = c.validate_etiquetas().unwrap_err();
9594 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
9595 panic!("expected EtiquetaInvalid, got {err:?}");
9596 };
9597 assert_eq!(etiqueta, " mesh");
9598 assert!(reason.contains("whitespace"), "got: {reason}");
9599 }
9600
9601 #[test]
9602 fn validate_etiquetas_rejects_embedded_newline_entry() {
9603 // Canonical paste-from-multiline-doc footgun — the author
9604 // pasted a multi-tag block into one `:etiquetas` entry
9605 // instead of splitting into one entry per tag. Without the
9606 // shape gate `"mesh\nhttp"` silently passed validate and
9607 // landed as a YAML-illegal multi-line scalar in the rendered
9608 // Chart.yaml `keywords:` array.
9609 let c = caixa_with_etiquetas(vec!["mesh\nhttp"]);
9610 let err = c.validate_etiquetas().unwrap_err();
9611 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
9612 panic!("expected EtiquetaInvalid, got {err:?}");
9613 };
9614 assert_eq!(etiqueta, "mesh\nhttp");
9615 assert!(reason.contains("newline"), "got: {reason}");
9616 }
9617
9618 #[test]
9619 fn validate_etiquetas_rejects_embedded_comma_entry() {
9620 // Canonical CSV-list-separator-confusion footgun: the author
9621 // confused the CSV-style separator convention with the
9622 // `:etiquetas` list grammar. Without the shape gate
9623 // `"mesh,http,grpc"` silently passed validate and landed as a
9624 // single malformed search tag in the rendered Chart.yaml
9625 // `keywords:` array — Artifact Hub's keyword index would
9626 // either silently drop the tag or index it as
9627 // `mesh,http,grpc` instead of three separate tags.
9628 let c = caixa_with_etiquetas(vec!["mesh,http,grpc"]);
9629 let err = c.validate_etiquetas().unwrap_err();
9630 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
9631 panic!("expected EtiquetaInvalid, got {err:?}");
9632 };
9633 assert_eq!(etiqueta, "mesh,http,grpc");
9634 assert!(reason.contains('`'), "got: {reason}");
9635 assert!(reason.contains(','), "got: {reason}");
9636 }
9637
9638 #[test]
9639 fn validate_etiquetas_rejects_embedded_slash_entry() {
9640 // Canonical path-separator-confusion footgun: the author
9641 // confused namespace-path notation with the keyword grammar.
9642 let c = caixa_with_etiquetas(vec!["caixa/servico"]);
9643 let err = c.validate_etiquetas().unwrap_err();
9644 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
9645 panic!("expected EtiquetaInvalid, got {err:?}");
9646 };
9647 assert_eq!(etiqueta, "caixa/servico");
9648 assert!(reason.contains('/'), "got: {reason}");
9649 }
9650
9651 #[test]
9652 fn validate_etiquetas_rejects_leading_digit_entry() {
9653 // Canonical paste-from-numbered-list footgun: the author
9654 // copied `1. mesh` from a numbered doc and the `1` leaked
9655 // into the tag.
9656 let c = caixa_with_etiquetas(vec!["1mesh"]);
9657 let err = c.validate_etiquetas().unwrap_err();
9658 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
9659 panic!("expected EtiquetaInvalid, got {err:?}");
9660 };
9661 assert_eq!(etiqueta, "1mesh");
9662 assert!(reason.contains("digit"), "got: {reason}");
9663 }
9664
9665 #[test]
9666 fn validate_etiquetas_rejects_leading_hyphen_entry() {
9667 // Canonical kebab-leak footgun.
9668 let c = caixa_with_etiquetas(vec!["-foo"]);
9669 let err = c.validate_etiquetas().unwrap_err();
9670 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
9671 panic!("expected EtiquetaInvalid, got {err:?}");
9672 };
9673 assert_eq!(etiqueta, "-foo");
9674 assert!(reason.contains('-'), "got: {reason}");
9675 }
9676
9677 #[test]
9678 fn validate_etiquetas_rejects_non_ascii_entry() {
9679 // Canonical paste-from-Unicode-doc footgun. Every legitimate
9680 // search tag is strict ASCII; raw non-ASCII silently
9681 // round-trips inconsistently across NFC/NFD normalization on
9682 // APFS / case-folding filesystems and breaks the Artifact Hub
9683 // keyword search index lookup.
9684 let c = caixa_with_etiquetas(vec!["café"]);
9685 let err = c.validate_etiquetas().unwrap_err();
9686 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
9687 panic!("expected EtiquetaInvalid, got {err:?}");
9688 };
9689 assert_eq!(etiqueta, "café");
9690 assert!(reason.contains("non-ASCII"), "got: {reason}");
9691 }
9692
9693 #[test]
9694 fn validate_etiquetas_rejects_period_entry() {
9695 // Canonical namespace-confusion / version-suffix footgun
9696 // (`"http.1"` / `"v1.0"`): Cargo's crates.io keyword grammar
9697 // excludes `.` from the continuation set even though the
9698 // sibling `:caracteristicas` axis (Cargo's feature-name
9699 // grammar) admits it. Tighter than the sibling axis, peer
9700 // with Cargo's own crates.io keyword shape.
9701 let c = caixa_with_etiquetas(vec!["http.1"]);
9702 let err = c.validate_etiquetas().unwrap_err();
9703 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
9704 panic!("expected EtiquetaInvalid, got {err:?}");
9705 };
9706 assert_eq!(etiqueta, "http.1");
9707 assert!(reason.contains('.'), "got: {reason}");
9708 }
9709
9710 #[test]
9711 fn validate_etiquetas_empty_takes_precedence_over_shape() {
9712 // Per-entry empty-first cascade pin: an entry that is both
9713 // empty *and* shape-invalid surfaces `EtiquetaEmpty` (the
9714 // narrower "this entry has no value" structural defect
9715 // dominates the broader shape-predicate diagnostic). The
9716 // empty arm fires before the shape predicate is consulted,
9717 // mirroring the peer `validate_autores_empty_takes_precedence_over_shape`
9718 // cascade established on the sibling universal-axis Vec<String>
9719 // surface.
9720 let c = caixa_with_etiquetas(vec![""]);
9721 let err = c.validate_etiquetas().unwrap_err();
9722 assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
9723 }
9724
9725 #[test]
9726 fn validate_etiquetas_shape_takes_precedence_over_duplicate() {
9727 // Per-entry shape-before-cross-entry-duplicate cascade pin: an
9728 // entry that is malformed surfaces `EtiquetaInvalid` even when
9729 // a later entry would have collided on duplicate. The
9730 // per-entry shape arm fires inside the same loop iteration as
9731 // the empty arm, before the seen-set insert at end-of-iteration
9732 // — structural per-entry defects dominate the cross-entry
9733 // uniqueness diagnostic. Mirrors the peer
9734 // `validate_autores_shape_takes_precedence_over_duplicate`.
9735 let c = caixa_with_etiquetas(vec!["mesh\nhttp", "mesh\nhttp"]);
9736 let err = c.validate_etiquetas().unwrap_err();
9737 assert!(
9738 matches!(err, ManifestError::EtiquetaInvalid { .. }),
9739 "got {err:?}",
9740 );
9741 }
9742
9743 #[test]
9744 fn validate_etiquetas_invalid_diagnostic_names_offending_slot_and_value() {
9745 // Diagnostic-shape pin on the new shape arm (peer with
9746 // `validate_autores_invalid_diagnostic_names_offending_slot_and_value`):
9747 // the rendered Display surfaces both the offending slot name
9748 // and the offending value verbatim, so a `feira lint` run
9749 // points the author at the exact `:etiquetas` entry to fix.
9750 let c = caixa_with_etiquetas(vec!["mesh\nhttp"]);
9751 let rendered = c.validate_etiquetas().unwrap_err().to_string();
9752 assert!(
9753 rendered.contains(":etiquetas"),
9754 "diagnostic must name the offending slot: {rendered}",
9755 );
9756 assert!(
9757 rendered.contains("mesh\\nhttp"),
9758 "diagnostic must quote the offending value (debug-escaped): {rendered}",
9759 );
9760 }
9761
9762 #[test]
9763 fn validate_etiquetas_rejects_at_21_byte_boundary() {
9764 // The 20-byte cap pin — boundary-exceeding case rejected,
9765 // boundary-accepting case passes. Mirrors the peer
9766 // `chart_keyword_shape_rejects_at_21_byte_boundary` substrate-
9767 // side pin, surfaced at the per-axis caller so the cap
9768 // propagates through validate end-to-end. Constructed as a
9769 // single all-`a` token so only the cap arm fires.
9770 let max_ok = "a".repeat(20);
9771 let c = caixa_with_etiquetas(vec![max_ok.as_str()]);
9772 c.validate_etiquetas().unwrap();
9773 let too_long = "a".repeat(21);
9774 let c = caixa_with_etiquetas(vec![too_long.as_str()]);
9775 let err = c.validate_etiquetas().unwrap_err();
9776 let ManifestError::EtiquetaInvalid { reason, .. } = err else {
9777 panic!("expected EtiquetaInvalid, got {err:?}");
9778 };
9779 assert!(reason.contains("20"), "got: {reason}");
9780 assert!(reason.contains("21"), "got: {reason}");
9781 }
9782
9783 #[test]
9784 fn validate_etiquetas_accepts_canonical_shaped_forms() {
9785 // Positive control sweep: every canonical-shaped tag from the
9786 // hello-rio / checkout-aplicacao / pangea-tatara-akeyless
9787 // example fixtures plus the substrate-fixed tags caixa-helm
9788 // unions in at chart render. Drift between this list and the
9789 // substrate-side `chart_keyword_shape_accepts_canonical_forms`
9790 // sweep surfaces here — one source of truth for the rule.
9791 let c = caixa_with_etiquetas(vec![
9792 "example",
9793 "aplicacao",
9794 "mesh",
9795 "ecommerce",
9796 "demo",
9797 "infrastructure",
9798 "aws",
9799 "akeyless",
9800 "pangea-native",
9801 "hello-world",
9802 "wasm",
9803 "rust",
9804 "tatara-lisp",
9805 "caixa-servico",
9806 "lareira",
9807 ]);
9808 c.validate_etiquetas().unwrap();
9809 }
9810
9811 // ── validate_autores — universal-axis maintainer shape ────────────
9812
9813 fn caixa_with_autores(autores: Vec<&str>) -> Caixa {
9814 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9815 c.autores = autores.into_iter().map(String::from).collect();
9816 c
9817 }
9818
9819 #[test]
9820 fn validate_autores_accepts_empty_list() {
9821 // The empty-list identity: `Caixa::template` emits `:autores ()`,
9822 // so the gate is non-disruptive against every existing manifest.
9823 let c = caixa_with_autores(vec![]);
9824 c.validate_autores().unwrap();
9825 }
9826
9827 #[test]
9828 fn validate_autores_accepts_canonical_forms() {
9829 // Positive control sweep: every canonical-shaped non-empty
9830 // distinct maintainer list passes — the hello-rio / checkout-
9831 // aplicacao fixtures' `:autores ("pleme-io")` shape, plus the
9832 // multi-author shape downstream packaging surfaces emit.
9833 let c = caixa_with_autores(vec!["pleme-io"]);
9834 c.validate_autores().unwrap();
9835 let c = caixa_with_autores(vec!["alice <alice@example.com>", "bob <bob@example.com>"]);
9836 c.validate_autores().unwrap();
9837 }
9838
9839 #[test]
9840 fn validate_autores_rejects_empty_entry() {
9841 // Canonical paste-from-blank-doc footgun. Without the gate the
9842 // empty entry rendered as `maintainers: [{name: "", email: null}]`
9843 // in `Chart.yaml`, a no-op maintainer the substrate cannot route
9844 // to.
9845 let c = caixa_with_autores(vec![""]);
9846 let err = c.validate_autores().unwrap_err();
9847 assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
9848 }
9849
9850 #[test]
9851 fn validate_autores_rejects_duplicate_entry() {
9852 // Canonical copy-paste-the-wrong-author footgun. Unlike the
9853 // `:etiquetas` peer (caixa-helm's `BTreeSet` collect silently
9854 // dedups the rendered `keywords:` array), the `maintainers:`
9855 // rendering has *no* dedup — duplicates stack verbatim. The
9856 // duplicate-arm names the offending author verbatim.
9857 let c = caixa_with_autores(vec!["pleme-io", "pleme-io"]);
9858 let err = c.validate_autores().unwrap_err();
9859 let ManifestError::AutorDuplicate { autor } = err else {
9860 panic!("expected AutorDuplicate, got {err:?}");
9861 };
9862 assert_eq!(autor, "pleme-io");
9863 }
9864
9865 #[test]
9866 fn validate_autores_empty_takes_precedence_over_duplicate() {
9867 // Empty-first cascade pin: `("" "pleme-io" "pleme-io")` surfaces
9868 // `AutorEmpty` not `AutorDuplicate` — the narrower structural
9869 // "this entry has no value" defect dominates the cross-entry
9870 // uniqueness diagnostic. Mirrors the peer empty-before-duplicate
9871 // cascades on `:etiquetas` (`EtiquetaEmpty` before
9872 // `EtiquetaDuplicate`, 360a499), `:caracteristicas`
9873 // (`CaracteristicaEmpty` before `CaracteristicaDuplicate`,
9874 // fc3b4d5), and `:membros :caixa` (`MembroCaixaEmpty` before
9875 // `MembroDuplicate`).
9876 let c = caixa_with_autores(vec!["", "pleme-io", "pleme-io"]);
9877 let err = c.validate_autores().unwrap_err();
9878 assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
9879 }
9880
9881 #[test]
9882 fn validate_autores_duplicate_reports_first_collision() {
9883 // First-collision pin: `("a" "b" "a" "b")` surfaces the `"a"`
9884 // duplicate (the lexicographically-earliest offending position
9885 // — the second `"a"` at index 2 collides with the first `"a"`
9886 // at index 0), not the later `"b"` collision at index 3,
9887 // peer with every other first-collision diagnostic posture on
9888 // this surface.
9889 let c = caixa_with_autores(vec!["a", "b", "a", "b"]);
9890 let err = c.validate_autores().unwrap_err();
9891 let ManifestError::AutorDuplicate { autor } = err else {
9892 panic!("expected AutorDuplicate, got {err:?}");
9893 };
9894 assert_eq!(autor, "a");
9895 }
9896
9897 #[test]
9898 fn validate_autores_case_sensitive() {
9899 // Case-sensitivity pin: `("Pleme-io" "pleme-io")` is two distinct
9900 // entries, mirroring the peer `:etiquetas` / `:membros :caixa`
9901 // / `:children :caixa` exact-string-match discipline.
9902 let c = caixa_with_autores(vec!["Pleme-io", "pleme-io"]);
9903 c.validate_autores().unwrap();
9904 }
9905
9906 #[test]
9907 fn validate_autores_diagnostic_carries_offending_author() {
9908 // Diagnostic-shape pin (peer with
9909 // `validate_etiquetas_diagnostic_carries_offending_tag`): the
9910 // error's Display surfaces the offending author verbatim, so a
9911 // `feira lint` run can render the diagnostic without re-parsing
9912 // and the author can grep their caixa.lisp for the offending
9913 // value.
9914 let c = caixa_with_autores(vec!["pleme-io", "pleme-io"]);
9915 let rendered = c.validate_autores().unwrap_err().to_string();
9916 assert!(
9917 rendered.contains(":autores"),
9918 "diagnostic must name the offending slot: {rendered}",
9919 );
9920 assert!(
9921 rendered.contains("pleme-io"),
9922 "diagnostic must quote the offending author: {rendered}",
9923 );
9924 }
9925
9926 #[test]
9927 fn validate_autores_rejects_leading_whitespace_entry() {
9928 // Canonical paste-from-aligned-doc footgun. Without the shape
9929 // gate `" pleme-io"` silently passed validate and landed as a
9930 // YAML plain-style scalar with leading whitespace in the
9931 // rendered Chart.yaml `maintainers:` array — every YAML 1.2
9932 // dumper trims leading whitespace from plain-style scalars, so
9933 // the authored space round-tripped inconsistently back through
9934 // `caixa.lisp`. Mirrors the peer
9935 // `validate_descricao_rejects_leading_whitespace`.
9936 let c = caixa_with_autores(vec![" pleme-io"]);
9937 let err = c.validate_autores().unwrap_err();
9938 let ManifestError::AutorInvalid { autor, reason } = err else {
9939 panic!("expected AutorInvalid, got {err:?}");
9940 };
9941 assert_eq!(autor, " pleme-io");
9942 assert!(reason.contains("whitespace"), "got: {reason}");
9943 }
9944
9945 #[test]
9946 fn validate_autores_rejects_trailing_whitespace_entry() {
9947 // Canonical paste-from-doc footgun.
9948 let c = caixa_with_autores(vec!["pleme-io "]);
9949 let err = c.validate_autores().unwrap_err();
9950 let ManifestError::AutorInvalid { autor, reason } = err else {
9951 panic!("expected AutorInvalid, got {err:?}");
9952 };
9953 assert_eq!(autor, "pleme-io ");
9954 assert!(reason.contains("whitespace"), "got: {reason}");
9955 }
9956
9957 #[test]
9958 fn validate_autores_rejects_embedded_newline_entry() {
9959 // Canonical paste-from-multiline-doc footgun — the author
9960 // pasted a multi-line block of author records into one
9961 // `:autores` entry instead of splitting into one entry per
9962 // author. Without the shape gate `"alice\nbob"` silently
9963 // passed validate and landed as a YAML-illegal multi-line
9964 // scalar in the rendered Chart.yaml `maintainers:` array.
9965 let c = caixa_with_autores(vec!["alice\nbob"]);
9966 let err = c.validate_autores().unwrap_err();
9967 let ManifestError::AutorInvalid { autor, reason } = err else {
9968 panic!("expected AutorInvalid, got {err:?}");
9969 };
9970 assert_eq!(autor, "alice\nbob");
9971 assert!(reason.contains("newline"), "got: {reason}");
9972 }
9973
9974 #[test]
9975 fn validate_autores_rejects_embedded_carriage_return_entry() {
9976 // Canonical paste-from-Windows-CRLF-doc footgun.
9977 let c = caixa_with_autores(vec!["alice\rbob"]);
9978 let err = c.validate_autores().unwrap_err();
9979 let ManifestError::AutorInvalid { autor, reason } = err else {
9980 panic!("expected AutorInvalid, got {err:?}");
9981 };
9982 assert_eq!(autor, "alice\rbob");
9983 assert!(reason.contains("carriage return"), "got: {reason}");
9984 }
9985
9986 #[test]
9987 fn validate_autores_rejects_embedded_tab_entry() {
9988 // Canonical tab-from-aligned-doc footgun.
9989 let c = caixa_with_autores(vec!["Pleme\tContributors"]);
9990 let err = c.validate_autores().unwrap_err();
9991 let ManifestError::AutorInvalid { autor, reason } = err else {
9992 panic!("expected AutorInvalid, got {err:?}");
9993 };
9994 assert_eq!(autor, "Pleme\tContributors");
9995 assert!(reason.contains("tab"), "got: {reason}");
9996 }
9997
9998 #[test]
9999 fn validate_autores_rejects_embedded_control_bytes_entry() {
10000 // Paste-from-binary-blob footguns: NUL, BEL, ESC, DEL all
10001 // surface the same control-byte arm.
10002 for entry in [
10003 "alice\x00bob",
10004 "alice\x07bob",
10005 "alice\x1bbob",
10006 "alice\x7fbob",
10007 ] {
10008 let c = caixa_with_autores(vec![entry]);
10009 let err = c.validate_autores().unwrap_err();
10010 let ManifestError::AutorInvalid { autor, reason } = err else {
10011 panic!("expected AutorInvalid for {entry:?}, got {err:?}");
10012 };
10013 assert_eq!(autor, entry);
10014 assert!(
10015 reason.contains("control character"),
10016 "{entry:?} reason: {reason}",
10017 );
10018 }
10019 }
10020
10021 #[test]
10022 fn validate_autores_accepts_unicode_entry() {
10023 // Unicode positive control: realistic maintainer names carry
10024 // Unicode (`François`, `日本語`, `naïve`). The predicate must
10025 // round-trip Unicode losslessly, peer with the
10026 // `chart_maintainer_name_shape_accepts_unicode` substrate-side
10027 // sweep.
10028 let c = caixa_with_autores(vec![
10029 "François Dupont",
10030 "日本語の名前",
10031 "naïve <naive@example.com>",
10032 ]);
10033 c.validate_autores().unwrap();
10034 }
10035
10036 #[test]
10037 fn validate_autores_empty_takes_precedence_over_shape() {
10038 // Per-entry empty-first cascade pin: an entry that is both
10039 // empty *and* shape-invalid surfaces `AutorEmpty` (the narrower
10040 // "this entry has no value" structural defect dominates the
10041 // broader shape-predicate diagnostic). The empty arm fires
10042 // before the shape predicate is consulted, mirroring the peer
10043 // `validate_repositorio_empty_takes_precedence_over_shape`
10044 // cascade on the universal `Option<String>` siblings — and now
10045 // established on the Vec<String> per-entry surface.
10046 let c = caixa_with_autores(vec![""]);
10047 let err = c.validate_autores().unwrap_err();
10048 assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
10049 }
10050
10051 #[test]
10052 fn validate_autores_shape_takes_precedence_over_duplicate() {
10053 // Per-entry shape-before-cross-entry-duplicate cascade pin: an
10054 // entry that is malformed surfaces `AutorInvalid` even when a
10055 // later entry would have collided on duplicate. The per-entry
10056 // shape arm fires inside the same loop iteration as the empty
10057 // arm, before the seen-set insert at end-of-iteration —
10058 // structural per-entry defects dominate the cross-entry
10059 // uniqueness diagnostic.
10060 let c = caixa_with_autores(vec!["alice\nbob", "alice\nbob"]);
10061 let err = c.validate_autores().unwrap_err();
10062 assert!(
10063 matches!(err, ManifestError::AutorInvalid { .. }),
10064 "got {err:?}",
10065 );
10066 }
10067
10068 #[test]
10069 fn validate_autores_invalid_diagnostic_names_offending_slot_and_value() {
10070 // Diagnostic-shape pin on the new shape arm (peer with
10071 // `validate_descricao_invalid_diagnostic_carries_offending_value`):
10072 // the rendered Display surfaces both the offending slot name
10073 // and the offending value verbatim, so a `feira lint` run
10074 // points the author at the exact `:autores` entry to fix.
10075 let c = caixa_with_autores(vec!["alice\nbob"]);
10076 let rendered = c.validate_autores().unwrap_err().to_string();
10077 assert!(
10078 rendered.contains(":autores"),
10079 "diagnostic must name the offending slot: {rendered}",
10080 );
10081 assert!(
10082 rendered.contains("alice\\nbob"),
10083 "diagnostic must quote the offending value (debug-escaped): {rendered}",
10084 );
10085 }
10086
10087 #[test]
10088 fn validate_autores_rejects_at_129_byte_boundary() {
10089 // The 128-byte cap pin — boundary-exceeding case rejected,
10090 // boundary-accepting case passes. Mirrors the peer
10091 // `chart_maintainer_name_shape_rejects_at_129_byte_boundary`
10092 // substrate-side pin, surfaced at the per-axis caller so the
10093 // cap propagates through validate end-to-end. Constructed as
10094 // a single all-`a` token so only the cap arm fires.
10095 let max_ok = "a".repeat(128);
10096 let c = caixa_with_autores(vec![max_ok.as_str()]);
10097 c.validate_autores().unwrap();
10098 let too_long = "a".repeat(129);
10099 let c = caixa_with_autores(vec![too_long.as_str()]);
10100 let err = c.validate_autores().unwrap_err();
10101 let ManifestError::AutorInvalid { reason, .. } = err else {
10102 panic!("expected AutorInvalid, got {err:?}");
10103 };
10104 assert!(reason.contains("128"), "got: {reason}");
10105 assert!(reason.contains("129"), "got: {reason}");
10106 }
10107
10108 // ── validate_repositorio — universal-axis git-repo-URL shape ──────
10109
10110 fn caixa_with_repositorio(repositorio: Option<&str>) -> Caixa {
10111 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10112 c.repositorio = repositorio.map(String::from);
10113 c
10114 }
10115
10116 #[test]
10117 fn validate_repositorio_accepts_none() {
10118 // The omit-the-slot identity: `:repositorio` is optional. The
10119 // gate is a no-op when the author didn't declare a value —
10120 // every caixa without a `:repositorio` line trivially passes,
10121 // and the substrate-side renderers fall back to their
10122 // documented placeholder (`caixa-helm`'s `home: None`,
10123 // `caixa-flux`'s `https://github.com/pleme-io/<nome>` derived
10124 // URL). Mirrors the peer `validate_restart_window_accepts_none`
10125 // posture on the other `Option<String>` Caixa slot.
10126 let c = caixa_with_repositorio(None);
10127 c.validate_repositorio().unwrap();
10128 }
10129
10130 #[test]
10131 fn validate_repositorio_accepts_canonical_forms() {
10132 // Positive control sweep across every documented `:repositorio`
10133 // authoring shape — the same union the shared
10134 // `crate::render::is_git_repo_url` predicate accepts and the
10135 // peer `:deps :fonte :repo` axis already routes through.
10136 // Covers the `github:` shorthand (the canonical pleme-io
10137 // convention used in the `:repositorio` field of every
10138 // manifest fixture across `caixa-helm` / `caixa-mesh` and the
10139 // `examples/`), the `https://…` URL the README quickstart uses,
10140 // the `ssh://`, `git://`, `git@host:path` scp-style SSH, and
10141 // `file://` URL schemes the shared predicate documents.
10142 for repo in [
10143 "github:pleme-io/hello-rio",
10144 "github:pleme-io/checkout",
10145 "https://github.com/pleme-io/hello-rio",
10146 "ssh://git@github.com/pleme-io/hello-rio.git",
10147 "git://github.com/pleme-io/hello-rio.git",
10148 "git@github.com:pleme-io/hello-rio.git",
10149 "file:///srv/pleme/hello-rio",
10150 ] {
10151 let c = caixa_with_repositorio(Some(repo));
10152 c.validate_repositorio()
10153 .unwrap_or_else(|err| panic!("canonical {repo:?} must pass: {err:?}"));
10154 }
10155 }
10156
10157 #[test]
10158 fn validate_repositorio_rejects_empty_some() {
10159 // Canonical paste-from-blank-doc footgun. The narrower
10160 // [`ManifestError::RepositorioEmpty`] arm fires before the
10161 // shape predicate is consulted, mirroring the empty-first
10162 // cascade every peer per-axis identity gate uses
10163 // (`NomeEmpty` → `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid`,
10164 // `FonteRepoEmpty` → `FonteRepoInvalid`). Without this gate
10165 // the empty `Some("")` silently passed the renderer's
10166 // `Option::unwrap_or_else(|| <fallback>)` (which only fires
10167 // on `None`) and landed as `home: ""` in `Chart.yaml` /
10168 // `url: ""` in the FluxCD `GitRepository`.
10169 let c = caixa_with_repositorio(Some(""));
10170 let err = c.validate_repositorio().unwrap_err();
10171 assert!(
10172 matches!(err, ManifestError::RepositorioEmpty),
10173 "got {err:?}",
10174 );
10175 }
10176
10177 #[test]
10178 fn validate_repositorio_rejects_whitespace() {
10179 // Paste-from-doc whitespace footgun. The shared
10180 // `is_git_repo_url` predicate refuses any whitespace byte; a
10181 // trailing space in a `:repositorio` value silently broke
10182 // `git clone '<value> '` at clone time. The diagnostic names
10183 // the offending value verbatim.
10184 let c = caixa_with_repositorio(Some("github:pleme-io/hello-rio "));
10185 let err = c.validate_repositorio().unwrap_err();
10186 let ManifestError::RepositorioInvalid { repositorio, .. } = err else {
10187 panic!("expected RepositorioInvalid, got {err:?}");
10188 };
10189 assert_eq!(repositorio, "github:pleme-io/hello-rio ");
10190 }
10191
10192 #[test]
10193 fn validate_repositorio_rejects_control_char() {
10194 // Paste-from-multiline-doc CRLF footgun — control characters
10195 // at the URL boundary are a class of subprocess-arg injection
10196 // and break git's URL parser at every porcelain entry point.
10197 let c = caixa_with_repositorio(Some("https://example.com/repo\n"));
10198 let err = c.validate_repositorio().unwrap_err();
10199 assert!(
10200 matches!(err, ManifestError::RepositorioInvalid { .. }),
10201 "got {err:?}",
10202 );
10203 }
10204
10205 #[test]
10206 fn validate_repositorio_rejects_leading_dash() {
10207 // Canonical CLI-argument-injection footgun: `git clone <repo>`
10208 // interprets a leading `-` as a CLI flag, so a
10209 // `-upload-pack=…` value escapes the subprocess argument
10210 // boundary. The shared predicate refuses every leading-`-`
10211 // shape at validate time.
10212 let c = caixa_with_repositorio(Some("-upload-pack=evil"));
10213 let err = c.validate_repositorio().unwrap_err();
10214 assert!(
10215 matches!(err, ManifestError::RepositorioInvalid { .. }),
10216 "got {err:?}",
10217 );
10218 }
10219
10220 #[test]
10221 fn validate_repositorio_rejects_missing_colon_separator() {
10222 // The bare `org/repo` ambiguity footgun — `git clone` reads
10223 // a no-`:` form as a relative filesystem path rather than the
10224 // GitHub-shorthand expansion the author probably intended.
10225 // The shared predicate refuses every shape without a `:`
10226 // separator.
10227 let c = caixa_with_repositorio(Some("pleme-io/hello-rio"));
10228 let err = c.validate_repositorio().unwrap_err();
10229 assert!(
10230 matches!(err, ManifestError::RepositorioInvalid { .. }),
10231 "got {err:?}",
10232 );
10233 }
10234
10235 #[test]
10236 fn validate_repositorio_rejects_fragment_anchor() {
10237 // Paste-from-browser-address-bar footgun on the
10238 // `:repositorio` axis — an author copies a GitHub permalink
10239 // to a README section / line-permalink and forgets to trim
10240 // the `#fragment` tail. The shared `is_git_repo_url`
10241 // predicate refuses the byte at the URL-grammar layer
10242 // (libcurl strips the fragment before opening the
10243 // transport, so the byte rides verbatim into the rendered
10244 // `Chart.yaml` `home:` and FluxCD `GitRepository` `url:`
10245 // fields but is silently dropped on the wire — two
10246 // manifest variants whose values differ only in their
10247 // fragment anchor lock to two distinct rendered artifacts
10248 // for the byte-identical clone, defeating the THEORY.md
10249 // §V.2 render-determinism contract on the `:repositorio`
10250 // axis the peer `:fonte :repo` axis already closes).
10251 let c = caixa_with_repositorio(Some("https://github.com/pleme-io/hello-rio#readme"));
10252 let err = c.validate_repositorio().unwrap_err();
10253 let ManifestError::RepositorioInvalid {
10254 repositorio,
10255 reason,
10256 } = err
10257 else {
10258 panic!("expected RepositorioInvalid, got {err:?}");
10259 };
10260 assert_eq!(repositorio, "https://github.com/pleme-io/hello-rio#readme");
10261 assert!(
10262 reason.contains("must not contain `#`"),
10263 "reason must surface the fragment-`#` arm, got {reason:?}"
10264 );
10265 }
10266
10267 #[test]
10268 fn validate_repositorio_rejects_query_string() {
10269 // Paste-from-browser-address-bar footgun on the
10270 // `:repositorio` axis (peer with the a68f818 fragment-`#`
10271 // arm on the same axis). An author copies a GitHub tab
10272 // deep-link out of the address bar and forgets to trim
10273 // the `?tab=…` query tail. The shared `is_git_repo_url`
10274 // predicate refuses the byte at the URL-grammar layer
10275 // (GitHub / GitLab / Bitbucket silently ignore the
10276 // `?query` tail and serve the same repo regardless, so
10277 // the byte rides verbatim into the rendered `Chart.yaml`
10278 // `home:` and FluxCD `GitRepository` `url:` fields but
10279 // is silently masked at the wire — two manifest variants
10280 // whose values differ only in their query tail lock to
10281 // two distinct rendered artifacts for the byte-identical
10282 // clone, defeating the THEORY.md §V.2 render-determinism
10283 // contract on the `:repositorio` axis the peer `:fonte
10284 // :repo` axis already closes).
10285 let c = caixa_with_repositorio(Some(
10286 "https://github.com/pleme-io/hello-rio?tab=readme-ov-file",
10287 ));
10288 let err = c.validate_repositorio().unwrap_err();
10289 let ManifestError::RepositorioInvalid {
10290 repositorio,
10291 reason,
10292 } = err
10293 else {
10294 panic!("expected RepositorioInvalid, got {err:?}");
10295 };
10296 assert_eq!(
10297 repositorio,
10298 "https://github.com/pleme-io/hello-rio?tab=readme-ov-file"
10299 );
10300 assert!(
10301 reason.contains("must not contain `?`"),
10302 "reason must surface the query-`?` arm, got {reason:?}"
10303 );
10304 }
10305
10306 #[test]
10307 fn validate_repositorio_rejects_embedded_backslash() {
10308 // Windows-file-path-confusion footgun on the `:repositorio`
10309 // axis (peer with the prior fragment-`#` / query-`?` arms on
10310 // the same axis, and peer with the new dep-level `:fonte :repo`
10311 // backslash arm on the URL-grammar trajectory). An author
10312 // pastes a Windows Explorer address-bar `file:///C:\Users\me\
10313 // hello-rio` into the `:repositorio` slot, expecting the
10314 // `lareira-<nome>` chart's `home:` field and the FluxCD
10315 // `GitRepository` `url:` field to render the canonical local
10316 // file-URI. The shared `is_git_repo_url` predicate refuses
10317 // the byte at the URL-grammar layer (libcurl silently
10318 // translates `\` → `/` on some platforms and refuses it on
10319 // others, so the byte rides verbatim into the rendered
10320 // artifacts but is silently rewritten or rejected at the wire
10321 // — two manifest variants whose values differ only in
10322 // backslash-vs-forward-slash lock to two distinct rendered
10323 // artifacts for the byte-identical clone, defeating the
10324 // THEORY.md §V.2 render-determinism contract on the
10325 // `:repositorio` axis the peer `:fonte :repo` axis already
10326 // closes).
10327 let c = caixa_with_repositorio(Some("file:///C:\\Users\\me\\hello-rio"));
10328 let err = c.validate_repositorio().unwrap_err();
10329 let ManifestError::RepositorioInvalid {
10330 repositorio,
10331 reason,
10332 } = err
10333 else {
10334 panic!("expected RepositorioInvalid, got {err:?}");
10335 };
10336 assert_eq!(repositorio, "file:///C:\\Users\\me\\hello-rio");
10337 assert!(
10338 reason.contains("must not contain `\\`"),
10339 "reason must surface the backslash-`\\` arm, got {reason:?}"
10340 );
10341 }
10342
10343 #[test]
10344 fn validate_repositorio_rejects_uri_template_placeholder() {
10345 // URI Template (RFC 6570) placeholder footgun on the
10346 // `:repositorio` axis (peer with the prior fragment-`#` /
10347 // query-`?` / backslash-`\` arms on the same axis, and peer
10348 // with the new dep-level `:fonte :repo` `{` / `}` arm on the
10349 // URL-grammar trajectory). An author pastes a quick-start
10350 // README snippet / OpenAPI `servers:` URL / Helm chart
10351 // `home:` template carrying unresolved `{org}` / `{repo}`
10352 // placeholders into the `:repositorio` slot, expecting the
10353 // substrate to resolve the placeholder downstream. The
10354 // shared `is_git_repo_url` predicate refuses the byte at the
10355 // URL-grammar layer (libcurl percent-encodes `{` / `}` to
10356 // `%7B` / `%7D` on the wire, so the byte round-trips
10357 // inconsistently between the rendered `Chart.yaml home:` /
10358 // FluxCD `GitRepository url:` and the resolver's `git clone`
10359 // invocation, defeating the THEORY.md §V.2 render-
10360 // determinism contract on the `:repositorio` axis the peer
10361 // `:fonte :repo` axis already closes; every git porcelain
10362 // entry-point additionally fetches a nonexistent literal-
10363 // `{placeholder}`-named path far from the source caixa.lisp).
10364 let c = caixa_with_repositorio(Some("https://github.com/{org}/hello-rio"));
10365 let err = c.validate_repositorio().unwrap_err();
10366 let ManifestError::RepositorioInvalid {
10367 repositorio,
10368 reason,
10369 } = err
10370 else {
10371 panic!("expected RepositorioInvalid, got {err:?}");
10372 };
10373 assert_eq!(repositorio, "https://github.com/{org}/hello-rio");
10374 assert!(
10375 reason.contains("must not contain `{`"),
10376 "reason must surface the open-brace `{{` arm, got {reason:?}"
10377 );
10378 assert!(
10379 reason.contains("URI Template") || reason.contains("RFC 6570"),
10380 "reason must name the RFC 6570 URI Template grammar, got {reason:?}"
10381 );
10382 }
10383
10384 #[test]
10385 fn validate_repositorio_empty_takes_precedence_over_shape() {
10386 // Empty-first cascade pin: the empty `Some("")` surfaces the
10387 // narrower `RepositorioEmpty` not the shape-predicate-wrapped
10388 // `RepositorioInvalid`, mirroring the peer
10389 // `NomeEmpty` → `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid`,
10390 // `FonteRepoEmpty` → `FonteRepoInvalid` cascades. The shared
10391 // `is_git_repo_url` predicate also rejects the empty input
10392 // (defensively, with its own `"must not be empty"` reason),
10393 // but the manifest-layer empty arm runs first to surface the
10394 // narrower diagnostic verbatim.
10395 let c = caixa_with_repositorio(Some(""));
10396 let err = c.validate_repositorio().unwrap_err();
10397 assert!(
10398 matches!(err, ManifestError::RepositorioEmpty),
10399 "got {err:?}",
10400 );
10401 }
10402
10403 #[test]
10404 fn validate_repositorio_diagnostic_carries_offending_value() {
10405 // Diagnostic-shape pin (peer with
10406 // `validate_autores_diagnostic_carries_offending_author`): the
10407 // error's Display surfaces the offending value + slot name
10408 // verbatim, so a `feira lint` run can render the diagnostic
10409 // without re-parsing and the author can grep their caixa.lisp
10410 // for the offending `:repositorio` value.
10411 let c = caixa_with_repositorio(Some("pleme-io/hello-rio"));
10412 let rendered = c.validate_repositorio().unwrap_err().to_string();
10413 assert!(
10414 rendered.contains(":repositorio"),
10415 "diagnostic must name the offending slot: {rendered}",
10416 );
10417 assert!(
10418 rendered.contains("pleme-io/hello-rio"),
10419 "diagnostic must quote the offending value: {rendered}",
10420 );
10421 }
10422
10423 // ── validate_descricao — universal-axis Chart.yaml description shape ──
10424
10425 fn caixa_with_descricao(descricao: Option<&str>) -> Caixa {
10426 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10427 c.descricao = descricao.map(String::from);
10428 c
10429 }
10430
10431 #[test]
10432 fn validate_descricao_accepts_none() {
10433 // The omit-the-slot identity: `:descricao` is optional. The
10434 // gate is a no-op when the author didn't declare a value —
10435 // every caixa without a `:descricao` line trivially passes,
10436 // and the substrate-side renderers fall back to their
10437 // documented `caixa.nome`-derived placeholder. Mirrors the
10438 // peer `validate_repositorio_accepts_none` posture on the
10439 // sibling `Option<String>` Caixa slot.
10440 let c = caixa_with_descricao(None);
10441 c.validate_descricao().unwrap();
10442 }
10443
10444 #[test]
10445 fn validate_descricao_accepts_canonical_summary() {
10446 // Positive control: the canonical pleme-io descricao shape —
10447 // a short free-form prose summary — passes the gate. Covers
10448 // the fixture shapes the `caixa-helm` / `caixa-flux` /
10449 // `caixa-mesh` test fixtures use (`"Canonical Rust→wasm32-
10450 // wasip2 caixa Servico."`, `"Checkout flow."`).
10451 for desc in [
10452 "Canonical Rust→wasm32-wasip2 caixa Servico.",
10453 "Checkout flow.",
10454 "AWS provider caixa for tatara-lisp",
10455 "FIXME — describe this caixa",
10456 "x",
10457 ] {
10458 let c = caixa_with_descricao(Some(desc));
10459 c.validate_descricao()
10460 .unwrap_or_else(|err| panic!("canonical {desc:?} must pass: {err:?}"));
10461 }
10462 }
10463
10464 #[test]
10465 fn validate_descricao_rejects_empty_some() {
10466 // Canonical paste-from-blank-doc footgun. Without this gate
10467 // the empty `Some("")` silently passed the renderer's
10468 // `Option::unwrap_or_else(|| <fallback>)` (which only fires
10469 // on `None`) and landed as `description: ""` in `Chart.yaml`
10470 // and a blank `README.md` header. Mirrors the peer
10471 // [`ManifestError::RepositorioEmpty`] empty-arm on the
10472 // sibling `Option<String>` Caixa slot.
10473 let c = caixa_with_descricao(Some(""));
10474 let err = c.validate_descricao().unwrap_err();
10475 assert!(matches!(err, ManifestError::DescricaoEmpty), "got {err:?}",);
10476 }
10477
10478 #[test]
10479 fn validate_descricao_rejects_leading_whitespace() {
10480 // Paste-from-aligned-doc footgun: a leading ASCII space the
10481 // bare empty-arm gate accepted, the shape predicate now
10482 // refuses. The diagnostic carries the offending value
10483 // verbatim (with the leading space preserved) so the author
10484 // can grep their caixa.lisp for the exact `:descricao` line
10485 // and fix the round-trip-inconsistent leading whitespace.
10486 // Mirrors the peer
10487 // `validate_licenca_rejects_leading_whitespace` arm on the
10488 // sibling `:licenca` axis.
10489 let c = caixa_with_descricao(Some(" Checkout flow."));
10490 let err = c.validate_descricao().unwrap_err();
10491 let ManifestError::DescricaoInvalid { descricao, reason } = err else {
10492 panic!("expected DescricaoInvalid, got {err:?}");
10493 };
10494 assert_eq!(descricao, " Checkout flow.");
10495 assert!(reason.contains("whitespace"), "got: {reason:?}");
10496 }
10497
10498 #[test]
10499 fn validate_descricao_rejects_trailing_whitespace() {
10500 // Paste-from-doc footgun: a trailing ASCII space the bare
10501 // empty-arm gate accepted, the shape predicate now refuses.
10502 let c = caixa_with_descricao(Some("Checkout flow. "));
10503 let err = c.validate_descricao().unwrap_err();
10504 let ManifestError::DescricaoInvalid { descricao, reason } = err else {
10505 panic!("expected DescricaoInvalid, got {err:?}");
10506 };
10507 assert_eq!(descricao, "Checkout flow. ");
10508 assert!(reason.contains("whitespace"), "got: {reason:?}");
10509 }
10510
10511 #[test]
10512 fn validate_descricao_rejects_embedded_newline() {
10513 // Paste-from-multiline-doc footgun: an embedded LF the bare
10514 // empty-arm gate accepted, the shape predicate now refuses.
10515 // Without this gate the embedded newline silently landed in
10516 // the rendered Chart.yaml as a multi-line YAML block scalar,
10517 // and every chart-aware UI (`helm list`, `helm search`,
10518 // Artifact Hub) renders the description in a single-line
10519 // column so the embedded newline is silently dropped at
10520 // every downstream consumer.
10521 let c = caixa_with_descricao(Some("Checkout\nflow."));
10522 let err = c.validate_descricao().unwrap_err();
10523 assert!(
10524 matches!(err, ManifestError::DescricaoInvalid { .. }),
10525 "got {err:?}",
10526 );
10527 assert!(err.to_string().contains("newline"), "got {err}");
10528 }
10529
10530 #[test]
10531 fn validate_descricao_rejects_embedded_carriage_return() {
10532 // Paste-from-Windows-CRLF-doc footgun.
10533 let c = caixa_with_descricao(Some("Checkout\rflow."));
10534 let err = c.validate_descricao().unwrap_err();
10535 assert!(
10536 matches!(err, ManifestError::DescricaoInvalid { .. }),
10537 "got {err:?}",
10538 );
10539 assert!(err.to_string().contains("carriage return"), "got {err}");
10540 }
10541
10542 #[test]
10543 fn validate_descricao_rejects_embedded_tab() {
10544 // Tab-from-aligned-doc footgun.
10545 let c = caixa_with_descricao(Some("Checkout\tflow."));
10546 let err = c.validate_descricao().unwrap_err();
10547 assert!(
10548 matches!(err, ManifestError::DescricaoInvalid { .. }),
10549 "got {err:?}",
10550 );
10551 assert!(err.to_string().contains("tab"), "got {err}");
10552 }
10553
10554 #[test]
10555 fn validate_descricao_rejects_embedded_control_bytes() {
10556 // Paste-from-binary-blob footgun: every other control byte
10557 // (NUL, BEL, ESC, DEL) is refused at validate time. Mirrors
10558 // the peer SPDX-expression control-byte arm.
10559 for s in [
10560 "Checkout\x00flow.",
10561 "Checkout\x07flow.",
10562 "Checkout\x1bflow.",
10563 "Checkout\x7fflow.",
10564 ] {
10565 let c = caixa_with_descricao(Some(s));
10566 let err = c.validate_descricao().unwrap_err();
10567 assert!(
10568 matches!(err, ManifestError::DescricaoInvalid { .. }),
10569 "{s:?} got {err:?}",
10570 );
10571 assert!(
10572 err.to_string().contains("control character"),
10573 "{s:?} got {err}",
10574 );
10575 }
10576 }
10577
10578 #[test]
10579 fn validate_descricao_accepts_unicode_prose() {
10580 // Positive control: Unicode prose is accepted — the
10581 // canonical fixtures carry `→` (U+2192) and `—` (U+2014),
10582 // and `Caixa::template`'s `"FIXME — describe this caixa"`
10583 // scaffold every `feira init` emits must continue to pass.
10584 for s in [
10585 "Canonical Rust→wasm32-wasip2 caixa Servico.",
10586 "FIXME — describe this caixa",
10587 "Caixa pour le projet tâche",
10588 "日本語の説明",
10589 ] {
10590 let c = caixa_with_descricao(Some(s));
10591 c.validate_descricao()
10592 .unwrap_or_else(|err| panic!("Unicode {s:?} must pass: {err:?}"));
10593 }
10594 }
10595
10596 #[test]
10597 fn validate_descricao_empty_takes_precedence_over_shape() {
10598 // Cascade pin: a `Some("")` surfaces the narrower
10599 // `DescricaoEmpty` arm, not the broader `DescricaoInvalid`
10600 // shape-predicate arm. Mirrors the peer
10601 // `validate_licenca_empty_takes_precedence_over_shape` pin
10602 // on the sibling `:licenca` axis.
10603 let c = caixa_with_descricao(Some(""));
10604 let err = c.validate_descricao().unwrap_err();
10605 assert!(matches!(err, ManifestError::DescricaoEmpty), "got {err:?}",);
10606 }
10607
10608 #[test]
10609 fn validate_descricao_invalid_diagnostic_carries_offending_value_and_slot() {
10610 // Diagnostic-shape pin: the error's Display surfaces both
10611 // the `:descricao` slot name and the offending value
10612 // verbatim, so a `feira lint` run can render the diagnostic
10613 // without re-parsing and the author can grep their caixa.lisp
10614 // for the offending `:descricao` line. Mirrors the peer
10615 // `validate_licenca_invalid_diagnostic_carries_offending_value_and_slot`
10616 // pin (ee2e888) on the sibling `:licenca` axis.
10617 // The `{descricao:?}` Debug format escapes embedded control
10618 // bytes; the quoted offending value surfaces as
10619 // `"Checkout\nflow."` (literal backslash-n) in the rendered
10620 // diagnostic. The author can grep their caixa.lisp for the
10621 // literal `Checkout` summary prefix.
10622 let c = caixa_with_descricao(Some("Checkout\nflow."));
10623 let rendered = c.validate_descricao().unwrap_err().to_string();
10624 assert!(
10625 rendered.contains(":descricao"),
10626 "diagnostic must name the offending slot: {rendered}",
10627 );
10628 assert!(
10629 rendered.contains("Checkout\\nflow."),
10630 "diagnostic must quote the offending value (debug-escaped): {rendered}",
10631 );
10632 }
10633
10634 #[test]
10635 fn validate_descricao_template_passes() {
10636 // Round-trip pin: the bare `Caixa::template` shape carries
10637 // `:descricao "FIXME — describe this caixa"` (a non-empty
10638 // sentinel), so the template-derived Caixa passes the gate by
10639 // construction. A future template-shape change that omits or
10640 // empties `:descricao` would surface here as a regression.
10641 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10642 c.validate_descricao().unwrap();
10643 }
10644
10645 #[test]
10646 fn validate_descricao_diagnostic_names_offending_slot() {
10647 // Diagnostic-shape pin (peer with
10648 // `validate_repositorio_diagnostic_carries_offending_value`):
10649 // the error's Display surfaces the `:descricao` slot name
10650 // verbatim, so a `feira lint` run can render the diagnostic
10651 // without re-parsing and the author can grep their caixa.lisp
10652 // for the offending `:descricao` line.
10653 let c = caixa_with_descricao(Some(""));
10654 let rendered = c.validate_descricao().unwrap_err().to_string();
10655 assert!(
10656 rendered.contains(":descricao"),
10657 "diagnostic must name the offending slot: {rendered}",
10658 );
10659 }
10660
10661 // ── validate_licenca — universal-axis chart README license shape ──
10662
10663 fn caixa_with_licenca(licenca: Option<&str>) -> Caixa {
10664 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10665 c.licenca = licenca.map(String::from);
10666 c
10667 }
10668
10669 #[test]
10670 fn validate_licenca_accepts_none() {
10671 // The omit-the-slot identity: `:licenca` is optional. The
10672 // gate is a no-op when the author didn't declare a value —
10673 // every caixa without a `:licenca` line trivially passes,
10674 // and the substrate-side `caixa-helm` renderer falls back to
10675 // the documented `"MIT"` placeholder. Mirrors the peer
10676 // `validate_descricao_accepts_none` posture on the sibling
10677 // `Option<String>` Caixa slot.
10678 let c = caixa_with_licenca(None);
10679 c.validate_licenca().unwrap();
10680 }
10681
10682 #[test]
10683 fn validate_licenca_accepts_canonical_expressions() {
10684 // Positive control: every canonical SPDX expression shape
10685 // pleme-io carries in its existing fixtures + the canonical
10686 // SPDX dual-license / with-exception / `+`-suffix / grouped /
10687 // user-defined-reference shapes all pass the gate. Covers
10688 // the single-license, `OR`-compound, `AND`-compound,
10689 // `WITH`-exception, parenthesis-grouped, `+`-suffix, and
10690 // `LicenseRef-` / `DocumentRef-:LicenseRef-` shapes — every
10691 // production the SPDX 2.1 expression grammar admits that
10692 // sits within the alphabet floor the
10693 // `is_spdx_expression_shape` predicate enforces.
10694 for lic in [
10695 "MIT",
10696 "Apache-2.0",
10697 "Apache-2.0 OR MIT",
10698 "Apache-2.0 AND MIT",
10699 "BSD-3-Clause",
10700 "MPL-2.0",
10701 "GPL-3.0-or-later",
10702 "GPL-2.0+",
10703 "Apache-2.0 WITH LLVM-exception",
10704 "(MIT OR Apache-2.0) AND BSD-3-Clause",
10705 "(MIT OR Apache-2.0) AND BSD-3-Clause AND ISC",
10706 "LicenseRef-MyLicense",
10707 "DocumentRef-spdx-tool:LicenseRef-MIT-Style",
10708 "x",
10709 ] {
10710 let c = caixa_with_licenca(Some(lic));
10711 c.validate_licenca()
10712 .unwrap_or_else(|err| panic!("canonical {lic:?} must pass: {err:?}"));
10713 }
10714 }
10715
10716 #[test]
10717 fn validate_licenca_rejects_trailing_whitespace() {
10718 // Paste-from-doc whitespace footgun. A trailing space in the
10719 // `:licenca` value would silently break a downstream SPDX
10720 // parser that splits on exact `AND` / `OR` / `WITH` keyword
10721 // boundaries. The shape predicate refuses every trailing
10722 // whitespace byte by construction. Peer with
10723 // `validate_repositorio_rejects_whitespace` and
10724 // `validate_edicao_rejects_trailing_whitespace`.
10725 let c = caixa_with_licenca(Some("MIT "));
10726 let err = c.validate_licenca().unwrap_err();
10727 let ManifestError::LicencaInvalid { licenca, .. } = err else {
10728 panic!("expected LicencaInvalid, got {err:?}");
10729 };
10730 assert_eq!(licenca, "MIT ");
10731 }
10732
10733 #[test]
10734 fn validate_licenca_rejects_leading_whitespace() {
10735 // Symmetric paste-from-doc whitespace footgun on the leading
10736 // boundary — the gate refuses every shape that starts with a
10737 // space byte by construction. Peer with
10738 // `validate_edicao_rejects_leading_whitespace`.
10739 let c = caixa_with_licenca(Some(" MIT"));
10740 let err = c.validate_licenca().unwrap_err();
10741 assert!(
10742 matches!(err, ManifestError::LicencaInvalid { .. }),
10743 "got {err:?}",
10744 );
10745 }
10746
10747 #[test]
10748 fn validate_licenca_rejects_control_char() {
10749 // Paste-from-multiline-doc CRLF footgun — control characters
10750 // at the value boundary land as a malformed line in the
10751 // rendered chart `README.md` `## License` section. Peer with
10752 // `validate_repositorio_rejects_control_char` and
10753 // `validate_edicao_rejects_control_char`.
10754 for lic in ["MIT\n", "MIT\r\n", "MIT\rApache-2.0"] {
10755 let c = caixa_with_licenca(Some(lic));
10756 let err = c.validate_licenca().unwrap_err();
10757 assert!(
10758 matches!(err, ManifestError::LicencaInvalid { .. }),
10759 "expected LicencaInvalid on {lic:?}, got {err:?}",
10760 );
10761 }
10762 }
10763
10764 #[test]
10765 fn validate_licenca_rejects_tab() {
10766 // Tab-from-aligned-doc footgun — SPDX expressions use a
10767 // single ASCII space between tokens; a tab breaks every
10768 // downstream SPDX parser that splits on exact `" "`
10769 // boundaries.
10770 let c = caixa_with_licenca(Some("MIT\tOR Apache-2.0"));
10771 let err = c.validate_licenca().unwrap_err();
10772 assert!(
10773 matches!(err, ManifestError::LicencaInvalid { .. }),
10774 "got {err:?}",
10775 );
10776 }
10777
10778 #[test]
10779 fn validate_licenca_rejects_non_ascii() {
10780 // Smart-quote / non-ASCII paste footgun — SPDX identifiers
10781 // are ASCII per the `idstring = 1*(ALPHA / DIGIT / "-" /
10782 // ".")` production. The shape predicate refuses every
10783 // non-ASCII byte by construction; peer with
10784 // `validate_edicao_rejects_non_ascii_lookalike`.
10785 for lic in ["MIT\u{a0}OR Apache-2.0", "MIT\u{2013}1.0", "Café-1.0"] {
10786 let c = caixa_with_licenca(Some(lic));
10787 let err = c.validate_licenca().unwrap_err();
10788 assert!(
10789 matches!(err, ManifestError::LicencaInvalid { .. }),
10790 "expected LicencaInvalid on {lic:?}, got {err:?}",
10791 );
10792 }
10793 }
10794
10795 #[test]
10796 fn validate_licenca_rejects_underscore() {
10797 // Underscore-instead-of-hyphen typo footgun — `Apache_2.0` /
10798 // `MIT_Style` / `BSD_3_Clause` are familiar shapes from
10799 // snake-case identifier conventions that don't apply to the
10800 // SPDX `idstring` grammar (which admits only `ALPHA / DIGIT /
10801 // "-" / "."`). The shape predicate refuses every underscore
10802 // byte by construction.
10803 for lic in ["Apache_2.0", "MIT_Style", "BSD_3_Clause"] {
10804 let c = caixa_with_licenca(Some(lic));
10805 let err = c.validate_licenca().unwrap_err();
10806 assert!(
10807 matches!(err, ManifestError::LicencaInvalid { .. }),
10808 "expected LicencaInvalid on {lic:?}, got {err:?}",
10809 );
10810 }
10811 }
10812
10813 #[test]
10814 fn validate_licenca_rejects_comma_separator() {
10815 // Comma-instead-of-`OR`-keyword colloquial idiom footgun —
10816 // SPDX expressions compose multiple licenses via `AND` / `OR`
10817 // keywords, not the comma separator. The shape predicate
10818 // refuses every comma byte by construction.
10819 for lic in ["MIT, Apache-2.0", "MIT,Apache-2.0"] {
10820 let c = caixa_with_licenca(Some(lic));
10821 let err = c.validate_licenca().unwrap_err();
10822 assert!(
10823 matches!(err, ManifestError::LicencaInvalid { .. }),
10824 "expected LicencaInvalid on {lic:?}, got {err:?}",
10825 );
10826 }
10827 }
10828
10829 #[test]
10830 fn validate_licenca_rejects_slash_dual_license() {
10831 // Slash-dual-license colloquial idiom footgun — the
10832 // `MIT/Apache-2.0` shape is common in Cargo's pre-SPDX
10833 // `package.license` field but non-SPDX; the SPDX equivalent
10834 // is `MIT OR Apache-2.0`. The shape predicate refuses every
10835 // forward-slash byte by construction.
10836 for lic in ["MIT/Apache-2.0", "MIT/BSD-3-Clause"] {
10837 let c = caixa_with_licenca(Some(lic));
10838 let err = c.validate_licenca().unwrap_err();
10839 assert!(
10840 matches!(err, ManifestError::LicencaInvalid { .. }),
10841 "expected LicencaInvalid on {lic:?}, got {err:?}",
10842 );
10843 }
10844 }
10845
10846 #[test]
10847 fn validate_licenca_rejects_semicolon_separator() {
10848 // Semicolon-list-separator confusion footgun — adjacent to
10849 // the comma-separator idiom, every list-separator-belongs-
10850 // to-list-grammar confusion lands here.
10851 let c = caixa_with_licenca(Some("MIT; Apache-2.0"));
10852 let err = c.validate_licenca().unwrap_err();
10853 assert!(
10854 matches!(err, ManifestError::LicencaInvalid { .. }),
10855 "got {err:?}",
10856 );
10857 }
10858
10859 #[test]
10860 fn validate_licenca_empty_takes_precedence_over_shape() {
10861 // Empty-first cascade pin: the empty `Some("")` surfaces the
10862 // narrower `LicencaEmpty` not the shape-predicate-wrapped
10863 // `LicencaInvalid`, mirroring the peer
10864 // `validate_edicao_empty_takes_precedence_over_shape` and
10865 // `validate_repositorio_empty_takes_precedence_over_shape`
10866 // (`RepositorioEmpty` → `RepositorioInvalid`), `NomeEmpty` →
10867 // `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid` cascades.
10868 // The shape predicate also refuses the empty input
10869 // (defensively — `"must not be empty"`), but the manifest-
10870 // layer empty arm runs first to surface the narrower
10871 // diagnostic verbatim.
10872 let c = caixa_with_licenca(Some(""));
10873 let err = c.validate_licenca().unwrap_err();
10874 assert!(matches!(err, ManifestError::LicencaEmpty), "got {err:?}",);
10875 }
10876
10877 #[test]
10878 fn validate_licenca_invalid_diagnostic_carries_offending_value() {
10879 // Diagnostic-shape pin on the shape-predicate arm (peer with
10880 // `validate_edicao_invalid_diagnostic_carries_offending_value`
10881 // and `validate_repositorio_diagnostic_carries_offending_value`):
10882 // the error's Display surfaces the offending value + slot
10883 // name verbatim, so a `feira lint` run can render the
10884 // diagnostic without re-parsing and the author can grep
10885 // their caixa.lisp for the offending `:licenca` value.
10886 let c = caixa_with_licenca(Some("Apache_2.0"));
10887 let rendered = c.validate_licenca().unwrap_err().to_string();
10888 assert!(
10889 rendered.contains(":licenca"),
10890 "diagnostic must name the offending slot: {rendered}",
10891 );
10892 assert!(
10893 rendered.contains("Apache_2.0"),
10894 "diagnostic must quote the offending value: {rendered}",
10895 );
10896 }
10897
10898 #[test]
10899 fn validate_licenca_rejects_empty_some() {
10900 // Canonical paste-from-blank-doc footgun. Without this gate
10901 // the empty `Some("")` silently passed the renderer's
10902 // `Option::unwrap_or_else(|| "MIT".into())` (which only
10903 // fires on `None`) and landed as a bare trailing period in
10904 // the rendered chart `README.md` `## License` section.
10905 // Mirrors the peer [`ManifestError::DescricaoEmpty`] empty-
10906 // arm on the sibling `Option<String>` Caixa slot.
10907 let c = caixa_with_licenca(Some(""));
10908 let err = c.validate_licenca().unwrap_err();
10909 assert!(matches!(err, ManifestError::LicencaEmpty), "got {err:?}",);
10910 }
10911
10912 #[test]
10913 fn validate_licenca_template_passes() {
10914 // Round-trip pin: the bare `Caixa::template` shape (whether
10915 // it carries `:licenca` or omits it) passes the gate by
10916 // construction. A future template-shape change that
10917 // introduced `(:licenca "")` would surface here as a
10918 // regression. Mirrors the peer
10919 // `validate_descricao_template_passes` pin.
10920 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10921 c.validate_licenca().unwrap();
10922 }
10923
10924 #[test]
10925 fn validate_licenca_diagnostic_names_offending_slot() {
10926 // Diagnostic-shape pin (peer with
10927 // `validate_descricao_diagnostic_names_offending_slot`):
10928 // the error's Display surfaces the `:licenca` slot name
10929 // verbatim, so a `feira lint` run can render the diagnostic
10930 // without re-parsing and the author can grep their caixa.lisp
10931 // for the offending `:licenca` line.
10932 let c = caixa_with_licenca(Some(""));
10933 let rendered = c.validate_licenca().unwrap_err().to_string();
10934 assert!(
10935 rendered.contains(":licenca"),
10936 "diagnostic must name the offending slot: {rendered}",
10937 );
10938 }
10939
10940 // ── Caixa::licenca — outer top-level Option<&str> scalar accessor ──
10941
10942 #[test]
10943 fn licenca_returns_licenca_byte_string_verbatim_across_permutations() {
10944 // The canonical per-`Caixa` `:licenca` SPDX-expression scalar
10945 // pin: [`Caixa::licenca`] must return the `:licenca` typed
10946 // byte-string verbatim as an `Option<&str>`, byte-equal to the
10947 // raw `self.licenca.as_deref()` access across every
10948 // representative value in the accept-set — `None` (the "omit
10949 // the slot to defer to the caixa-helm renderer's `MIT`
10950 // fallback" arm every existing fixture without a `:licenca`
10951 // line carries), `Some("")` (a past-the-guard sentinel that
10952 // pins the accessor doesn't perform a silent
10953 // `Some("") → None` collapse on the empty arm — validate
10954 // rejects `Some("")` through `LicencaEmpty` but the accessor
10955 // must ship the raw slot verbatim so a validate-time gate
10956 // regression surfaces at the caixa-helm emit boundary rather
10957 // than being silently absorbed into the fallback), `Some("MIT")`
10958 // (the canonical single-license shape every `feira init`
10959 // template scaffolds), `Some("Apache-2.0 OR MIT")` (the
10960 // canonical `OR`-compound shape the peer
10961 // `validate_licenca_accepts_canonical_expressions` positive
10962 // sweep exercises), `Some("(MIT OR Apache-2.0) AND
10963 // BSD-3-Clause")` (the canonical parenthesis-grouped shape),
10964 // `Some("MIT ")` / `Some(" MIT")` / `Some("MIT\n")` /
10965 // `Some("Apache_2.0")` / `Some("MIT,Apache-2.0")` (past-the-
10966 // guard sentinels — validate rejects each through
10967 // `LicencaInvalid` but the accessor must ship the raw slot
10968 // verbatim).
10969 //
10970 // First outer top-level [`Caixa`] `Option<&str>`-return scalar
10971 // accessor pin on the substrate primitive — opens the "outer
10972 // [`Caixa`] `Option<&str>` scalar" projection pattern the
10973 // sibling per-`Caixa` `:descricao` / `:repositorio` / `:edicao`
10974 // future lifts fold on. Sibling in shape to the peer per-`:placement`
10975 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
10976 // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
10977 // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
10978 // axes, extended onto the outer top-level [`Caixa`] universal-
10979 // axis surface. Pins against a future silent detour that
10980 // returned an owned `Option<String>` (which would type-check
10981 // but silently allocate on every accessor call, breaking the
10982 // zero-cost projection every peer sibling accessor carries), a
10983 // `Some("") → None` collapse (which would silently absorb the
10984 // `LicencaEmpty` refusal case at the accessor boundary and the
10985 // caixa-helm emit path would silently fall back to `"MIT"` on
10986 // a struct-literal `Caixa { licenca: Some(""), .. }`), or a
10987 // `None → Some("MIT")` collapse (which would silently reify
10988 // the caixa-helm renderer's `"MIT"` fallback at the accessor
10989 // boundary and every downstream consumer keying off the
10990 // `Option::is_none()` discriminator would lose the "author
10991 // omitted the slot" signal).
10992 for licenca in [
10993 None,
10994 Some(""),
10995 Some("MIT"),
10996 Some("Apache-2.0 OR MIT"),
10997 Some("(MIT OR Apache-2.0) AND BSD-3-Clause"),
10998 Some("MIT "),
10999 Some(" MIT"),
11000 Some("MIT\n"),
11001 Some("Apache_2.0"),
11002 Some("MIT,Apache-2.0"),
11003 ] {
11004 let c = caixa_with_licenca(licenca);
11005 assert_eq!(
11006 c.licenca(),
11007 licenca,
11008 "Caixa::licenca must return :licenca verbatim (got {:?}, \
11009 expected {licenca:?})",
11010 c.licenca(),
11011 );
11012 assert_eq!(
11013 c.licenca(),
11014 c.licenca.as_deref(),
11015 "Caixa::licenca must byte-equal the raw \
11016 `self.licenca.as_deref()` field access across every \
11017 value in the Option<&str> accept-set",
11018 );
11019 }
11020 }
11021
11022 #[test]
11023 fn validate_licenca_empty_arm_routes_through_accessor() {
11024 // Composition pin: [`Caixa::validate_licenca`]'s empty-arm gate
11025 // must key off [`Caixa::licenca`], not the raw
11026 // `self.licenca.as_deref()` field access. Structurally: a
11027 // `Caixa { licenca: Some(""), .. }` must surface the
11028 // `LicencaEmpty` refusal exactly, and a
11029 // `Caixa { licenca: Some("MIT"), .. }` (the canonical
11030 // single-license form) must pass validate. The pair jointly
11031 // pins the accessor + validate-gate composition: any future
11032 // silent detour that had the accessor return `None` on the
11033 // empty arm (a `.filter(|s| !s.is_empty())` collapse) would
11034 // silently absorb the `LicencaEmpty` refusal at the accessor
11035 // boundary and the validate gate would accept a struct-literal
11036 // `Caixa { licenca: Some(""), .. }` — the composition pin
11037 // catches that at caixa-core build time.
11038 //
11039 // Peer of the per-`:politicas :circuit-breaker`
11040 // [`crate::aplicacao::CircuitBreaker::max_failures`] (3a74062)
11041 // accessor-composition pin
11042 // (`validate_politicas_max_failures_zero_floor_arm_routes_through_accessor`)
11043 // on the sibling per-M3-mesh-slot required-`u32` axis — same
11044 // "the validate / shape-gate predicate must route through the
11045 // substrate-primitive typed dispatch" discipline extended onto
11046 // the outer top-level [`Caixa`] universal-axis
11047 // `Option<&str>`-composition surface.
11048 let c = caixa_with_licenca(Some(""));
11049 assert!(
11050 matches!(c.validate_licenca(), Err(ManifestError::LicencaEmpty)),
11051 "validate_licenca must reject licenca == Some(\"\") with \
11052 LicencaEmpty — the accessor and the validate gate must \
11053 route through the same substrate-primitive typed dispatch \
11054 on the :licenca empty arm",
11055 );
11056 let c = caixa_with_licenca(Some("MIT"));
11057 assert!(
11058 c.validate_licenca().is_ok(),
11059 "validate_licenca must accept licenca == Some(\"MIT\") \
11060 (the canonical single-license SPDX shape)",
11061 );
11062 }
11063
11064 #[test]
11065 fn licenca_projects_option_str_by_borrow() {
11066 // The by-borrow pin: [`Caixa::licenca`] returns
11067 // `Option<&str>` by borrow — the `&str` borrows the underlying
11068 // `String` storage of the `Option<String>` slot and the
11069 // accessor must not allocate a fresh `String` on every call.
11070 // Peer of the per-`:placement`
11071 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
11072 // borrow pin on the peer per-M3-mesh-slot
11073 // `Option<&str>`-return axis, extended onto the outer top-
11074 // level [`Caixa`] universal-axis `Option<&str>` shape — the
11075 // accessor's returned `&str` must borrow from `&self` (the
11076 // returned reference's lifetime is tied to `&self`), and
11077 // calling the accessor twice on the same [`Caixa`] must yield
11078 // the same `Option<&str>` verbatim (idempotent, no side
11079 // effects on `&self`).
11080 //
11081 // Pins against a future silent detour that returned an owned
11082 // `Option<String>` (which would type-check but silently
11083 // allocate on every call, breaking the zero-cost projection
11084 // every peer sibling accessor carries), or a one-arm-only
11085 // accessor that returned a saturating value on some sentinel
11086 // input (breaking the pass-through invariant the sibling
11087 // required-scalar accessors carry).
11088 for licenca in [None, Some(""), Some("MIT"), Some("Apache-2.0 OR MIT")] {
11089 let c = caixa_with_licenca(licenca);
11090 let first = c.licenca();
11091 let second = c.licenca();
11092 assert_eq!(
11093 first, second,
11094 "Caixa::licenca must be idempotent — two successive \
11095 calls on the same &self must return the same \
11096 Option<&str>",
11097 );
11098 assert_eq!(
11099 first, licenca,
11100 "Caixa::licenca must return :licenca verbatim by \
11101 borrow — got {first:?}, expected {licenca:?}",
11102 );
11103 }
11104 }
11105
11106 // ── Caixa::repositorio — outer top-level Option<&str> scalar accessor ──
11107
11108 #[test]
11109 fn repositorio_returns_repositorio_byte_string_verbatim_across_permutations() {
11110 // The canonical per-`Caixa` `:repositorio` git-repo-URL scalar
11111 // pin: [`Caixa::repositorio`] must return the `:repositorio`
11112 // typed byte-string verbatim as an `Option<&str>`, byte-equal
11113 // to the raw `self.repositorio.as_deref()` access across every
11114 // representative value in the accept-set — `None` (the "omit
11115 // the slot to defer to the per-renderer placeholder" arm every
11116 // existing fixture without a `:repositorio` line carries),
11117 // `Some("")` (a past-the-guard sentinel that pins the accessor
11118 // doesn't perform a silent `Some("") → None` collapse on the
11119 // empty arm — validate rejects `Some("")` through
11120 // `RepositorioEmpty` but the accessor must ship the raw slot
11121 // verbatim so a validate-time gate regression surfaces at the
11122 // caixa-helm / caixa-flux emit boundary rather than being
11123 // silently absorbed into the per-renderer fallback),
11124 // `Some("github:pleme-io/hello-rio")` (the canonical `github:`
11125 // shorthand every existing manifest fixture across
11126 // `caixa-helm` / `caixa-mesh` and the `examples/` uses),
11127 // `Some("https://github.com/pleme-io/checkout")` (the canonical
11128 // `https://` URL the README quickstart uses),
11129 // `Some("ssh://git@github.com/pleme-io/checkout.git")` /
11130 // `Some("git://github.com/pleme-io/checkout.git")` /
11131 // `Some("git@github.com:pleme-io/checkout.git")` /
11132 // `Some("file:///opt/mirrors/pleme-io/checkout")` (every non-
11133 // github scheme the shared `is_git_repo_url` predicate
11134 // documents), and five past-the-guard sentinels for the
11135 // `RepositorioInvalid` refusal cases (`Some("pleme-io/checkout")`
11136 // missing-colon, `Some("-upload-pack=evil")` leading-dash, /
11137 // `Some("github:pleme-io/checkout?ref=main")` query-string, /
11138 // `Some("github:pleme-io/checkout#main")` fragment-anchor, /
11139 // `Some("github:pleme-io/{tpl}")` URI-template-placeholder — the
11140 // sentinels pin the accessor doesn't silently absorb the
11141 // refusal cases into a fallback).
11142 //
11143 // Second outer top-level [`Caixa`] `Option<&str>`-return scalar
11144 // accessor pin on the substrate primitive — sibling of the peer
11145 // [`Caixa::licenca`] (6d5bc28) pin
11146 // (`licenca_returns_licenca_byte_string_verbatim_across_permutations`)
11147 // that opened the "outer [`Caixa`] `Option<&str>` scalar"
11148 // projection pin pattern this pin folds on. Sibling in shape to
11149 // the peer per-`:placement`
11150 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
11151 // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
11152 // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
11153 // axes, extended onto the outer top-level [`Caixa`] universal-
11154 // axis surface. Pins against a future silent detour that
11155 // returned an owned `Option<String>` (which would type-check
11156 // but silently allocate on every accessor call, breaking the
11157 // zero-cost projection every peer sibling accessor carries), a
11158 // `Some("") → None` collapse (which would silently absorb the
11159 // `RepositorioEmpty` refusal case at the accessor boundary and
11160 // the caixa-helm `Chart.yaml` `home:` fold would silently
11161 // render a `home: null` / omitted field on a struct-literal
11162 // `Caixa { repositorio: Some(""), .. }`), or a
11163 // `None → Some(<default>)` collapse (which would silently reify
11164 // the per-renderer fallback at the accessor boundary and every
11165 // downstream consumer keying off the `Option::is_none()`
11166 // discriminator would lose the "author omitted the slot"
11167 // signal).
11168 for repositorio in [
11169 None,
11170 Some(""),
11171 Some("github:pleme-io/hello-rio"),
11172 Some("https://github.com/pleme-io/checkout"),
11173 Some("ssh://git@github.com/pleme-io/checkout.git"),
11174 Some("git://github.com/pleme-io/checkout.git"),
11175 Some("git@github.com:pleme-io/checkout.git"),
11176 Some("file:///opt/mirrors/pleme-io/checkout"),
11177 Some("pleme-io/checkout"),
11178 Some("-upload-pack=evil"),
11179 Some("github:pleme-io/checkout?ref=main"),
11180 Some("github:pleme-io/checkout#main"),
11181 Some("github:pleme-io/{tpl}"),
11182 ] {
11183 let c = caixa_with_repositorio(repositorio);
11184 assert_eq!(
11185 c.repositorio(),
11186 repositorio,
11187 "Caixa::repositorio must return :repositorio verbatim \
11188 (got {:?}, expected {repositorio:?})",
11189 c.repositorio(),
11190 );
11191 assert_eq!(
11192 c.repositorio(),
11193 c.repositorio.as_deref(),
11194 "Caixa::repositorio must byte-equal the raw \
11195 `self.repositorio.as_deref()` field access across every \
11196 value in the Option<&str> accept-set",
11197 );
11198 }
11199 }
11200
11201 #[test]
11202 fn validate_repositorio_empty_arm_routes_through_accessor() {
11203 // Composition pin: [`Caixa::validate_repositorio`]'s empty-arm
11204 // gate must key off [`Caixa::repositorio`], not the raw
11205 // `self.repositorio.as_deref()` field access. Structurally: a
11206 // `Caixa { repositorio: Some(""), .. }` must surface the
11207 // `RepositorioEmpty` refusal exactly, and a
11208 // `Caixa { repositorio: Some("github:pleme-io/hello-rio"), .. }`
11209 // (the canonical `github:` shorthand form) must pass validate.
11210 // The pair jointly pins the accessor + validate-gate
11211 // composition: any future silent detour that had the accessor
11212 // return `None` on the empty arm (a `.filter(|s| !s.is_empty())`
11213 // collapse) would silently absorb the `RepositorioEmpty` refusal
11214 // at the accessor boundary and the validate gate would accept a
11215 // struct-literal `Caixa { repositorio: Some(""), .. }` — the
11216 // composition pin catches that at caixa-core build time.
11217 //
11218 // Peer of the [`Caixa::licenca`] (6d5bc28)
11219 // `validate_licenca_empty_arm_routes_through_accessor`
11220 // composition pin on the sibling outer top-level [`Caixa`]
11221 // `Option<&str>` universal-axis surface — same "the validate /
11222 // shape-gate predicate must route through the substrate-
11223 // primitive typed dispatch" discipline extended onto the second
11224 // outer top-level [`Caixa`] universal-axis `Option<&str>`-
11225 // composition surface.
11226 let c = caixa_with_repositorio(Some(""));
11227 assert!(
11228 matches!(
11229 c.validate_repositorio(),
11230 Err(ManifestError::RepositorioEmpty),
11231 ),
11232 "validate_repositorio must reject repositorio == Some(\"\") \
11233 with RepositorioEmpty — the accessor and the validate gate \
11234 must route through the same substrate-primitive typed \
11235 dispatch on the :repositorio empty arm",
11236 );
11237 let c = caixa_with_repositorio(Some("github:pleme-io/hello-rio"));
11238 assert!(
11239 c.validate_repositorio().is_ok(),
11240 "validate_repositorio must accept repositorio == \
11241 Some(\"github:pleme-io/hello-rio\") (the canonical \
11242 `github:` shorthand git-repo-URL shape)",
11243 );
11244 }
11245
11246 #[test]
11247 fn repositorio_projects_option_str_by_borrow() {
11248 // The by-borrow pin: [`Caixa::repositorio`] returns
11249 // `Option<&str>` by borrow — the `&str` borrows the underlying
11250 // `String` storage of the `Option<String>` slot and the
11251 // accessor must not allocate a fresh `String` on every call.
11252 // Peer of the per-`:placement`
11253 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) and the
11254 // [`Caixa::licenca`] (6d5bc28) by-borrow pins on the peer
11255 // `Option<&str>`-return axes, extended onto the second outer
11256 // top-level [`Caixa`] universal-axis `Option<&str>` shape —
11257 // the accessor's returned `&str` must borrow from `&self` (the
11258 // returned reference's lifetime is tied to `&self`), and
11259 // calling the accessor twice on the same [`Caixa`] must yield
11260 // the same `Option<&str>` verbatim (idempotent, no side effects
11261 // on `&self`).
11262 //
11263 // Pins against a future silent detour that returned an owned
11264 // `Option<String>` (which would type-check but silently
11265 // allocate on every call, breaking the zero-cost projection
11266 // every peer sibling accessor carries), or a one-arm-only
11267 // accessor that returned a saturating value on some sentinel
11268 // input (breaking the pass-through invariant the sibling
11269 // required-scalar accessors carry).
11270 for repositorio in [
11271 None,
11272 Some(""),
11273 Some("github:pleme-io/hello-rio"),
11274 Some("https://github.com/pleme-io/checkout"),
11275 ] {
11276 let c = caixa_with_repositorio(repositorio);
11277 let first = c.repositorio();
11278 let second = c.repositorio();
11279 assert_eq!(
11280 first, second,
11281 "Caixa::repositorio must be idempotent — two successive \
11282 calls on the same &self must return the same \
11283 Option<&str>",
11284 );
11285 assert_eq!(
11286 first, repositorio,
11287 "Caixa::repositorio must return :repositorio verbatim by \
11288 borrow — got {first:?}, expected {repositorio:?}",
11289 );
11290 }
11291 }
11292
11293 // ── Caixa::canonical_git_url — resolved-git-URL composer ──────────
11294
11295 #[test]
11296 fn canonical_git_url_returns_repositorio_verbatim_on_some_arm() {
11297 // Fail-before-pass-after pin: [`Caixa::canonical_git_url`] must
11298 // return the author-declared `:repositorio` byte-string verbatim
11299 // on the `Some` arm — no scheme rewrite, no trailing-slash
11300 // canonicalization, no `github:` → `https://github.com/`
11301 // desugaring. The resolved-URL composer is the projection of
11302 // the raw [`Caixa::repositorio`] `Option<&str>` accessor onto
11303 // the `String`-return arity every substrate-side field-fill
11304 // consumer keys off; on the `Some` arm the projection is
11305 // `str::to_owned` verbatim, so every accept-set value the
11306 // sibling `repositorio_returns_repositorio_byte_string_verbatim_
11307 // across_permutations` pin covers (`https://…`, `github:…`,
11308 // `ssh://…`, `git://…`, `git@…`, `file://…`, and the past-the-
11309 // guard sentinel `pleme-io/…`) must survive the accessor
11310 // byte-equal. Pins against a future silent detour that rewrote
11311 // the `github:` shorthand to the `https://github.com/` full URL
11312 // at the accessor boundary (which would silently split the
11313 // resolved-URL surface from the raw [`Caixa::repositorio`]
11314 // accessor's documented pass-through invariant), or a trailing-
11315 // slash normalization (which would silently break the
11316 // FluxCD `GitRepository` `spec.url` byte-exact match every
11317 // downstream consumer keys the source-controller reconcile off).
11318 for repositorio in [
11319 "github:pleme-io/hello-rio",
11320 "https://github.com/pleme-io/checkout",
11321 "ssh://git@github.com/pleme-io/checkout.git",
11322 "git://github.com/pleme-io/checkout.git",
11323 "git@github.com:pleme-io/checkout.git",
11324 "file:///opt/mirrors/pleme-io/checkout",
11325 ] {
11326 let c = caixa_with_repositorio(Some(repositorio));
11327 assert_eq!(
11328 c.canonical_git_url(),
11329 repositorio,
11330 "Caixa::canonical_git_url on the Some arm must return \
11331 :repositorio verbatim (got {:?}, expected {repositorio:?})",
11332 c.canonical_git_url(),
11333 );
11334 }
11335 }
11336
11337 #[test]
11338 fn canonical_git_url_falls_back_to_pleme_org_url_on_none_arm() {
11339 // Fail-before-pass-after pin: [`Caixa::canonical_git_url`] on the
11340 // `None` arm must emit the substrate's canonical pleme-org github
11341 // URL derived from `caixa.nome()` — `https://github.com/<org>/
11342 // <nome>` with `<org>` bound to [`crate::DEFAULT_PLEME_GIT_ORG`]
11343 // and `<nome>` bound to the typed [`Caixa::nome`] accessor. This
11344 // is the exact byte-image of the prior inline
11345 // [`caixa-flux::ClusterBundleOpts::for_caixa`] `git_url`
11346 // composer at caixa-flux/src/lib.rs:2080 that every prior caller
11347 // re-derived open-coded. Pins against a future silent detour
11348 // that migrated the `<org>` segment to a different constant (a
11349 // fork rebranding that split off a new
11350 // `DEFAULT_PLEME_GIT_ORG_MIRROR` const the accessor would need
11351 // to migrate onto), a scheme change (`https://` → `git://` or
11352 // `ssh://`), or a per-`Caixa` `.canonical_git_url_prefix`
11353 // override (which would break the substrate-wide single-source-
11354 // of-truth guarantee this method encodes).
11355 let c = caixa_with_repositorio(None);
11356 let expected = format!(
11357 "https://github.com/{org}/{nome}",
11358 org = crate::DEFAULT_PLEME_GIT_ORG,
11359 nome = c.nome(),
11360 );
11361 assert_eq!(
11362 c.canonical_git_url(),
11363 expected,
11364 "Caixa::canonical_git_url on the None arm must fold through \
11365 the substrate's canonical pleme-org github URL fallback \
11366 `https://github.com/<DEFAULT_PLEME_GIT_ORG>/<nome>` — got \
11367 {:?}, expected {expected:?}",
11368 c.canonical_git_url(),
11369 );
11370 }
11371
11372 #[test]
11373 fn canonical_git_url_byte_matches_manual_composition() {
11374 // Byte-parity pin: [`Caixa::canonical_git_url`] must render
11375 // byte-identically to the manual open-coded
11376 // `caixa.repositorio().map(str::to_owned).unwrap_or_else(||
11377 // format!("https://github.com/{org}/{nome}", ...))` composition
11378 // every prior substrate-side caller re-derived. Guards the
11379 // paired-site convergence just applied at caixa-flux's
11380 // [`ClusterBundleOpts::for_caixa`] `git_url` composer (which
11381 // now routes through this accessor): a future implementation of
11382 // this method that reordered the format arguments, swapped the
11383 // `<org>` constant for a different one, or interposed a
11384 // canonicalization pass on the `Some` arm surfaces here as a
11385 // caixa-core build-time test failure rather than as a downstream
11386 // FluxCD `GitRepository` reconcile mismatch far from this
11387 // method's source.
11388 for repositorio in [
11389 None,
11390 Some("github:pleme-io/hello-rio"),
11391 Some("https://github.com/pleme-io/checkout"),
11392 Some("ssh://git@github.com/pleme-io/checkout.git"),
11393 ] {
11394 let c = caixa_with_repositorio(repositorio);
11395 let manual = c.repositorio().map_or_else(
11396 || {
11397 format!(
11398 "https://github.com/{org}/{nome}",
11399 org = crate::DEFAULT_PLEME_GIT_ORG,
11400 nome = c.nome(),
11401 )
11402 },
11403 str::to_owned,
11404 );
11405 assert_eq!(
11406 c.canonical_git_url(),
11407 manual,
11408 "Caixa::canonical_git_url must byte-equal the manual \
11409 open-coded `repositorio().map(str::to_owned)\
11410 .unwrap_or_else(|| format!(...))` composition across \
11411 every representative :repositorio input — got {:?}, \
11412 expected {manual:?}",
11413 c.canonical_git_url(),
11414 );
11415 }
11416 }
11417
11418 // ── Caixa::publish_tag — resolved-publish-tag composer ───────────
11419
11420 #[test]
11421 fn publish_tag_composes_prefix_and_versao_on_all_shapes() {
11422 // Fail-before-pass-after pin: [`Caixa::publish_tag`] must compose
11423 // [`crate::DEFAULT_PUBLISH_TAG_PREFIX`] against the caixa's typed
11424 // [`Caixa::versao`] byte-string across every SemVer-2 shape the
11425 // sibling [`validate_versao_accepts_canonical_forms`] positive-set
11426 // sweep documents — bare MAJOR.MINOR.PATCH, pre-release tags
11427 // (`-rc.1`), build metadata (`+build.42`), the combined form, and
11428 // the `0.0.0` boundary case. Every accept-set value the peer
11429 // validate gate lets through must survive the resolved-tag
11430 // projection byte-equal.
11431 for versao in [
11432 "0.1.0",
11433 "0.0.0",
11434 "1.0.0",
11435 "1.2.3-rc.1",
11436 "1.2.3+build.42",
11437 "1.2.3-rc.1+build.42",
11438 ] {
11439 let c = caixa_with_versao(versao);
11440 let expected = format!(
11441 "{prefix}{versao}",
11442 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
11443 );
11444 assert_eq!(
11445 c.publish_tag(),
11446 expected,
11447 "Caixa::publish_tag must compose \
11448 DEFAULT_PUBLISH_TAG_PREFIX ({prefix:?}) against \
11449 :versao ({versao:?}) verbatim — got {got:?}, \
11450 expected {expected:?}",
11451 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
11452 got = c.publish_tag(),
11453 );
11454 }
11455 }
11456
11457 #[test]
11458 fn publish_tag_starts_with_default_publish_tag_prefix() {
11459 // Prefix-shape pin: every [`Caixa::publish_tag`] emission must
11460 // begin with the canonical [`crate::DEFAULT_PUBLISH_TAG_PREFIX`]
11461 // byte-string on every input, guarding a hypothetical future
11462 // implementation that migrated the prefix segment to an inline
11463 // literal (`"v"`) that would silently drift from any rebrand of
11464 // the lifted constant. Peer to the sibling caixa-flux
11465 // `cluster_bundle_default_git_tag_uses_lifted_caixa_core_prefix`
11466 // test which pins the same prefix invariant at the reader-side
11467 // `GitRefSpec::Tag` emit site.
11468 for versao in ["0.0.0", "0.1.0", "1.2.3-rc.1", "9.9.9+build.1"] {
11469 let c = caixa_with_versao(versao);
11470 let tag = c.publish_tag();
11471 assert!(
11472 tag.starts_with(crate::DEFAULT_PUBLISH_TAG_PREFIX),
11473 "Caixa::publish_tag emission {tag:?} must start with \
11474 the lifted crate::DEFAULT_PUBLISH_TAG_PREFIX \
11475 ({prefix:?})",
11476 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
11477 );
11478 }
11479 }
11480
11481 #[test]
11482 fn publish_tag_byte_matches_manual_composition() {
11483 // Byte-parity pin: [`Caixa::publish_tag`] must render byte-
11484 // identically to the manual open-coded
11485 // `format!("{prefix}{versao}", prefix =
11486 // caixa_core::DEFAULT_PUBLISH_TAG_PREFIX, versao =
11487 // caixa.versao())` composition every prior substrate-side
11488 // caller re-derived. Guards the paired-site convergence just
11489 // applied at caixa-flux's [`ClusterBundleOpts::for_caixa`]
11490 // `git_ref` composer (which now routes through this accessor):
11491 // a future implementation of this method that reordered the
11492 // format arguments, swapped the `<prefix>` constant for a
11493 // different one, or interposed a canonicalization pass on the
11494 // `:versao` axis surfaces here as a caixa-core build-time test
11495 // failure rather than as a downstream FluxCD `GitRepository`
11496 // reconcile mismatch far from this method's source.
11497 for versao in [
11498 "0.1.0",
11499 "0.0.0",
11500 "1.2.3-rc.1",
11501 "1.2.3+build.42",
11502 "1.2.3-rc.1+build.42",
11503 ] {
11504 let c = caixa_with_versao(versao);
11505 let manual = format!(
11506 "{prefix}{versao}",
11507 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
11508 versao = c.versao(),
11509 );
11510 assert_eq!(
11511 c.publish_tag(),
11512 manual,
11513 "Caixa::publish_tag must byte-equal the manual \
11514 open-coded `format!(\"{{prefix}}{{versao}}\", ...)` \
11515 composition across every representative :versao input \
11516 — got {got:?}, expected {manual:?}",
11517 got = c.publish_tag(),
11518 );
11519 }
11520 }
11521
11522 // ── Caixa::lareira_chart_name — resolved-chart-name composer ─────
11523
11524 #[test]
11525 fn lareira_chart_name_composes_prefix_and_nome_on_all_shapes() {
11526 // Fail-before-pass-after pin: [`Caixa::lareira_chart_name`] must
11527 // compose [`crate::LAREIRA_CHART_NAME_PREFIX`] against the caixa's
11528 // typed [`Caixa::nome`] byte-string across every DNS-1123 shape
11529 // the sibling [`validate_nome_accepts_canonical_forms`] positive-
11530 // set sweep documents — single-word, hyphen-joined, version-
11531 // suffixed, single-char, two-char, digit-start, retry-suffixed.
11532 // Every accept-set value the peer validate gate lets through must
11533 // survive the resolved-chart-name projection byte-equal.
11534 for nome in [
11535 "checkout",
11536 "cart-v2",
11537 "a",
11538 "db",
11539 "3rd-party-shim",
11540 "payment-retry",
11541 "0",
11542 ] {
11543 let c = caixa_with_nome(nome);
11544 let expected = format!("{prefix}{nome}", prefix = crate::LAREIRA_CHART_NAME_PREFIX);
11545 assert_eq!(
11546 c.lareira_chart_name(),
11547 expected,
11548 "Caixa::lareira_chart_name must compose \
11549 LAREIRA_CHART_NAME_PREFIX ({prefix:?}) against \
11550 :nome ({nome:?}) verbatim — got {got:?}, \
11551 expected {expected:?}",
11552 prefix = crate::LAREIRA_CHART_NAME_PREFIX,
11553 got = c.lareira_chart_name(),
11554 );
11555 }
11556 }
11557
11558 #[test]
11559 fn lareira_chart_name_starts_with_lifted_prefix() {
11560 // Prefix-shape pin: every [`Caixa::lareira_chart_name`] emission
11561 // must begin with the canonical
11562 // [`crate::LAREIRA_CHART_NAME_PREFIX`] byte-string on every
11563 // input, guarding a hypothetical future implementation that
11564 // migrated the prefix segment to an inline literal (`"lareira-"`)
11565 // that would silently drift from any rebrand of the lifted
11566 // constant. Peer to the sibling
11567 // [`publish_tag_starts_with_default_publish_tag_prefix`] pin on
11568 // the co-resident resolved-publish-tag composer's prefix axis.
11569 for nome in ["checkout", "cart", "a", "payment-retry", "0"] {
11570 let c = caixa_with_nome(nome);
11571 let chart = c.lareira_chart_name();
11572 assert!(
11573 chart.starts_with(crate::LAREIRA_CHART_NAME_PREFIX),
11574 "Caixa::lareira_chart_name emission {chart:?} must start \
11575 with the lifted crate::LAREIRA_CHART_NAME_PREFIX \
11576 ({prefix:?})",
11577 prefix = crate::LAREIRA_CHART_NAME_PREFIX,
11578 );
11579 }
11580 }
11581
11582 #[test]
11583 fn lareira_chart_name_byte_matches_canonical_helper_composition() {
11584 // Byte-parity pin: [`Caixa::lareira_chart_name`] must render
11585 // byte-identically to the manual open-coded
11586 // `caixa_core::lareira_chart_name(caixa.nome())` two-step
11587 // composition every prior substrate-side caller re-derived.
11588 // Guards the paired-site convergence just applied at caixa-helm's
11589 // [`render_chart_for_servico_with`] `ChartDir.name` composer,
11590 // caixa-flux's [`cluster_bundle`] per-CR `chart_name` binding,
11591 // and caixa-tatara's [`process_for_aplicacao`] `release_name`
11592 // composer (all of which now route through this accessor): a
11593 // future implementation of this method that reordered the
11594 // composition arguments, swapped the `<prefix>` constant for a
11595 // different one, or interposed a canonicalization pass on the
11596 // `:nome` axis surfaces here as a caixa-core build-time test
11597 // failure rather than as a downstream Helm chart-render / FluxCD
11598 // reconcile / tatara Process-CR mismatch far from this method's
11599 // source.
11600 for nome in [
11601 "checkout",
11602 "cart-v2",
11603 "a",
11604 "db",
11605 "3rd-party-shim",
11606 "payment-retry",
11607 ] {
11608 let c = caixa_with_nome(nome);
11609 let manual = crate::lareira_chart_name(c.nome());
11610 assert_eq!(
11611 c.lareira_chart_name(),
11612 manual,
11613 "Caixa::lareira_chart_name must byte-equal the manual \
11614 open-coded `caixa_core::lareira_chart_name(caixa.nome())` \
11615 composition across every representative :nome input — \
11616 got {got:?}, expected {manual:?}",
11617 got = c.lareira_chart_name(),
11618 );
11619 }
11620 }
11621
11622 // ── Caixa::oci_chart_ref — resolved-OCI-chart-ref composer ────────
11623
11624 #[test]
11625 fn oci_chart_ref_composes_scheme_and_lareira_chart_name_on_all_shapes() {
11626 // Fail-before-pass-after pin: [`Caixa::oci_chart_ref`] must
11627 // compose [`crate::OCI_SCHEME_PREFIX`] + the caller-supplied
11628 // `registry` + [`crate::lareira_chart_name`]-of-[`Caixa::nome`]
11629 // across the full paired `(registry, :nome)` accept-set — every
11630 // representative registry the substrate-side emitters carry
11631 // (`ghcr.io/pleme-io/charts`, the canonical CAIXA-SDLC §II
11632 // ArtifactHub-tier registry; `ghcr.io/pleme-io`, the bare-org
11633 // arm the sibling `oci_chart_ref_pins_byte_shape_against_prior_
11634 // inline_format` render-side pin exercises; `registry.example.
11635 // com`, an off-org shape; `localhost:5000`, the local-dev shape
11636 // every `feira chart` iteration path lands under) × every DNS-
11637 // 1123 `:nome` shape the peer `validate_nome_accepts_canonical_
11638 // forms` positive-set sweep documents (single-word, hyphen-
11639 // joined, single-char, two-char, digit-start, retry-suffixed).
11640 // Every accept-set pair the peer validate gates let through must
11641 // survive the resolved-OCI-ref projection byte-equal.
11642 for registry in [
11643 "ghcr.io/pleme-io/charts",
11644 "ghcr.io/pleme-io",
11645 "registry.example.com",
11646 "localhost:5000",
11647 ] {
11648 for nome in [
11649 "checkout",
11650 "cart-v2",
11651 "a",
11652 "db",
11653 "3rd-party-shim",
11654 "payment-retry",
11655 "0",
11656 ] {
11657 let c = caixa_with_nome(nome);
11658 let expected = format!(
11659 "{scheme}{registry}/{chart}",
11660 scheme = crate::OCI_SCHEME_PREFIX,
11661 chart = crate::lareira_chart_name(nome),
11662 );
11663 assert_eq!(
11664 c.oci_chart_ref(registry),
11665 expected,
11666 "Caixa::oci_chart_ref must compose \
11667 OCI_SCHEME_PREFIX ({scheme:?}) + registry ({registry:?}) + \
11668 lareira_chart_name(:nome ({nome:?})) verbatim — got {got:?}, \
11669 expected {expected:?}",
11670 scheme = crate::OCI_SCHEME_PREFIX,
11671 got = c.oci_chart_ref(registry),
11672 );
11673 }
11674 }
11675 }
11676
11677 #[test]
11678 fn oci_chart_ref_starts_with_lifted_scheme_prefix() {
11679 // Scheme-prefix-shape pin: every [`Caixa::oci_chart_ref`]
11680 // emission must begin with the canonical
11681 // [`crate::OCI_SCHEME_PREFIX`] byte-string on every input, guarding
11682 // a hypothetical future implementation that migrated the scheme
11683 // segment to an inline literal (`"oci://"`) that would silently
11684 // drift from any rebrand of the lifted constant. Peer to the
11685 // sibling [`publish_tag_starts_with_default_publish_tag_prefix`]
11686 // + [`lareira_chart_name_starts_with_lifted_prefix`] pins on the
11687 // co-resident resolved-publish-tag / resolved-chart-name
11688 // composers' prefix axes.
11689 for registry in [
11690 "ghcr.io/pleme-io/charts",
11691 "ghcr.io/pleme-io",
11692 "localhost:5000",
11693 ] {
11694 for nome in ["checkout", "cart", "a", "payment-retry", "0"] {
11695 let c = caixa_with_nome(nome);
11696 let ref_ = c.oci_chart_ref(registry);
11697 assert!(
11698 ref_.starts_with(crate::OCI_SCHEME_PREFIX),
11699 "Caixa::oci_chart_ref emission {ref_:?} must start \
11700 with the lifted crate::OCI_SCHEME_PREFIX ({scheme:?}) \
11701 — registry ({registry:?}), :nome ({nome:?})",
11702 scheme = crate::OCI_SCHEME_PREFIX,
11703 );
11704 }
11705 }
11706 }
11707
11708 #[test]
11709 fn oci_chart_ref_byte_matches_canonical_helper_composition() {
11710 // Byte-parity pin: [`Caixa::oci_chart_ref`] must render byte-
11711 // identically to the manual open-coded
11712 // `caixa_core::oci_chart_ref(registry, caixa.nome())` two-step
11713 // composition every prior substrate-side caller re-derived.
11714 // Guards the paired-site convergence just applied at caixa-
11715 // tatara's [`derive_chart_ref`] helper (which now routes through
11716 // this accessor): a future implementation of this method that
11717 // reordered the composition arguments, swapped the `<scheme>`
11718 // constant for a different one, migrated the `<chart>` segment
11719 // off the paired [`crate::lareira_chart_name`] composer, or
11720 // interposed a canonicalization pass on either input axis
11721 // surfaces here as a caixa-core build-time test failure rather
11722 // than as a downstream `helm install` / FluxCD OCI-source
11723 // reconcile / tatara `Process`-CR mismatch far from this
11724 // method's source. Sibling to the peer
11725 // [`lareira_chart_name_byte_matches_canonical_helper_composition`]
11726 // / [`publish_tag_byte_matches_manual_composition`] /
11727 // [`canonical_git_url_byte_matches_manual_composition`] byte-
11728 // parity pins that carry the same discipline on the co-resident
11729 // resolved-chart-name / resolved-publish-tag / resolved-git-URL
11730 // composers.
11731 for registry in [
11732 "ghcr.io/pleme-io/charts",
11733 "ghcr.io/pleme-io",
11734 "registry.example.com",
11735 "localhost:5000",
11736 ] {
11737 for nome in [
11738 "checkout",
11739 "cart-v2",
11740 "a",
11741 "db",
11742 "3rd-party-shim",
11743 "payment-retry",
11744 ] {
11745 let c = caixa_with_nome(nome);
11746 let manual = crate::oci_chart_ref(registry, c.nome());
11747 assert_eq!(
11748 c.oci_chart_ref(registry),
11749 manual,
11750 "Caixa::oci_chart_ref must byte-equal the manual \
11751 open-coded `caixa_core::oci_chart_ref(registry, \
11752 caixa.nome())` composition across every representative \
11753 (registry, :nome) pair — registry ({registry:?}), \
11754 :nome ({nome:?}), got {got:?}, expected {manual:?}",
11755 got = c.oci_chart_ref(registry),
11756 );
11757 }
11758 }
11759 }
11760
11761 // ── Caixa::descricao — outer top-level Option<&str> scalar accessor ──
11762
11763 #[test]
11764 fn descricao_returns_descricao_byte_string_verbatim_across_permutations() {
11765 // The canonical per-`Caixa` `:descricao` free-form-prose scalar
11766 // pin: [`Caixa::descricao`] must return the `:descricao` typed
11767 // byte-string verbatim as an `Option<&str>`, byte-equal to the
11768 // raw `self.descricao.as_deref()` access across every
11769 // representative value in the accept-set — `None` (the "omit
11770 // the slot to defer to the per-renderer `caixa.nome`-derived
11771 // fallback" arm every existing fixture without a `:descricao`
11772 // line carries), `Some("")` (a past-the-guard sentinel that
11773 // pins the accessor doesn't perform a silent `Some("") → None`
11774 // collapse on the empty arm — validate rejects `Some("")`
11775 // through `DescricaoEmpty` but the accessor must ship the raw
11776 // slot verbatim so a validate-time gate regression surfaces at
11777 // the caixa-helm / caixa-feira emit boundary rather than being
11778 // silently absorbed into the per-renderer `caixa.nome`-derived
11779 // fallback), `Some("Checkout flow.")` (the canonical one-line
11780 // prose descriptor the peer
11781 // `validate_descricao_accepts_canonical_value` positive sweep
11782 // exercises), `Some("Canonical Rust→wasm32-wasip2 caixa
11783 // Servico.")` (the multi-byte Unicode continuation-byte shape
11784 // the `hello-rio` fixture carries), `Some("→ — · ✓")` (a
11785 // multi-glyph Unicode shape the peer
11786 // `is_chart_description_shape` predicate accepts), and five
11787 // past-the-guard sentinels for the `DescricaoInvalid` refusal
11788 // cases (`Some(" Checkout flow.")` leading-whitespace,
11789 // `Some("Checkout flow. ")` trailing-whitespace,
11790 // `Some("Checkout\nflow.")` embedded-LF,
11791 // `Some("Checkout\tflow.")` embedded-TAB, and
11792 // `Some("Checkout\x00flow.")` embedded-NUL — the sentinels pin
11793 // the accessor doesn't silently absorb the refusal cases into
11794 // a fallback).
11795 //
11796 // Third outer top-level [`Caixa`] `Option<&str>`-return scalar
11797 // accessor pin on the substrate primitive — sibling of the peer
11798 // [`Caixa::licenca`] (6d5bc28) and [`Caixa::repositorio`]
11799 // (cc7332d) pins that opened the "outer [`Caixa`]
11800 // `Option<&str>` scalar" projection pin pattern this pin folds
11801 // on. Sibling in shape to the peer per-`:placement`
11802 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
11803 // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
11804 // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
11805 // axes, extended onto the outer top-level [`Caixa`] universal-
11806 // axis surface. Pins against a future silent detour that
11807 // returned an owned `Option<String>` (which would type-check
11808 // but silently allocate on every accessor call, breaking the
11809 // zero-cost projection every peer sibling accessor carries), a
11810 // `Some("") → None` collapse (which would silently absorb the
11811 // `DescricaoEmpty` refusal case at the accessor boundary and
11812 // the caixa-helm `Chart.yaml` `description:` fold would
11813 // silently render a `caixa.nome`-derived fallback on a
11814 // struct-literal `Caixa { descricao: Some(""), .. }`), or a
11815 // `None → Some(<default>)` collapse (which would silently
11816 // reify the per-renderer `caixa.nome`-derived fallback at the
11817 // accessor boundary and every downstream consumer keying off
11818 // the `Option::is_none()` discriminator would lose the "author
11819 // omitted the slot" signal).
11820 for descricao in [
11821 None,
11822 Some(""),
11823 Some("Checkout flow."),
11824 Some("Canonical Rust→wasm32-wasip2 caixa Servico."),
11825 Some("→ — · ✓"),
11826 Some(" Checkout flow."),
11827 Some("Checkout flow. "),
11828 Some("Checkout\nflow."),
11829 Some("Checkout\tflow."),
11830 Some("Checkout\x00flow."),
11831 ] {
11832 let c = caixa_with_descricao(descricao);
11833 assert_eq!(
11834 c.descricao(),
11835 descricao,
11836 "Caixa::descricao must return :descricao verbatim (got \
11837 {:?}, expected {descricao:?})",
11838 c.descricao(),
11839 );
11840 assert_eq!(
11841 c.descricao(),
11842 c.descricao.as_deref(),
11843 "Caixa::descricao must byte-equal the raw \
11844 `self.descricao.as_deref()` field access across every \
11845 value in the Option<&str> accept-set",
11846 );
11847 }
11848 }
11849
11850 #[test]
11851 fn validate_descricao_empty_arm_routes_through_accessor() {
11852 // Composition pin: [`Caixa::validate_descricao`]'s empty-arm
11853 // gate must key off [`Caixa::descricao`], not the raw
11854 // `self.descricao.as_deref()` field access. Structurally: a
11855 // `Caixa { descricao: Some(""), .. }` must surface the
11856 // `DescricaoEmpty` refusal exactly, and a
11857 // `Caixa { descricao: Some("Checkout flow."), .. }` (the
11858 // canonical one-line-prose form) must pass validate. The pair
11859 // jointly pins the accessor + validate-gate composition: any
11860 // future silent detour that had the accessor return `None` on
11861 // the empty arm (a `.filter(|s| !s.is_empty())` collapse) would
11862 // silently absorb the `DescricaoEmpty` refusal at the accessor
11863 // boundary and the validate gate would accept a struct-literal
11864 // `Caixa { descricao: Some(""), .. }` — the composition pin
11865 // catches that at caixa-core build time.
11866 //
11867 // Peer of the [`Caixa::licenca`] (6d5bc28)
11868 // `validate_licenca_empty_arm_routes_through_accessor` and
11869 // [`Caixa::repositorio`] (cc7332d)
11870 // `validate_repositorio_empty_arm_routes_through_accessor`
11871 // composition pins on the sibling outer top-level [`Caixa`]
11872 // `Option<&str>` universal-axis surface — same "the validate /
11873 // shape-gate predicate must route through the substrate-
11874 // primitive typed dispatch" discipline extended onto the third
11875 // outer top-level [`Caixa`] universal-axis `Option<&str>`-
11876 // composition surface.
11877 let c = caixa_with_descricao(Some(""));
11878 assert!(
11879 matches!(c.validate_descricao(), Err(ManifestError::DescricaoEmpty),),
11880 "validate_descricao must reject descricao == Some(\"\") \
11881 with DescricaoEmpty — the accessor and the validate gate \
11882 must route through the same substrate-primitive typed \
11883 dispatch on the :descricao empty arm",
11884 );
11885 let c = caixa_with_descricao(Some("Checkout flow."));
11886 assert!(
11887 c.validate_descricao().is_ok(),
11888 "validate_descricao must accept descricao == \
11889 Some(\"Checkout flow.\") (the canonical one-line-prose \
11890 chart-description shape)",
11891 );
11892 }
11893
11894 #[test]
11895 fn descricao_projects_option_str_by_borrow() {
11896 // The by-borrow pin: [`Caixa::descricao`] returns
11897 // `Option<&str>` by borrow — the `&str` borrows the underlying
11898 // `String` storage of the `Option<String>` slot and the
11899 // accessor must not allocate a fresh `String` on every call.
11900 // Peer of the [`Caixa::licenca`] (6d5bc28) and
11901 // [`Caixa::repositorio`] (cc7332d) by-borrow pins on the peer
11902 // outer top-level [`Caixa`] `Option<&str>`-return axes, and of
11903 // the per-`:placement`
11904 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
11905 // borrow pin on the peer per-M3-mesh-slot `Option<&str>`-
11906 // return axis, extended onto the third outer top-level
11907 // [`Caixa`] universal-axis `Option<&str>` shape — the
11908 // accessor's returned `&str` must borrow from `&self` (the
11909 // returned reference's lifetime is tied to `&self`), and
11910 // calling the accessor twice on the same [`Caixa`] must yield
11911 // the same `Option<&str>` verbatim (idempotent, no side
11912 // effects on `&self`).
11913 //
11914 // Pins against a future silent detour that returned an owned
11915 // `Option<String>` (which would type-check but silently
11916 // allocate on every call, breaking the zero-cost projection
11917 // every peer sibling accessor carries), or a one-arm-only
11918 // accessor that returned a saturating value on some sentinel
11919 // input (breaking the pass-through invariant the sibling
11920 // required-scalar accessors carry).
11921 for descricao in [
11922 None,
11923 Some(""),
11924 Some("Checkout flow."),
11925 Some("Canonical Rust→wasm32-wasip2 caixa Servico."),
11926 ] {
11927 let c = caixa_with_descricao(descricao);
11928 let first = c.descricao();
11929 let second = c.descricao();
11930 assert_eq!(
11931 first, second,
11932 "Caixa::descricao must be idempotent — two successive \
11933 calls on the same &self must return the same \
11934 Option<&str>",
11935 );
11936 assert_eq!(
11937 first, descricao,
11938 "Caixa::descricao must return :descricao verbatim by \
11939 borrow — got {first:?}, expected {descricao:?}",
11940 );
11941 }
11942 }
11943
11944 // ── validate_edicao — universal-axis language-edition shape ──
11945
11946 fn caixa_with_edicao(edicao: Option<&str>) -> Caixa {
11947 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
11948 c.edicao = edicao.map(String::from);
11949 c
11950 }
11951
11952 #[test]
11953 fn validate_edicao_accepts_none() {
11954 // The omit-the-slot identity: `:edicao` is optional. The
11955 // gate is a no-op when the author didn't declare a value —
11956 // every caixa without an `:edicao` line trivially passes,
11957 // and the substrate-side build pipeline falls back to the
11958 // documented default edition. Mirrors the peer
11959 // `validate_licenca_accepts_none` posture on the sibling
11960 // `Option<String>` Caixa slot.
11961 let c = caixa_with_edicao(None);
11962 c.validate_edicao().unwrap();
11963 }
11964
11965 #[test]
11966 fn validate_edicao_accepts_canonical_value() {
11967 // Positive control: the canonical `"2026"` edition every
11968 // existing renderer-side fixture (`caixa-helm`, `caixa-flux`,
11969 // `caixa-mesh`) carries by construction passes the gate.
11970 // Future-introduced sibling editions (`"2027"`, `"2030"`,
11971 // `"2049"`) that match the same 4-digit ASCII decimal year
11972 // shape must also trivially pass — the structural shape
11973 // predicate accepts every well-formed year regardless of
11974 // whether the substrate yet understands the specific value
11975 // (a future known-edition allowlist tightens that).
11976 for ed in ["2026", "2027", "2030", "2049"] {
11977 let c = caixa_with_edicao(Some(ed));
11978 c.validate_edicao()
11979 .unwrap_or_else(|err| panic!("canonical {ed:?} must pass: {err:?}"));
11980 }
11981 }
11982
11983 #[test]
11984 fn validate_edicao_rejects_empty_some() {
11985 // Canonical paste-from-blank-doc footgun. Without this gate
11986 // the empty `Some("")` silently lands as `(:edicao "")` in
11987 // the rendered caixa.lisp and a future renderer-side
11988 // consumer's `Option::unwrap_or_else` (which only fires on
11989 // `None`) skips its fallback. Mirrors the peer
11990 // [`ManifestError::LicencaEmpty`] empty-arm on the sibling
11991 // `Option<String>` Caixa slot.
11992 let c = caixa_with_edicao(Some(""));
11993 let err = c.validate_edicao().unwrap_err();
11994 assert!(matches!(err, ManifestError::EdicaoEmpty), "got {err:?}",);
11995 }
11996
11997 #[test]
11998 fn validate_edicao_rejects_free_form_non_year() {
11999 // Free-form non-year footgun: the bare `"x"` / `"latest"` /
12000 // `"nightly"` shapes carry no operational meaning on the
12001 // substrate's build-time edition selector. Until this gate
12002 // landed the bare empty-arm check let every such value
12003 // through and broke far from the source caixa.lisp. Peer
12004 // with the shape-predicate cascade
12005 // `validate_repositorio_rejects_missing_colon_separator`
12006 // establishes past its own empty arm.
12007 for ed in ["x", "latest", "nightly", "stable"] {
12008 let c = caixa_with_edicao(Some(ed));
12009 let err = c.validate_edicao().unwrap_err();
12010 assert!(
12011 matches!(err, ManifestError::EdicaoInvalid { .. }),
12012 "expected EdicaoInvalid on {ed:?}, got {err:?}",
12013 );
12014 }
12015 }
12016
12017 #[test]
12018 fn validate_edicao_rejects_trailing_whitespace() {
12019 // Paste-from-doc whitespace footgun. A trailing space in
12020 // the `:edicao` value would silently break the substrate's
12021 // build-time edition match-table lookup at the rendered
12022 // artifact's edition-selector consumer. The shape predicate
12023 // refuses every whitespace byte by construction (any byte
12024 // outside `0-9` fails `is_ascii_digit`). Peer with
12025 // `validate_repositorio_rejects_whitespace`.
12026 let c = caixa_with_edicao(Some("2026 "));
12027 let err = c.validate_edicao().unwrap_err();
12028 let ManifestError::EdicaoInvalid { edicao, .. } = err else {
12029 panic!("expected EdicaoInvalid, got {err:?}");
12030 };
12031 assert_eq!(edicao, "2026 ");
12032 }
12033
12034 #[test]
12035 fn validate_edicao_rejects_leading_whitespace() {
12036 // Symmetric paste-from-doc whitespace footgun on the leading
12037 // boundary — the gate refuses every shape with a non-digit
12038 // byte by construction.
12039 let c = caixa_with_edicao(Some(" 2026"));
12040 let err = c.validate_edicao().unwrap_err();
12041 assert!(
12042 matches!(err, ManifestError::EdicaoInvalid { .. }),
12043 "got {err:?}",
12044 );
12045 }
12046
12047 #[test]
12048 fn validate_edicao_rejects_control_char() {
12049 // Paste-from-multiline-doc CRLF footgun — control characters
12050 // at the value boundary break the substrate's build-time
12051 // edition-selector parser. Peer with
12052 // `validate_repositorio_rejects_control_char`.
12053 let c = caixa_with_edicao(Some("2026\n"));
12054 let err = c.validate_edicao().unwrap_err();
12055 assert!(
12056 matches!(err, ManifestError::EdicaoInvalid { .. }),
12057 "got {err:?}",
12058 );
12059 }
12060
12061 #[test]
12062 fn validate_edicao_rejects_non_ascii_lookalike() {
12063 // Fullwidth-keyboard look-alike footgun — `"2026"` is
12064 // the U+FF12 U+FF10 U+FF12 U+FF16 sequence (CJK fullwidth
12065 // digits), 4 codepoints but 12 UTF-8 bytes; the substrate's
12066 // edition selector wants an ASCII year, and the gate
12067 // refuses every non-ASCII shape by construction (length in
12068 // bytes is 12 ≠ 4, *and* every byte falls outside
12069 // `is_ascii_digit`'s `0-9` range).
12070 let c = caixa_with_edicao(Some("2026"));
12071 let err = c.validate_edicao().unwrap_err();
12072 assert!(
12073 matches!(err, ManifestError::EdicaoInvalid { .. }),
12074 "got {err:?}",
12075 );
12076 }
12077
12078 #[test]
12079 fn validate_edicao_rejects_version_tag_prefix() {
12080 // Common version-tag idiom footgun — `"v2026"` / `"e2026"`
12081 // / `"r2026"` are familiar shapes from git-tag / Rust
12082 // edition / release-tag conventions that don't apply to
12083 // the year-shaped edition axis. The shape predicate refuses
12084 // every leading non-digit prefix.
12085 for ed in ["v2026", "e2026", "r2026"] {
12086 let c = caixa_with_edicao(Some(ed));
12087 let err = c.validate_edicao().unwrap_err();
12088 assert!(
12089 matches!(err, ManifestError::EdicaoInvalid { .. }),
12090 "expected EdicaoInvalid on {ed:?}, got {err:?}",
12091 );
12092 }
12093 }
12094
12095 #[test]
12096 fn validate_edicao_rejects_decimal_shape() {
12097 // Decimal-shaped pseudo-version footgun — `"2026.1"` /
12098 // `"2026.0"` are familiar shapes from semver / float
12099 // conventions that don't apply to the year-shaped edition
12100 // axis. The shape predicate refuses every non-digit byte
12101 // (`.` falls outside `is_ascii_digit`).
12102 for ed in ["2026.1", "2026.0", "2026.0.1"] {
12103 let c = caixa_with_edicao(Some(ed));
12104 let err = c.validate_edicao().unwrap_err();
12105 assert!(
12106 matches!(err, ManifestError::EdicaoInvalid { .. }),
12107 "expected EdicaoInvalid on {ed:?}, got {err:?}",
12108 );
12109 }
12110 }
12111
12112 #[test]
12113 fn validate_edicao_rejects_wrong_length_numeric() {
12114 // Wrong-length numeric footgun — `"26"` (truncated) /
12115 // `"202"` (truncated) / `"20260"` (extra digit) / `"00026"`
12116 // (zero-padded too wide) all parse as integers but don't
12117 // name a 4-digit year. The shape predicate refuses every
12118 // value whose length isn't exactly 4 bytes.
12119 for ed in ["26", "202", "20260", "00026", "9"] {
12120 let c = caixa_with_edicao(Some(ed));
12121 let err = c.validate_edicao().unwrap_err();
12122 assert!(
12123 matches!(err, ManifestError::EdicaoInvalid { .. }),
12124 "expected EdicaoInvalid on {ed:?}, got {err:?}",
12125 );
12126 }
12127 }
12128
12129 #[test]
12130 fn validate_edicao_empty_takes_precedence_over_shape() {
12131 // Empty-first cascade pin: the empty `Some("")` surfaces
12132 // the narrower `EdicaoEmpty` not the shape-predicate-
12133 // wrapped `EdicaoInvalid`, mirroring the peer
12134 // `validate_repositorio_empty_takes_precedence_over_shape`
12135 // (`RepositorioEmpty` → `RepositorioInvalid`),
12136 // `NomeEmpty` → `NomeInvalid`, `VersaoEmpty` →
12137 // `VersaoInvalid`, `FonteRepoEmpty` → `FonteRepoInvalid`
12138 // cascades. The shape predicate also refuses the empty
12139 // input (defensively — `s.len() != 4`), but the
12140 // manifest-layer empty arm runs first to surface the
12141 // narrower diagnostic verbatim.
12142 let c = caixa_with_edicao(Some(""));
12143 let err = c.validate_edicao().unwrap_err();
12144 assert!(matches!(err, ManifestError::EdicaoEmpty), "got {err:?}",);
12145 }
12146
12147 #[test]
12148 fn validate_edicao_template_passes() {
12149 // Round-trip pin: the bare `Caixa::template` shape (which
12150 // carries `:edicao "2026"` verbatim) passes the gate by
12151 // construction. A future template-shape change that
12152 // introduced `(:edicao "")` or a non-year value would
12153 // surface here as a regression. Mirrors the peer
12154 // `validate_licenca_template_passes` pin.
12155 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12156 c.validate_edicao().unwrap();
12157 }
12158
12159 #[test]
12160 fn validate_edicao_diagnostic_names_offending_slot() {
12161 // Diagnostic-shape pin (peer with
12162 // `validate_licenca_diagnostic_names_offending_slot`): the
12163 // error's Display surfaces the `:edicao` slot name verbatim,
12164 // so a `feira lint` run can render the diagnostic without
12165 // re-parsing and the author can grep their caixa.lisp for
12166 // the offending `:edicao` line.
12167 let c = caixa_with_edicao(Some(""));
12168 let rendered = c.validate_edicao().unwrap_err().to_string();
12169 assert!(
12170 rendered.contains(":edicao"),
12171 "diagnostic must name the offending slot: {rendered}",
12172 );
12173 }
12174
12175 #[test]
12176 fn validate_edicao_invalid_diagnostic_carries_offending_value() {
12177 // Diagnostic-shape pin on the shape-predicate arm (peer
12178 // with `validate_repositorio_diagnostic_carries_offending_value`):
12179 // the error's Display surfaces the offending value + slot
12180 // name verbatim, so a `feira lint` run can render the
12181 // diagnostic without re-parsing and the author can grep
12182 // their caixa.lisp for the offending `:edicao` value.
12183 let c = caixa_with_edicao(Some("v2026"));
12184 let rendered = c.validate_edicao().unwrap_err().to_string();
12185 assert!(
12186 rendered.contains(":edicao"),
12187 "diagnostic must name the offending slot: {rendered}",
12188 );
12189 assert!(
12190 rendered.contains("v2026"),
12191 "diagnostic must quote the offending value: {rendered}",
12192 );
12193 }
12194
12195 // ── Caixa::edicao — outer top-level Option<&str> scalar accessor ──
12196
12197 #[test]
12198 fn edicao_returns_edicao_byte_string_verbatim_across_permutations() {
12199 // The canonical per-`Caixa` `:edicao` language-edition scalar
12200 // pin: [`Caixa::edicao`] must return the `:edicao` typed
12201 // byte-string verbatim as an `Option<&str>`, byte-equal to the
12202 // raw `self.edicao.as_deref()` access across every representative
12203 // value in the accept-set — `None` (the "omit the slot to defer
12204 // to the substrate's default edition" arm every existing
12205 // [`caixa-resolver`] fixture without an `:edicao` line carries),
12206 // `Some("")` (a past-the-guard sentinel that pins the accessor
12207 // doesn't perform a silent `Some("") → None` collapse on the
12208 // empty arm — validate rejects `Some("")` through `EdicaoEmpty`
12209 // but the accessor must ship the raw slot verbatim so a
12210 // validate-time gate regression surfaces at any future edition-
12211 // aware consumer's boundary rather than being silently absorbed
12212 // into the substrate's default edition), `Some("2026")` (the
12213 // canonical 4-digit-ASCII-decimal-year shape every `feira init`
12214 // template scaffolds via [`Caixa::template`] and every
12215 // renderer-side fixture at `caixa-helm/src/lib.rs:978` /
12216 // `caixa-flux/src/lib.rs:2319` / `caixa-mesh/src/lib.rs:3208`
12217 // carries by construction), `Some("2018")` / `Some("2021")` /
12218 // `Some("2024")` (canonical 4-digit-ASCII-decimal-year shapes
12219 // peer with Cargo's `[package] edition` grammar every future-
12220 // introduced sibling to `"2026"` will follow), and eight
12221 // past-the-guard sentinels for the `EdicaoInvalid` refusal cases
12222 // (`Some("2026 ")` trailing-whitespace, `Some(" 2026")` leading-
12223 // whitespace, `Some("2026\n")` embedded-LF, `Some("2026")`
12224 // fullwidth-non-ASCII-lookalike, `Some("v2026")` version-tag-
12225 // prefix, `Some("2026.1")` decimal-shape, `Some("26")` wrong-
12226 // length-numeric, `Some("latest")` free-form-non-year — the
12227 // sentinels pin the accessor doesn't silently absorb the
12228 // refusal cases into a substrate-default-edition fallback).
12229 //
12230 // Fourth and final outer top-level [`Caixa`] `Option<&str>`-
12231 // return scalar accessor pin on the substrate primitive —
12232 // sibling of the peer [`Caixa::licenca`] (6d5bc28),
12233 // [`Caixa::repositorio`] (cc7332d), and [`Caixa::descricao`]
12234 // (3f16e2f) pins that opened the "outer [`Caixa`]
12235 // `Option<&str>` scalar" projection pin pattern this pin folds
12236 // on. Sibling in shape to the peer per-`:placement`
12237 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
12238 // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
12239 // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
12240 // axes, extended onto the outer top-level [`Caixa`] universal-
12241 // axis surface's last unlifted `Option<String>` slot. Pins
12242 // against a future silent detour that returned an owned
12243 // `Option<String>` (which would type-check but silently
12244 // allocate on every accessor call, breaking the zero-cost
12245 // projection every peer sibling accessor carries), a
12246 // `Some("") → None` collapse (which would silently absorb the
12247 // `EdicaoEmpty` refusal case at the accessor boundary and any
12248 // future edition-aware consumer would silently fall back to
12249 // the substrate's default edition on a struct-literal
12250 // `Caixa { edicao: Some(""), .. }`), or a
12251 // `None → Some("2026")` collapse (which would silently reify
12252 // the substrate's default edition at the accessor boundary
12253 // and every downstream consumer keying off the
12254 // `Option::is_none()` discriminator would lose the "author
12255 // omitted the slot" signal).
12256 for edicao in [
12257 None,
12258 Some(""),
12259 Some("2026"),
12260 Some("2018"),
12261 Some("2021"),
12262 Some("2024"),
12263 Some("2026 "),
12264 Some(" 2026"),
12265 Some("2026\n"),
12266 Some("2026"),
12267 Some("v2026"),
12268 Some("2026.1"),
12269 Some("26"),
12270 Some("latest"),
12271 ] {
12272 let c = caixa_with_edicao(edicao);
12273 assert_eq!(
12274 c.edicao(),
12275 edicao,
12276 "Caixa::edicao must return :edicao verbatim (got {:?}, \
12277 expected {edicao:?})",
12278 c.edicao(),
12279 );
12280 assert_eq!(
12281 c.edicao(),
12282 c.edicao.as_deref(),
12283 "Caixa::edicao must byte-equal the raw \
12284 `self.edicao.as_deref()` field access across every \
12285 value in the Option<&str> accept-set",
12286 );
12287 }
12288 }
12289
12290 #[test]
12291 fn validate_edicao_empty_arm_routes_through_accessor() {
12292 // Composition pin: [`Caixa::validate_edicao`]'s empty-arm gate
12293 // must key off [`Caixa::edicao`], not the raw
12294 // `self.edicao.as_deref()` field access. Structurally: a
12295 // `Caixa { edicao: Some(""), .. }` must surface the
12296 // `EdicaoEmpty` refusal exactly, and a
12297 // `Caixa { edicao: Some("2026"), .. }` (the canonical
12298 // 4-digit-ASCII-decimal-year form) must pass validate. The
12299 // pair jointly pins the accessor + validate-gate composition:
12300 // any future silent detour that had the accessor return `None`
12301 // on the empty arm (a `.filter(|s| !s.is_empty())` collapse)
12302 // would silently absorb the `EdicaoEmpty` refusal at the
12303 // accessor boundary and the validate gate would accept a
12304 // struct-literal `Caixa { edicao: Some(""), .. }` — the
12305 // composition pin catches that at caixa-core build time.
12306 //
12307 // Peer of the [`Caixa::licenca`] (6d5bc28)
12308 // `validate_licenca_empty_arm_routes_through_accessor`,
12309 // [`Caixa::repositorio`] (cc7332d)
12310 // `validate_repositorio_empty_arm_routes_through_accessor`,
12311 // and [`Caixa::descricao`] (3f16e2f)
12312 // `validate_descricao_empty_arm_routes_through_accessor`
12313 // composition pins on the sibling outer top-level [`Caixa`]
12314 // `Option<&str>` universal-axis surface — same "the validate /
12315 // shape-gate predicate must route through the substrate-
12316 // primitive typed dispatch" discipline extended onto the
12317 // fourth and final outer top-level [`Caixa`] universal-axis
12318 // `Option<&str>`-composition surface, closing the accessor-
12319 // composition family.
12320 let c = caixa_with_edicao(Some(""));
12321 assert!(
12322 matches!(c.validate_edicao(), Err(ManifestError::EdicaoEmpty)),
12323 "validate_edicao must reject edicao == Some(\"\") with \
12324 EdicaoEmpty — the accessor and the validate gate must \
12325 route through the same substrate-primitive typed dispatch \
12326 on the :edicao empty arm",
12327 );
12328 let c = caixa_with_edicao(Some("2026"));
12329 assert!(
12330 c.validate_edicao().is_ok(),
12331 "validate_edicao must accept edicao == Some(\"2026\") \
12332 (the canonical 4-digit-ASCII-decimal-year shape)",
12333 );
12334 }
12335
12336 #[test]
12337 fn edicao_projects_option_str_by_borrow() {
12338 // The by-borrow pin: [`Caixa::edicao`] returns
12339 // `Option<&str>` by borrow — the `&str` borrows the underlying
12340 // `String` storage of the `Option<String>` slot and the
12341 // accessor must not allocate a fresh `String` on every call.
12342 // Peer of the [`Caixa::licenca`] (6d5bc28),
12343 // [`Caixa::repositorio`] (cc7332d), and [`Caixa::descricao`]
12344 // (3f16e2f) by-borrow pins on the peer outer top-level
12345 // [`Caixa`] `Option<&str>`-return axes, and of the
12346 // per-`:placement`
12347 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
12348 // borrow pin on the peer per-M3-mesh-slot `Option<&str>`-
12349 // return axis, extended onto the fourth and final outer top-
12350 // level [`Caixa`] universal-axis `Option<&str>` shape — the
12351 // accessor's returned `&str` must borrow from `&self` (the
12352 // returned reference's lifetime is tied to `&self`), and
12353 // calling the accessor twice on the same [`Caixa`] must yield
12354 // the same `Option<&str>` verbatim (idempotent, no side
12355 // effects on `&self`).
12356 //
12357 // Pins against a future silent detour that returned an owned
12358 // `Option<String>` (which would type-check but silently
12359 // allocate on every call, breaking the zero-cost projection
12360 // every peer sibling accessor carries), or a one-arm-only
12361 // accessor that returned a saturating value on some sentinel
12362 // input (breaking the pass-through invariant the sibling
12363 // required-scalar accessors carry).
12364 for edicao in [None, Some(""), Some("2026"), Some("2018")] {
12365 let c = caixa_with_edicao(edicao);
12366 let first = c.edicao();
12367 let second = c.edicao();
12368 assert_eq!(
12369 first, second,
12370 "Caixa::edicao must be idempotent — two successive \
12371 calls on the same &self must return the same \
12372 Option<&str>",
12373 );
12374 assert_eq!(
12375 first, edicao,
12376 "Caixa::edicao must return :edicao verbatim by \
12377 borrow — got {first:?}, expected {edicao:?}",
12378 );
12379 }
12380 }
12381
12382 #[test]
12383 fn nome_returns_nome_byte_string_verbatim_across_permutations() {
12384 // The canonical per-`Caixa` `:nome` universal-axis DNS-1123-
12385 // label caixa-identity scalar pin: [`Caixa::nome`] must return
12386 // the `:nome` typed `String` verbatim as `&str`, byte-equal to
12387 // the raw field access across every representative value in
12388 // the accept-set — the canonical `"demo"` template baseline
12389 // (the same `feira init`-scaffolded default the sibling
12390 // `validate_nome_accepts_canonical_template` positive-control
12391 // gate pins), plus every sibling per-typed-slot atom accessor's
12392 // canonical positive-arm byte-string (`"catalog"` per
12393 // [`crate::aplicacao::Membro::nome`], `"cart"` per the peer
12394 // per-`:contratos` `:de`, `"hello-rio"` per the canonical
12395 // `caixa-helm`/`caixa-flux` cross-crate integration-test
12396 // fixture, `"checkout"` per the M3 mesh-slot Aplicacao
12397 // canonical example), plus every past-the-guard sentinel for
12398 // the `NomeEmpty` / `NomeInvalid` / `NomeChartNameBudgetExceeded`
12399 // refusal cases (`""`, `"Bad_Name"`, `"a"` × 56 — 56 bytes fits
12400 // the bare DNS-1123 63-byte cap but overflows the joint
12401 // `lareira-<nome>` chart-name budget the sibling
12402 // [`Caixa::validate_nome_chart_name_budget`] gate closes on).
12403 //
12404 // The past-the-guard sentinels pin the accessor doesn't
12405 // silently absorb the refusal cases into a template-derived
12406 // fallback (a future `.nome().is_empty().then(|| "demo")`
12407 // collapse would silently absorb the `NomeEmpty` refusal at
12408 // the accessor boundary and the validate gate would accept a
12409 // struct-literal `Caixa { nome: "".into(), .. }` — the pin
12410 // catches that at caixa-core build time).
12411 //
12412 // First outer top-level [`Caixa`] `&str`-return required-
12413 // scalar accessor pin — opens the "outer [`Caixa`] `&str`
12414 // required-scalar" projection pattern the sibling per-`Caixa`
12415 // `:versao` future lift folds on. Sibling in shape to the peer
12416 // per-`:membros` [`crate::aplicacao::Membro::nome`] (4a32abf)
12417 // required-`String`-carry accessor pin on the sibling per-
12418 // sub-struct required-axis, extended onto the outer top-level
12419 // [`Caixa`] universal-axis required-`String`-carry axis.
12420 for nome in [
12421 "demo",
12422 "catalog",
12423 "cart",
12424 "hello-rio",
12425 "checkout",
12426 "",
12427 "Bad_Name",
12428 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
12429 ] {
12430 let c = caixa_with_nome(nome);
12431 assert_eq!(
12432 c.nome(),
12433 nome,
12434 "Caixa::nome must return :nome verbatim (got {}, \
12435 expected {nome})",
12436 c.nome(),
12437 );
12438 assert_eq!(
12439 c.nome(),
12440 c.nome.as_str(),
12441 "Caixa::nome must byte-equal the raw .nome field \
12442 access across every value in the String accept-set",
12443 );
12444 }
12445 }
12446
12447 #[test]
12448 fn validate_nome_empty_arm_routes_through_accessor() {
12449 // Composition pin: [`Caixa::validate_nome`]'s empty-arm must
12450 // key off [`Caixa::nome`], not the raw `.nome` field access.
12451 // Structurally: a `Caixa { nome: "".into(), .. }` must surface
12452 // the `NomeEmpty` refusal exactly, and the canonical `"demo"`
12453 // template baseline (the peer positive-arm the sibling
12454 // `validate_nome_accepts_canonical_template` gate carves out)
12455 // must pass validate. The pair jointly pins the accessor +
12456 // validate-gate composition: any future silent detour that
12457 // had the accessor return a fresh `"demo"` on the empty arm
12458 // (a `.nome().is_empty().then(|| "demo")` fallback collapse)
12459 // would silently absorb the `NomeEmpty` refusal at the
12460 // accessor boundary and the validate gate would accept a
12461 // struct-literal `Caixa { nome: "".into(), .. }` — the
12462 // composition pin catches that at caixa-core build time.
12463 //
12464 // Peer of the sibling per-`Caixa`
12465 // `validate_licenca_empty_arm_routes_through_accessor` (6d5bc28)
12466 // / `validate_repositorio_empty_arm_routes_through_accessor`
12467 // (cc7332d) / `validate_descricao_empty_arm_routes_through_accessor`
12468 // (3f16e2f) / `validate_edicao_empty_arm_routes_through_accessor`
12469 // (2641cbd) composition pins on the sibling outer top-level
12470 // [`Caixa`] `Option<&str>` axes — same "the validate /
12471 // shape-gate predicate must route through the substrate-
12472 // primitive typed dispatch" discipline extended onto the peer
12473 // outer top-level [`Caixa`] required-`&str` composition axis.
12474 let c = caixa_with_nome("");
12475 assert!(
12476 matches!(c.validate_nome(), Err(ManifestError::NomeEmpty)),
12477 "validate_nome must reject nome == \"\" with NomeEmpty — \
12478 the accessor and the validate gate must route through the \
12479 same substrate-primitive typed dispatch on the :nome \
12480 empty-arm",
12481 );
12482 let c = caixa_with_nome("demo");
12483 assert!(
12484 c.validate_nome().is_ok(),
12485 "validate_nome must accept nome == \"demo\" (the canonical \
12486 DNS-1123-label template baseline)",
12487 );
12488 }
12489
12490 #[test]
12491 fn nome_projects_str_by_borrow() {
12492 // The by-borrow pin: [`Caixa::nome`] returns `&str` by borrow
12493 // — the `&str` borrows the underlying `String` storage of the
12494 // required `nome` slot and the accessor must not allocate a
12495 // fresh `String` on every call. Peer of the [`Caixa::licenca`]
12496 // (6d5bc28) / [`Caixa::repositorio`] (cc7332d) /
12497 // [`Caixa::descricao`] (3f16e2f) / [`Caixa::edicao`] (2641cbd)
12498 // by-borrow pins on the peer outer top-level [`Caixa`]
12499 // `Option<&str>`-return axes, extended onto the first outer
12500 // top-level [`Caixa`] required-`&str`-return axis — the
12501 // accessor's returned `&str` must borrow from `&self` (the
12502 // returned reference's lifetime is tied to `&self`), and
12503 // calling the accessor twice on the same [`Caixa`] must yield
12504 // the same `&str` verbatim (idempotent, no side effects on
12505 // `&self`).
12506 //
12507 // Pins against a future silent detour that returned an owned
12508 // `String` (which would type-check but silently allocate on
12509 // every call, breaking the zero-cost projection every peer
12510 // sibling accessor carries), an accidental
12511 // `.nome.to_lowercase()` detour that returned a fresh
12512 // allocation through an already-DNS-1123-lowercase-only
12513 // string (breaking a future `const fn` regression), or a
12514 // one-arm-only accessor that returned a canonicalized value
12515 // on some sentinel input (breaking the pass-through invariant
12516 // the sibling required-scalar accessors carry).
12517 for nome in ["demo", "catalog", "hello-rio", "checkout"] {
12518 let c = caixa_with_nome(nome);
12519 let first = c.nome();
12520 let second = c.nome();
12521 assert_eq!(
12522 first, second,
12523 "Caixa::nome must be idempotent — two successive calls \
12524 on the same &self must return the same &str",
12525 );
12526 assert_eq!(
12527 first, nome,
12528 "Caixa::nome must return :nome verbatim by borrow — \
12529 got {first}, expected {nome}",
12530 );
12531 }
12532 }
12533
12534 #[test]
12535 fn versao_returns_versao_byte_string_verbatim_across_permutations() {
12536 // The canonical per-`Caixa` `:versao` universal-axis SemVer-2
12537 // pinned-version scalar pin: [`Caixa::versao`] must return the
12538 // `:versao` typed `String` verbatim as `&str`, byte-equal to the
12539 // raw `.versao` field access across every representative value
12540 // in the accept-set — the canonical `"0.1.0"` template baseline
12541 // (the same `feira init`-scaffolded default the sibling
12542 // `validate_versao_accepts_canonical_template` positive-control
12543 // gate pins), plus every canonical SemVer-2 shape the sibling
12544 // `validate_versao_accepts_canonical_forms` positive-arm sweep
12545 // covers (`"0.0.0"`, `"1.0.0"`, `"0.2.0-rc.1"`,
12546 // `"1.0.0-alpha.0"`, `"1.0.0+build.42"`, `"1.0.0-rc.1+build.42"`,
12547 // `"10.20.30"`), plus every past-the-guard sentinel for the
12548 // `VersaoEmpty` / `VersaoInvalid` refusal cases (`""` the empty
12549 // arm, `"v0.1.0"` the git-tag-shape-leak footgun, `"0.1"` the
12550 // missing-patch footgun, `"^0.1"` the requirement-shape-leak
12551 // footgun, `"0.1.0.0"` the four-part-Java-convention footgun,
12552 // `"latest"` the docker-tag-shape footgun — the sentinels pin
12553 // the accessor doesn't silently absorb the refusal cases into a
12554 // template-derived fallback like `"0.1.0"`).
12555 //
12556 // The past-the-guard sentinels pin the accessor doesn't silently
12557 // absorb the refusal cases into a template-derived fallback (a
12558 // future `.versao().is_empty().then(|| "0.1.0")` collapse would
12559 // silently absorb the `VersaoEmpty` refusal at the accessor
12560 // boundary and the validate gate would accept a struct-literal
12561 // `Caixa { versao: "".into(), .. }` — the pin catches that at
12562 // caixa-core build time).
12563 //
12564 // Second outer top-level [`Caixa`] `&str`-return required-scalar
12565 // accessor pin — folds on the "outer [`Caixa`] `&str` required-
12566 // scalar" projection pattern the sibling per-`Caixa`
12567 // [`Caixa::nome`] (e6b7d97) opened. Sibling in shape to the peer
12568 // per-`:membros` [`crate::aplicacao::Membro::versao_requirement`]
12569 // (4127bb6) / per-`:children`
12570 // [`crate::supervisor::ChildSpec::versao_requirement`] (2c053c8)
12571 // / per-`:upgrade-from`
12572 // [`crate::UpgradeFromEntry::prior_versao`] (75d27a8) per-sub-
12573 // struct `:versao`-shaped `&str`-return accessor pins on the
12574 // sibling per-typed-slot version-carrier axes, extended onto the
12575 // second outer top-level [`Caixa`] universal-axis required-
12576 // `String`-carry axis so the two universal-axis identity-
12577 // carrying scalars every `defcaixa` form supplies (`:nome` +
12578 // `:versao`) share the same "one typed dispatch per axis" pin
12579 // discipline.
12580 for versao in [
12581 "0.1.0",
12582 "0.0.0",
12583 "1.0.0",
12584 "0.2.0-rc.1",
12585 "1.0.0-alpha.0",
12586 "1.0.0+build.42",
12587 "1.0.0-rc.1+build.42",
12588 "10.20.30",
12589 "",
12590 "v0.1.0",
12591 "0.1",
12592 "^0.1",
12593 "0.1.0.0",
12594 "latest",
12595 ] {
12596 let c = caixa_with_versao(versao);
12597 assert_eq!(
12598 c.versao(),
12599 versao,
12600 "Caixa::versao must return :versao verbatim (got {}, \
12601 expected {versao})",
12602 c.versao(),
12603 );
12604 assert_eq!(
12605 c.versao(),
12606 c.versao.as_str(),
12607 "Caixa::versao must byte-equal the raw .versao field \
12608 access across every value in the String accept-set",
12609 );
12610 }
12611 }
12612
12613 #[test]
12614 fn validate_versao_empty_arm_routes_through_accessor() {
12615 // Composition pin: [`Caixa::validate_versao`]'s empty-arm gate
12616 // must key off [`Caixa::versao`], not the raw `.versao` field
12617 // access. Structurally: a `Caixa { versao: "".into(), .. }` must
12618 // surface the `VersaoEmpty` refusal exactly, and the canonical
12619 // `"0.1.0"` template baseline (the peer positive-arm the sibling
12620 // `validate_versao_accepts_canonical_template` gate carves out)
12621 // must pass validate. The pair jointly pins the accessor +
12622 // validate-gate composition: any future silent detour that had
12623 // the accessor return a fresh `"0.1.0"` on the empty arm
12624 // (a `.versao().is_empty().then(|| "0.1.0")` fallback collapse)
12625 // would silently absorb the `VersaoEmpty` refusal at the
12626 // accessor boundary and the validate gate would accept a
12627 // struct-literal `Caixa { versao: "".into(), .. }` — the
12628 // composition pin catches that at caixa-core build time.
12629 //
12630 // Peer of the sibling per-`Caixa`
12631 // `validate_nome_empty_arm_routes_through_accessor` (e6b7d97)
12632 // composition pin on the sibling outer top-level [`Caixa`]
12633 // required-`&str` universal-axis surface — same "the validate /
12634 // shape-gate predicate must route through the substrate-
12635 // primitive typed dispatch" discipline extended onto the peer
12636 // outer top-level [`Caixa`] required-`&str` universal-axis
12637 // pinned-version composition axis, closing the second
12638 // coordinate of the "one canonical typed dispatch per per-Caixa
12639 // required-`&str` universal-axis" discipline.
12640 let c = caixa_with_versao("");
12641 assert!(
12642 matches!(c.validate_versao(), Err(ManifestError::VersaoEmpty)),
12643 "validate_versao must reject versao == \"\" with VersaoEmpty — \
12644 the accessor and the validate gate must route through the \
12645 same substrate-primitive typed dispatch on the :versao \
12646 empty-arm",
12647 );
12648 let c = caixa_with_versao("0.1.0");
12649 assert!(
12650 c.validate_versao().is_ok(),
12651 "validate_versao must accept versao == \"0.1.0\" (the \
12652 canonical SemVer-2 template baseline)",
12653 );
12654 }
12655
12656 #[test]
12657 fn versao_projects_str_by_borrow() {
12658 // The by-borrow pin: [`Caixa::versao`] returns `&str` by borrow
12659 // — the `&str` borrows the underlying `String` storage of the
12660 // required `versao` slot and the accessor must not allocate a
12661 // fresh `String` on every call. Peer of the [`Caixa::nome`]
12662 // (e6b7d97) by-borrow pin on the sibling outer top-level
12663 // [`Caixa`] required-`&str`-return axis, extended onto the
12664 // second outer top-level [`Caixa`] required-`&str`-return
12665 // universal-axis pinned-version surface — the accessor's
12666 // returned `&str` must borrow from `&self` (the returned
12667 // reference's lifetime is tied to `&self`), and calling the
12668 // accessor twice on the same [`Caixa`] must yield the same
12669 // `&str` verbatim (idempotent, no side effects on `&self`).
12670 //
12671 // Pins against a future silent detour that returned an owned
12672 // `String` (which would type-check but silently allocate on
12673 // every call, breaking the zero-cost projection every peer
12674 // sibling accessor carries), an accidental
12675 // `semver::Version::parse(&self.versao).unwrap().to_string()`
12676 // detour that returned a canonicalized fresh allocation through
12677 // an already-canonical byte-string (breaking a future `const fn`
12678 // regression and silently absorbing the `VersaoInvalid` refusal
12679 // at the accessor boundary), or a one-arm-only accessor that
12680 // returned a canonicalized value on some sentinel input
12681 // (breaking the pass-through invariant the sibling required-
12682 // scalar accessors carry).
12683 for versao in ["0.1.0", "1.0.0", "0.2.0-rc.1", "1.0.0+build.42"] {
12684 let c = caixa_with_versao(versao);
12685 let first = c.versao();
12686 let second = c.versao();
12687 assert_eq!(
12688 first, second,
12689 "Caixa::versao must be idempotent — two successive \
12690 calls on the same &self must return the same &str",
12691 );
12692 assert_eq!(
12693 first, versao,
12694 "Caixa::versao must return :versao verbatim by borrow \
12695 — got {first}, expected {versao}",
12696 );
12697 }
12698 }
12699
12700 fn caixa_with_kind(kind: CaixaKind) -> Caixa {
12701 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12702 c.kind = kind;
12703 c
12704 }
12705
12706 #[test]
12707 fn kind_returns_kind_variant_verbatim_across_permutations() {
12708 // The canonical per-`Caixa` `:kind` universal-axis closed-set-
12709 // enum discriminant pin: [`Caixa::kind`] must return the `:kind`
12710 // typed [`CaixaKind`] variant verbatim by `Copy`, byte-equal to
12711 // the raw `.kind` field access across every variant in the
12712 // closed accept-set (`Biblioteca` — the library kind that
12713 // exports lisp forms; `Binario` — the nix-built executable kind
12714 // under `exe/`; `Servico` — the wasm-component daemon kind
12715 // under `servicos/`; `Supervisor` — the OTP-shaped hierarchical
12716 // reconciliation kind; `Aplicacao` — the M3 typed-mesh
12717 // composition kind).
12718 //
12719 // Pins against a future silent detour that re-derived the kind
12720 // from a peer axis (an accidental fallback to
12721 // `if !servicos.is_empty() { Servico } else if
12722 // !membros.is_empty() { Aplicacao } else { Biblioteca }`
12723 // collapse that read the code-surface / mesh-slot columns into
12724 // the kind discriminator), a variant remap the operator
12725 // authors on one consumer without the other, or a stale-derive
12726 // detour that substituted [`CaixaKind::Biblioteca`] as the
12727 // default when the field held any other variant (which would
12728 // silently collapse the distinction between "author explicitly
12729 // declared `:kind Servico`" and "author declared any other
12730 // kind" every downstream renderer-dispatch site depends on).
12731 //
12732 // First outer top-level [`Caixa`] `Copy`-return required-enum-
12733 // discriminant accessor pin — opens the "outer [`Caixa`]
12734 // `Copy`-return required-discriminant" projection pattern.
12735 // Sibling in shape to the peer per-`:supervisor`
12736 // [`crate::supervisor::SupervisorSpec::estrategia`] (eafb619),
12737 // per-`:placement` [`crate::aplicacao::Placement::estrategia`]
12738 // (921fe1b), and per-`:children`
12739 // [`crate::supervisor::ChildSpec::restart`] (dfb4a81)
12740 // `Copy`-return closed-set-enum discriminant accessor pins on
12741 // the sibling nested-spec typed-slot discriminator axes,
12742 // extended here to the outer top-level [`Caixa`] universal-
12743 // axis surface.
12744 for kind in [
12745 CaixaKind::Biblioteca,
12746 CaixaKind::Binario,
12747 CaixaKind::Servico,
12748 CaixaKind::Supervisor,
12749 CaixaKind::Aplicacao,
12750 ] {
12751 let c = caixa_with_kind(kind);
12752 assert_eq!(
12753 c.kind(),
12754 kind,
12755 "Caixa::kind must return :kind verbatim (got {:?}, \
12756 expected {kind:?})",
12757 c.kind(),
12758 );
12759 assert_eq!(
12760 c.kind(),
12761 c.kind,
12762 "Caixa::kind accessor and .kind field access must \
12763 byte-equal — the accessor is the substrate-primitive \
12764 typed dispatch every downstream kind-gate consumer \
12765 must route through",
12766 );
12767 }
12768 }
12769
12770 #[test]
12771 fn require_kind_reads_through_lifted_kind_accessor() {
12772 // Two-consumer coherence pin: the [`crate::render::require_kind`]
12773 // entry-gate predicate (the canonical two-line
12774 // `require_kind(caixa, Servico)?` prelude every per-Servico /
12775 // per-Aplicacao renderer runs at its entry-point) and the
12776 // sibling [`crate::render::KindMismatch`] error carrier's
12777 // `actual:` field (which names the offending caixa's variant
12778 // in the diagnostic) must both key off the lifted accessor, so
12779 // any future rebrand on the typed slot's reader shape lands at
12780 // exactly one place. Pins the two-site coherence by exercising
12781 // every off-diagonal `(actual, expected)` pair across the
12782 // closed accept-set — the `KindMismatch { actual, expected }`
12783 // surfaced on the mismatch arm must byte-equal the pair the
12784 // accessor returns for each side.
12785 //
12786 // Peer of the sibling per-`:placement`
12787 // `validate_placement_reads_through_lifted_estrategia_accessor`
12788 // (921fe1b) two-arm consumer-coherence pin on the M3 mesh-slot
12789 // `Copy`-return discriminant axis — same "the entry-gate
12790 // predicate and the error carrier's `actual:` field must route
12791 // through the substrate-primitive typed dispatch" discipline
12792 // extended onto the outer top-level [`Caixa`] universal-axis
12793 // discriminant surface.
12794 for expected in [
12795 CaixaKind::Biblioteca,
12796 CaixaKind::Binario,
12797 CaixaKind::Servico,
12798 CaixaKind::Supervisor,
12799 CaixaKind::Aplicacao,
12800 ] {
12801 for actual in [
12802 CaixaKind::Biblioteca,
12803 CaixaKind::Binario,
12804 CaixaKind::Servico,
12805 CaixaKind::Supervisor,
12806 CaixaKind::Aplicacao,
12807 ] {
12808 let c = caixa_with_kind(actual);
12809 let result = crate::render::require_kind(&c, expected);
12810 if expected == actual {
12811 assert!(
12812 result.is_ok(),
12813 "require_kind must accept when actual == expected \
12814 (actual={actual:?}, expected={expected:?})",
12815 );
12816 } else {
12817 let err = result.expect_err("require_kind must reject when actual != expected");
12818 assert_eq!(
12819 err.actual,
12820 c.kind(),
12821 "KindMismatch.actual must byte-equal Caixa::kind() \
12822 — the error carrier's `actual:` field reads \
12823 through the lifted accessor",
12824 );
12825 assert_eq!(
12826 err.expected, expected,
12827 "KindMismatch.expected must byte-equal the \
12828 expected variant passed to require_kind",
12829 );
12830 }
12831 }
12832 }
12833 }
12834
12835 #[test]
12836 fn aplicacao_view_kind_gate_routes_through_accessor() {
12837 // Composition pin: [`Caixa::aplicacao_view`]'s kind-gate arm
12838 // must key off [`Caixa::kind`], not the raw `.kind` field
12839 // access. Structurally: a `Caixa { kind: X, .. }` for any
12840 // non-`Aplicacao` variant must fold to `None` on the
12841 // `aplicacao_view` composer (the "kind mismatch → no typed
12842 // view" contract every downstream Aplicacao consumer keys off
12843 // via `?`), and a `Caixa { kind: Aplicacao, .. }` must fold to
12844 // `Some(_)`. The pair jointly pins the accessor + view-gate
12845 // composition: any future silent detour that had the accessor
12846 // return a fresh [`CaixaKind::Aplicacao`] on some sentinel
12847 // input would silently absorb the kind-mismatch case at the
12848 // accessor boundary and every per-Aplicacao renderer would
12849 // silently render a non-Aplicacao caixa's mesh slots — the
12850 // composition pin catches that at caixa-core build time.
12851 //
12852 // Peer of the sibling per-`Caixa`
12853 // `validate_nome_empty_arm_routes_through_accessor` (e6b7d97) /
12854 // `validate_versao_empty_arm_routes_through_accessor` (20c0539)
12855 // composition pins on the sibling outer top-level [`Caixa`]
12856 // required-`&str` universal-axis surfaces — same "the
12857 // composer / validate gate must route through the substrate-
12858 // primitive typed dispatch" discipline extended onto the
12859 // outer top-level [`Caixa`] `Copy`-return required-
12860 // discriminant composition axis.
12861 for kind in [
12862 CaixaKind::Biblioteca,
12863 CaixaKind::Binario,
12864 CaixaKind::Servico,
12865 CaixaKind::Supervisor,
12866 ] {
12867 let c = caixa_with_kind(kind);
12868 assert!(
12869 c.aplicacao_view().is_none(),
12870 "aplicacao_view must return None on non-Aplicacao \
12871 kind {kind:?} — the composer's kind-gate must route \
12872 through Caixa::kind()",
12873 );
12874 }
12875 let c = caixa_with_kind(CaixaKind::Aplicacao);
12876 assert!(
12877 c.aplicacao_view().is_some(),
12878 "aplicacao_view must return Some on kind Aplicacao — \
12879 the composer's kind-gate must accept the matching arm \
12880 through Caixa::kind()",
12881 );
12882 }
12883
12884 #[test]
12885 fn supervisor_view_kind_gate_routes_through_accessor() {
12886 // Composition pin (mirror of the sibling
12887 // `aplicacao_view_kind_gate_routes_through_accessor` on the
12888 // second `_view` composer): [`Caixa::supervisor_view`]'s kind-
12889 // gate arm must key off [`Caixa::kind`], not the raw `.kind`
12890 // field access. A `Caixa { kind: X, .. }` for any non-
12891 // `Supervisor` variant must fold to `None` on the
12892 // `supervisor_view` composer, and a `Caixa { kind:
12893 // Supervisor, .. }` must fold to `Some(_)`. Same peer
12894 // composition pin discipline on the second `_view` composer
12895 // axis.
12896 for kind in [
12897 CaixaKind::Biblioteca,
12898 CaixaKind::Binario,
12899 CaixaKind::Servico,
12900 CaixaKind::Aplicacao,
12901 ] {
12902 let c = caixa_with_kind(kind);
12903 assert!(
12904 c.supervisor_view().is_none(),
12905 "supervisor_view must return None on non-Supervisor \
12906 kind {kind:?} — the composer's kind-gate must route \
12907 through Caixa::kind()",
12908 );
12909 }
12910 let mut c = caixa_with_kind(CaixaKind::Supervisor);
12911 // A Supervisor caixa needs a strategy + at least one child to
12912 // fold to a Some(_) that also validates; the composer itself
12913 // requires only the kind arm, so bare kind flip is enough to
12914 // pin the `Some(_)` return, but we populate the minimum
12915 // supervisor shape so a future strengthening of the composer
12916 // to reject an empty spec doesn't false-positive this pin.
12917 c.estrategia = Some(crate::supervisor::RestartStrategy::OneForOne);
12918 c.children = vec![crate::supervisor::ChildSpec {
12919 caixa: "child".into(),
12920 versao: "^0.1".into(),
12921 restart: crate::supervisor::RestartPolicy::Permanent,
12922 }];
12923 assert!(
12924 c.supervisor_view().is_some(),
12925 "supervisor_view must return Some on kind Supervisor — \
12926 the composer's kind-gate must accept the matching arm \
12927 through Caixa::kind()",
12928 );
12929 }
12930
12931 #[test]
12932 fn kind_projects_by_copy() {
12933 // The by-`Copy` pin: [`Caixa::kind`] returns a fresh
12934 // [`CaixaKind`] by `Copy` — the accessor must not borrow from
12935 // `&self` (the returned value is owned, `Copy`-projected from
12936 // the underlying [`CaixaKind`] storage; two calls on the same
12937 // [`Caixa`] must yield byte-equal values). Peer of the peer
12938 // per-`:placement` `Placement::estrategia` / per-`:supervisor`
12939 // `SupervisorSpec::estrategia` / per-`:children`
12940 // `ChildSpec::restart` `Copy`-return discriminant accessor
12941 // pins on the sibling nested-spec typed-slot discriminator
12942 // axes, extended onto the first outer top-level [`Caixa`]
12943 // required-`Copy`-return axis — pins against a future silent
12944 // detour that returned `&CaixaKind` (which would type-check
12945 // but silently constrain every consumer's callsite to a
12946 // borrow-shaped dispatch, breaking the zero-cost `Copy`
12947 // projection every peer sibling accessor carries).
12948 for kind in [
12949 CaixaKind::Biblioteca,
12950 CaixaKind::Binario,
12951 CaixaKind::Servico,
12952 CaixaKind::Supervisor,
12953 CaixaKind::Aplicacao,
12954 ] {
12955 let c = caixa_with_kind(kind);
12956 let first: CaixaKind = c.kind();
12957 let second: CaixaKind = c.kind();
12958 assert_eq!(
12959 first, second,
12960 "Caixa::kind must be idempotent — two successive \
12961 calls on the same &self must return the same \
12962 CaixaKind variant",
12963 );
12964 assert_eq!(
12965 first, kind,
12966 "Caixa::kind must return :kind verbatim by Copy — \
12967 got {first:?}, expected {kind:?}",
12968 );
12969 }
12970 }
12971
12972 // ── Caixa::autores — outer top-level &[T] slice accessor ──────────
12973
12974 #[test]
12975 fn autores_returns_autores_slice_verbatim_across_permutations() {
12976 // The canonical per-`Caixa` `:autores` universal-axis maintainer-
12977 // name-list slice pin: [`Caixa::autores`] must return the
12978 // `:autores` typed [`Vec<String>`] list verbatim as a
12979 // `&[String]`, byte-equal to the raw `self.autores.as_slice()`
12980 // access across every representative value in the accept-set —
12981 // `[]` (the "no maintainers declared" arm every existing
12982 // fixture without an `:autores` line carries), `[""]` (a past-
12983 // the-guard sentinel that pins the accessor doesn't perform a
12984 // silent `[""] → []` collapse on the empty-entry arm — validate
12985 // rejects `[""]` through `AutorEmpty` but the accessor must
12986 // ship the raw slot verbatim so a validate-time gate regression
12987 // surfaces at the caixa-helm emit boundary rather than being
12988 // silently absorbed into a maintainer-drop), `["pleme-io"]` (the
12989 // canonical single-maintainer form every `feira init` template
12990 // scaffolds), `["alice", "bob"]` (a canonical multi-maintainer
12991 // form), `["alice <alice@example.com>", "bob <bob@example.com>"]`
12992 // (the canonical RFC-5322 `<name> <email>` form the
12993 // `is_chart_maintainer_name_shape` predicate accepts), and
12994 // `["pleme-io", "pleme-io"]` (a past-the-guard duplicate
12995 // sentinel — validate rejects through `AutorDuplicate` but the
12996 // accessor must ship the raw slot verbatim).
12997 //
12998 // First outer top-level [`Caixa`] `&[T]`-return slice accessor
12999 // pin on the substrate primitive — opens the "outer [`Caixa`]
13000 // `&[T]` slice" projection pattern the sibling per-`Caixa`
13001 // `:etiquetas` / `:deps` / `:deps-dev` / `:exe` / `:bibliotecas`
13002 // / `:servicos` / `:upgrade-from` / `:children` future lifts
13003 // fold on. Sibling in shape to the peer per-`:supervisor`
13004 // [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
13005 // per-`:placement` [`crate::aplicacao::Placement::clusters`]
13006 // (a6e18d7), per-`:membros`
13007 // [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
13008 // per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
13009 // (0dcc926), and per-`:upgrade-from :instructions`
13010 // [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
13011 // `&[T]`-return slice accessor pins on the sibling per-M2 /
13012 // per-M3 typed-slot list axes, extended onto the outer top-
13013 // level [`Caixa`] universal-axis surface. Pins against a future
13014 // silent detour that returned an owned `Vec<String>` (which
13015 // would type-check but silently clone on every accessor call,
13016 // breaking the zero-cost projection every peer sibling slice
13017 // accessor carries), a `[""] → []` collapse (which would
13018 // silently absorb the `AutorEmpty` refusal case at the accessor
13019 // boundary), or a `["a", "a"] → ["a"]` dedup collapse (which
13020 // would silently absorb the `AutorDuplicate` refusal case at
13021 // the accessor boundary and the caixa-helm `maintainers:` fold
13022 // would silently render a dedupped list on a struct-literal
13023 // `Caixa { autores: vec!["a".into(), "a".into()], .. }`).
13024 for autores in [
13025 vec![],
13026 vec![""],
13027 vec!["pleme-io"],
13028 vec!["alice", "bob"],
13029 vec!["alice <alice@example.com>", "bob <bob@example.com>"],
13030 vec!["pleme-io", "pleme-io"],
13031 ] {
13032 let c = caixa_with_autores(autores.clone());
13033 let expected: Vec<String> = autores.iter().map(|s| (*s).to_string()).collect();
13034 assert_eq!(
13035 c.autores(),
13036 expected.as_slice(),
13037 "Caixa::autores must return :autores verbatim (got {:?}, \
13038 expected {expected:?})",
13039 c.autores(),
13040 );
13041 assert_eq!(
13042 c.autores(),
13043 c.autores.as_slice(),
13044 "Caixa::autores must byte-equal the raw \
13045 `self.autores.as_slice()` field access across every \
13046 value in the Vec<String> accept-set",
13047 );
13048 }
13049 }
13050
13051 #[test]
13052 fn validate_autores_empty_entry_arm_routes_through_accessor() {
13053 // Composition pin: [`Caixa::validate_autores`]'s per-entry
13054 // empty-arm gate must key off [`Caixa::autores`], not the raw
13055 // `&self.autores` field-borrow walk. Structurally: a
13056 // `Caixa { autores: vec!["".into()], .. }` must surface the
13057 // `AutorEmpty` refusal exactly, and a
13058 // `Caixa { autores: vec!["pleme-io".into()], .. }` (the
13059 // canonical single-maintainer form) must pass validate. The
13060 // pair jointly pins the accessor + validate-gate composition:
13061 // any future silent detour that had the accessor return an
13062 // empty slice on the `[""]` arm (a
13063 // `.iter().filter(|s| !s.is_empty()).collect()` collapse)
13064 // would silently absorb the `AutorEmpty` refusal at the
13065 // accessor boundary and the validate gate would accept a
13066 // struct-literal `Caixa { autores: vec!["".into()], .. }` —
13067 // the composition pin catches that at caixa-core build time.
13068 //
13069 // Peer of the per-`Caixa` [`Caixa::validate_licenca`] (6d5bc28)
13070 // accessor-composition pin
13071 // (`validate_licenca_empty_arm_routes_through_accessor`) on the
13072 // sibling `Option<&str>`-composition axis and the
13073 // per-`:politicas :circuit-breaker`
13074 // [`crate::aplicacao::CircuitBreaker::max_failures`] (3a74062)
13075 // accessor-composition pin
13076 // (`validate_politicas_max_failures_zero_floor_arm_routes_through_accessor`)
13077 // on the sibling required-`u32`-composition axis — same "the
13078 // validate / shape-gate predicate must route through the
13079 // substrate-primitive typed dispatch" discipline extended onto
13080 // the outer top-level [`Caixa`] universal-axis `&[T]`-
13081 // composition surface.
13082 let c = caixa_with_autores(vec![""]);
13083 assert!(
13084 matches!(c.validate_autores(), Err(ManifestError::AutorEmpty)),
13085 "validate_autores must reject autores == vec![\"\"] with \
13086 AutorEmpty — the accessor and the validate gate must \
13087 route through the same substrate-primitive typed dispatch \
13088 on the :autores per-entry empty arm",
13089 );
13090 let c = caixa_with_autores(vec!["pleme-io"]);
13091 assert!(
13092 c.validate_autores().is_ok(),
13093 "validate_autores must accept autores == vec![\"pleme-io\"] \
13094 (the canonical single-maintainer shape every `feira init` \
13095 template scaffolds)",
13096 );
13097 }
13098
13099 #[test]
13100 fn autores_projects_slice_by_borrow() {
13101 // The by-borrow pin: [`Caixa::autores`] returns `&[String]` by
13102 // borrow — the returned slice borrows the underlying
13103 // `Vec<String>` storage of the `:autores` slot and the
13104 // accessor must not clone the backing `Vec` on every call.
13105 // Peer of the per-`:membros`
13106 // [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36) /
13107 // per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
13108 // (0dcc926) / per-`:placement`
13109 // [`crate::aplicacao::Placement::clusters`] (a6e18d7) /
13110 // per-`:supervisor` [`crate::supervisor::SupervisorSpec::children`]
13111 // (bc92bce) by-borrow pins on the sibling per-M2 / per-M3
13112 // typed-slot `&[T]`-return axes, extended onto the outer top-
13113 // level [`Caixa`] universal-axis `&[String]` shape — the
13114 // accessor's returned slice must borrow from `&self` (the
13115 // returned reference's lifetime is tied to `&self`), and
13116 // calling the accessor twice on the same [`Caixa`] must yield
13117 // slices that are pointer-equal (the underlying byte-buffer is
13118 // the storage `Vec`'s allocation, not a fresh copy) as well as
13119 // value-equal (idempotent, no side effects on `&self`).
13120 //
13121 // Pins against a future silent detour that returned an owned
13122 // `Vec<String>` (which would type-check but silently clone on
13123 // every call, breaking the zero-cost projection every peer
13124 // sibling slice accessor carries), a `&Vec<String>` return
13125 // (which would leak the backing `Vec`'s grow/push/reserve
13126 // surface no downstream consumer reaches for), or a one-arm-
13127 // only accessor that returned a saturating value on some
13128 // sentinel input (breaking the pass-through invariant the
13129 // sibling slice accessors carry).
13130 for autores in [
13131 vec![],
13132 vec!["pleme-io"],
13133 vec!["alice", "bob"],
13134 vec!["pleme-io", "pleme-io"],
13135 ] {
13136 let c = caixa_with_autores(autores.clone());
13137 let expected: Vec<String> = autores.iter().map(|s| (*s).to_string()).collect();
13138 let first = c.autores();
13139 let second = c.autores();
13140 assert_eq!(
13141 first, second,
13142 "Caixa::autores must be idempotent — two successive \
13143 calls on the same &self must return the same \
13144 &[String]",
13145 );
13146 assert_eq!(
13147 first.as_ptr(),
13148 second.as_ptr(),
13149 "Caixa::autores must borrow the underlying Vec<String> \
13150 storage — two successive calls must return slices \
13151 with the same backing pointer (a fresh Vec<String> \
13152 clone would change the pointer on every call)",
13153 );
13154 assert_eq!(
13155 first,
13156 expected.as_slice(),
13157 "Caixa::autores must return :autores verbatim by \
13158 borrow — got {first:?}, expected {expected:?}",
13159 );
13160 }
13161 }
13162
13163 // ── Caixa::etiquetas — outer top-level &[T] slice accessor ────────
13164
13165 #[test]
13166 fn etiquetas_returns_etiquetas_slice_verbatim_across_permutations() {
13167 // The canonical per-`Caixa` `:etiquetas` universal-axis
13168 // registry-search-tag-list slice pin: [`Caixa::etiquetas`] must
13169 // return the `:etiquetas` typed [`Vec<String>`] list verbatim
13170 // as a `&[String]`, byte-equal to the raw
13171 // `self.etiquetas.as_slice()` access across every representative
13172 // value in the accept-set — `[]` (the "no tags declared" arm
13173 // every existing fixture without an `:etiquetas` line carries),
13174 // `[""]` (a past-the-guard sentinel that pins the accessor
13175 // doesn't perform a silent `[""] → []` collapse on the empty-
13176 // entry arm — validate rejects `[""]` through `EtiquetaEmpty`
13177 // but the accessor must ship the raw slot verbatim so a
13178 // validate-time gate regression surfaces at the caixa-helm emit
13179 // boundary rather than being silently absorbed into a keyword-
13180 // drop), `["demo"]` (the canonical single-tag form every
13181 // `feira init` template scaffolds), `["example", "aplicacao",
13182 // "mesh", "ecommerce", "demo"]` (the canonical multi-tag form
13183 // the checkout-aplicacao fixture emits), and `["demo", "demo"]`
13184 // (a past-the-guard duplicate sentinel — validate rejects
13185 // through `EtiquetaDuplicate` but the accessor must ship the
13186 // raw slot verbatim so the caixa-helm `BTreeSet::collect` dedup
13187 // at chart-render time isn't silently promoted into the
13188 // accessor boundary and struct-literal
13189 // `Caixa { etiquetas: vec!["demo".into(), "demo".into()], .. }`
13190 // fixtures continue to expose the duplicate at the accessor).
13191 //
13192 // Second outer top-level [`Caixa`] `&[T]`-return slice accessor
13193 // pin on the substrate primitive — folds on the "outer
13194 // [`Caixa`] `&[T]` slice" projection pattern
13195 // `autores_returns_autores_slice_verbatim_across_permutations`
13196 // (b5d813f) opened, sibling in shape and idiom. Pins against a
13197 // future silent detour that returned an owned `Vec<String>`
13198 // (which would type-check but silently clone on every accessor
13199 // call, breaking the zero-cost projection every peer sibling
13200 // slice accessor carries), a `[""] → []` collapse (which would
13201 // silently absorb the `EtiquetaEmpty` refusal case at the
13202 // accessor boundary), or a `["a", "a"] → ["a"]` dedup collapse
13203 // (which would silently absorb the `EtiquetaDuplicate` refusal
13204 // case at the accessor boundary — the caixa-helm chart-render
13205 // `BTreeSet::collect` dedup is downstream of the accessor and
13206 // must not be silently promoted into it).
13207 for etiquetas in [
13208 vec![],
13209 vec![""],
13210 vec!["demo"],
13211 vec!["example", "aplicacao", "mesh", "ecommerce", "demo"],
13212 vec!["demo", "demo"],
13213 ] {
13214 let c = caixa_with_etiquetas(etiquetas.clone());
13215 let expected: Vec<String> = etiquetas.iter().map(|s| (*s).to_string()).collect();
13216 assert_eq!(
13217 c.etiquetas(),
13218 expected.as_slice(),
13219 "Caixa::etiquetas must return :etiquetas verbatim (got \
13220 {:?}, expected {expected:?})",
13221 c.etiquetas(),
13222 );
13223 assert_eq!(
13224 c.etiquetas(),
13225 c.etiquetas.as_slice(),
13226 "Caixa::etiquetas must byte-equal the raw \
13227 `self.etiquetas.as_slice()` field access across every \
13228 value in the Vec<String> accept-set",
13229 );
13230 }
13231 }
13232
13233 #[test]
13234 fn validate_etiquetas_empty_entry_arm_routes_through_accessor() {
13235 // Composition pin: [`Caixa::validate_etiquetas`]'s per-entry
13236 // empty-arm gate must key off [`Caixa::etiquetas`], not the raw
13237 // `&self.etiquetas` field-borrow walk. Structurally: a
13238 // `Caixa { etiquetas: vec!["".into()], .. }` must surface the
13239 // `EtiquetaEmpty` refusal exactly, and a
13240 // `Caixa { etiquetas: vec!["demo".into()], .. }` (the canonical
13241 // single-tag form) must pass validate. The pair jointly pins
13242 // the accessor + validate-gate composition: any future silent
13243 // detour that had the accessor return an empty slice on the
13244 // `[""]` arm (a
13245 // `.iter().filter(|s| !s.is_empty()).collect()` collapse) would
13246 // silently absorb the `EtiquetaEmpty` refusal at the accessor
13247 // boundary and the validate gate would accept a struct-literal
13248 // `Caixa { etiquetas: vec!["".into()], .. }` — the composition
13249 // pin catches that at caixa-core build time.
13250 //
13251 // Peer of the per-`Caixa` `validate_autores_empty_arm_routes_
13252 // through_accessor` (b5d813f) accessor-composition pin on the
13253 // sibling `&[T]`-composition axis — same "the validate / shape-
13254 // gate predicate must route through the substrate-primitive
13255 // typed dispatch" discipline extended onto the sibling outer
13256 // top-level [`Caixa`] `&[T]`-composition surface.
13257 let c = caixa_with_etiquetas(vec![""]);
13258 assert!(
13259 matches!(c.validate_etiquetas(), Err(ManifestError::EtiquetaEmpty)),
13260 "validate_etiquetas must reject etiquetas == vec![\"\"] \
13261 with EtiquetaEmpty — the accessor and the validate gate \
13262 must route through the same substrate-primitive typed \
13263 dispatch on the :etiquetas per-entry empty arm",
13264 );
13265 let c = caixa_with_etiquetas(vec!["demo"]);
13266 assert!(
13267 c.validate_etiquetas().is_ok(),
13268 "validate_etiquetas must accept etiquetas == vec![\"demo\"] \
13269 (the canonical single-tag shape every `feira init` \
13270 template scaffolds)",
13271 );
13272 }
13273
13274 #[test]
13275 fn etiquetas_projects_slice_by_borrow() {
13276 // The by-borrow pin: [`Caixa::etiquetas`] returns `&[String]`
13277 // by borrow — the returned slice borrows the underlying
13278 // `Vec<String>` storage of the `:etiquetas` slot and the
13279 // accessor must not clone the backing `Vec` on every call.
13280 // Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
13281 // (b5d813f) by-borrow pin on the sibling outer top-level
13282 // [`Caixa`] `&[String]`-return axis — the accessor's returned
13283 // slice must borrow from `&self` (the returned reference's
13284 // lifetime is tied to `&self`), and calling the accessor twice
13285 // on the same [`Caixa`] must yield slices that are pointer-
13286 // equal (the underlying byte-buffer is the storage `Vec`'s
13287 // allocation, not a fresh copy) as well as value-equal
13288 // (idempotent, no side effects on `&self`).
13289 //
13290 // Pins against a future silent detour that returned an owned
13291 // `Vec<String>` (which would type-check but silently clone on
13292 // every call, breaking the zero-cost projection every peer
13293 // sibling slice accessor carries), a `&Vec<String>` return
13294 // (which would leak the backing `Vec`'s grow/push/reserve
13295 // surface no downstream consumer reaches for), or a one-arm-
13296 // only accessor that returned a saturating value on some
13297 // sentinel input (breaking the pass-through invariant the
13298 // sibling slice accessors carry).
13299 for etiquetas in [
13300 vec![],
13301 vec!["demo"],
13302 vec!["example", "aplicacao", "mesh"],
13303 vec!["demo", "demo"],
13304 ] {
13305 let c = caixa_with_etiquetas(etiquetas.clone());
13306 let expected: Vec<String> = etiquetas.iter().map(|s| (*s).to_string()).collect();
13307 let first = c.etiquetas();
13308 let second = c.etiquetas();
13309 assert_eq!(
13310 first, second,
13311 "Caixa::etiquetas must be idempotent — two successive \
13312 calls on the same &self must return the same \
13313 &[String]",
13314 );
13315 assert_eq!(
13316 first.as_ptr(),
13317 second.as_ptr(),
13318 "Caixa::etiquetas must borrow the underlying \
13319 Vec<String> storage — two successive calls must \
13320 return slices with the same backing pointer (a fresh \
13321 Vec<String> clone would change the pointer on every \
13322 call)",
13323 );
13324 assert_eq!(
13325 first,
13326 expected.as_slice(),
13327 "Caixa::etiquetas must return :etiquetas verbatim by \
13328 borrow — got {first:?}, expected {expected:?}",
13329 );
13330 }
13331 }
13332
13333 // ── Caixa::bibliotecas — outer top-level &[T] slice accessor ──────
13334
13335 #[test]
13336 fn bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations() {
13337 // The canonical per-`Caixa` `:bibliotecas` universal-axis
13338 // library-source-path-list slice pin: [`Caixa::bibliotecas`]
13339 // must return the `:bibliotecas` typed [`Vec<String>`] list
13340 // verbatim as a `&[String]`, byte-equal to the raw
13341 // `self.bibliotecas.as_slice()` access across every
13342 // representative value in the accept-set — `[]` (the "no
13343 // libraries declared" arm every `:kind` other than `Biblioteca`
13344 // + every `Biblioteca` relying on the canonical
13345 // `lib/<nome>.lisp` implicit-default path carries; the
13346 // layout's [`crate::LayoutInvariants`] `MissingLib` arm-gate
13347 // fires exactly on this empty-slot + `Biblioteca`-kind
13348 // combination), `[""]` (a past-the-guard sentinel that pins
13349 // the accessor doesn't perform a silent `[""] → []` collapse
13350 // on the empty-entry arm — validate rejects `[""]` through
13351 // `CodePathEmpty { slot: ":bibliotecas" }` but the accessor
13352 // must ship the raw slot verbatim so a validate-time gate
13353 // regression surfaces at the `feira build` phase-1 parse
13354 // boundary rather than being silently absorbed into a
13355 // library-drop), `["lib/demo.lisp"]` (the canonical single-
13356 // entry form `Caixa::template` scaffolds and every `feira init`
13357 // template emits), `["lib/demo.lisp", "lib/helpers.lisp"]`
13358 // (the canonical multi-library form the
13359 // `validate_code_paths_accepts_explicit_relative_paths_on_
13360 // every_slot` fixture emits), and `["lib/foo.lisp",
13361 // "lib/foo.lisp"]` (a past-the-guard duplicate sentinel —
13362 // validate rejects through `CodePathDuplicate { slot:
13363 // ":bibliotecas" }` per the per-slot set-not-multiset gate,
13364 // but the accessor must ship the raw slot verbatim so the
13365 // `feira build` `for entry in caixa.bibliotecas()` parse walk
13366 // sees the duplicate at the accessor boundary and struct-
13367 // literal `Caixa { bibliotecas: vec!["lib/foo.lisp".into(),
13368 // "lib/foo.lisp".into()], .. }` fixtures continue to expose
13369 // the duplicate at the accessor).
13370 //
13371 // Third outer top-level [`Caixa`] `&[T]`-return slice accessor
13372 // pin on the substrate primitive — folds on the "outer
13373 // [`Caixa`] `&[T]` slice" projection pattern
13374 // `autores_returns_autores_slice_verbatim_across_permutations`
13375 // (b5d813f) opened and
13376 // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
13377 // (78c7d3c) folded on, sibling in shape and idiom. Pins
13378 // against a future silent detour that returned an owned
13379 // `Vec<String>` (which would type-check but silently clone on
13380 // every accessor call, breaking the zero-cost projection
13381 // every peer sibling slice accessor carries), a `[""] → []`
13382 // collapse (which would silently absorb the `CodePathEmpty`
13383 // refusal case at the accessor boundary), or a `["lib/foo.lisp",
13384 // "lib/foo.lisp"] → ["lib/foo.lisp"]` dedup collapse (which
13385 // would silently absorb the `CodePathDuplicate` refusal case
13386 // at the accessor boundary — the per-slot set-not-multiset
13387 // gate is downstream of the accessor and must not be silently
13388 // promoted into it).
13389 for bibliotecas in [
13390 vec![],
13391 vec![""],
13392 vec!["lib/demo.lisp"],
13393 vec!["lib/demo.lisp", "lib/helpers.lisp"],
13394 vec!["lib/foo.lisp", "lib/foo.lisp"],
13395 ] {
13396 let c = caixa_with_code_paths(bibliotecas.clone(), vec![], vec![]);
13397 let expected: Vec<String> = bibliotecas.iter().map(|s| (*s).to_string()).collect();
13398 assert_eq!(
13399 c.bibliotecas(),
13400 expected.as_slice(),
13401 "Caixa::bibliotecas must return :bibliotecas verbatim \
13402 (got {:?}, expected {expected:?})",
13403 c.bibliotecas(),
13404 );
13405 assert_eq!(
13406 c.bibliotecas(),
13407 c.bibliotecas.as_slice(),
13408 "Caixa::bibliotecas must byte-equal the raw \
13409 `self.bibliotecas.as_slice()` field access across \
13410 every value in the Vec<String> accept-set",
13411 );
13412 }
13413 }
13414
13415 #[test]
13416 fn validate_code_paths_bibliotecas_empty_arm_routes_through_accessor() {
13417 // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
13418 // empty-arm gate on the `:bibliotecas` slot must key off
13419 // [`Caixa::bibliotecas`], not a divergent raw
13420 // `&self.bibliotecas` field-borrow walk. Structurally: a
13421 // `Caixa { bibliotecas: vec!["".into()], .. }` must surface
13422 // the `CodePathEmpty { slot: ":bibliotecas" }` refusal
13423 // exactly, and a `Caixa { bibliotecas: vec!["lib/demo.lisp".
13424 // into()], .. }` (the canonical single-library form
13425 // `Caixa::template` scaffolds) must pass validate. The pair
13426 // jointly pins the accessor + validate-gate composition: any
13427 // future silent detour that had the accessor return an empty
13428 // slice on the `[""]` arm (a `.iter().filter(|s|
13429 // !s.is_empty()).collect()` collapse) would silently absorb
13430 // the `CodePathEmpty` refusal at the accessor boundary and
13431 // the validate gate would accept a struct-literal
13432 // `Caixa { bibliotecas: vec!["".into()], .. }` — the
13433 // composition pin catches that at caixa-core build time.
13434 //
13435 // Peer of the per-`Caixa` `validate_autores_empty_arm_routes_
13436 // through_accessor` (b5d813f) and
13437 // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
13438 // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
13439 // composition axes — same "the validate / shape-gate
13440 // predicate must route through the substrate-primitive typed
13441 // dispatch" discipline extended onto the sibling outer top-
13442 // level [`Caixa`] `&[T]`-composition surface. Nominally the
13443 // in-tree `validate_code_paths` production body still keys
13444 // off the internal `[(":bibliotecas", &self.bibliotecas,
13445 // CodePathFileType::LispSource), (":exe", &self.exe, ..),
13446 // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
13447 // (the tuple's homogeneous slice-typed shape blocks a per-
13448 // element accessor swap in isolation — a future companion
13449 // lift for `:exe` and `:servicos` on the same outer-`Caixa`
13450 // `&[T]` slice-accessor axis closes that tuple onto the
13451 // triple of typed dispatches as a unit); the composition pin
13452 // catches any future accessor-side silent filter drop against
13453 // that eventual tuple-closure regardless of whether the
13454 // `:bibliotecas` slot is threaded through the accessor or the
13455 // raw field access at the tuple's construction site.
13456 let c = caixa_with_code_paths(vec![""], vec![], vec![]);
13457 assert!(
13458 matches!(
13459 c.validate_code_paths(),
13460 Err(ManifestError::CodePathEmpty {
13461 slot: ":bibliotecas"
13462 })
13463 ),
13464 "validate_code_paths must reject bibliotecas == vec![\"\"] \
13465 with CodePathEmpty {{ slot: \":bibliotecas\" }} — the \
13466 accessor and the validate gate must route through the \
13467 same substrate-primitive typed dispatch on the \
13468 :bibliotecas per-entry empty arm",
13469 );
13470 let c = caixa_with_code_paths(vec!["lib/demo.lisp"], vec![], vec![]);
13471 assert!(
13472 c.validate_code_paths().is_ok(),
13473 "validate_code_paths must accept bibliotecas == \
13474 vec![\"lib/demo.lisp\"] (the canonical single-library \
13475 shape every `feira init` template scaffolds)",
13476 );
13477 }
13478
13479 #[test]
13480 fn bibliotecas_projects_slice_by_borrow() {
13481 // The by-borrow pin: [`Caixa::bibliotecas`] returns
13482 // `&[String]` by borrow — the returned slice borrows the
13483 // underlying `Vec<String>` storage of the `:bibliotecas` slot
13484 // and the accessor must not clone the backing `Vec` on every
13485 // call. Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
13486 // (b5d813f) and `etiquetas_projects_slice_by_borrow` (78c7d3c)
13487 // by-borrow pins on the sibling outer top-level [`Caixa`]
13488 // `&[String]`-return axes — the accessor's returned slice
13489 // must borrow from `&self` (the returned reference's lifetime
13490 // is tied to `&self`), and calling the accessor twice on the
13491 // same [`Caixa`] must yield slices that are pointer-equal
13492 // (the underlying byte-buffer is the storage `Vec`'s
13493 // allocation, not a fresh copy) as well as value-equal
13494 // (idempotent, no side effects on `&self`).
13495 //
13496 // Pins against a future silent detour that returned an owned
13497 // `Vec<String>` (which would type-check but silently clone on
13498 // every call, breaking the zero-cost projection every peer
13499 // sibling slice accessor carries), a `&Vec<String>` return
13500 // (which would leak the backing `Vec`'s grow/push/reserve
13501 // surface no downstream consumer reaches for), or a one-arm-
13502 // only accessor that returned a saturating value on some
13503 // sentinel input (breaking the pass-through invariant the
13504 // sibling slice accessors carry).
13505 for bibliotecas in [
13506 vec![],
13507 vec!["lib/demo.lisp"],
13508 vec!["lib/demo.lisp", "lib/helpers.lisp"],
13509 vec!["lib/foo.lisp", "lib/foo.lisp"],
13510 ] {
13511 let c = caixa_with_code_paths(bibliotecas.clone(), vec![], vec![]);
13512 let expected: Vec<String> = bibliotecas.iter().map(|s| (*s).to_string()).collect();
13513 let first = c.bibliotecas();
13514 let second = c.bibliotecas();
13515 assert_eq!(
13516 first, second,
13517 "Caixa::bibliotecas must be idempotent — two \
13518 successive calls on the same &self must return the \
13519 same &[String]",
13520 );
13521 assert_eq!(
13522 first.as_ptr(),
13523 second.as_ptr(),
13524 "Caixa::bibliotecas must borrow the underlying \
13525 Vec<String> storage — two successive calls must \
13526 return slices with the same backing pointer (a \
13527 fresh Vec<String> clone would change the pointer on \
13528 every call)",
13529 );
13530 assert_eq!(
13531 first,
13532 expected.as_slice(),
13533 "Caixa::bibliotecas must return :bibliotecas verbatim \
13534 by borrow — got {first:?}, expected {expected:?}",
13535 );
13536 }
13537 }
13538
13539 // ── Caixa::exe — outer top-level &[T] slice accessor ──────────────
13540
13541 #[test]
13542 fn exe_returns_exe_slice_verbatim_across_permutations() {
13543 // The canonical per-`Caixa` `:exe` universal-axis
13544 // nix-built-executable-entry-path-list slice pin: [`Caixa::exe`]
13545 // must return the `:exe` typed [`Vec<String>`] list verbatim as
13546 // a `&[String]`, byte-equal to the raw `self.exe.as_slice()`
13547 // access across every representative value in the accept-set —
13548 // `[]` (the "no executable declared" arm every `:kind` other
13549 // than `Binario` carries; the layout's [`crate::LayoutInvariants`]
13550 // `BinarioWithoutExe` arm-gate fires exactly on this empty-slot
13551 // + `Binario`-kind combination), `[""]` (a past-the-guard
13552 // sentinel that pins the accessor doesn't perform a silent
13553 // `[""] → []` collapse on the empty-entry arm — validate rejects
13554 // `[""]` through `CodePathEmpty { slot: ":exe" }` but the
13555 // accessor must ship the raw slot verbatim so a validate-time
13556 // gate regression surfaces at the layout / `feira nix` boundary
13557 // rather than being silently absorbed into an executable-drop),
13558 // `["exe/cli"]` (the canonical single-entry Binario form every
13559 // in-tree `caixa_with_code_paths` positive control uses),
13560 // `["exe/cli", "exe/serve"]` (the canonical multi-executable
13561 // form the `validate_code_paths_accepts_explicit_relative_paths_
13562 // on_every_slot` fixture emits), and `["exe/cli", "exe/cli"]`
13563 // (a past-the-guard duplicate sentinel — validate rejects
13564 // through `CodePathDuplicate { slot: ":exe" }` per the per-slot
13565 // set-not-multiset gate, but the accessor must ship the raw
13566 // slot verbatim so struct-literal `Caixa { exe: vec!["exe/cli".
13567 // into(), "exe/cli".into()], .. }` fixtures continue to expose
13568 // the duplicate at the accessor).
13569 //
13570 // Fourth outer top-level [`Caixa`] `&[T]`-return slice accessor
13571 // pin on the substrate primitive — folds on the "outer
13572 // [`Caixa`] `&[T]` slice" projection pattern
13573 // `autores_returns_autores_slice_verbatim_across_permutations`
13574 // (b5d813f) opened,
13575 // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
13576 // (78c7d3c) folded on, and
13577 // `bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
13578 // (8a36c23) closed the universal-axis text-tag family of.
13579 // Opens the outer-`Caixa` foreign-code-slot `&[T]` sub-family
13580 // the sibling `:servicos` future lift closes onto. Pins against
13581 // a future silent detour that returned an owned `Vec<String>`
13582 // (which would type-check but silently clone on every accessor
13583 // call, breaking the zero-cost projection every peer sibling
13584 // slice accessor carries), a `[""] → []` collapse (which would
13585 // silently absorb the `CodePathEmpty` refusal case at the
13586 // accessor boundary), or an `["exe/cli", "exe/cli"] →
13587 // ["exe/cli"]` dedup collapse (which would silently absorb the
13588 // `CodePathDuplicate` refusal case at the accessor boundary —
13589 // the per-slot set-not-multiset gate is downstream of the
13590 // accessor and must not be silently promoted into it).
13591 for exe in [
13592 vec![],
13593 vec![""],
13594 vec!["exe/cli"],
13595 vec!["exe/cli", "exe/serve"],
13596 vec!["exe/cli", "exe/cli"],
13597 ] {
13598 let c = caixa_with_code_paths(vec![], exe.clone(), vec![]);
13599 let expected: Vec<String> = exe.iter().map(|s| (*s).to_string()).collect();
13600 assert_eq!(
13601 c.exe(),
13602 expected.as_slice(),
13603 "Caixa::exe must return :exe verbatim (got {:?}, \
13604 expected {expected:?})",
13605 c.exe(),
13606 );
13607 assert_eq!(
13608 c.exe(),
13609 c.exe.as_slice(),
13610 "Caixa::exe must byte-equal the raw \
13611 `self.exe.as_slice()` field access across every value \
13612 in the Vec<String> accept-set",
13613 );
13614 }
13615 }
13616
13617 #[test]
13618 fn validate_code_paths_exe_empty_arm_routes_through_accessor() {
13619 // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
13620 // empty-arm gate on the `:exe` slot must key off
13621 // [`Caixa::exe`], not a divergent raw `&self.exe` field-borrow
13622 // walk. Structurally: a `Caixa { exe: vec!["".into()], .. }`
13623 // must surface the `CodePathEmpty { slot: ":exe" }` refusal
13624 // exactly, and a `Caixa { exe: vec!["exe/cli".into()], .. }`
13625 // (the canonical single-executable form every in-tree
13626 // `caixa_with_code_paths` positive control uses) must pass
13627 // validate. The pair jointly pins the accessor + validate-gate
13628 // composition: any future silent detour that had the accessor
13629 // return an empty slice on the `[""]` arm (a
13630 // `.iter().filter(|s| !s.is_empty()).collect()` collapse) would
13631 // silently absorb the `CodePathEmpty` refusal at the accessor
13632 // boundary and the validate gate would accept a struct-literal
13633 // `Caixa { exe: vec!["".into()], .. }` — the composition pin
13634 // catches that at caixa-core build time.
13635 //
13636 // Peer of the per-`Caixa`
13637 // `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
13638 // (8a36c23), `validate_autores_empty_arm_routes_through_accessor`
13639 // (b5d813f), and
13640 // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
13641 // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
13642 // composition axes — same "the validate / shape-gate predicate
13643 // must route through the substrate-primitive typed dispatch"
13644 // discipline extended onto the sibling outer top-level [`Caixa`]
13645 // `&[T]`-composition surface. Nominally the in-tree
13646 // `validate_code_paths` production body still keys off the
13647 // internal `[(":bibliotecas", &self.bibliotecas,
13648 // CodePathFileType::LispSource), (":exe", &self.exe, ..),
13649 // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
13650 // (the tuple's homogeneous slice-typed shape blocks a per-
13651 // element accessor swap in isolation — a future companion lift
13652 // for `:servicos` on the same outer-`Caixa` `&[T]` slice-
13653 // accessor axis closes that tuple onto the triple of typed
13654 // dispatches as a unit); the composition pin catches any future
13655 // accessor-side silent filter drop against that eventual tuple-
13656 // closure regardless of whether the `:exe` slot is threaded
13657 // through the accessor or the raw field access at the tuple's
13658 // construction site.
13659 let c = caixa_with_code_paths(vec![], vec![""], vec![]);
13660 assert!(
13661 matches!(
13662 c.validate_code_paths(),
13663 Err(ManifestError::CodePathEmpty { slot: ":exe" })
13664 ),
13665 "validate_code_paths must reject exe == vec![\"\"] \
13666 with CodePathEmpty {{ slot: \":exe\" }} — the \
13667 accessor and the validate gate must route through the \
13668 same substrate-primitive typed dispatch on the \
13669 :exe per-entry empty arm",
13670 );
13671 let c = caixa_with_code_paths(vec![], vec!["exe/cli"], vec![]);
13672 assert!(
13673 c.validate_code_paths().is_ok(),
13674 "validate_code_paths must accept exe == vec![\"exe/cli\"] \
13675 (the canonical single-executable shape every in-tree \
13676 `caixa_with_code_paths` positive control uses)",
13677 );
13678 }
13679
13680 #[test]
13681 fn exe_projects_slice_by_borrow() {
13682 // The by-borrow pin: [`Caixa::exe`] returns `&[String]` by
13683 // borrow — the returned slice borrows the underlying
13684 // `Vec<String>` storage of the `:exe` slot and the accessor
13685 // must not clone the backing `Vec` on every call. Peer of the
13686 // per-`Caixa` `autores_projects_slice_by_borrow` (b5d813f),
13687 // `etiquetas_projects_slice_by_borrow` (78c7d3c), and
13688 // `bibliotecas_projects_slice_by_borrow` (8a36c23) by-borrow
13689 // pins on the sibling outer top-level [`Caixa`] `&[String]`-
13690 // return axes — the accessor's returned slice must borrow from
13691 // `&self` (the returned reference's lifetime is tied to
13692 // `&self`), and calling the accessor twice on the same
13693 // [`Caixa`] must yield slices that are pointer-equal (the
13694 // underlying byte-buffer is the storage `Vec`'s allocation,
13695 // not a fresh copy) as well as value-equal (idempotent, no
13696 // side effects on `&self`).
13697 //
13698 // Pins against a future silent detour that returned an owned
13699 // `Vec<String>` (which would type-check but silently clone on
13700 // every call, breaking the zero-cost projection every peer
13701 // sibling slice accessor carries), a `&Vec<String>` return
13702 // (which would leak the backing `Vec`'s grow/push/reserve
13703 // surface no downstream consumer reaches for), or a one-arm-
13704 // only accessor that returned a saturating value on some
13705 // sentinel input (breaking the pass-through invariant the
13706 // sibling slice accessors carry).
13707 for exe in [
13708 vec![],
13709 vec!["exe/cli"],
13710 vec!["exe/cli", "exe/serve"],
13711 vec!["exe/cli", "exe/cli"],
13712 ] {
13713 let c = caixa_with_code_paths(vec![], exe.clone(), vec![]);
13714 let expected: Vec<String> = exe.iter().map(|s| (*s).to_string()).collect();
13715 let first = c.exe();
13716 let second = c.exe();
13717 assert_eq!(
13718 first, second,
13719 "Caixa::exe must be idempotent — two successive calls \
13720 on the same &self must return the same &[String]",
13721 );
13722 assert_eq!(
13723 first.as_ptr(),
13724 second.as_ptr(),
13725 "Caixa::exe must borrow the underlying Vec<String> \
13726 storage — two successive calls must return slices \
13727 with the same backing pointer (a fresh Vec<String> \
13728 clone would change the pointer on every call)",
13729 );
13730 assert_eq!(
13731 first,
13732 expected.as_slice(),
13733 "Caixa::exe must return :exe verbatim by borrow — \
13734 got {first:?}, expected {expected:?}",
13735 );
13736 }
13737 }
13738
13739 // ── Caixa::servicos — outer top-level &[T] slice accessor ─────────
13740
13741 #[test]
13742 fn servicos_returns_servicos_slice_verbatim_across_permutations() {
13743 // The canonical per-`Caixa` `:servicos` universal-axis
13744 // ComputeUnit-CR-YAML-entry-path-list slice pin:
13745 // [`Caixa::servicos`] must return the `:servicos` typed
13746 // [`Vec<String>`] list verbatim as a `&[String]`, byte-equal to
13747 // the raw `self.servicos.as_slice()` access across every
13748 // representative value in the accept-set — `[]` (the "no
13749 // ComputeUnit-CR declared" arm every `:kind` other than
13750 // `Servico` carries; the layout's [`crate::LayoutInvariants`]
13751 // `ServicoWithoutServicos` arm-gate fires exactly on this
13752 // empty-slot + `Servico`-kind combination), `[""]` (a past-the-
13753 // guard sentinel that pins the accessor doesn't perform a
13754 // silent `[""] → []` collapse on the empty-entry arm — validate
13755 // rejects `[""]` through `CodePathEmpty { slot: ":servicos" }`
13756 // but the accessor must ship the raw slot verbatim so a
13757 // validate-time gate regression surfaces at the layout /
13758 // per-Servico renderer boundary rather than being silently
13759 // absorbed into a component-drop),
13760 // `["servicos/demo.computeunit.yaml"]` (the canonical
13761 // singleton V0-shape every in-tree `caixa_with_code_paths`
13762 // positive control uses; the same shape
13763 // [`crate::require_single_servico`] admits),
13764 // `["servicos/a.computeunit.yaml", "servicos/b.computeunit.
13765 // yaml"]` (a past-the-guard `len != 1` sentinel — the V0
13766 // singularity gate rejects through `ServicoCountMismatch
13767 // { count: 2 }` but the accessor must ship the raw slot
13768 // verbatim so struct-literal `Caixa { servicos: vec![...,
13769 // ...], .. }` fixtures continue to expose the count at the
13770 // accessor), and `["servicos/a.computeunit.yaml",
13771 // "servicos/a.computeunit.yaml"]` (a past-the-guard duplicate
13772 // sentinel — validate rejects through
13773 // `CodePathDuplicate { slot: ":servicos" }` per the per-slot
13774 // set-not-multiset gate, but the accessor must ship the raw
13775 // slot verbatim so struct-literal fixtures continue to expose
13776 // the duplicate at the accessor).
13777 //
13778 // Fifth and final outer top-level [`Caixa`] `&[T]`-return
13779 // slice accessor pin on the substrate primitive — folds on the
13780 // "outer [`Caixa`] `&[T]` slice" projection pattern
13781 // `autores_returns_autores_slice_verbatim_across_permutations`
13782 // (b5d813f) opened,
13783 // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
13784 // (78c7d3c) folded on,
13785 // `bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
13786 // (8a36c23) closed the universal-axis text-tag family of, and
13787 // `exe_returns_exe_slice_verbatim_across_permutations`
13788 // (65d9527) opened the foreign-code-slot sub-family of. Closes
13789 // the outer-`Caixa` foreign-code-slot `&[T]` sub-family — the
13790 // trio of code-surface list slots (`:bibliotecas` + `:exe` +
13791 // `:servicos`) now each carries a substrate-canonical slice
13792 // accessor. Pins against a future silent detour that returned
13793 // an owned `Vec<String>` (which would type-check but silently
13794 // clone on every accessor call, breaking the zero-cost
13795 // projection every peer sibling slice accessor carries), a
13796 // `[""] → []` collapse (which would silently absorb the
13797 // `CodePathEmpty` refusal case at the accessor boundary), an
13798 // `[a, a] → [a]` dedup collapse (which would silently absorb
13799 // the `CodePathDuplicate` refusal case at the accessor
13800 // boundary — the per-slot set-not-multiset gate is downstream
13801 // of the accessor and must not be silently promoted into it),
13802 // or a `[a, b] → [a]` singleton collapse (which would silently
13803 // absorb the V0 `ServicoCountMismatch` refusal case at the
13804 // accessor boundary — the V0 singularity gate is downstream of
13805 // the accessor and must not be silently promoted into it).
13806 for servicos in [
13807 vec![],
13808 vec![""],
13809 vec!["servicos/demo.computeunit.yaml"],
13810 vec!["servicos/a.computeunit.yaml", "servicos/b.computeunit.yaml"],
13811 vec!["servicos/a.computeunit.yaml", "servicos/a.computeunit.yaml"],
13812 ] {
13813 let c = caixa_with_code_paths(vec![], vec![], servicos.clone());
13814 let expected: Vec<String> = servicos.iter().map(|s| (*s).to_string()).collect();
13815 assert_eq!(
13816 c.servicos(),
13817 expected.as_slice(),
13818 "Caixa::servicos must return :servicos verbatim (got \
13819 {:?}, expected {expected:?})",
13820 c.servicos(),
13821 );
13822 assert_eq!(
13823 c.servicos(),
13824 c.servicos.as_slice(),
13825 "Caixa::servicos must byte-equal the raw \
13826 `self.servicos.as_slice()` field access across every \
13827 value in the Vec<String> accept-set",
13828 );
13829 }
13830 }
13831
13832 #[test]
13833 fn validate_code_paths_servicos_empty_arm_routes_through_accessor() {
13834 // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
13835 // empty-arm gate on the `:servicos` slot must key off
13836 // [`Caixa::servicos`], not a divergent raw `&self.servicos`
13837 // field-borrow walk. Structurally: a `Caixa { servicos:
13838 // vec!["".into()], .. }` must surface the `CodePathEmpty
13839 // { slot: ":servicos" }` refusal exactly, and a `Caixa
13840 // { servicos: vec!["servicos/demo.computeunit.yaml".into()],
13841 // .. }` (the canonical singleton V0-shape every in-tree
13842 // `caixa_with_code_paths` positive control uses) must pass
13843 // validate. The pair jointly pins the accessor + validate-gate
13844 // composition: any future silent detour that had the accessor
13845 // return an empty slice on the `[""]` arm (a `.iter().filter
13846 // (|s| !s.is_empty()).collect()` collapse) would silently
13847 // absorb the `CodePathEmpty` refusal at the accessor boundary
13848 // and the validate gate would accept a struct-literal
13849 // `Caixa { servicos: vec!["".into()], .. }` — the composition
13850 // pin catches that at caixa-core build time.
13851 //
13852 // Peer of the per-`Caixa`
13853 // `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
13854 // (8a36c23), `validate_code_paths_exe_empty_arm_routes_through_accessor`
13855 // (65d9527), `validate_autores_empty_arm_routes_through_accessor`
13856 // (b5d813f), and
13857 // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
13858 // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
13859 // composition axes — same "the validate / shape-gate predicate
13860 // must route through the substrate-primitive typed dispatch"
13861 // discipline extended onto the sibling outer top-level
13862 // [`Caixa`] `&[T]`-composition surface, closing the trio of
13863 // code-surface accessor-composition pins on the same axis.
13864 // Nominally the in-tree `validate_code_paths` production body
13865 // still keys off the internal
13866 // `[(":bibliotecas", &self.bibliotecas,
13867 // CodePathFileType::LispSource), (":exe", &self.exe, ..),
13868 // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
13869 // (the tuple's homogeneous `&Vec<String>`-typed shape blocks a
13870 // per-element accessor swap in isolation — a future companion
13871 // lift promotes the tuple's element type to `&[String]` and
13872 // threads the triple of typed dispatches through as a unit);
13873 // the composition pin catches any future accessor-side silent
13874 // filter drop against that eventual tuple-closure regardless
13875 // of whether the `:servicos` slot is threaded through the
13876 // accessor or the raw field access at the tuple's construction
13877 // site.
13878 let c = caixa_with_code_paths(vec![], vec![], vec![""]);
13879 assert!(
13880 matches!(
13881 c.validate_code_paths(),
13882 Err(ManifestError::CodePathEmpty { slot: ":servicos" })
13883 ),
13884 "validate_code_paths must reject servicos == vec![\"\"] \
13885 with CodePathEmpty {{ slot: \":servicos\" }} — the \
13886 accessor and the validate gate must route through the \
13887 same substrate-primitive typed dispatch on the \
13888 :servicos per-entry empty arm",
13889 );
13890 let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/demo.computeunit.yaml"]);
13891 assert!(
13892 c.validate_code_paths().is_ok(),
13893 "validate_code_paths must accept servicos == \
13894 vec![\"servicos/demo.computeunit.yaml\"] (the canonical \
13895 singleton V0-shape every in-tree `caixa_with_code_paths` \
13896 positive control uses)",
13897 );
13898 }
13899
13900 #[test]
13901 fn servicos_projects_slice_by_borrow() {
13902 // The by-borrow pin: [`Caixa::servicos`] returns `&[String]` by
13903 // borrow — the returned slice borrows the underlying
13904 // `Vec<String>` storage of the `:servicos` slot and the
13905 // accessor must not clone the backing `Vec` on every call.
13906 // Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
13907 // (b5d813f), `etiquetas_projects_slice_by_borrow` (78c7d3c),
13908 // `bibliotecas_projects_slice_by_borrow` (8a36c23), and
13909 // `exe_projects_slice_by_borrow` (65d9527) by-borrow pins on
13910 // the sibling outer top-level [`Caixa`] `&[String]`-return
13911 // axes — the accessor's returned slice must borrow from
13912 // `&self` (the returned reference's lifetime is tied to
13913 // `&self`), and calling the accessor twice on the same
13914 // [`Caixa`] must yield slices that are pointer-equal (the
13915 // underlying byte-buffer is the storage `Vec`'s allocation,
13916 // not a fresh copy) as well as value-equal (idempotent, no
13917 // side effects on `&self`).
13918 //
13919 // Pins against a future silent detour that returned an owned
13920 // `Vec<String>` (which would type-check but silently clone on
13921 // every call, breaking the zero-cost projection every peer
13922 // sibling slice accessor carries), a `&Vec<String>` return
13923 // (which would leak the backing `Vec`'s grow/push/reserve
13924 // surface no downstream consumer reaches for), or a one-arm-
13925 // only accessor that returned a saturating value on some
13926 // sentinel input (breaking the pass-through invariant the
13927 // sibling slice accessors carry).
13928 for servicos in [
13929 vec![],
13930 vec!["servicos/demo.computeunit.yaml"],
13931 vec!["servicos/a.computeunit.yaml", "servicos/b.computeunit.yaml"],
13932 vec!["servicos/a.computeunit.yaml", "servicos/a.computeunit.yaml"],
13933 ] {
13934 let c = caixa_with_code_paths(vec![], vec![], servicos.clone());
13935 let expected: Vec<String> = servicos.iter().map(|s| (*s).to_string()).collect();
13936 let first = c.servicos();
13937 let second = c.servicos();
13938 assert_eq!(
13939 first, second,
13940 "Caixa::servicos must be idempotent — two successive \
13941 calls on the same &self must return the same &[String]",
13942 );
13943 assert_eq!(
13944 first.as_ptr(),
13945 second.as_ptr(),
13946 "Caixa::servicos must borrow the underlying \
13947 Vec<String> storage — two successive calls must \
13948 return slices with the same backing pointer (a fresh \
13949 Vec<String> clone would change the pointer on every \
13950 call)",
13951 );
13952 assert_eq!(
13953 first,
13954 expected.as_slice(),
13955 "Caixa::servicos must return :servicos verbatim by \
13956 borrow — got {first:?}, expected {expected:?}",
13957 );
13958 }
13959 }
13960
13961 // ── Caixa::deps — outer top-level &[Dep] slice accessor ───────────
13962
13963 fn caixa_with_deps(deps: Vec<Dep>) -> Caixa {
13964 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
13965 c.deps = deps;
13966 c
13967 }
13968
13969 #[test]
13970 fn deps_returns_deps_slice_verbatim_across_permutations() {
13971 // The canonical per-`Caixa` `:deps` universal-axis runtime-
13972 // dependency-declaration-list slice pin: [`Caixa::deps`] must
13973 // return the `:deps` typed [`Vec<Dep>`] list verbatim as a
13974 // `&[Dep]`, element-equal to the raw `self.deps.as_slice()`
13975 // access across every representative value in the accept-set —
13976 // `[]` (the "no runtime deps declared" arm every existing
13977 // fixture without a `:deps` line carries; the
13978 // [`Caixa::template`] scaffold emits `:deps ()`), a canonical
13979 // single-entry list (the shape most consumer caixas carry), a
13980 // canonical two-entry list (the multi-dep runtime closure), and
13981 // two past-the-guard sentinels — a `[""]`-`:nome` entry
13982 // ([`Self::validate_deps`] rejects through `NomeEmpty` /
13983 // `NomeInvalid` but the accessor must ship the raw slot
13984 // verbatim) and a `[a, a]` duplicate (validate rejects through
13985 // `DuplicateNome { list: ":deps" }` but the accessor must ship
13986 // the raw slot verbatim so struct-literal fixtures continue to
13987 // expose the duplicate at the accessor).
13988 //
13989 // First outer top-level [`Caixa`] `&[Dep]`-return slice accessor
13990 // pin on the substrate primitive — opens the outer-`Caixa`
13991 // dependency-slot `&[Dep]` sub-family the sibling `:deps-dev`
13992 // future lift closes on. Peer of the closed outer-`Caixa`
13993 // foreign-code-slot `&[String]` sub-family
13994 // (`bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
13995 // 8a36c23, `exe_returns_exe_slice_verbatim_across_permutations`
13996 // 65d9527, `servicos_returns_servicos_slice_verbatim_across_permutations`
13997 // 611f78b) and the outer-`Caixa` universal-axis text-tag family
13998 // (`autores_returns_autores_slice_verbatim_across_permutations`
13999 // b5d813f, `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
14000 // 78c7d3c) — extends the "outer [`Caixa`] `&[T]` slice"
14001 // projection pattern onto a novel element-type axis (`Dep`
14002 // composite vs the prior sibling family's `String` scalar).
14003 // Pins against a future silent detour that returned an owned
14004 // `Vec<Dep>` (which would type-check but silently clone on every
14005 // accessor call, breaking the zero-cost projection every peer
14006 // sibling slice accessor carries), a `[""] → []` collapse (which
14007 // would silently absorb the `NomeEmpty` refusal case at the
14008 // accessor boundary), or a `[a, a] → [a]` dedup collapse (which
14009 // would silently absorb the `DuplicateNome` refusal case at the
14010 // accessor boundary).
14011 for deps in [
14012 vec![],
14013 vec![Dep::simple("", "^0.1")],
14014 vec![Dep::simple("caixa-teia", "^0.1")],
14015 vec![
14016 Dep::simple("caixa-teia", "^0.1"),
14017 Dep::simple("caixa-core", "^0.1"),
14018 ],
14019 vec![
14020 Dep::simple("caixa-teia", "^0.1"),
14021 Dep::simple("caixa-teia", "^0.2"),
14022 ],
14023 ] {
14024 let c = caixa_with_deps(deps.clone());
14025 assert_eq!(
14026 c.deps(),
14027 deps.as_slice(),
14028 "Caixa::deps must return :deps verbatim (got {:?}, \
14029 expected {deps:?})",
14030 c.deps(),
14031 );
14032 assert_eq!(
14033 c.deps(),
14034 c.deps.as_slice(),
14035 "Caixa::deps must element-equal the raw \
14036 `self.deps.as_slice()` field access across every \
14037 value in the Vec<Dep> accept-set",
14038 );
14039 }
14040 }
14041
14042 #[test]
14043 fn validate_deps_duplicate_arm_routes_through_accessor() {
14044 // Composition pin: [`Caixa::validate_deps`]'s within-`:deps`
14045 // duplicate-`:nome` gate must key off [`Caixa::deps`], not the
14046 // raw `&self.deps` field-borrow walk. Structurally: a `Caixa
14047 // { deps: vec![Dep::simple("d", "^0.1"), Dep::simple("d",
14048 // "^0.2")], .. }` must surface the `DuplicateNome { list:
14049 // ":deps" }` refusal exactly, and a `Caixa { deps: vec![
14050 // Dep::simple("d", "^0.1")], .. }` (the canonical single-entry
14051 // form) must pass validate. The pair jointly pins the accessor +
14052 // validate-gate composition: any future silent detour that had
14053 // the accessor return a dedupped slice on the `[a, a]` arm (a
14054 // `.iter().unique_by(|d| d.nome.as_str()).collect()` collapse)
14055 // would silently absorb the `DuplicateNome` refusal at the
14056 // accessor boundary and the validate gate would accept a
14057 // struct-literal `Caixa` carrying the drift — the composition
14058 // pin catches that at caixa-core build time.
14059 //
14060 // Peer of the per-`Caixa`
14061 // `validate_autores_empty_entry_arm_routes_through_accessor`
14062 // (b5d813f), `validate_etiquetas_empty_entry_arm_routes_through_accessor`
14063 // (78c7d3c), `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
14064 // (8a36c23), `validate_code_paths_exe_empty_arm_routes_through_accessor`
14065 // (65d9527), and `validate_code_paths_servicos_empty_arm_routes_through_accessor`
14066 // (611f78b) accessor-composition pins on the sibling `&[T]`-
14067 // composition axes — same "the validate gate must route through
14068 // the substrate-primitive typed dispatch" discipline extended
14069 // onto the sibling outer top-level [`Caixa`] `&[Dep]`-
14070 // composition surface, opening the outer-`Caixa` dependency-slot
14071 // arm of the composition-pin family.
14072 let c = caixa_with_deps(vec![Dep::simple("d", "^0.1"), Dep::simple("d", "^0.2")]);
14073 let err = c.validate_deps().unwrap_err();
14074 assert!(
14075 matches!(
14076 err,
14077 DepError::DuplicateNome { ref nome, list } if nome == "d"
14078 && list == crate::render::DEP_AUTHOR_KEY_DEPS
14079 ),
14080 "validate_deps must reject deps == \
14081 vec![Dep(\"d\",\"^0.1\"), Dep(\"d\",\"^0.2\")] with \
14082 DuplicateNome {{ nome: \"d\", list: \":deps\" }} — the \
14083 accessor and the validate gate must route through the \
14084 same substrate-primitive typed dispatch on the :deps \
14085 within-list duplicate arm (got {err:?})",
14086 );
14087 let c = caixa_with_deps(vec![Dep::simple("d", "^0.1")]);
14088 assert!(
14089 c.validate_deps().is_ok(),
14090 "validate_deps must accept deps == vec![Dep(\"d\",\"^0.1\")] \
14091 (the canonical single-entry form)",
14092 );
14093 }
14094
14095 #[test]
14096 fn deps_projects_slice_by_borrow() {
14097 // The by-borrow pin: [`Caixa::deps`] returns `&[Dep]` by borrow
14098 // — the returned slice borrows the underlying `Vec<Dep>` storage
14099 // of the `:deps` slot and the accessor must not clone the
14100 // backing `Vec` on every call. Peer of the per-`Caixa`
14101 // `autores_projects_slice_by_borrow` (b5d813f),
14102 // `etiquetas_projects_slice_by_borrow` (78c7d3c),
14103 // `bibliotecas_projects_slice_by_borrow` (8a36c23),
14104 // `exe_projects_slice_by_borrow` (65d9527), and
14105 // `servicos_projects_slice_by_borrow` (611f78b) by-borrow pins
14106 // on the sibling outer top-level [`Caixa`] `&[String]`-return
14107 // axes — the accessor's returned slice must borrow from `&self`
14108 // (the returned reference's lifetime is tied to `&self`), and
14109 // calling the accessor twice on the same [`Caixa`] must yield
14110 // slices that are pointer-equal (the underlying byte-buffer is
14111 // the storage `Vec`'s allocation, not a fresh copy) as well as
14112 // value-equal (idempotent, no side effects on `&self`).
14113 //
14114 // Pins against a future silent detour that returned an owned
14115 // `Vec<Dep>` (which would type-check but silently clone on
14116 // every call), a `&Vec<Dep>` return (which would leak the
14117 // backing `Vec`'s grow/push/reserve surface no downstream
14118 // consumer reaches for), or a one-arm-only accessor that
14119 // returned a saturating value on some sentinel input.
14120 for deps in [
14121 vec![],
14122 vec![Dep::simple("caixa-teia", "^0.1")],
14123 vec![
14124 Dep::simple("caixa-teia", "^0.1"),
14125 Dep::simple("caixa-core", "^0.1"),
14126 ],
14127 ] {
14128 let c = caixa_with_deps(deps.clone());
14129 let first = c.deps();
14130 let second = c.deps();
14131 assert_eq!(
14132 first, second,
14133 "Caixa::deps must be idempotent — two successive calls \
14134 on the same &self must return the same &[Dep]",
14135 );
14136 assert_eq!(
14137 first.as_ptr(),
14138 second.as_ptr(),
14139 "Caixa::deps must borrow the underlying Vec<Dep> \
14140 storage — two successive calls must return slices \
14141 with the same backing pointer (a fresh Vec<Dep> clone \
14142 would change the pointer on every call)",
14143 );
14144 assert_eq!(
14145 first,
14146 deps.as_slice(),
14147 "Caixa::deps must return :deps verbatim by borrow — \
14148 got {first:?}, expected {deps:?}",
14149 );
14150 }
14151 }
14152
14153 // ── Caixa::deps_dev — outer top-level &[Dep] slice accessor ──────
14154
14155 fn caixa_with_deps_dev(deps_dev: Vec<Dep>) -> Caixa {
14156 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
14157 c.deps_dev = deps_dev;
14158 c
14159 }
14160
14161 #[test]
14162 fn deps_dev_returns_deps_dev_slice_verbatim_across_permutations() {
14163 // The canonical per-`Caixa` `:deps-dev` universal-axis dev-only-
14164 // dependency-declaration-list slice pin: [`Caixa::deps_dev`]
14165 // must return the `:deps-dev` typed [`Vec<Dep>`] list verbatim as
14166 // a `&[Dep]`, element-equal to the raw `self.deps_dev.as_slice()`
14167 // access across every representative value in the accept-set —
14168 // `[]` (the "no dev deps declared" arm every existing fixture
14169 // without a `:deps-dev` line carries; the [`Caixa::template`]
14170 // scaffold emits `:deps-dev ()`), a canonical single-entry list
14171 // (the shape most consumer caixas carry — a `tatara-check` dev
14172 // pin), a canonical two-entry list (the multi-dev-dep closure),
14173 // and two past-the-guard sentinels — a `[""]`-`:nome` entry
14174 // ([`Self::validate_deps`] rejects through `NomeEmpty` /
14175 // `NomeInvalid` but the accessor must ship the raw slot
14176 // verbatim) and a `[a, a]` duplicate (validate rejects through
14177 // `DuplicateNome { list: ":deps-dev" }` but the accessor must
14178 // ship the raw slot verbatim so struct-literal fixtures continue
14179 // to expose the duplicate at the accessor).
14180 //
14181 // Second outer top-level [`Caixa`] `&[Dep]`-return slice-accessor
14182 // pin on the substrate primitive — closes the outer-`Caixa`
14183 // dependency-slot `&[Dep]` sub-family the sibling
14184 // `deps_returns_deps_slice_verbatim_across_permutations`
14185 // (ad34b4e) opened on. Folds the "outer [`Caixa`] `&[Dep]`
14186 // slice" projection pattern onto the sibling dev-dep axis —
14187 // pins against a future silent detour that returned an owned
14188 // `Vec<Dep>` (which would type-check but silently clone on every
14189 // accessor call, breaking the zero-cost projection every peer
14190 // sibling slice accessor carries), a `[""] → []` collapse (which
14191 // would silently absorb the `NomeEmpty` refusal case at the
14192 // accessor boundary), or a `[a, a] → [a]` dedup collapse (which
14193 // would silently absorb the `DuplicateNome` refusal case at the
14194 // accessor boundary).
14195 for deps_dev in [
14196 vec![],
14197 vec![Dep::simple("", "^0.1")],
14198 vec![Dep::simple("tatara-check", "^0.1")],
14199 vec![
14200 Dep::simple("tatara-check", "^0.1"),
14201 Dep::simple("caixa-lint", "^0.1"),
14202 ],
14203 vec![
14204 Dep::simple("tatara-check", "^0.1"),
14205 Dep::simple("tatara-check", "^0.2"),
14206 ],
14207 ] {
14208 let c = caixa_with_deps_dev(deps_dev.clone());
14209 assert_eq!(
14210 c.deps_dev(),
14211 deps_dev.as_slice(),
14212 "Caixa::deps_dev must return :deps-dev verbatim (got \
14213 {:?}, expected {deps_dev:?})",
14214 c.deps_dev(),
14215 );
14216 assert_eq!(
14217 c.deps_dev(),
14218 c.deps_dev.as_slice(),
14219 "Caixa::deps_dev must element-equal the raw \
14220 `self.deps_dev.as_slice()` field access across every \
14221 value in the Vec<Dep> accept-set",
14222 );
14223 }
14224 }
14225
14226 #[test]
14227 fn validate_deps_duplicate_deps_dev_arm_routes_through_accessor() {
14228 // Composition pin: [`Caixa::validate_deps`]'s within-`:deps-dev`
14229 // duplicate-`:nome` gate must key off [`Caixa::deps_dev`], not
14230 // the raw `&self.deps_dev` field-borrow walk. Structurally: a
14231 // `Caixa { deps_dev: vec![Dep::simple("d", "^0.1"),
14232 // Dep::simple("d", "^0.2")], .. }` must surface the
14233 // `DuplicateNome { list: ":deps-dev" }` refusal exactly, and a
14234 // `Caixa { deps_dev: vec![Dep::simple("d", "^0.1")], .. }` (the
14235 // canonical single-entry form) must pass validate. The pair
14236 // jointly pins the accessor + validate-gate composition: any
14237 // future silent detour that had the accessor return a dedupped
14238 // slice on the `[a, a]` arm (a
14239 // `.iter().unique_by(|d| d.nome.as_str()).collect()` collapse)
14240 // would silently absorb the `DuplicateNome` refusal at the
14241 // accessor boundary and the validate gate would accept a
14242 // struct-literal `Caixa` carrying the drift — the composition
14243 // pin catches that at caixa-core build time.
14244 //
14245 // Peer of `validate_deps_duplicate_arm_routes_through_accessor`
14246 // (ad34b4e) on the sibling `:deps` axis — same "the validate
14247 // gate must route through the substrate-primitive typed
14248 // dispatch" discipline folded onto the sibling `:deps-dev`
14249 // axis, closing the two-list dep-graph composition-pin family.
14250 // The `:deps-dev` diagnostic must carry the
14251 // `DEP_AUTHOR_KEY_DEPS_DEV` list-tag (not
14252 // `DEP_AUTHOR_KEY_DEPS`) so the emitted error names the
14253 // offending list unambiguously.
14254 let c = caixa_with_deps_dev(vec![Dep::simple("d", "^0.1"), Dep::simple("d", "^0.2")]);
14255 let err = c.validate_deps().unwrap_err();
14256 assert!(
14257 matches!(
14258 err,
14259 DepError::DuplicateNome { ref nome, list } if nome == "d"
14260 && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
14261 ),
14262 "validate_deps must reject deps_dev == \
14263 vec![Dep(\"d\",\"^0.1\"), Dep(\"d\",\"^0.2\")] with \
14264 DuplicateNome {{ nome: \"d\", list: \":deps-dev\" }} — the \
14265 accessor and the validate gate must route through the \
14266 same substrate-primitive typed dispatch on the :deps-dev \
14267 within-list duplicate arm (got {err:?})",
14268 );
14269 let c = caixa_with_deps_dev(vec![Dep::simple("d", "^0.1")]);
14270 assert!(
14271 c.validate_deps().is_ok(),
14272 "validate_deps must accept deps_dev == \
14273 vec![Dep(\"d\",\"^0.1\")] (the canonical single-entry form)",
14274 );
14275 }
14276
14277 #[test]
14278 fn deps_dev_projects_slice_by_borrow() {
14279 // The by-borrow pin: [`Caixa::deps_dev`] returns `&[Dep]` by
14280 // borrow — the returned slice borrows the underlying `Vec<Dep>`
14281 // storage of the `:deps-dev` slot and the accessor must not
14282 // clone the backing `Vec` on every call. Peer of
14283 // `deps_projects_slice_by_borrow` (ad34b4e) on the sibling
14284 // `:deps` axis, and of the per-`Caixa`
14285 // `autores_projects_slice_by_borrow` (b5d813f),
14286 // `etiquetas_projects_slice_by_borrow` (78c7d3c),
14287 // `bibliotecas_projects_slice_by_borrow` (8a36c23),
14288 // `exe_projects_slice_by_borrow` (65d9527), and
14289 // `servicos_projects_slice_by_borrow` (611f78b) by-borrow pins
14290 // on the sibling outer top-level [`Caixa`] `&[String]`-return
14291 // axes — the accessor's returned slice must borrow from `&self`
14292 // (the returned reference's lifetime is tied to `&self`), and
14293 // calling the accessor twice on the same [`Caixa`] must yield
14294 // slices that are pointer-equal (the underlying byte-buffer is
14295 // the storage `Vec`'s allocation, not a fresh copy) as well as
14296 // value-equal (idempotent, no side effects on `&self`).
14297 //
14298 // Pins against a future silent detour that returned an owned
14299 // `Vec<Dep>` (which would type-check but silently clone on
14300 // every call), a `&Vec<Dep>` return (which would leak the
14301 // backing `Vec`'s grow/push/reserve surface no downstream
14302 // consumer reaches for), or a one-arm-only accessor that
14303 // returned a saturating value on some sentinel input.
14304 for deps_dev in [
14305 vec![],
14306 vec![Dep::simple("tatara-check", "^0.1")],
14307 vec![
14308 Dep::simple("tatara-check", "^0.1"),
14309 Dep::simple("caixa-lint", "^0.1"),
14310 ],
14311 ] {
14312 let c = caixa_with_deps_dev(deps_dev.clone());
14313 let first = c.deps_dev();
14314 let second = c.deps_dev();
14315 assert_eq!(
14316 first, second,
14317 "Caixa::deps_dev must be idempotent — two successive \
14318 calls on the same &self must return the same &[Dep]",
14319 );
14320 assert_eq!(
14321 first.as_ptr(),
14322 second.as_ptr(),
14323 "Caixa::deps_dev must borrow the underlying Vec<Dep> \
14324 storage — two successive calls must return slices \
14325 with the same backing pointer (a fresh Vec<Dep> clone \
14326 would change the pointer on every call)",
14327 );
14328 assert_eq!(
14329 first,
14330 deps_dev.as_slice(),
14331 "Caixa::deps_dev must return :deps-dev verbatim by \
14332 borrow — got {first:?}, expected {deps_dev:?}",
14333 );
14334 }
14335 }
14336
14337 // ── Caixa::limits — outer top-level Option<&LimitsSpec> composite-reference accessor ──
14338
14339 fn caixa_with_limits(limits: Option<crate::LimitsSpec>) -> Caixa {
14340 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
14341 c.limits = limits;
14342 c
14343 }
14344
14345 #[test]
14346 fn limits_returns_limits_option_ref_verbatim_across_permutations() {
14347 // The canonical per-`Caixa` `:limits` M2 typed-slot outer-
14348 // composite optional-composite-reference-shape pin:
14349 // [`Caixa::limits`] must return the `:limits` typed
14350 // `Option<LimitsSpec>` verbatim as an `Option<&LimitsSpec>`
14351 // reference over the same backing storage the raw
14352 // `self.limits.as_ref()` field access borrows from, byte-equal
14353 // across every representative fixture in the accept-set — the
14354 // author-omitted `None` shape (the "engine-default applies"
14355 // partition every downstream Servico M2 overlay emitter treats
14356 // as "emit nothing"), the empty-composite `Some(LimitsSpec {
14357 // .. default })` shape ([`LimitsSpec::is_empty`] holds — every
14358 // per-axis cap is `None`, so the peer M2 overlay emitter's
14359 // `.is_empty()`-gated projection still emits nothing but the
14360 // outer presence-bit is `Some`, so [`Caixa::declared_servico_slots`]
14361 // still pushes the `M2_AUTHOR_KEY_LIMITS` label), a single-axis
14362 // fixture (only `:memory` set — the canonical shape most
14363 // memory-heavy Servicos carry), and a fully-populated composite
14364 // (every per-axis cap set — the canonical shape a
14365 // sandboxed-by-default Servico carries).
14366 //
14367 // Pins against a future silent detour that returned a fresh-
14368 // cloned [`LimitsSpec`] copy (which would type-check via the
14369 // `Clone` impl but silently break every downstream caller that
14370 // relied on the reference sharing the composite's backing
14371 // identity), a reference to an operator-resolved overlay (the
14372 // future per-cluster `:limits-overrides` slot — its resolution
14373 // must land at exactly this accessor body, not silently divert
14374 // the raw slot away from a second consumer), a
14375 // `None` → `Some(LimitsSpec::default)` cluster-default
14376 // projection (which would collapse the load-bearing
14377 // "author-omitted `:limits` ⇒ engine-default applies" partition
14378 // the peer [`crate::render::servico_m2_overlay`] emitter and
14379 // the peer [`Caixa::declared_servico_slots`] enumerator both
14380 // read), or an axis-shuffled projection (a future detour that
14381 // swapped `memory` and `fuel` through the accessor would
14382 // silently split the paired [`crate::StandardLayout::verify`]
14383 // per-`:limits` shape gate's traversal input from the peer
14384 // `servico_m2_overlay` emitter's projection input).
14385 //
14386 // First outer top-level [`Caixa`] `Option<&Composite>`-return
14387 // composite-reference accessor pin on the substrate primitive
14388 // — opens the outer-`Caixa` `Option<&Composite>` composite-
14389 // reference projection pattern the sibling `:behavior`
14390 // [`crate::BehaviorSpec`] / `:politicas`
14391 // [`crate::aplicacao::MeshPolicy`] / `:placement`
14392 // [`crate::aplicacao::Placement`] / `:entrada`
14393 // [`crate::aplicacao::Entrada`] future outer-composite lifts
14394 // fold on. Peer of the closed M3 outer-composite family the
14395 // sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
14396 // [`crate::AplicacaoSpec::placement`] (9abb8f0) /
14397 // [`crate::AplicacaoSpec::entrada`] (d32111c) composite-
14398 // reference accessor pins already carry on the outer
14399 // [`crate::AplicacaoSpec`] altitude — extends the outer-
14400 // accessor byte-equal-projection discipline onto the outer
14401 // top-level [`Caixa`] M2 Servico-runtime slot altitude.
14402 use crate::LimitsSpec;
14403 use std::time::Duration;
14404 let fixtures: Vec<Option<LimitsSpec>> = vec![
14405 None,
14406 Some(LimitsSpec::default()),
14407 Some(LimitsSpec {
14408 memory: Some(64 * 1024 * 1024),
14409 ..Default::default()
14410 }),
14411 Some(LimitsSpec {
14412 memory: Some(64 * 1024 * 1024),
14413 fuel: Some(1_000_000),
14414 wall_clock: Some(Duration::from_secs(30)),
14415 cpu: Some(500),
14416 }),
14417 ];
14418 for limits in fixtures {
14419 let c = caixa_with_limits(limits.clone());
14420 assert_eq!(
14421 c.limits(),
14422 limits.as_ref(),
14423 "Caixa::limits must return :limits verbatim (got {:?}, \
14424 expected {:?})",
14425 c.limits(),
14426 limits.as_ref(),
14427 );
14428 match (c.limits(), c.limits.as_ref()) {
14429 (Some(a), Some(b)) => assert!(
14430 std::ptr::eq(a, b),
14431 "Caixa::limits accessor and self.limits.as_ref() \
14432 field access must borrow the same backing storage \
14433 — the accessor is the substrate-primitive typed \
14434 dispatch every downstream Servico-M2-overlay \
14435 composite consumer must route through, and a \
14436 reference-identity split would silently break \
14437 every consumer that relied on the borrow sharing \
14438 the composite's storage",
14439 ),
14440 (None, None) => {}
14441 _ => panic!(
14442 "Caixa::limits presence bit must byte-equal \
14443 self.limits.is_some() — a presence-bit drift would \
14444 silently split the paired StandardLayout::verify \
14445 per-`:limits` shape gate's traversal head from \
14446 the peer render::servico_m2_overlay M2 overlay \
14447 emitter's traversal head from the peer \
14448 Caixa::declared_servico_slots M2 declared-slot \
14449 enumerator's presence probe",
14450 ),
14451 }
14452 assert_eq!(
14453 c.limits().is_some(),
14454 c.limits.is_some(),
14455 "Caixa::limits().is_some() must byte-equal \
14456 self.limits.is_some() — a presence-bit drift would \
14457 silently split every downstream Option<&LimitsSpec> \
14458 consumer's partition on the engine-default arm",
14459 );
14460 }
14461 }
14462
14463 #[test]
14464 fn declared_servico_slots_limits_arm_routes_through_accessor() {
14465 // Composition pin: [`Caixa::declared_servico_slots`]'s
14466 // `:limits` presence-probe arm must key off [`Caixa::limits`],
14467 // not the raw `self.limits.is_some()` field-probe. Structurally:
14468 // a `Caixa { limits: Some(LimitsSpec::default()), .. }` must
14469 // still push `M2_AUTHOR_KEY_LIMITS` onto the declared-slot list
14470 // (the presence bit is `Some`, so the M2 kind-coherence gate
14471 // must surface the slot as "declared" even when every per-axis
14472 // cap is unset), and a `Caixa { limits: None, .. }` must NOT
14473 // push the label (the "author omitted the slot entirely"
14474 // partition). The pair jointly pins the accessor + declared-
14475 // slot enumerator composition: any future silent detour that
14476 // had the accessor collapse `Some(LimitsSpec::default())` to
14477 // `None` (a `.filter(|l| !l.is_empty())` projection) would
14478 // silently absorb the "declared but empty" arm at the
14479 // accessor boundary and the [`crate::LayoutError::ServicoSlotsOnNonServico`]
14480 // kind-coherence gate would silently accept a
14481 // struct-literal `Caixa` carrying the drift.
14482 //
14483 // Peer of the sibling per-`Caixa`
14484 // `validate_deps_duplicate_arm_routes_through_accessor` (ad34b4e)
14485 // and `validate_deps_duplicate_deps_dev_arm_routes_through_accessor`
14486 // (f7fd81e) accessor-composition pins on the sibling `:deps` /
14487 // `:deps-dev` outer-`&[Dep]`-composition axes — same "the
14488 // enumerator gate must route through the substrate-primitive
14489 // typed dispatch" discipline extended onto the outer top-level
14490 // [`Caixa`] `Option<&LimitsSpec>`-composition surface, opening
14491 // the outer-`Caixa` M2 Servico-runtime-slot arm of the
14492 // composition-pin family.
14493 use crate::LimitsSpec;
14494 let c = caixa_with_limits(Some(LimitsSpec::default()));
14495 let slots = c.declared_servico_slots();
14496 assert!(
14497 slots.contains(&crate::render::M2_AUTHOR_KEY_LIMITS),
14498 "declared_servico_slots must push M2_AUTHOR_KEY_LIMITS \
14499 when `:limits` is Some (even for LimitsSpec::default()) \
14500 — the accessor and the enumerator gate must route through \
14501 the same substrate-primitive typed dispatch on the outer \
14502 :limits presence bit (got slots={slots:?})",
14503 );
14504 let c = caixa_with_limits(None);
14505 let slots = c.declared_servico_slots();
14506 assert!(
14507 !slots.contains(&crate::render::M2_AUTHOR_KEY_LIMITS),
14508 "declared_servico_slots must NOT push M2_AUTHOR_KEY_LIMITS \
14509 when `:limits` is None — the author-omitted arm must \
14510 route through the accessor's None-return unchanged (got \
14511 slots={slots:?})",
14512 );
14513 }
14514
14515 #[test]
14516 fn servico_m2_overlay_limits_arm_routes_through_accessor() {
14517 // Composition pin: [`crate::render::servico_m2_overlay`]'s
14518 // per-`:limits` M2 overlay emit arm must key off
14519 // [`Caixa::limits`], not the raw `&caixa.limits` field-borrow.
14520 // Structurally: a `Caixa { limits: Some(LimitsSpec { memory:
14521 // Some(64 MiB), .. default }), .. }` must surface the
14522 // `M2_KEY_LIMITS` key with the per-axis
14523 // `memory: "64MiB"` sub-mapping in the overlay, a `Caixa {
14524 // limits: Some(LimitsSpec::default()), .. }` must omit the
14525 // key entirely (the `.is_empty()`-gated inner arm elides an
14526 // empty composite even when the outer presence bit is `Some`),
14527 // and a `Caixa { limits: None, .. }` must also omit the key
14528 // (the "author omitted the slot entirely" partition). The
14529 // three-fixture family jointly pins the accessor + M2 overlay
14530 // emitter composition: any future silent detour that had the
14531 // accessor return a fresh-cloned copy on the `Some` arm (a
14532 // `LimitsSpec::clone()` projection) would silently break the
14533 // reference-identity pin the peer per-axis
14534 // `serde_yaml::to_value(limits)` projection reads from.
14535 use crate::LimitsSpec;
14536 use crate::render::{M2_KEY_LIMITS, servico_m2_overlay};
14537 let c = caixa_with_limits(Some(LimitsSpec {
14538 memory: Some(64 * 1024 * 1024),
14539 ..Default::default()
14540 }));
14541 let overlay = servico_m2_overlay(&c).unwrap();
14542 assert!(
14543 overlay.contains_key(M2_KEY_LIMITS),
14544 "servico_m2_overlay must surface M2_KEY_LIMITS when \
14545 `:limits` carries a non-empty composite — the accessor \
14546 and the M2 overlay emitter must route through the same \
14547 substrate-primitive typed dispatch on the outer :limits \
14548 composite (got overlay={overlay:?})",
14549 );
14550 let c = caixa_with_limits(Some(LimitsSpec::default()));
14551 let overlay = servico_m2_overlay(&c).unwrap();
14552 assert!(
14553 !overlay.contains_key(M2_KEY_LIMITS),
14554 "servico_m2_overlay must omit M2_KEY_LIMITS when \
14555 `:limits` is Some(LimitsSpec::default()) — the empty \
14556 composite's `.is_empty()`-gated inner arm must elide \
14557 the key regardless of the outer presence bit (got \
14558 overlay={overlay:?})",
14559 );
14560 let c = caixa_with_limits(None);
14561 let overlay = servico_m2_overlay(&c).unwrap();
14562 assert!(
14563 !overlay.contains_key(M2_KEY_LIMITS),
14564 "servico_m2_overlay must omit M2_KEY_LIMITS when \
14565 `:limits` is None — the author-omitted arm must route \
14566 through the accessor's None-return unchanged (got \
14567 overlay={overlay:?})",
14568 );
14569 }
14570
14571 #[test]
14572 fn limits_projects_option_ref_by_borrow() {
14573 // The by-borrow pin: [`Caixa::limits`] returns
14574 // `Option<&LimitsSpec>` by borrow — the returned reference
14575 // borrows the underlying `Option<LimitsSpec>` storage of the
14576 // `:limits` slot and the accessor must not clone the backing
14577 // composite on every call. Peer of the sibling
14578 // `deps_projects_slice_by_borrow` (ad34b4e) /
14579 // `deps_dev_projects_slice_by_borrow` (f7fd81e) by-borrow pins
14580 // on the outer top-level [`Caixa`] `&[Dep]`-return axes —
14581 // extended here to the outer [`Caixa`] `Option<&Composite>`-
14582 // return axis: the accessor's returned reference must borrow
14583 // from `&self` (the returned reference's lifetime is tied to
14584 // `&self`), and calling the accessor twice on the same
14585 // [`Caixa`] must yield references that are pointer-equal (the
14586 // underlying byte-buffer is the storage `LimitsSpec`'s
14587 // allocation, not a fresh copy) as well as value-equal
14588 // (idempotent, no side effects on `&self`).
14589 //
14590 // Pins against a future silent detour that returned an owned
14591 // `LimitsSpec` (which would type-check via the `Clone` impl
14592 // but silently clone on every call), a `&LimitsSpec` panic-
14593 // return on the `None` arm (which would collapse the load-
14594 // bearing `Option` presence-bit into a runtime panic), or a
14595 // one-arm-only accessor that returned a saturating composite
14596 // on some sentinel input.
14597 use crate::LimitsSpec;
14598 use std::time::Duration;
14599 for limits in [
14600 Some(LimitsSpec::default()),
14601 Some(LimitsSpec {
14602 memory: Some(64 * 1024 * 1024),
14603 fuel: Some(1_000_000),
14604 wall_clock: Some(Duration::from_secs(30)),
14605 cpu: Some(500),
14606 }),
14607 ] {
14608 let c = caixa_with_limits(limits.clone());
14609 let first = c.limits().unwrap();
14610 let second = c.limits().unwrap();
14611 assert_eq!(
14612 first, second,
14613 "Caixa::limits must be idempotent — two successive \
14614 calls on the same &self must return the same \
14615 &LimitsSpec",
14616 );
14617 assert!(
14618 std::ptr::eq(first, second),
14619 "Caixa::limits must borrow the underlying \
14620 Option<LimitsSpec> storage — two successive calls \
14621 must return references with the same backing pointer \
14622 (a fresh LimitsSpec clone would change the pointer \
14623 on every call)",
14624 );
14625 assert_eq!(
14626 Some(first),
14627 limits.as_ref(),
14628 "Caixa::limits must return :limits verbatim by borrow \
14629 — got {first:?}, expected {:?}",
14630 limits.as_ref(),
14631 );
14632 }
14633 let c = caixa_with_limits(None);
14634 assert!(
14635 c.limits().is_none(),
14636 "Caixa::limits must return None when :limits is absent — \
14637 the author-omitted arm must project through the \
14638 accessor's Option::None unchanged",
14639 );
14640 }
14641
14642 // ── Caixa::behavior — outer top-level Option<&BehaviorSpec> composite-reference accessor ──
14643
14644 fn caixa_with_behavior(behavior: Option<crate::BehaviorSpec>) -> Caixa {
14645 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
14646 c.behavior = behavior;
14647 c
14648 }
14649
14650 #[test]
14651 fn behavior_returns_behavior_option_ref_verbatim_across_permutations() {
14652 // The canonical per-`Caixa` `:behavior` M2 typed-slot outer-
14653 // composite optional-composite-reference-shape pin:
14654 // [`Caixa::behavior`] must return the `:behavior` typed
14655 // `Option<BehaviorSpec>` verbatim as an `Option<&BehaviorSpec>`
14656 // reference over the same backing storage the raw
14657 // `self.behavior.as_ref()` field access borrows from, byte-equal
14658 // across every representative fixture in the accept-set — the
14659 // author-omitted `None` shape (the "runtime-default applies"
14660 // partition every downstream Servico M2 overlay emitter treats
14661 // as "emit nothing"), the empty-composite `Some(BehaviorSpec {
14662 // .. default })` shape ([`BehaviorSpec::is_empty`] holds —
14663 // every per-callback path is `None`, so the peer M2 overlay
14664 // emitter's `.is_empty()`-gated projection still emits nothing
14665 // but the outer presence-bit is `Some`, so
14666 // [`Caixa::declared_servico_slots`] still pushes the
14667 // `M2_AUTHOR_KEY_BEHAVIOR` label), a single-callback fixture
14668 // (only `:on-state-change` set — the canonical shape a caixa
14669 // that only wires the hot-upgrade migration path carries), and
14670 // a fully-populated composite (every per-callback path set —
14671 // the canonical shape a fully-instrumented gen_server-shaped
14672 // Servico carries).
14673 //
14674 // Peer of the sibling
14675 // `limits_returns_limits_option_ref_verbatim_across_permutations`
14676 // (b2bd9d7) opening fixture-family + reference-identity +
14677 // presence-bit tetrad pin on the outer top-level [`Caixa`]
14678 // `Option<&Composite>`-return sub-family — extended here to the
14679 // second axis of that sub-family so both of the currently-lifted
14680 // M2 Servico-runtime `Option<&Composite>` slots (`:limits` /
14681 // `:behavior`) carry the same "byte-equal, borrow-shared,
14682 // presence-bit-preserved" outer-accessor discipline.
14683 //
14684 // Pins against a future silent detour that returned a fresh-
14685 // cloned [`crate::BehaviorSpec`] copy (which would type-check
14686 // via the `Clone` impl but silently break every downstream
14687 // caller that relied on the reference sharing the composite's
14688 // backing identity), a reference to an operator-resolved
14689 // overlay (a future per-cluster `:behavior-overrides` slot —
14690 // its resolution must land at exactly this accessor body, not
14691 // silently divert the raw slot away from a second consumer), a
14692 // `None` → `Some(BehaviorSpec::default)` cluster-default
14693 // projection (which would collapse the load-bearing
14694 // "author-omitted `:behavior` ⇒ runtime-default applies"
14695 // partition the peer [`crate::render::servico_m2_overlay`]
14696 // emitter, the peer [`Caixa::declared_servico_slots`]
14697 // enumerator, and the cross-slot
14698 // [`crate::upgrade::validate_upgrade_from_against_behavior`]
14699 // gate all read), or a callback-shuffled projection (a future
14700 // detour that swapped `on_init` and `on_terminate` through the
14701 // accessor would silently split the paired
14702 // [`crate::StandardLayout::verify`] per-`:behavior` shape gate's
14703 // traversal input from the peer `servico_m2_overlay` emitter's
14704 // projection input from the cross-slot `:state-change`
14705 // composition gate's traversal input).
14706 use crate::BehaviorSpec;
14707 use std::path::PathBuf;
14708 let fixtures: Vec<Option<BehaviorSpec>> = vec![
14709 None,
14710 Some(BehaviorSpec::default()),
14711 Some(BehaviorSpec {
14712 on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
14713 ..Default::default()
14714 }),
14715 Some(BehaviorSpec {
14716 on_init: Some(PathBuf::from("lib/init.lisp")),
14717 on_call: Some(PathBuf::from("lib/handlers.lisp")),
14718 on_cast: Some(PathBuf::from("lib/handlers.lisp")),
14719 on_info: Some(PathBuf::from("lib/handlers.lisp")),
14720 on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
14721 on_terminate: Some(PathBuf::from("lib/cleanup.lisp")),
14722 }),
14723 ];
14724 for behavior in fixtures {
14725 let c = caixa_with_behavior(behavior.clone());
14726 assert_eq!(
14727 c.behavior(),
14728 behavior.as_ref(),
14729 "Caixa::behavior must return :behavior verbatim (got \
14730 {:?}, expected {:?})",
14731 c.behavior(),
14732 behavior.as_ref(),
14733 );
14734 match (c.behavior(), c.behavior.as_ref()) {
14735 (Some(a), Some(b)) => assert!(
14736 std::ptr::eq(a, b),
14737 "Caixa::behavior accessor and self.behavior.as_ref() \
14738 field access must borrow the same backing storage \
14739 — the accessor is the substrate-primitive typed \
14740 dispatch every downstream Servico-M2-overlay \
14741 composite consumer must route through, and a \
14742 reference-identity split would silently break \
14743 every consumer that relied on the borrow sharing \
14744 the composite's storage",
14745 ),
14746 (None, None) => {}
14747 _ => panic!(
14748 "Caixa::behavior presence bit must byte-equal \
14749 self.behavior.is_some() — a presence-bit drift \
14750 would silently split the paired \
14751 StandardLayout::verify per-`:behavior` shape \
14752 gate's traversal head from the peer \
14753 render::servico_m2_overlay M2 overlay emitter's \
14754 traversal head from the cross-slot \
14755 validate_upgrade_from_against_behavior \
14756 composition gate's traversal head from the peer \
14757 Caixa::declared_servico_slots M2 declared-slot \
14758 enumerator's presence probe",
14759 ),
14760 }
14761 assert_eq!(
14762 c.behavior().is_some(),
14763 c.behavior.is_some(),
14764 "Caixa::behavior().is_some() must byte-equal \
14765 self.behavior.is_some() — a presence-bit drift would \
14766 silently split every downstream Option<&BehaviorSpec> \
14767 consumer's partition on the runtime-default arm",
14768 );
14769 }
14770 }
14771
14772 #[test]
14773 fn declared_servico_slots_behavior_arm_routes_through_accessor() {
14774 // Composition pin: [`Caixa::declared_servico_slots`]'s
14775 // `:behavior` presence-probe arm must key off
14776 // [`Caixa::behavior`], not the raw `self.behavior.is_some()`
14777 // field-probe. Structurally: a `Caixa { behavior:
14778 // Some(BehaviorSpec::default()), .. }` must still push
14779 // `M2_AUTHOR_KEY_BEHAVIOR` onto the declared-slot list (the
14780 // presence bit is `Some`, so the M2 kind-coherence gate must
14781 // surface the slot as "declared" even when every per-callback
14782 // path is unset), and a `Caixa { behavior: None, .. }` must
14783 // NOT push the label (the "author omitted the slot entirely"
14784 // partition). The pair jointly pins the accessor + declared-
14785 // slot enumerator composition: any future silent detour that
14786 // had the accessor collapse `Some(BehaviorSpec::default())`
14787 // to `None` (a `.filter(|b| !b.is_empty())` projection) would
14788 // silently absorb the "declared but empty" arm at the
14789 // accessor boundary and the
14790 // [`crate::LayoutError::ServicoSlotsOnNonServico`]
14791 // kind-coherence gate would silently accept a struct-literal
14792 // `Caixa` carrying the drift.
14793 //
14794 // Peer of the sibling
14795 // `declared_servico_slots_limits_arm_routes_through_accessor`
14796 // (b2bd9d7) composition pin on the sibling `:limits` outer-
14797 // `Option<&LimitsSpec>` arm of the same
14798 // [`Caixa::declared_servico_slots`] M2 declared-slot
14799 // enumerator's traversal — same "the enumerator gate must
14800 // route through the substrate-primitive typed dispatch"
14801 // discipline extended onto the outer top-level [`Caixa`]
14802 // `Option<&BehaviorSpec>`-composition surface.
14803 use crate::BehaviorSpec;
14804 let c = caixa_with_behavior(Some(BehaviorSpec::default()));
14805 let slots = c.declared_servico_slots();
14806 assert!(
14807 slots.contains(&crate::render::M2_AUTHOR_KEY_BEHAVIOR),
14808 "declared_servico_slots must push M2_AUTHOR_KEY_BEHAVIOR \
14809 when `:behavior` is Some (even for BehaviorSpec::default()) \
14810 — the accessor and the enumerator gate must route through \
14811 the same substrate-primitive typed dispatch on the outer \
14812 :behavior presence bit (got slots={slots:?})",
14813 );
14814 let c = caixa_with_behavior(None);
14815 let slots = c.declared_servico_slots();
14816 assert!(
14817 !slots.contains(&crate::render::M2_AUTHOR_KEY_BEHAVIOR),
14818 "declared_servico_slots must NOT push M2_AUTHOR_KEY_BEHAVIOR \
14819 when `:behavior` is None — the author-omitted arm must \
14820 route through the accessor's None-return unchanged (got \
14821 slots={slots:?})",
14822 );
14823 }
14824
14825 #[test]
14826 fn servico_m2_overlay_behavior_arm_routes_through_accessor() {
14827 // Composition pin: [`crate::render::servico_m2_overlay`]'s
14828 // per-`:behavior` M2 overlay emit arm must key off
14829 // [`Caixa::behavior`], not the raw `&caixa.behavior`
14830 // field-borrow. Structurally: a `Caixa { behavior:
14831 // Some(BehaviorSpec { on_state_change: Some(...), .. default
14832 // }), .. }` must surface the `M2_KEY_BEHAVIOR` key with the
14833 // per-callback `onStateChange` sub-mapping in the overlay, a
14834 // `Caixa { behavior: Some(BehaviorSpec::default()), .. }`
14835 // must omit the key entirely (the `.is_empty()`-gated inner
14836 // arm elides an empty composite even when the outer presence
14837 // bit is `Some`), and a `Caixa { behavior: None, .. }` must
14838 // also omit the key (the "author omitted the slot entirely"
14839 // partition). The three-fixture family jointly pins the
14840 // accessor + M2 overlay emitter composition: any future
14841 // silent detour that had the accessor return a fresh-cloned
14842 // copy on the `Some` arm (a `BehaviorSpec::clone()`
14843 // projection) would silently break the reference-identity
14844 // pin the peer per-callback `serde_yaml::to_value(behavior)`
14845 // projection reads from.
14846 //
14847 // Peer of the sibling
14848 // `servico_m2_overlay_limits_arm_routes_through_accessor`
14849 // (b2bd9d7) composition pin on the sibling `:limits` outer-
14850 // `Option<&LimitsSpec>` arm of the same
14851 // [`crate::render::servico_m2_overlay`] M2 overlay emitter's
14852 // traversal — same "the emitter must route through the
14853 // substrate-primitive typed dispatch on the outer composite"
14854 // discipline extended onto the outer top-level [`Caixa`]
14855 // `Option<&BehaviorSpec>`-composition surface.
14856 use crate::BehaviorSpec;
14857 use crate::render::{M2_KEY_BEHAVIOR, servico_m2_overlay};
14858 use std::path::PathBuf;
14859 let c = caixa_with_behavior(Some(BehaviorSpec {
14860 on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
14861 ..Default::default()
14862 }));
14863 let overlay = servico_m2_overlay(&c).unwrap();
14864 assert!(
14865 overlay.contains_key(M2_KEY_BEHAVIOR),
14866 "servico_m2_overlay must surface M2_KEY_BEHAVIOR when \
14867 `:behavior` carries a non-empty composite — the accessor \
14868 and the M2 overlay emitter must route through the same \
14869 substrate-primitive typed dispatch on the outer :behavior \
14870 composite (got overlay={overlay:?})",
14871 );
14872 let c = caixa_with_behavior(Some(BehaviorSpec::default()));
14873 let overlay = servico_m2_overlay(&c).unwrap();
14874 assert!(
14875 !overlay.contains_key(M2_KEY_BEHAVIOR),
14876 "servico_m2_overlay must omit M2_KEY_BEHAVIOR when \
14877 `:behavior` is Some(BehaviorSpec::default()) — the empty \
14878 composite's `.is_empty()`-gated inner arm must elide the \
14879 key regardless of the outer presence bit (got \
14880 overlay={overlay:?})",
14881 );
14882 let c = caixa_with_behavior(None);
14883 let overlay = servico_m2_overlay(&c).unwrap();
14884 assert!(
14885 !overlay.contains_key(M2_KEY_BEHAVIOR),
14886 "servico_m2_overlay must omit M2_KEY_BEHAVIOR when \
14887 `:behavior` is None — the author-omitted arm must route \
14888 through the accessor's None-return unchanged (got \
14889 overlay={overlay:?})",
14890 );
14891 }
14892
14893 #[test]
14894 fn behavior_projects_option_ref_by_borrow() {
14895 // The by-borrow pin: [`Caixa::behavior`] returns
14896 // `Option<&BehaviorSpec>` by borrow — the returned reference
14897 // borrows the underlying `Option<BehaviorSpec>` storage of the
14898 // `:behavior` slot and the accessor must not clone the backing
14899 // composite on every call. Peer of the sibling
14900 // `limits_projects_option_ref_by_borrow` (b2bd9d7) by-borrow
14901 // pin on the outer top-level [`Caixa`] `Option<&Composite>`-
14902 // return sub-family — extended here to the second axis of the
14903 // same sub-family: the accessor's returned reference must
14904 // borrow from `&self` (the returned reference's lifetime is
14905 // tied to `&self`), and calling the accessor twice on the same
14906 // [`Caixa`] must yield references that are pointer-equal (the
14907 // underlying byte-buffer is the storage `BehaviorSpec`'s
14908 // allocation, not a fresh copy) as well as value-equal
14909 // (idempotent, no side effects on `&self`).
14910 //
14911 // Pins against a future silent detour that returned an owned
14912 // `BehaviorSpec` (which would type-check via the `Clone` impl
14913 // but silently clone on every call), a `&BehaviorSpec` panic-
14914 // return on the `None` arm (which would collapse the load-
14915 // bearing `Option` presence-bit into a runtime panic), or a
14916 // one-arm-only accessor that returned a saturating composite
14917 // on some sentinel input.
14918 use crate::BehaviorSpec;
14919 use std::path::PathBuf;
14920 for behavior in [
14921 Some(BehaviorSpec::default()),
14922 Some(BehaviorSpec {
14923 on_init: Some(PathBuf::from("lib/init.lisp")),
14924 on_call: Some(PathBuf::from("lib/handlers.lisp")),
14925 on_cast: Some(PathBuf::from("lib/handlers.lisp")),
14926 on_info: Some(PathBuf::from("lib/handlers.lisp")),
14927 on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
14928 on_terminate: Some(PathBuf::from("lib/cleanup.lisp")),
14929 }),
14930 ] {
14931 let c = caixa_with_behavior(behavior.clone());
14932 let first = c.behavior().unwrap();
14933 let second = c.behavior().unwrap();
14934 assert_eq!(
14935 first, second,
14936 "Caixa::behavior must be idempotent — two successive \
14937 calls on the same &self must return the same \
14938 &BehaviorSpec",
14939 );
14940 assert!(
14941 std::ptr::eq(first, second),
14942 "Caixa::behavior must borrow the underlying \
14943 Option<BehaviorSpec> storage — two successive calls \
14944 must return references with the same backing pointer \
14945 (a fresh BehaviorSpec clone would change the pointer \
14946 on every call)",
14947 );
14948 assert_eq!(
14949 Some(first),
14950 behavior.as_ref(),
14951 "Caixa::behavior must return :behavior verbatim by \
14952 borrow — got {first:?}, expected {:?}",
14953 behavior.as_ref(),
14954 );
14955 }
14956 let c = caixa_with_behavior(None);
14957 assert!(
14958 c.behavior().is_none(),
14959 "Caixa::behavior must return None when :behavior is absent \
14960 — the author-omitted arm must project through the \
14961 accessor's Option::None unchanged",
14962 );
14963 }
14964
14965 // ── Caixa::politicas — outer top-level Option<&MeshPolicy> composite-reference accessor ──
14966
14967 fn caixa_aplicacao_with_politicas(politicas: Option<crate::aplicacao::MeshPolicy>) -> Caixa {
14968 use crate::aplicacao::{Membro, WitContract};
14969 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
14970 c.kind = CaixaKind::Aplicacao;
14971 c.membros = vec![Membro {
14972 caixa: "a".into(),
14973 versao: "^0.1".into(),
14974 }];
14975 c.contratos = vec![WitContract {
14976 de: "a".into(),
14977 para: "a".into(),
14978 wit: "wasi:http/proxy".into(),
14979 endpoint: Some("/x".into()),
14980 subject: None,
14981 slot: None,
14982 }];
14983 c.politicas = politicas;
14984 c
14985 }
14986
14987 #[test]
14988 fn politicas_returns_politicas_option_ref_verbatim_across_permutations() {
14989 // The canonical per-`Caixa` `:politicas` M3 mesh-slot outer-
14990 // composite optional-composite-reference-shape pin:
14991 // [`Caixa::politicas`] must return the `:politicas` typed
14992 // `Option<MeshPolicy>` verbatim as an `Option<&MeshPolicy>`
14993 // reference over the same backing storage the raw
14994 // `self.politicas.as_ref()` field access borrows from,
14995 // byte-equal across every representative fixture in the
14996 // accept-set — the author-omitted `None` shape (the "cluster-
14997 // default applies" partition every downstream mesh-artifact
14998 // emitter treats as "emit no `:politicas` overlay"), the
14999 // empty-composite `Some(MeshPolicy { .. default })` shape
15000 // ([`crate::aplicacao::MeshPolicy::is_empty`] holds — every
15001 // per-axis mesh-policy scalar is `None`, so the peer inner
15002 // [`crate::AplicacaoSpec::politicas`] `.is_empty()`-gated
15003 // caixa-mesh overlay elides every per-axis emit but the outer
15004 // presence-bit is `Some`, so [`Caixa::declared_mesh_slots`]
15005 // still pushes the `M3_AUTHOR_KEY_POLITICAS` label), a
15006 // single-axis fixture (only `:timeout` set — the canonical
15007 // shape a latency-sensitive Aplicacao carries), and a
15008 // fully-populated composite (every per-axis mesh-policy
15009 // scalar set — the canonical shape a fully-governed
15010 // Aplicacao carries).
15011 //
15012 // Pins against a future silent detour that returned a fresh-
15013 // cloned [`crate::aplicacao::MeshPolicy`] copy (which would
15014 // type-check via the `Clone` impl but silently break every
15015 // downstream caller that relied on the reference sharing the
15016 // composite's backing identity), a reference to an operator-
15017 // resolved overlay (the future per-cluster
15018 // `:politicas-overrides` slot — its resolution must land at
15019 // exactly this accessor body, not silently divert the raw
15020 // slot away from the peer [`Caixa::declared_mesh_slots`]
15021 // enumerator's presence probe), a
15022 // `None` → `Some(MeshPolicy::default)` cluster-default
15023 // projection (which would collapse the load-bearing
15024 // "author-omitted `:politicas` ⇒ cluster-default applies"
15025 // partition the peer [`Caixa::declared_mesh_slots`]
15026 // enumerator and the peer [`Caixa::aplicacao_view`]
15027 // Aplicacao-composition seed both read), or an axis-shuffled
15028 // projection (a future detour that swapped `timeout` and
15029 // `retries` through the accessor would silently split the
15030 // paired [`Caixa::aplicacao_view`] seed's fold input from the
15031 // sibling M3 mesh-artifact emitter's projection input).
15032 //
15033 // Third outer top-level [`Caixa`] `Option<&Composite>`-return
15034 // composite-reference accessor pin on the substrate primitive
15035 // — peer of the sibling
15036 // `limits_returns_limits_option_ref_verbatim_across_permutations`
15037 // (b2bd9d7) and
15038 // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
15039 // (35d8b52) opening tetrad pins on the outer top-level
15040 // [`Caixa`] `Option<&Composite>`-return sub-family — extended
15041 // here to the first of the three M3 mesh-slot axes so the
15042 // opening third of the outer `Option<&Composite>` sub-family
15043 // carries the same "byte-equal, borrow-shared, presence-bit-
15044 // preserved" outer-accessor discipline.
15045 use crate::aplicacao::{CircuitBreaker, MeshPolicy, RateLimit};
15046 use std::time::Duration;
15047 let fixtures: Vec<Option<MeshPolicy>> = vec![
15048 None,
15049 Some(MeshPolicy::default()),
15050 Some(MeshPolicy {
15051 timeout: Some(Duration::from_secs(30)),
15052 ..Default::default()
15053 }),
15054 Some(MeshPolicy {
15055 timeout: Some(Duration::from_secs(30)),
15056 retries: Some(3),
15057 circuit_breaker: Some(CircuitBreaker {
15058 max_failures: 5,
15059 window: Duration::from_secs(60),
15060 }),
15061 mtls_required: Some(true),
15062 rate_limit: Some(RateLimit {
15063 rate: 100,
15064 window: Duration::from_secs(1),
15065 }),
15066 }),
15067 ];
15068 for politicas in fixtures {
15069 let c = caixa_aplicacao_with_politicas(politicas.clone());
15070 assert_eq!(
15071 c.politicas(),
15072 politicas.as_ref(),
15073 "Caixa::politicas must return :politicas verbatim (got \
15074 {:?}, expected {:?})",
15075 c.politicas(),
15076 politicas.as_ref(),
15077 );
15078 match (c.politicas(), c.politicas.as_ref()) {
15079 (Some(a), Some(b)) => assert!(
15080 std::ptr::eq(a, b),
15081 "Caixa::politicas accessor and self.politicas.as_ref() \
15082 field access must borrow the same backing storage \
15083 — the accessor is the substrate-primitive typed \
15084 dispatch every downstream Aplicacao-mesh-overlay \
15085 composite consumer must route through, and a \
15086 reference-identity split would silently break \
15087 every consumer that relied on the borrow sharing \
15088 the composite's storage",
15089 ),
15090 (None, None) => {}
15091 _ => panic!(
15092 "Caixa::politicas presence bit must byte-equal \
15093 self.politicas.is_some() — a presence-bit drift \
15094 would silently split the paired \
15095 Caixa::aplicacao_view Aplicacao-composition seed's \
15096 traversal head from the peer \
15097 Caixa::declared_mesh_slots M3 declared-slot \
15098 enumerator's presence probe",
15099 ),
15100 }
15101 assert_eq!(
15102 c.politicas().is_some(),
15103 c.politicas.is_some(),
15104 "Caixa::politicas().is_some() must byte-equal \
15105 self.politicas.is_some() — a presence-bit drift would \
15106 silently split every downstream Option<&MeshPolicy> \
15107 consumer's partition on the cluster-default arm",
15108 );
15109 }
15110 }
15111
15112 #[test]
15113 fn declared_mesh_slots_politicas_arm_routes_through_accessor() {
15114 // Composition pin: [`Caixa::declared_mesh_slots`]'s
15115 // `:politicas` presence-probe arm must key off
15116 // [`Caixa::politicas`], not the raw `self.politicas.is_some()`
15117 // field-probe. Structurally: a `Caixa { politicas:
15118 // Some(MeshPolicy::default()), .. }` must still push
15119 // `M3_AUTHOR_KEY_POLITICAS` onto the declared-slot list (the
15120 // presence bit is `Some`, so the M3 kind-coherence gate must
15121 // surface the slot as "declared" even when every per-axis
15122 // scalar is unset), and a `Caixa { politicas: None, .. }` must
15123 // NOT push the label (the "author omitted the slot entirely"
15124 // partition). The pair jointly pins the accessor + declared-
15125 // slot enumerator composition: any future silent detour that
15126 // had the accessor collapse `Some(MeshPolicy::default())` to
15127 // `None` (a `.filter(|p| !p.is_empty())` projection) would
15128 // silently absorb the "declared but empty" arm at the
15129 // accessor boundary and the
15130 // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
15131 // coherence gate would silently accept a struct-literal
15132 // `Caixa` carrying the drift.
15133 //
15134 // Peer of the sibling
15135 // `declared_servico_slots_limits_arm_routes_through_accessor`
15136 // (b2bd9d7) and
15137 // `declared_servico_slots_behavior_arm_routes_through_accessor`
15138 // (35d8b52) composition pins on the sibling `:limits` /
15139 // `:behavior` outer-`Option<&Composite>` arms of the peer
15140 // [`Caixa::declared_servico_slots`] M2 declared-slot
15141 // enumerator's traversal — same "the enumerator gate must
15142 // route through the substrate-primitive typed dispatch"
15143 // discipline extended onto the outer top-level [`Caixa`] M3
15144 // mesh-slot family so the [`Caixa::declared_mesh_slots`]
15145 // enumerator carries the same routing invariant as its M2
15146 // sibling.
15147 use crate::aplicacao::MeshPolicy;
15148 let c = caixa_aplicacao_with_politicas(Some(MeshPolicy::default()));
15149 let slots = c.declared_mesh_slots();
15150 assert!(
15151 slots.contains(&crate::render::M3_AUTHOR_KEY_POLITICAS),
15152 "declared_mesh_slots must push M3_AUTHOR_KEY_POLITICAS \
15153 when `:politicas` is Some (even for MeshPolicy::default()) \
15154 — the accessor and the enumerator gate must route through \
15155 the same substrate-primitive typed dispatch on the outer \
15156 :politicas presence bit (got slots={slots:?})",
15157 );
15158 let c = caixa_aplicacao_with_politicas(None);
15159 let slots = c.declared_mesh_slots();
15160 assert!(
15161 !slots.contains(&crate::render::M3_AUTHOR_KEY_POLITICAS),
15162 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_POLITICAS \
15163 when `:politicas` is None — the author-omitted arm must \
15164 route through the accessor's None-return unchanged (got \
15165 slots={slots:?})",
15166 );
15167 }
15168
15169 #[test]
15170 fn aplicacao_view_politicas_arm_folds_through_accessor() {
15171 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:politicas`
15172 // Aplicacao-composition seed must fold through
15173 // [`Caixa::politicas`], not the raw
15174 // `self.politicas.clone().unwrap_or_default()` field-borrow.
15175 // Structurally: a `Caixa { politicas: Some(MeshPolicy {
15176 // timeout: Some(30s), .. default }), kind: Aplicacao, .. }`
15177 // must surface a projected [`crate::AplicacaoSpec`] whose
15178 // `politicas().timeout()` field byte-equals the outer
15179 // composite's `timeout` scalar (the fold must project the
15180 // authored composite verbatim), a `Caixa { politicas:
15181 // Some(MeshPolicy::default()), kind: Aplicacao, .. }` must
15182 // surface an [`crate::AplicacaoSpec`] whose `politicas()`
15183 // byte-equals [`crate::aplicacao::MeshPolicy::default`] (the
15184 // fold's empty-composite arm collapses to the same default the
15185 // author-omitted arm does), and a `Caixa { politicas: None,
15186 // kind: Aplicacao, .. }` must surface an
15187 // [`crate::AplicacaoSpec`] whose `politicas()` byte-equals
15188 // [`crate::aplicacao::MeshPolicy::default`] (the "author
15189 // omitted the slot entirely" arm folds through the
15190 // `unwrap_or_default` onto the cluster-default). The triad
15191 // jointly pins the accessor + Aplicacao-composition seed
15192 // composition: any future silent detour that had the accessor
15193 // divert the raw slot away from the seed's fold (an operator-
15194 // resolved overlay's default-fold arm silently differing from
15195 // the raw slot's default-fold arm) would silently split the
15196 // build-time mesh-artifact emission gate from the caixa-mesh
15197 // renderer's Aplicacao-view input at the composition boundary.
15198 use crate::aplicacao::MeshPolicy;
15199 use std::time::Duration;
15200 let c = caixa_aplicacao_with_politicas(Some(MeshPolicy {
15201 timeout: Some(Duration::from_secs(30)),
15202 ..Default::default()
15203 }));
15204 let view = c.aplicacao_view().unwrap();
15205 assert_eq!(
15206 view.politicas().timeout(),
15207 Some(Duration::from_secs(30)),
15208 "Caixa::aplicacao_view must fold the authored :politicas \
15209 :timeout scalar through the accessor verbatim onto the \
15210 projected AplicacaoSpec — a future silent detour at the \
15211 seed's fold arm would surface here as a projected-scalar \
15212 drift (got {:?})",
15213 view.politicas().timeout(),
15214 );
15215 let c = caixa_aplicacao_with_politicas(Some(MeshPolicy::default()));
15216 let view = c.aplicacao_view().unwrap();
15217 assert_eq!(
15218 view.politicas(),
15219 &MeshPolicy::default(),
15220 "Caixa::aplicacao_view must fold Some(MeshPolicy::default()) \
15221 through the accessor onto MeshPolicy::default — the empty- \
15222 composite arm collapses to the same default the author- \
15223 omitted arm does (got {:?})",
15224 view.politicas(),
15225 );
15226 let c = caixa_aplicacao_with_politicas(None);
15227 let view = c.aplicacao_view().unwrap();
15228 assert_eq!(
15229 view.politicas(),
15230 &MeshPolicy::default(),
15231 "Caixa::aplicacao_view must fold None through the accessor's \
15232 unwrap_or_default onto MeshPolicy::default — the author- \
15233 omitted arm must route through the accessor's None-return \
15234 unchanged (got {:?})",
15235 view.politicas(),
15236 );
15237 }
15238
15239 #[test]
15240 fn politicas_projects_option_ref_by_borrow() {
15241 // The by-borrow pin: [`Caixa::politicas`] returns
15242 // `Option<&MeshPolicy>` by borrow — the returned reference
15243 // borrows the underlying `Option<MeshPolicy>` storage of the
15244 // `:politicas` slot and the accessor must not clone the
15245 // backing composite on every call. Peer of the sibling
15246 // `limits_projects_option_ref_by_borrow` (b2bd9d7) and
15247 // `behavior_projects_option_ref_by_borrow` (35d8b52) by-borrow
15248 // pins on the outer top-level [`Caixa`]
15249 // `Option<&Composite>`-return sub-family — extended here to
15250 // the third axis of the same sub-family: the accessor's
15251 // returned reference must borrow from `&self` (the returned
15252 // reference's lifetime is tied to `&self`), and calling the
15253 // accessor twice on the same [`Caixa`] must yield references
15254 // that are pointer-equal (the underlying byte-buffer is the
15255 // storage `MeshPolicy`'s allocation, not a fresh copy) as
15256 // well as value-equal (idempotent, no side effects on
15257 // `&self`).
15258 //
15259 // Pins against a future silent detour that returned an owned
15260 // `MeshPolicy` (which would type-check via the `Clone` impl
15261 // but silently clone on every call), a `&MeshPolicy` panic-
15262 // return on the `None` arm (which would collapse the load-
15263 // bearing `Option` presence-bit into a runtime panic), or a
15264 // one-arm-only accessor that returned a saturating composite
15265 // on some sentinel input.
15266 use crate::aplicacao::{CircuitBreaker, MeshPolicy, RateLimit};
15267 use std::time::Duration;
15268 for politicas in [
15269 Some(MeshPolicy::default()),
15270 Some(MeshPolicy {
15271 timeout: Some(Duration::from_secs(30)),
15272 retries: Some(3),
15273 circuit_breaker: Some(CircuitBreaker {
15274 max_failures: 5,
15275 window: Duration::from_secs(60),
15276 }),
15277 mtls_required: Some(true),
15278 rate_limit: Some(RateLimit {
15279 rate: 100,
15280 window: Duration::from_secs(1),
15281 }),
15282 }),
15283 ] {
15284 let c = caixa_aplicacao_with_politicas(politicas.clone());
15285 let first = c.politicas().unwrap();
15286 let second = c.politicas().unwrap();
15287 assert_eq!(
15288 first, second,
15289 "Caixa::politicas must be idempotent — two successive \
15290 calls on the same &self must return the same \
15291 &MeshPolicy",
15292 );
15293 assert!(
15294 std::ptr::eq(first, second),
15295 "Caixa::politicas must borrow the underlying \
15296 Option<MeshPolicy> storage — two successive calls \
15297 must return references with the same backing pointer \
15298 (a fresh MeshPolicy clone would change the pointer on \
15299 every call)",
15300 );
15301 assert_eq!(
15302 Some(first),
15303 politicas.as_ref(),
15304 "Caixa::politicas must return :politicas verbatim by \
15305 borrow — got {first:?}, expected {:?}",
15306 politicas.as_ref(),
15307 );
15308 }
15309 let c = caixa_aplicacao_with_politicas(None);
15310 assert!(
15311 c.politicas().is_none(),
15312 "Caixa::politicas must return None when :politicas is \
15313 absent — the author-omitted arm must project through the \
15314 accessor's Option::None unchanged",
15315 );
15316 }
15317
15318 // ── Caixa::placement — outer top-level Option<&Placement> composite-reference accessor ──
15319
15320 fn caixa_aplicacao_with_placement(placement: Option<crate::aplicacao::Placement>) -> Caixa {
15321 use crate::aplicacao::{Membro, WitContract};
15322 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15323 c.kind = CaixaKind::Aplicacao;
15324 c.membros = vec![Membro {
15325 caixa: "a".into(),
15326 versao: "^0.1".into(),
15327 }];
15328 c.contratos = vec![WitContract {
15329 de: "a".into(),
15330 para: "a".into(),
15331 wit: "wasi:http/proxy".into(),
15332 endpoint: Some("/x".into()),
15333 subject: None,
15334 slot: None,
15335 }];
15336 c.placement = placement;
15337 c
15338 }
15339
15340 #[test]
15341 fn placement_returns_placement_option_ref_verbatim_across_permutations() {
15342 // The canonical per-`Caixa` `:placement` M3 mesh-slot outer-
15343 // composite optional-composite-reference-shape pin:
15344 // [`Caixa::placement`] must return the `:placement` typed
15345 // `Option<Placement>` verbatim as an `Option<&Placement>`
15346 // reference over the same backing storage the raw
15347 // `self.placement.as_ref()` field access borrows from,
15348 // byte-equal across every representative fixture in the
15349 // accept-set — the author-omitted `None` shape (the
15350 // "cluster-default applies" partition every downstream mesh-
15351 // artifact emitter treats as "emit no `:placement` overlay"),
15352 // the empty-composite `Some(Placement { .. default })` shape
15353 // (`estrategia: SingleNode`, empty clusters, no shard-key /
15354 // affinity — the outer presence-bit is `Some` so
15355 // [`Caixa::declared_mesh_slots`] still pushes the
15356 // `M3_AUTHOR_KEY_PLACEMENT` label), a single-axis
15357 // `Replicated`-on-two-clusters fixture (the canonical shape a
15358 // stateless HTTP Aplicacao carries), and a fully-populated
15359 // `Sharded`-with-shard-key-and-affinity fixture (the canonical
15360 // shape a stateful Akka-style cluster-sharding Aplicacao
15361 // carries).
15362 //
15363 // Pins against a future silent detour that returned a fresh-
15364 // cloned [`crate::aplicacao::Placement`] copy (which would
15365 // type-check via the `Clone` impl but silently break every
15366 // downstream caller that relied on the reference sharing the
15367 // composite's backing identity), a reference to an operator-
15368 // resolved overlay (the future per-cluster
15369 // `:placement-overrides` slot — its resolution must land at
15370 // exactly this accessor body, not silently divert the raw
15371 // slot away from the peer [`Caixa::declared_mesh_slots`]
15372 // enumerator's presence probe), a `None` →
15373 // `Some(Placement::default)` cluster-default projection (which
15374 // would collapse the load-bearing "author-omitted `:placement`
15375 // ⇒ cluster-default applies" partition the peer
15376 // [`Caixa::declared_mesh_slots`] enumerator and the peer
15377 // [`Caixa::aplicacao_view`] Aplicacao-composition seed both
15378 // read), or an axis-shuffled projection (a future detour that
15379 // swapped `clusters` and `affinity` through the accessor would
15380 // silently split the paired [`Caixa::aplicacao_view`] seed's
15381 // fold input from the sibling M3 mesh-artifact emitter's
15382 // projection input).
15383 //
15384 // Fourth outer top-level [`Caixa`] `Option<&Composite>`-return
15385 // composite-reference accessor pin on the substrate primitive
15386 // — peer of the sibling
15387 // `limits_returns_limits_option_ref_verbatim_across_permutations`
15388 // (b2bd9d7),
15389 // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
15390 // (35d8b52), and
15391 // `politicas_returns_politicas_option_ref_verbatim_across_permutations`
15392 // (5d23d29) opening triad pins on the outer top-level
15393 // [`Caixa`] `Option<&Composite>`-return sub-family — extended
15394 // here to the second of the three M3 mesh-slot axes so the
15395 // opening four-fifths of the outer `Option<&Composite>` sub-
15396 // family carries the same "byte-equal, borrow-shared,
15397 // presence-bit-preserved" outer-accessor discipline.
15398 use crate::aplicacao::{Placement, PlacementStrategy};
15399 let fixtures: Vec<Option<Placement>> = vec![
15400 None,
15401 Some(Placement::default()),
15402 Some(Placement {
15403 estrategia: PlacementStrategy::Replicated,
15404 clusters: vec!["rio".into(), "sao-paulo".into()],
15405 affinity: None,
15406 shard_key: None,
15407 }),
15408 Some(Placement {
15409 estrategia: PlacementStrategy::Sharded,
15410 clusters: vec!["rio".into(), "sao-paulo".into(), "brasilia".into()],
15411 affinity: Some("data-locality".into()),
15412 shard_key: Some("$tenantId".into()),
15413 }),
15414 ];
15415 for placement in fixtures {
15416 let c = caixa_aplicacao_with_placement(placement.clone());
15417 assert_eq!(
15418 c.placement(),
15419 placement.as_ref(),
15420 "Caixa::placement must return :placement verbatim (got \
15421 {:?}, expected {:?})",
15422 c.placement(),
15423 placement.as_ref(),
15424 );
15425 match (c.placement(), c.placement.as_ref()) {
15426 (Some(a), Some(b)) => assert!(
15427 std::ptr::eq(a, b),
15428 "Caixa::placement accessor and self.placement.as_ref() \
15429 field access must borrow the same backing storage \
15430 — the accessor is the substrate-primitive typed \
15431 dispatch every downstream Aplicacao-distribution- \
15432 overlay composite consumer must route through, and \
15433 a reference-identity split would silently break \
15434 every consumer that relied on the borrow sharing \
15435 the composite's storage",
15436 ),
15437 (None, None) => {}
15438 _ => panic!(
15439 "Caixa::placement presence bit must byte-equal \
15440 self.placement.is_some() — a presence-bit drift \
15441 would silently split the paired \
15442 Caixa::aplicacao_view Aplicacao-composition seed's \
15443 traversal head from the peer \
15444 Caixa::declared_mesh_slots M3 declared-slot \
15445 enumerator's presence probe",
15446 ),
15447 }
15448 assert_eq!(
15449 c.placement().is_some(),
15450 c.placement.is_some(),
15451 "Caixa::placement().is_some() must byte-equal \
15452 self.placement.is_some() — a presence-bit drift would \
15453 silently split every downstream Option<&Placement> \
15454 consumer's partition on the cluster-default arm",
15455 );
15456 }
15457 }
15458
15459 #[test]
15460 fn declared_mesh_slots_placement_arm_routes_through_accessor() {
15461 // Composition pin: [`Caixa::declared_mesh_slots`]'s
15462 // `:placement` presence-probe arm must key off
15463 // [`Caixa::placement`], not the raw `self.placement.is_some()`
15464 // field-probe. Structurally: a `Caixa { placement:
15465 // Some(Placement::default()), .. }` must still push
15466 // `M3_AUTHOR_KEY_PLACEMENT` onto the declared-slot list (the
15467 // presence bit is `Some`, so the M3 kind-coherence gate must
15468 // surface the slot as "declared" even when every per-axis
15469 // scalar defers to the cluster-default arm), and a `Caixa {
15470 // placement: None, .. }` must NOT push the label (the "author
15471 // omitted the slot entirely" partition). The pair jointly pins
15472 // the accessor + declared-slot enumerator composition: any
15473 // future silent detour that had the accessor collapse
15474 // `Some(Placement::default())` to `None` (a `.filter(|p|
15475 // p.clusters().is_empty().not())` projection) would silently
15476 // absorb the "declared but empty" arm at the accessor boundary
15477 // and the [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
15478 // kind-coherence gate would silently accept a struct-literal
15479 // `Caixa` carrying the drift.
15480 //
15481 // Peer of the sibling
15482 // `declared_servico_slots_limits_arm_routes_through_accessor`
15483 // (b2bd9d7),
15484 // `declared_servico_slots_behavior_arm_routes_through_accessor`
15485 // (35d8b52), and
15486 // `declared_mesh_slots_politicas_arm_routes_through_accessor`
15487 // (5d23d29) composition pins on the sibling `:limits` /
15488 // `:behavior` / `:politicas` outer-`Option<&Composite>` arms
15489 // — same "the enumerator gate must route through the
15490 // substrate-primitive typed dispatch" discipline extended onto
15491 // the second of the three M3 mesh-slot axes so the
15492 // [`Caixa::declared_mesh_slots`] enumerator carries the same
15493 // routing invariant on the `:placement` arm as the peer
15494 // `:politicas` arm.
15495 use crate::aplicacao::Placement;
15496 let c = caixa_aplicacao_with_placement(Some(Placement::default()));
15497 let slots = c.declared_mesh_slots();
15498 assert!(
15499 slots.contains(&crate::render::M3_AUTHOR_KEY_PLACEMENT),
15500 "declared_mesh_slots must push M3_AUTHOR_KEY_PLACEMENT \
15501 when `:placement` is Some (even for Placement::default()) \
15502 — the accessor and the enumerator gate must route through \
15503 the same substrate-primitive typed dispatch on the outer \
15504 :placement presence bit (got slots={slots:?})",
15505 );
15506 let c = caixa_aplicacao_with_placement(None);
15507 let slots = c.declared_mesh_slots();
15508 assert!(
15509 !slots.contains(&crate::render::M3_AUTHOR_KEY_PLACEMENT),
15510 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_PLACEMENT \
15511 when `:placement` is None — the author-omitted arm must \
15512 route through the accessor's None-return unchanged (got \
15513 slots={slots:?})",
15514 );
15515 }
15516
15517 #[test]
15518 fn aplicacao_view_placement_arm_folds_through_accessor() {
15519 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:placement`
15520 // Aplicacao-composition seed must fold through
15521 // [`Caixa::placement`], not the raw
15522 // `self.placement.clone().unwrap_or_default()` field-borrow.
15523 // Structurally: a `Caixa { placement: Some(Placement {
15524 // estrategia: Replicated, clusters: ["rio"], .. default }),
15525 // kind: Aplicacao, .. }` must surface a projected
15526 // [`crate::AplicacaoSpec`] whose `placement().estrategia()` +
15527 // `placement().clusters()` byte-equal the outer composite's
15528 // authored values (the fold must project the authored
15529 // composite verbatim), a `Caixa { placement:
15530 // Some(Placement::default()), kind: Aplicacao, .. }` must
15531 // surface an [`crate::AplicacaoSpec`] whose `placement()`
15532 // byte-equals [`crate::aplicacao::Placement::default`] (the
15533 // fold's empty-composite arm collapses to the same default
15534 // the author-omitted arm does), and a `Caixa { placement:
15535 // None, kind: Aplicacao, .. }` must surface an
15536 // [`crate::AplicacaoSpec`] whose `placement()` byte-equals
15537 // [`crate::aplicacao::Placement::default`] (the "author
15538 // omitted the slot entirely" arm folds through the
15539 // `unwrap_or_default` onto the cluster-default). The triad
15540 // jointly pins the accessor + Aplicacao-composition seed
15541 // composition: any future silent detour that had the accessor
15542 // divert the raw slot away from the seed's fold (an operator-
15543 // resolved overlay's default-fold arm silently differing from
15544 // the raw slot's default-fold arm) would silently split the
15545 // build-time distribution-artifact emission gate from the
15546 // caixa-mesh renderer's Aplicacao-view input at the
15547 // composition boundary.
15548 use crate::aplicacao::{Placement, PlacementStrategy};
15549 let c = caixa_aplicacao_with_placement(Some(Placement {
15550 estrategia: PlacementStrategy::Replicated,
15551 clusters: vec!["rio".into()],
15552 affinity: None,
15553 shard_key: None,
15554 }));
15555 let view = c.aplicacao_view().unwrap();
15556 assert_eq!(
15557 view.placement().estrategia(),
15558 PlacementStrategy::Replicated,
15559 "Caixa::aplicacao_view must fold the authored :placement \
15560 :estrategia scalar through the accessor verbatim onto the \
15561 projected AplicacaoSpec — a future silent detour at the \
15562 seed's fold arm would surface here as a projected-scalar \
15563 drift (got {:?})",
15564 view.placement().estrategia(),
15565 );
15566 assert_eq!(
15567 view.placement().clusters(),
15568 &["rio"],
15569 "Caixa::aplicacao_view must fold the authored :placement \
15570 :clusters list through the accessor verbatim onto the \
15571 projected AplicacaoSpec — a future silent detour at the \
15572 seed's fold arm would surface here as a projected-list \
15573 drift (got {:?})",
15574 view.placement().clusters(),
15575 );
15576 let c = caixa_aplicacao_with_placement(Some(Placement::default()));
15577 let view = c.aplicacao_view().unwrap();
15578 assert_eq!(
15579 view.placement(),
15580 &Placement::default(),
15581 "Caixa::aplicacao_view must fold Some(Placement::default()) \
15582 through the accessor onto Placement::default — the empty- \
15583 composite arm collapses to the same default the author- \
15584 omitted arm does (got {:?})",
15585 view.placement(),
15586 );
15587 let c = caixa_aplicacao_with_placement(None);
15588 let view = c.aplicacao_view().unwrap();
15589 assert_eq!(
15590 view.placement(),
15591 &Placement::default(),
15592 "Caixa::aplicacao_view must fold None through the accessor's \
15593 unwrap_or_default onto Placement::default — the author- \
15594 omitted arm must route through the accessor's None-return \
15595 unchanged (got {:?})",
15596 view.placement(),
15597 );
15598 }
15599
15600 #[test]
15601 fn placement_projects_option_ref_by_borrow() {
15602 // The by-borrow pin: [`Caixa::placement`] returns
15603 // `Option<&Placement>` by borrow — the returned reference
15604 // borrows the underlying `Option<Placement>` storage of the
15605 // `:placement` slot and the accessor must not clone the
15606 // backing composite on every call. Peer of the sibling
15607 // `limits_projects_option_ref_by_borrow` (b2bd9d7),
15608 // `behavior_projects_option_ref_by_borrow` (35d8b52), and
15609 // `politicas_projects_option_ref_by_borrow` (5d23d29) by-borrow
15610 // pins on the outer top-level [`Caixa`]
15611 // `Option<&Composite>`-return sub-family — extended here to
15612 // the fourth axis of the same sub-family: the accessor's
15613 // returned reference must borrow from `&self` (the returned
15614 // reference's lifetime is tied to `&self`), and calling the
15615 // accessor twice on the same [`Caixa`] must yield references
15616 // that are pointer-equal (the underlying byte-buffer is the
15617 // storage `Placement`'s allocation, not a fresh copy) as well
15618 // as value-equal (idempotent, no side effects on `&self`).
15619 //
15620 // Pins against a future silent detour that returned an owned
15621 // `Placement` (which would type-check via the `Clone` impl
15622 // but silently clone on every call), a `&Placement` panic-
15623 // return on the `None` arm (which would collapse the load-
15624 // bearing `Option` presence-bit into a runtime panic), or a
15625 // one-arm-only accessor that returned a saturating composite
15626 // on some sentinel input.
15627 use crate::aplicacao::{Placement, PlacementStrategy};
15628 for placement in [
15629 Some(Placement::default()),
15630 Some(Placement {
15631 estrategia: PlacementStrategy::Sharded,
15632 clusters: vec!["rio".into(), "sao-paulo".into()],
15633 affinity: Some("data-locality".into()),
15634 shard_key: Some("$tenantId".into()),
15635 }),
15636 ] {
15637 let c = caixa_aplicacao_with_placement(placement.clone());
15638 let first = c.placement().unwrap();
15639 let second = c.placement().unwrap();
15640 assert_eq!(
15641 first, second,
15642 "Caixa::placement must be idempotent — two successive \
15643 calls on the same &self must return the same \
15644 &Placement",
15645 );
15646 assert!(
15647 std::ptr::eq(first, second),
15648 "Caixa::placement must borrow the underlying \
15649 Option<Placement> storage — two successive calls \
15650 must return references with the same backing pointer \
15651 (a fresh Placement clone would change the pointer on \
15652 every call)",
15653 );
15654 assert_eq!(
15655 Some(first),
15656 placement.as_ref(),
15657 "Caixa::placement must return :placement verbatim by \
15658 borrow — got {first:?}, expected {:?}",
15659 placement.as_ref(),
15660 );
15661 }
15662 let c = caixa_aplicacao_with_placement(None);
15663 assert!(
15664 c.placement().is_none(),
15665 "Caixa::placement must return None when :placement is \
15666 absent — the author-omitted arm must project through the \
15667 accessor's Option::None unchanged",
15668 );
15669 }
15670
15671 // ── Caixa::entrada — outer top-level Option<&Entrada> composite-reference accessor ──
15672
15673 fn caixa_aplicacao_with_entrada(entrada: Option<crate::aplicacao::Entrada>) -> Caixa {
15674 use crate::aplicacao::{Membro, WitContract};
15675 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15676 c.kind = CaixaKind::Aplicacao;
15677 c.membros = vec![Membro {
15678 caixa: "a".into(),
15679 versao: "^0.1".into(),
15680 }];
15681 c.contratos = vec![WitContract {
15682 de: "a".into(),
15683 para: "a".into(),
15684 wit: "wasi:http/proxy".into(),
15685 endpoint: Some("/x".into()),
15686 subject: None,
15687 slot: None,
15688 }];
15689 c.entrada = entrada;
15690 c
15691 }
15692
15693 #[test]
15694 fn entrada_returns_entrada_option_ref_verbatim_across_permutations() {
15695 // The canonical per-`Caixa` `:entrada` M3 mesh-slot outer-
15696 // composite optional-composite-reference-shape pin:
15697 // [`Caixa::entrada`] must return the `:entrada` typed
15698 // `Option<Entrada>` verbatim as an `Option<&Entrada>`
15699 // reference over the same backing storage the raw
15700 // `self.entrada.as_ref()` field access borrows from,
15701 // byte-equal across every representative fixture in the
15702 // accept-set — the author-omitted `None` shape (the
15703 // "cluster-internal Aplicacao" partition every downstream
15704 // Gateway-API emitter treats as "emit no listener + no
15705 // HTTPRoute"), a bare-`host`/`para` minimum-composite fixture
15706 // (empty `paths` — the resolved-paths fallback the peer
15707 // [`crate::aplicacao::Entrada::resolved_paths`] cascade folds
15708 // onto the substrate catch-all), and a fully-populated
15709 // multi-path-with-non-default-port fixture (the canonical
15710 // shape a public HTTP Aplicacao carries).
15711 //
15712 // Pins against a future silent detour that returned a fresh-
15713 // cloned [`crate::aplicacao::Entrada`] copy (which would
15714 // type-check via the `Clone` impl but silently break every
15715 // downstream caller that relied on the reference sharing the
15716 // composite's backing identity), a reference to an operator-
15717 // resolved overlay (the future per-cluster
15718 // `:entrada-overrides` slot — its resolution must land at
15719 // exactly this accessor body, not silently divert the raw
15720 // slot away from the peer [`Caixa::declared_mesh_slots`]
15721 // enumerator's presence probe), or an axis-shuffled projection
15722 // (a future detour that swapped `host` and `para` through the
15723 // accessor would silently split the paired
15724 // [`Caixa::aplicacao_view`] seed's forward input from the
15725 // sibling M3 gateway-artifact emitter's projection input).
15726 //
15727 // Fifth and final outer top-level [`Caixa`]
15728 // `Option<&Composite>`-return composite-reference accessor pin
15729 // on the substrate primitive — peer of the sibling
15730 // `limits_returns_limits_option_ref_verbatim_across_permutations`
15731 // (b2bd9d7),
15732 // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
15733 // (35d8b52),
15734 // `politicas_returns_politicas_option_ref_verbatim_across_permutations`
15735 // (5d23d29), and
15736 // `placement_returns_placement_option_ref_verbatim_across_permutations`
15737 // (4fb8074) opening tetrad pins on the outer top-level
15738 // [`Caixa`] `Option<&Composite>`-return sub-family — extended
15739 // here to the third and final M3 mesh-slot axis so the closed
15740 // outer `Option<&Composite>` sub-family carries the same
15741 // "byte-equal, borrow-shared, presence-bit-preserved" outer-
15742 // accessor discipline across all five arms.
15743 use crate::aplicacao::Entrada;
15744 let fixtures: Vec<Option<Entrada>> = vec![
15745 None,
15746 Some(Entrada {
15747 host: "checkout.quero.cloud".into(),
15748 para: "gateway".into(),
15749 paths: Vec::new(),
15750 port: crate::DEFAULT_SERVICO_PORT,
15751 }),
15752 Some(Entrada {
15753 host: "api.pleme.io".into(),
15754 para: "public-api".into(),
15755 paths: vec!["/v1".into(), "/v2".into()],
15756 port: 8080,
15757 }),
15758 ];
15759 for entrada in fixtures {
15760 let c = caixa_aplicacao_with_entrada(entrada.clone());
15761 assert_eq!(
15762 c.entrada(),
15763 entrada.as_ref(),
15764 "Caixa::entrada must return :entrada verbatim (got \
15765 {:?}, expected {:?})",
15766 c.entrada(),
15767 entrada.as_ref(),
15768 );
15769 match (c.entrada(), c.entrada.as_ref()) {
15770 (Some(a), Some(b)) => assert!(
15771 std::ptr::eq(a, b),
15772 "Caixa::entrada accessor and self.entrada.as_ref() \
15773 field access must borrow the same backing storage \
15774 — the accessor is the substrate-primitive typed \
15775 dispatch every downstream Aplicacao-external- \
15776 gateway composite consumer must route through, and \
15777 a reference-identity split would silently break \
15778 every consumer that relied on the borrow sharing \
15779 the composite's storage",
15780 ),
15781 (None, None) => {}
15782 _ => panic!(
15783 "Caixa::entrada presence bit must byte-equal \
15784 self.entrada.is_some() — a presence-bit drift \
15785 would silently split the paired \
15786 Caixa::aplicacao_view Aplicacao-composition seed's \
15787 traversal head from the peer \
15788 Caixa::declared_mesh_slots M3 declared-slot \
15789 enumerator's presence probe",
15790 ),
15791 }
15792 assert_eq!(
15793 c.entrada().is_some(),
15794 c.entrada.is_some(),
15795 "Caixa::entrada().is_some() must byte-equal \
15796 self.entrada.is_some() — a presence-bit drift would \
15797 silently split every downstream Option<&Entrada> \
15798 consumer's partition on the cluster-internal arm",
15799 );
15800 }
15801 }
15802
15803 #[test]
15804 fn declared_mesh_slots_entrada_arm_routes_through_accessor() {
15805 // Composition pin: [`Caixa::declared_mesh_slots`]'s `:entrada`
15806 // presence-probe arm must key off [`Caixa::entrada`], not the
15807 // raw `self.entrada.is_some()` field-probe. Structurally: a
15808 // `Caixa { entrada: Some(Entrada { host: "...", para: "...",
15809 // paths: [], port: DEFAULT_SERVICO_PORT }), .. }` must push
15810 // `M3_AUTHOR_KEY_ENTRADA` onto the declared-slot list (the
15811 // presence bit is `Some`, so the M3 kind-coherence gate must
15812 // surface the slot as "declared" even when every per-axis
15813 // scalar defers to the substrate catch-all / default port),
15814 // and a `Caixa { entrada: None, .. }` must NOT push the label
15815 // (the "author omitted the slot entirely" partition). The pair
15816 // jointly pins the accessor + declared-slot enumerator
15817 // composition: any future silent detour that had the accessor
15818 // collapse `Some(Entrada { paths: [], .. })` to `None` (a
15819 // `.filter(|e| !e.paths.is_empty())` projection) would silently
15820 // absorb the "declared but empty-paths" arm at the accessor
15821 // boundary and the
15822 // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
15823 // coherence gate would silently accept a struct-literal
15824 // `Caixa` carrying the drift.
15825 //
15826 // Peer of the sibling
15827 // `declared_servico_slots_limits_arm_routes_through_accessor`
15828 // (b2bd9d7),
15829 // `declared_servico_slots_behavior_arm_routes_through_accessor`
15830 // (35d8b52),
15831 // `declared_mesh_slots_politicas_arm_routes_through_accessor`
15832 // (5d23d29), and
15833 // `declared_mesh_slots_placement_arm_routes_through_accessor`
15834 // (4fb8074) composition pins on the sibling `:limits` /
15835 // `:behavior` / `:politicas` / `:placement` outer-
15836 // `Option<&Composite>` arms — same "the enumerator gate must
15837 // route through the substrate-primitive typed dispatch"
15838 // discipline extended onto the third and final M3 mesh-slot
15839 // axis so the [`Caixa::declared_mesh_slots`] enumerator now
15840 // carries the routing invariant on every M3 mesh-slot arm.
15841 use crate::aplicacao::Entrada;
15842 let c = caixa_aplicacao_with_entrada(Some(Entrada {
15843 host: "checkout.quero.cloud".into(),
15844 para: "gateway".into(),
15845 paths: Vec::new(),
15846 port: crate::DEFAULT_SERVICO_PORT,
15847 }));
15848 let slots = c.declared_mesh_slots();
15849 assert!(
15850 slots.contains(&crate::render::M3_AUTHOR_KEY_ENTRADA),
15851 "declared_mesh_slots must push M3_AUTHOR_KEY_ENTRADA when \
15852 `:entrada` is Some (even for empty-paths / default-port) \
15853 — the accessor and the enumerator gate must route through \
15854 the same substrate-primitive typed dispatch on the outer \
15855 :entrada presence bit (got slots={slots:?})",
15856 );
15857 let c = caixa_aplicacao_with_entrada(None);
15858 let slots = c.declared_mesh_slots();
15859 assert!(
15860 !slots.contains(&crate::render::M3_AUTHOR_KEY_ENTRADA),
15861 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_ENTRADA \
15862 when `:entrada` is None — the author-omitted arm must \
15863 route through the accessor's None-return unchanged (got \
15864 slots={slots:?})",
15865 );
15866 }
15867
15868 #[test]
15869 fn aplicacao_view_entrada_arm_folds_through_accessor() {
15870 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:entrada`
15871 // Aplicacao-composition seed must fold through
15872 // [`Caixa::entrada`], not the raw `self.entrada.clone()` field-
15873 // borrow. Structurally: a `Caixa { entrada: Some(Entrada {
15874 // host: "api.pleme.io", para: "public-api", paths: ["/v1"],
15875 // port: 8080 }), kind: Aplicacao, .. }` must surface a projected
15876 // [`crate::AplicacaoSpec`] whose `entrada().unwrap()` byte-
15877 // equals the outer composite's authored value (the fold must
15878 // project the authored composite verbatim), and a `Caixa {
15879 // entrada: None, kind: Aplicacao, .. }` must surface an
15880 // [`crate::AplicacaoSpec`] whose `entrada()` is `None` (the
15881 // "author omitted the slot entirely" arm folds through the
15882 // accessor's `Option::cloned` onto the same `None` presence
15883 // bit — unlike the peer `:politicas` / `:placement` arms
15884 // `:entrada` has no cluster-default fold, the omitted arm
15885 // stays omitted). The pair jointly pins the accessor +
15886 // Aplicacao-composition seed composition: any future silent
15887 // detour that had the accessor divert the raw slot away from
15888 // the seed's fold (an operator-resolved overlay's forward arm
15889 // silently differing from the raw slot's forward arm) would
15890 // silently split the build-time gateway-artifact emission gate
15891 // from the caixa-mesh renderer's Aplicacao-view input at the
15892 // composition boundary.
15893 use crate::aplicacao::Entrada;
15894 let authored = Entrada {
15895 host: "api.pleme.io".into(),
15896 para: "public-api".into(),
15897 paths: vec!["/v1".into()],
15898 port: 8080,
15899 };
15900 let c = caixa_aplicacao_with_entrada(Some(authored.clone()));
15901 let view = c.aplicacao_view().unwrap();
15902 assert_eq!(
15903 view.entrada(),
15904 Some(&authored),
15905 "Caixa::aplicacao_view must fold the authored :entrada \
15906 composite through the accessor verbatim onto the \
15907 projected AplicacaoSpec — a future silent detour at the \
15908 seed's fold arm would surface here as a projected- \
15909 composite drift (got {:?})",
15910 view.entrada(),
15911 );
15912 let c = caixa_aplicacao_with_entrada(None);
15913 let view = c.aplicacao_view().unwrap();
15914 assert!(
15915 view.entrada().is_none(),
15916 "Caixa::aplicacao_view must fold None through the \
15917 accessor's Option::cloned onto None — the author- \
15918 omitted arm must route through the accessor's None-return \
15919 unchanged (got {:?})",
15920 view.entrada(),
15921 );
15922 }
15923
15924 #[test]
15925 fn entrada_projects_option_ref_by_borrow() {
15926 // The by-borrow pin: [`Caixa::entrada`] returns
15927 // `Option<&Entrada>` by borrow — the returned reference
15928 // borrows the underlying `Option<Entrada>` storage of the
15929 // `:entrada` slot and the accessor must not clone the backing
15930 // composite on every call. Peer of the sibling
15931 // `limits_projects_option_ref_by_borrow` (b2bd9d7),
15932 // `behavior_projects_option_ref_by_borrow` (35d8b52),
15933 // `politicas_projects_option_ref_by_borrow` (5d23d29), and
15934 // `placement_projects_option_ref_by_borrow` (4fb8074) by-
15935 // borrow pins on the outer top-level [`Caixa`]
15936 // `Option<&Composite>`-return sub-family — extended here to
15937 // the fifth and final axis of the same sub-family, closing
15938 // the discipline: the accessor's returned reference must
15939 // borrow from `&self` (the returned reference's lifetime is
15940 // tied to `&self`), and calling the accessor twice on the
15941 // same [`Caixa`] must yield references that are pointer-equal
15942 // (the underlying byte-buffer is the storage `Entrada`'s
15943 // allocation, not a fresh copy) as well as value-equal
15944 // (idempotent, no side effects on `&self`).
15945 //
15946 // Pins against a future silent detour that returned an owned
15947 // `Entrada` (which would type-check via the `Clone` impl but
15948 // silently clone on every call), a `&Entrada` panic-return on
15949 // the `None` arm (which would collapse the load-bearing
15950 // `Option` presence-bit into a runtime panic), or a one-arm-
15951 // only accessor that returned a saturating composite on some
15952 // sentinel input.
15953 use crate::aplicacao::Entrada;
15954 for entrada in [
15955 Some(Entrada {
15956 host: "checkout.quero.cloud".into(),
15957 para: "gateway".into(),
15958 paths: Vec::new(),
15959 port: crate::DEFAULT_SERVICO_PORT,
15960 }),
15961 Some(Entrada {
15962 host: "api.pleme.io".into(),
15963 para: "public-api".into(),
15964 paths: vec!["/v1".into(), "/v2".into()],
15965 port: 8080,
15966 }),
15967 ] {
15968 let c = caixa_aplicacao_with_entrada(entrada.clone());
15969 let first = c.entrada().unwrap();
15970 let second = c.entrada().unwrap();
15971 assert_eq!(
15972 first, second,
15973 "Caixa::entrada must be idempotent — two successive \
15974 calls on the same &self must return the same &Entrada",
15975 );
15976 assert!(
15977 std::ptr::eq(first, second),
15978 "Caixa::entrada must borrow the underlying \
15979 Option<Entrada> storage — two successive calls must \
15980 return references with the same backing pointer (a \
15981 fresh Entrada clone would change the pointer on every \
15982 call)",
15983 );
15984 assert_eq!(
15985 Some(first),
15986 entrada.as_ref(),
15987 "Caixa::entrada must return :entrada verbatim by \
15988 borrow — got {first:?}, expected {:?}",
15989 entrada.as_ref(),
15990 );
15991 }
15992 let c = caixa_aplicacao_with_entrada(None);
15993 assert!(
15994 c.entrada().is_none(),
15995 "Caixa::entrada must return None when :entrada is absent \
15996 — the author-omitted arm must project through the \
15997 accessor's Option::None unchanged",
15998 );
15999 }
16000
16001 // ── Caixa::estrategia — outer top-level Option<RestartStrategy> flat-spread supervisor-tree accessor ──
16002
16003 fn caixa_with_estrategia(estrategia: Option<crate::supervisor::RestartStrategy>) -> Caixa {
16004 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16005 c.estrategia = estrategia;
16006 c
16007 }
16008
16009 #[test]
16010 fn estrategia_returns_estrategia_option_verbatim_across_permutations() {
16011 // The canonical per-`Caixa` `:estrategia` M2 supervisor-tree-slot
16012 // flat-spread `Option<RestartStrategy>`-return `Copy`-composite-
16013 // enum-arm scalar shape pin: [`Caixa::estrategia`] must return
16014 // the `:estrategia` typed `Option<crate::supervisor::RestartStrategy>`
16015 // verbatim as an `Option<RestartStrategy>` `Copy`-projected value
16016 // over the same discriminant the raw `self.estrategia` field
16017 // access carries, byte-equal across every representative fixture
16018 // in the accept-set — the author-omitted `None` shape (the
16019 // "defer to [`RestartStrategy::default`] through the
16020 // [`Self::supervisor_view`] `unwrap_or_default()` fold" partition
16021 // every non-`Supervisor`-kind `defcaixa` carries by
16022 // `#[serde(default)]`), and each of the four closed-set variants
16023 // [`RestartStrategy::OneForOne`] / [`RestartStrategy::OneForAll`]
16024 // / [`RestartStrategy::RestForOne`] /
16025 // [`RestartStrategy::SimpleOneForOne`] the author-declared arm
16026 // partitions on.
16027 //
16028 // Pins against a future silent detour that re-derived the
16029 // strategy from a peer axis (an accidental fallback to
16030 // `if children.is_empty() { SimpleOneForOne } else { OneForOne }`
16031 // collapse that read the outer `:children` list-length axis into
16032 // the strategy discriminator at the accessor boundary), a
16033 // stale-derive detour that substituted [`RestartStrategy::default`]
16034 // when the outer `Option` held `None` (which would silently
16035 // collapse the load-bearing "author explicitly declared
16036 // `:estrategia OneForOne`" vs "author omitted the slot and
16037 // inherited the default" partition the [`Self::declared_supervisor_slots`]
16038 // presence-probe reads — the enumerator gate would still push
16039 // `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA` on the omitted arm, silently
16040 // splitting the paired [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
16041 // kind-coherence gate's traversal head from the
16042 // [`Self::supervisor_view`] `unwrap_or_default()` fold's
16043 // composition head), a reference to an operator-resolved overlay
16044 // (the future per-cluster `:estrategia-overrides` slot — its
16045 // resolution must land at exactly this accessor body, not
16046 // silently divert the raw slot away from a second consumer), or
16047 // an axis-remap projection (a future detour that mapped
16048 // `OneForAll` through the accessor onto `OneForOne` would
16049 // silently split every downstream sibling-restart-strategy
16050 // consumer's per-arm fan-out).
16051 //
16052 // First outer top-level [`Caixa`] `Option<Copy>`-return
16053 // supervisor-tree-slot flat-spread accessor pin on the substrate
16054 // primitive — opens the outer-`Caixa` `Option<Copy>` flat-spread
16055 // projection pattern the sibling per-`Caixa` `:max-restarts` /
16056 // `:restart-window` future outer-scalar pins fold on. Peer of
16057 // the inner-altitude
16058 // `supervisor_spec_estrategia_returns_estrategia_verbatim_across_permutations`
16059 // (eafb619) pin on the post-composition [`SupervisorSpec`]
16060 // altitude — same "the substrate-primitive accessor must byte-
16061 // equal the raw field access verbatim across every author-
16062 // declared value" discipline extended onto the pre-composition
16063 // outer author-surface [`Caixa`] altitude. Peer of the closed
16064 // outer-`Caixa` `Option<&Composite>` composite-reference family
16065 // the sibling `limits` / `behavior` / `politicas` / `placement` /
16066 // `entrada`
16067 // `..._returns_..._option_ref_verbatim_across_permutations` pins
16068 // already carry on the outer `Option<&Composite>` altitude.
16069 use crate::supervisor::RestartStrategy;
16070 let fixtures: Vec<Option<RestartStrategy>> = vec![
16071 None,
16072 Some(RestartStrategy::OneForOne),
16073 Some(RestartStrategy::OneForAll),
16074 Some(RestartStrategy::RestForOne),
16075 Some(RestartStrategy::SimpleOneForOne),
16076 ];
16077 for estrategia in fixtures {
16078 let c = caixa_with_estrategia(estrategia);
16079 assert_eq!(
16080 c.estrategia(),
16081 estrategia,
16082 "Caixa::estrategia must return :estrategia verbatim (got \
16083 {:?}, expected {:?})",
16084 c.estrategia(),
16085 estrategia,
16086 );
16087 assert_eq!(
16088 c.estrategia(),
16089 c.estrategia,
16090 "Caixa::estrategia accessor and self.estrategia field \
16091 access must byte-equal — the accessor is the substrate-\
16092 primitive typed dispatch every downstream supervisor-\
16093 tree flat-spread consumer must route through, and a \
16094 discriminant split would silently break every consumer \
16095 that relied on the accessor sharing the field's own \
16096 Option<Copy> shape",
16097 );
16098 assert_eq!(
16099 c.estrategia().is_some(),
16100 c.estrategia.is_some(),
16101 "Caixa::estrategia().is_some() must byte-equal \
16102 self.estrategia.is_some() — a presence-bit drift would \
16103 silently split the paired Caixa::declared_supervisor_slots \
16104 presence-probe arm from the Caixa::supervisor_view \
16105 unwrap_or_default() fold's composition input",
16106 );
16107 }
16108 }
16109
16110 #[test]
16111 fn declared_supervisor_slots_estrategia_arm_routes_through_accessor() {
16112 // Composition pin: [`Caixa::declared_supervisor_slots`]'s
16113 // `:estrategia` presence-probe arm must key off
16114 // [`Caixa::estrategia`], not the raw `self.estrategia.is_some()`
16115 // field-probe. Structurally: every `Caixa { estrategia:
16116 // Some(RestartStrategy::_), .. }` variant must push
16117 // `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA` onto the declared-slot list
16118 // (the presence bit is `Some` for every closed-set variant, so
16119 // the M2 supervisor-tree kind-coherence gate must surface the
16120 // slot as "declared" regardless of which variant the author
16121 // picked), and a `Caixa { estrategia: None, .. }` must NOT push
16122 // the label (the "author omitted the slot entirely, deferring
16123 // to [`RestartStrategy::default`] through the supervisor_view
16124 // fold" partition). The pair jointly pins the accessor +
16125 // declared-slot enumerator composition: any future silent detour
16126 // that had the accessor collapse `Some(RestartStrategy::default())`
16127 // to `None` (a `.filter(|e| *e != RestartStrategy::default())`
16128 // projection) would silently absorb the "declared but default-
16129 // valued" arm at the accessor boundary and the
16130 // [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
16131 // coherence gate would silently accept a struct-literal `Caixa`
16132 // carrying the drift.
16133 //
16134 // Peer of the sibling per-`Caixa`
16135 // `declared_servico_slots_limits_arm_routes_through_accessor`
16136 // (b2bd9d7) accessor-composition pin on the sibling outer-`Caixa`
16137 // `Option<&LimitsSpec>` composition axis — same "the enumerator
16138 // gate must route through the substrate-primitive typed
16139 // dispatch" discipline extended onto the flat-spread M2
16140 // supervisor-tree `Option<RestartStrategy>`-composition surface,
16141 // opening the outer-`Caixa` supervisor-tree-slot arm of the
16142 // composition-pin family.
16143 use crate::supervisor::RestartStrategy;
16144 for estrategia in [
16145 RestartStrategy::OneForOne,
16146 RestartStrategy::OneForAll,
16147 RestartStrategy::RestForOne,
16148 RestartStrategy::SimpleOneForOne,
16149 ] {
16150 let c = caixa_with_estrategia(Some(estrategia));
16151 let slots = c.declared_supervisor_slots();
16152 assert!(
16153 slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA),
16154 "declared_supervisor_slots must push \
16155 SUPERVISOR_AUTHOR_KEY_ESTRATEGIA when `:estrategia` is \
16156 Some({estrategia:?}) — the accessor and the enumerator \
16157 gate must route through the same substrate-primitive \
16158 typed dispatch on the outer :estrategia presence bit \
16159 (got slots={slots:?})",
16160 );
16161 }
16162 let c = caixa_with_estrategia(None);
16163 let slots = c.declared_supervisor_slots();
16164 assert!(
16165 !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA),
16166 "declared_supervisor_slots must NOT push \
16167 SUPERVISOR_AUTHOR_KEY_ESTRATEGIA when `:estrategia` is None \
16168 — the author-omitted arm must route through the accessor's \
16169 None-return unchanged (got slots={slots:?})",
16170 );
16171 }
16172
16173 #[test]
16174 fn supervisor_view_estrategia_arm_routes_through_accessor() {
16175 // Composition pin: [`Caixa::supervisor_view`]'s per-`:estrategia`
16176 // [`SupervisorSpec`] construction arm must key off
16177 // [`Caixa::estrategia`]'s `unwrap_or_default()` fold, not the raw
16178 // `self.estrategia.unwrap_or_default()` field-fold. Structurally:
16179 // for every `:kind Supervisor` `Caixa` carrying an author-
16180 // declared `Some(RestartStrategy::_)` variant, the composed
16181 // [`SupervisorSpec`]'s `.estrategia` field must byte-equal the
16182 // outer accessor's declared variant unchanged; and for a
16183 // `:kind Supervisor` `Caixa` carrying `None`, the composed
16184 // [`SupervisorSpec`]'s `.estrategia` field must byte-equal
16185 // [`RestartStrategy::default`] (the [`RestartStrategy::OneForOne`]
16186 // arm the flat-spread `unwrap_or_default()` fold projects to on
16187 // the author-omitted arm — this is the *composition* between the
16188 // outer `Option<RestartStrategy>` accessor's presence-bit
16189 // surface and the inner post-composition non-`Option`
16190 // [`SupervisorSpec::estrategia`] altitude). The pair jointly
16191 // pins the accessor + supervisor_view composition: any future
16192 // silent detour that had the accessor promote `None` to
16193 // `Some(RestartStrategy::default())` (a `.or_else(|| Some(RestartStrategy::default()))`
16194 // projection) would silently collapse the two arms into one at
16195 // the accessor boundary and the [`Self::declared_supervisor_slots`]
16196 // presence probe would silently drift from the composition site.
16197 //
16198 // Peer of the sibling M2 supervisor-slot post-composition
16199 // `validate_reads_through_lifted_estrategia_accessor` (eafb619)
16200 // pin on the [`SupervisorSpec::validate`] altitude — this pin
16201 // extends that inner-altitude accessor-routing discipline onto
16202 // the pre-composition outer author-surface [`Caixa`] altitude,
16203 // pinning the composition edge between the flat-spread outer
16204 // `Option<RestartStrategy>` and the composed [`SupervisorSpec`]
16205 // `RestartStrategy` axes.
16206 use crate::CaixaKind;
16207 use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
16208 for estrategia in [
16209 RestartStrategy::OneForOne,
16210 RestartStrategy::OneForAll,
16211 RestartStrategy::RestForOne,
16212 RestartStrategy::SimpleOneForOne,
16213 ] {
16214 let mut c = caixa_with_estrategia(Some(estrategia));
16215 c.kind = CaixaKind::Supervisor;
16216 // Route the `SimpleOneForOne ↔ non-SimpleOneForOne` fixture-
16217 // shape partition through the [`gen_platform::IsVariant`]
16218 // derive-generated
16219 // [`RestartStrategy::is_simple_one_for_one`] predicate rather
16220 // than the raw `matches!(estrategia, RestartStrategy::
16221 // SimpleOneForOne)` open-coded pattern-match — same closed-
16222 // set-typed-enum arm-discriminator dispatch discipline the
16223 // sibling [`crate::upgrade::UpgradeInstruction::is_restart`]
16224 // convergence (915a934) extended onto its two paired positive
16225 // / negated `matches!` sites and the peer
16226 // [`crate::aplicacao::PlacementStrategy`] `IsVariant`-derived
16227 // predicate convergence (766ec63) extended onto the M3 mesh-
16228 // slot per-`:placement` distribution-strategy discriminator
16229 // axis. See the sibling `supervisor::tests::
16230 // round_trip_all_strategies` and
16231 // `supervisor::tests::supervisor_spec_estrategia_returns_estrategia_verbatim_across_permutations`
16232 // fixtures — the three sites (all test-only,
16233 // acknowledged in 915a934's Prior-commits footnote as the
16234 // outstanding follow-up) now consult one typed dispatch on
16235 // the substrate primitive.
16236 c.children = if estrategia.is_simple_one_for_one() {
16237 Vec::new()
16238 } else {
16239 vec![ChildSpec {
16240 caixa: "worker".into(),
16241 versao: "^0.1".into(),
16242 restart: RestartPolicy::Permanent,
16243 }]
16244 };
16245 let view = c.supervisor_view().expect(
16246 "supervisor_view must materialize a SupervisorSpec for a \
16247 :kind Supervisor Caixa carrying a Some(:estrategia) slot",
16248 );
16249 assert_eq!(
16250 view.estrategia(),
16251 c.estrategia().unwrap(),
16252 "supervisor_view must carry the outer Caixa::estrategia() \
16253 declared variant onto the composed SupervisorSpec.estrategia \
16254 field verbatim on the Some arm (got {:?}, expected {:?})",
16255 view.estrategia(),
16256 c.estrategia().unwrap(),
16257 );
16258 }
16259 // The author-omitted arm: outer `None` → composed
16260 // `RestartStrategy::default()` through the flat-spread
16261 // `unwrap_or_default()` fold.
16262 let mut c = caixa_with_estrategia(None);
16263 c.kind = CaixaKind::Supervisor;
16264 // Populate children so the sibling supervisor slots are coherent
16265 // for the [`Self::supervisor_view`] projection; the `:estrategia`
16266 // arm still defers to [`RestartStrategy::default`] on the
16267 // author-omitted arm even when the sibling slots carry values.
16268 c.children = vec![ChildSpec {
16269 caixa: "worker".into(),
16270 versao: "^0.1".into(),
16271 restart: RestartPolicy::Permanent,
16272 }];
16273 let view = c.supervisor_view().expect(
16274 "supervisor_view must materialize a SupervisorSpec for a \
16275 :kind Supervisor Caixa carrying a None `:estrategia` slot",
16276 );
16277 assert_eq!(
16278 view.estrategia(),
16279 RestartStrategy::default(),
16280 "supervisor_view must project the outer Caixa::estrategia() \
16281 None arm onto RestartStrategy::default() through the flat-\
16282 spread unwrap_or_default() fold (got {:?}, expected {:?})",
16283 view.estrategia(),
16284 RestartStrategy::default(),
16285 );
16286 assert!(
16287 c.estrategia().is_none(),
16288 "Caixa::estrategia() must remain None on the author-omitted \
16289 arm — the supervisor_view fold must not mutate the outer \
16290 flat-spread presence bit",
16291 );
16292 }
16293
16294 #[test]
16295 fn estrategia_projects_option_by_copy() {
16296 // The by-`Copy` pin: [`Caixa::estrategia`] returns
16297 // `Option<RestartStrategy>` by value (`RestartStrategy: Copy`) —
16298 // the accessor does not borrow `&self` past the call (no
16299 // lifetime on the return type), and calling the accessor twice
16300 // on the same [`Caixa`] must yield discriminant-equal values
16301 // (idempotent, no side effects on `&self`). Peer of the sibling
16302 // outer-`Caixa` `Option<&Composite>` by-borrow
16303 // `limits_projects_option_ref_by_borrow` (b2bd9d7) /
16304 // `behavior_projects_option_ref_by_borrow` (35d8b52) /
16305 // `politicas_projects_option_ref_by_borrow` (5d23d29) /
16306 // `placement_projects_option_ref_by_borrow` (4fb8074) /
16307 // `entrada_projects_option_ref_by_borrow` (e4128e4) by-borrow
16308 // pins on the outer-`Caixa` `Option<&Composite>`-return axes —
16309 // extended here to the outer-`Caixa` `Option<Copy>`-return
16310 // flat-spread axis. The `Copy` discipline replaces the pointer-
16311 // equality claim the by-borrow siblings pin (a fresh `Copy` of a
16312 // `Copy` discriminant is definitionally the same discriminant, so
16313 // the axis reduces to discriminant equality).
16314 //
16315 // Pins against a future silent detour that returned a fresh
16316 // `Option<&RestartStrategy>` (which would type-check but silently
16317 // introduce a borrow of `&self` past the call, collapsing the
16318 // load-bearing "no lifetime on the return type" `Copy` projection
16319 // the flat-spread axis's `Option<Copy>` shape carries), a stale-
16320 // read side effect that flipped the outer discriminant on
16321 // successive calls, or an axis-remap projection that returned a
16322 // different variant than the field storage.
16323 use crate::supervisor::RestartStrategy;
16324 for estrategia in [
16325 Some(RestartStrategy::OneForOne),
16326 Some(RestartStrategy::OneForAll),
16327 Some(RestartStrategy::RestForOne),
16328 Some(RestartStrategy::SimpleOneForOne),
16329 ] {
16330 let c = caixa_with_estrategia(estrategia);
16331 let first = c.estrategia();
16332 let second = c.estrategia();
16333 assert_eq!(
16334 first, second,
16335 "Caixa::estrategia must be idempotent — two successive \
16336 calls on the same &self must return the same \
16337 Option<RestartStrategy>",
16338 );
16339 assert_eq!(
16340 first, estrategia,
16341 "Caixa::estrategia must return :estrategia verbatim by \
16342 Copy — got {first:?}, expected {estrategia:?}",
16343 );
16344 }
16345 let c = caixa_with_estrategia(None);
16346 assert!(
16347 c.estrategia().is_none(),
16348 "Caixa::estrategia must return None when :estrategia is \
16349 absent — the author-omitted arm must project through the \
16350 accessor's Option::None unchanged",
16351 );
16352 }
16353
16354 // ── Caixa::max_restarts / Caixa::restart_window —
16355 // outer top-level M2 supervisor-tree-slot flat-spread accessors
16356 // (Option<u32> / Option<&str>) folding on the ed04d3c
16357 // Caixa::estrategia Option<Copy> sub-family ─────────────────────
16358
16359 fn caixa_with_max_restarts(max_restarts: Option<u32>) -> Caixa {
16360 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16361 c.max_restarts = max_restarts;
16362 c
16363 }
16364
16365 fn caixa_supervisor_with_max_restarts_and_window(
16366 max_restarts: Option<u32>,
16367 restart_window: Option<&str>,
16368 ) -> Caixa {
16369 use crate::CaixaKind;
16370 use crate::supervisor::{ChildSpec, RestartPolicy};
16371 let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
16372 c.kind = CaixaKind::Supervisor;
16373 c.max_restarts = max_restarts;
16374 c.restart_window = restart_window.map(str::to_string);
16375 c.children = vec![ChildSpec {
16376 caixa: "worker".into(),
16377 versao: "^0.1".into(),
16378 restart: RestartPolicy::Permanent,
16379 }];
16380 c
16381 }
16382
16383 #[test]
16384 fn max_restarts_returns_max_restarts_option_verbatim_across_permutations() {
16385 // Value-shape pin: [`Caixa::max_restarts`] returns the
16386 // `:max-restarts` typed `Option<u32>` verbatim, `Copy`-projected
16387 // from the typed slot's own storage, byte-equal across the
16388 // author-omitted `None` arm (the "defer to the
16389 // [`Self::supervisor_view`] `unwrap_or(5)` OTP-canonical
16390 // `{intensity, 5, 60}` default" partition every
16391 // non-`Supervisor`-kind caixa carries by `#[serde(default)]`)
16392 // and each of the representative fixtures in the accept-set —
16393 // `0` (the zero-floor arm the peer
16394 // [`crate::supervisor::SupervisorSpec::validate`]
16395 // [`crate::SupervisorError::ZeroMaxRestarts`] gate refuses on
16396 // the post-composition altitude — the accessor must ship the
16397 // raw slot verbatim so struct-literal fixtures continue to
16398 // expose the zero at the accessor boundary), the OTP-canonical
16399 // `5` default (`{intensity, 5, 60}` worker-supervisor from
16400 // Learn You Some Erlang), `1000` (the
16401 // [`SUPERVISOR_MAX_RESTARTS_MAX`] cap the peer post-composition
16402 // upper-bound gate accepts on the boundary), `u32::MAX` (a
16403 // past-the-cap sentinel that the substrate-primitive accessor
16404 // must still ship verbatim). Second outer top-level
16405 // [`Caixa`] `Option<Copy>`-return supervisor-tree flat-spread
16406 // pin — folds on the sibling
16407 // `estrategia_returns_estrategia_option_verbatim_across_permutations`
16408 // (ed04d3c) pin's `Option<Copy>` shape, extending the sub-family
16409 // onto the sibling `Option<u32>` restart-budget-count arm.
16410 let fixtures: Vec<Option<u32>> = vec![None, Some(0), Some(5), Some(1000), Some(u32::MAX)];
16411 for max_restarts in fixtures {
16412 let c = caixa_with_max_restarts(max_restarts);
16413 assert_eq!(
16414 c.max_restarts(),
16415 max_restarts,
16416 "Caixa::max_restarts must return :max-restarts verbatim \
16417 (got {:?}, expected {max_restarts:?})",
16418 c.max_restarts(),
16419 );
16420 assert_eq!(
16421 c.max_restarts(),
16422 c.max_restarts,
16423 "Caixa::max_restarts accessor and self.max_restarts \
16424 field access must byte-equal — a presence-bit or count \
16425 drift would silently split the paired \
16426 Caixa::declared_supervisor_slots presence-probe arm \
16427 from the Caixa::supervisor_view unwrap_or(5) fold's \
16428 composition input",
16429 );
16430 }
16431 }
16432
16433 #[test]
16434 fn max_restarts_projects_option_by_copy() {
16435 // The by-`Copy` pin: [`Caixa::max_restarts`] returns
16436 // `Option<u32>` by value (`u32: Copy`) — the accessor does not
16437 // borrow `&self` past the call (no lifetime on the return type),
16438 // and calling the accessor twice on the same [`Caixa`] must
16439 // yield equal values (idempotent, no side effects). Peer of the
16440 // sibling `estrategia_projects_option_by_copy` (ed04d3c) pin on
16441 // the outer-`Caixa` `Option<Copy>`-return flat-spread axis.
16442 for max_restarts in [Some(0u32), Some(5), Some(1000), Some(u32::MAX), None] {
16443 let c = caixa_with_max_restarts(max_restarts);
16444 let first = c.max_restarts();
16445 let second = c.max_restarts();
16446 assert_eq!(
16447 first, second,
16448 "Caixa::max_restarts must be idempotent — two successive \
16449 calls on the same &self must return the same Option<u32>",
16450 );
16451 assert_eq!(
16452 first, max_restarts,
16453 "Caixa::max_restarts must return :max-restarts verbatim \
16454 by Copy — got {first:?}, expected {max_restarts:?}",
16455 );
16456 }
16457 }
16458
16459 #[test]
16460 fn declared_supervisor_slots_max_restarts_arm_routes_through_accessor() {
16461 // Composition pin: [`Caixa::declared_supervisor_slots`]'s
16462 // `:max-restarts` presence-probe arm must key off
16463 // [`Caixa::max_restarts`], not the raw
16464 // `self.max_restarts.is_some()` field-probe. Structurally: every
16465 // `Caixa { max_restarts: Some(_), .. }` variant must push
16466 // `SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS` onto the declared-slot
16467 // list (the presence bit is `Some` for every representative
16468 // count, so the M2 kind-coherence gate must surface the slot as
16469 // "declared"), and a `Caixa { max_restarts: None, .. }` must
16470 // NOT push the label. Peer of the sibling
16471 // `declared_supervisor_slots_estrategia_arm_routes_through_accessor`
16472 // (ed04d3c) composition pin — same routing-through-accessor
16473 // discipline extended onto the sibling flat-spread `Option<u32>`
16474 // arm.
16475 for max_restarts in [0u32, 5, 1000, u32::MAX] {
16476 let c = caixa_with_max_restarts(Some(max_restarts));
16477 let slots = c.declared_supervisor_slots();
16478 assert!(
16479 slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS),
16480 "declared_supervisor_slots must push \
16481 SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS when `:max-restarts` \
16482 is Some({max_restarts}) — the accessor and the \
16483 enumerator gate must route through the same \
16484 substrate-primitive typed dispatch on the outer \
16485 :max-restarts presence bit (got slots={slots:?})",
16486 );
16487 }
16488 let c = caixa_with_max_restarts(None);
16489 let slots = c.declared_supervisor_slots();
16490 assert!(
16491 !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS),
16492 "declared_supervisor_slots must NOT push \
16493 SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS when `:max-restarts` is \
16494 None — the author-omitted arm must route through the \
16495 accessor's None-return unchanged (got slots={slots:?})",
16496 );
16497 }
16498
16499 #[test]
16500 fn supervisor_view_max_restarts_arm_routes_through_accessor() {
16501 // Composition pin: [`Caixa::supervisor_view`]'s per-`:max-restarts`
16502 // [`SupervisorSpec`] construction arm must key off
16503 // [`Caixa::max_restarts`]'s `unwrap_or(5)` fold, not the raw
16504 // `self.max_restarts.unwrap_or(5)` field-fold. Structurally: for
16505 // every `:kind Supervisor` `Caixa` carrying an author-declared
16506 // `Some(n)`, the composed [`SupervisorSpec`]'s `.max_restarts()`
16507 // must byte-equal `n`; and for a `:kind Supervisor` `Caixa`
16508 // carrying `None`, the composed [`SupervisorSpec`]'s
16509 // `.max_restarts()` must byte-equal the OTP-canonical `5`. Peer
16510 // of the sibling
16511 // `supervisor_view_estrategia_arm_routes_through_accessor`
16512 // (ed04d3c) composition pin.
16513 for max_restarts in [1u32, 5, 1000] {
16514 let c = caixa_supervisor_with_max_restarts_and_window(Some(max_restarts), None);
16515 let view = c.supervisor_view().expect(
16516 "supervisor_view must materialize a SupervisorSpec for a \
16517 :kind Supervisor Caixa carrying a Some(:max-restarts)",
16518 );
16519 assert_eq!(
16520 view.max_restarts(),
16521 max_restarts,
16522 "supervisor_view must carry the outer \
16523 Caixa::max_restarts() Some arm onto the composed \
16524 SupervisorSpec.max_restarts field verbatim (got {}, \
16525 expected {max_restarts})",
16526 view.max_restarts(),
16527 );
16528 }
16529 let c = caixa_supervisor_with_max_restarts_and_window(None, None);
16530 let view = c.supervisor_view().expect(
16531 "supervisor_view must materialize a SupervisorSpec for a \
16532 :kind Supervisor Caixa carrying a None :max-restarts",
16533 );
16534 assert_eq!(
16535 view.max_restarts(),
16536 5,
16537 "supervisor_view must project the outer \
16538 Caixa::max_restarts() None arm onto the OTP-canonical \
16539 {{intensity, 5, 60}} default (5) through the flat-spread \
16540 unwrap_or(5) fold (got {})",
16541 view.max_restarts(),
16542 );
16543 assert!(
16544 c.max_restarts().is_none(),
16545 "Caixa::max_restarts() must remain None on the author-\
16546 omitted arm — the supervisor_view fold must not mutate \
16547 the outer flat-spread presence bit",
16548 );
16549 }
16550
16551 #[test]
16552 fn supervisor_view_estrategia_fallback_routes_through_lifted_default() {
16553 // Composition pin: [`Caixa::supervisor_view`]'s author-omitted
16554 // `:estrategia` arm must degrade onto the substrate-canonical
16555 // [`crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT`] typed
16556 // `pub const` — the Erlang/OTP-canonical `one_for_one` strategy
16557 // half of Learn You Some Erlang's `{one_for_one, intensity, 5, 60}`
16558 // worker-supervisor default — rather than the transitively-
16559 // derived [`crate::supervisor::RestartStrategy::default`] route
16560 // the prior `.unwrap_or_default()` fold reached for. Prior to the
16561 // lift the composition site carried `.unwrap_or_default()` with
16562 // no compile-time link back to the shared OTP-canonical strategy
16563 // default that the paired [`crate::supervisor::Default for
16564 // RestartStrategy`] impl and the [`crate::supervisor::Default for
16565 // SupervisorSpec`] impl's struct-literal `estrategia` field both
16566 // (now) route through the same lifted constant — so a future
16567 // rebrand of the OTP-canonical strategy default (an OTP
16568 // `rest_for_one` widening once the substrate discovers startup-
16569 // order-coupled child cohorts as the more common worker-
16570 // supervisor shape, a per-cluster overlay the operator pins
16571 // through the MESH-COMPOSITION §III.2 supervision-canary
16572 // `:estrategia-overrides` roadmap slot) would have had to migrate
16573 // the paired `MaxIntensity` + `Period` halves through the lifted
16574 // constants and the `one_for_one` half through a
16575 // `RestartStrategy::default()` route in lockstep or a
16576 // `:kind Supervisor` caixa carrying an author-omitted
16577 // `:estrategia` slot would silently resolve to a `SupervisorSpec`
16578 // whose `estrategia` disagreed with the paired
16579 // `SupervisorSpec::default()` view. Byte-parity against the
16580 // lifted constant closes the split. Peer of the sibling
16581 // [`supervisor_view_max_restarts_fallback_routes_through_lifted_default`]
16582 // composition pin on the paired `MaxIntensity` half + the
16583 // [`crate::supervisor::restart_strategy_default_routes_through_lifted_default`]
16584 // + [`crate::supervisor::supervisor_spec_default_estrategia_routes_through_lifted_default`]
16585 // pins on the sibling entry points onto the shared substrate
16586 // constant.
16587 use crate::CaixaKind;
16588 use crate::supervisor::{ChildSpec, RestartPolicy};
16589 let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
16590 c.kind = CaixaKind::Supervisor;
16591 c.estrategia = None;
16592 c.children = vec![ChildSpec {
16593 caixa: "worker".into(),
16594 versao: "^0.1".into(),
16595 restart: RestartPolicy::Permanent,
16596 }];
16597 let view = c.supervisor_view().expect(
16598 "supervisor_view must materialize a SupervisorSpec for a \
16599 :kind Supervisor Caixa carrying a None :estrategia",
16600 );
16601 assert_eq!(
16602 view.estrategia(),
16603 crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT,
16604 "supervisor_view must degrade the outer \
16605 Caixa::estrategia() None arm onto the lifted \
16606 SUPERVISOR_ESTRATEGIA_DEFAULT typed pub const (got {:?}, \
16607 expected {:?})",
16608 view.estrategia(),
16609 crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT,
16610 );
16611 }
16612
16613 #[test]
16614 fn supervisor_view_max_restarts_fallback_routes_through_lifted_default() {
16615 // Composition pin: [`Caixa::supervisor_view`]'s author-omitted
16616 // `:max-restarts` arm must degrade onto the substrate-canonical
16617 // [`crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT`] typed
16618 // `pub const` — the Erlang/OTP-canonical `{intensity, 5, 60}`
16619 // `MaxIntensity` default — rather than a raw `5` literal. Prior
16620 // to the lift the composition site carried an inline
16621 // `.unwrap_or(5)` with no compile-time link back to the shared
16622 // OTP-canonical default that the serde-side
16623 // `#[serde(default = "default_max_restarts")]` wire-format arm
16624 // and the [`Default for crate::supervisor::SupervisorSpec`]
16625 // struct-literal default arm both key off — so a future rebrand
16626 // of the OTP-canonical default (Elixir's `Supervisor` `3`
16627 // default, a per-cluster overlay the operator pins through the
16628 // MESH-COMPOSITION §III.2 supervision-canary
16629 // `:supervisor :max-restarts-overrides` roadmap slot) would
16630 // have had to be threaded through both the serde-side helper
16631 // and this view-construction arm in lockstep or a `:kind
16632 // Supervisor` caixa carrying `:max-restarts ()` would silently
16633 // resolve to a `SupervisorSpec` whose `max_restarts` disagreed
16634 // with the same fixture's serde-side `SupervisorSpec` view (an
16635 // author-omitted slot round-tripping through
16636 // `SupervisorSpec::default()` to the lifted constant, then
16637 // splitting to a stale literal past `supervisor_view`).
16638 // Byte-parity against the lifted constant closes the split.
16639 // Peer of the sibling
16640 // [`crate::supervisor::default_max_restarts_helper_routes_through_lifted_default`]
16641 // + [`crate::supervisor::supervisor_spec_default_max_restarts_routes_through_lifted_default`]
16642 // composition pins that close the same routing on the two
16643 // sibling entry points onto the shared substrate constant.
16644 let c = caixa_supervisor_with_max_restarts_and_window(None, None);
16645 let view = c.supervisor_view().expect(
16646 "supervisor_view must materialize a SupervisorSpec for a \
16647 :kind Supervisor Caixa carrying a None :max-restarts",
16648 );
16649 assert_eq!(
16650 view.max_restarts(),
16651 crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT,
16652 "supervisor_view must degrade the outer \
16653 Caixa::max_restarts() None arm onto the lifted \
16654 SUPERVISOR_MAX_RESTARTS_DEFAULT typed pub const (got {}, \
16655 expected {})",
16656 view.max_restarts(),
16657 crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT,
16658 );
16659 }
16660
16661 #[test]
16662 fn restart_window_returns_restart_window_option_verbatim_across_permutations() {
16663 // Value-shape pin: [`Caixa::restart_window`] returns the
16664 // `:restart-window` typed `Option<String>` verbatim as an
16665 // `Option<&str>`, borrowed from the typed slot's own storage,
16666 // byte-equal across the author-omitted `None` arm and each of
16667 // the representative fixtures in the accept-set — the canonical
16668 // `"60s"` from `{intensity, 5, 60}`, the sibling
16669 // canonical-magnitude forms (`"5m"` / `"1h"` / `"500ms"` / `"30"`
16670 // / `"0s"`) the shared codec's positive-set sweep pin covers,
16671 // plus a past-the-guard sentinel (`"1.5s"` — the fractional-
16672 // seconds drift the sibling [`Self::validate_restart_window`]
16673 // gate refuses; the accessor must ship the raw slot verbatim
16674 // so struct-literal fixtures continue to expose the drift at
16675 // the accessor boundary). Third outer top-level [`Caixa`]
16676 // supervisor-tree flat-spread pin — extends the sub-family onto
16677 // the sibling `Option<&str>` raw-duration-string arm.
16678 for window in [
16679 None,
16680 Some("60s"),
16681 Some("5m"),
16682 Some("1h"),
16683 Some("500ms"),
16684 Some("1.5s"),
16685 Some(""),
16686 ] {
16687 let c = caixa_with_restart_window(window);
16688 assert_eq!(
16689 c.restart_window(),
16690 window,
16691 "Caixa::restart_window must return :restart-window \
16692 verbatim as Option<&str> (got {:?}, expected {window:?})",
16693 c.restart_window(),
16694 );
16695 assert_eq!(
16696 c.restart_window(),
16697 c.restart_window.as_deref(),
16698 "Caixa::restart_window accessor and \
16699 self.restart_window.as_deref() field access must \
16700 byte-equal — a byte-level drift would silently split \
16701 the paired Caixa::declared_supervisor_slots \
16702 presence-probe arm from the \
16703 Caixa::validate_restart_window shared-codec gate and \
16704 the Caixa::supervisor_view soft-swallowing fold",
16705 );
16706 }
16707 }
16708
16709 #[test]
16710 fn restart_window_projects_slice_by_borrow() {
16711 // The by-borrow pin: [`Caixa::restart_window`] returns
16712 // `Option<&str>` by borrow — the returned string slice borrows
16713 // the underlying `Option<String>` storage of the `:restart-window`
16714 // slot and the accessor must not clone on every call. Peer of
16715 // the sibling outer top-level [`Caixa`] `Option<&str>`-return
16716 // by-borrow pins on the universal-axis scalar family
16717 // (`licenca_projects_option_ref_by_borrow` /
16718 // `descricao_projects_option_ref_by_borrow` and siblings) —
16719 // extended onto the M2 supervisor-tree flat-spread
16720 // `Option<&str>` raw-duration-string axis.
16721 for window in [None, Some("60s"), Some("5m"), Some("")] {
16722 let c = caixa_with_restart_window(window);
16723 let first = c.restart_window();
16724 let second = c.restart_window();
16725 assert_eq!(
16726 first, second,
16727 "Caixa::restart_window must be idempotent — two \
16728 successive calls on the same &self must return the \
16729 same Option<&str>",
16730 );
16731 if let (Some(a), Some(b)) = (first, second) {
16732 assert_eq!(
16733 a.as_ptr(),
16734 b.as_ptr(),
16735 "Caixa::restart_window must borrow the underlying \
16736 String storage — two successive Some-arm calls must \
16737 return slices with the same backing pointer (a fresh \
16738 String clone would change the pointer on every call)",
16739 );
16740 }
16741 assert_eq!(
16742 first, window,
16743 "Caixa::restart_window must return :restart-window \
16744 verbatim by borrow — got {first:?}, expected {window:?}",
16745 );
16746 }
16747 }
16748
16749 #[test]
16750 fn declared_supervisor_slots_restart_window_arm_routes_through_accessor() {
16751 // Composition pin: [`Caixa::declared_supervisor_slots`]'s
16752 // `:restart-window` presence-probe arm must key off
16753 // [`Caixa::restart_window`], not the raw
16754 // `self.restart_window.is_some()` field-probe. Structurally:
16755 // every `Caixa { restart_window: Some(_), .. }` must push
16756 // `SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW` onto the declared-slot
16757 // list, and a `Caixa { restart_window: None, .. }` must NOT
16758 // push the label. Peer of the sibling
16759 // `declared_supervisor_slots_max_restarts_arm_routes_through_accessor`
16760 // routing pin.
16761 for window in ["60s", "5m", "1h", "500ms", "1.5s", ""] {
16762 let c = caixa_with_restart_window(Some(window));
16763 let slots = c.declared_supervisor_slots();
16764 assert!(
16765 slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW),
16766 "declared_supervisor_slots must push \
16767 SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW when \
16768 `:restart-window` is Some({window:?}) — the accessor \
16769 and the enumerator gate must route through the same \
16770 substrate-primitive typed dispatch on the outer \
16771 :restart-window presence bit (got slots={slots:?})",
16772 );
16773 }
16774 let c = caixa_with_restart_window(None);
16775 let slots = c.declared_supervisor_slots();
16776 assert!(
16777 !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW),
16778 "declared_supervisor_slots must NOT push \
16779 SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW when `:restart-window` \
16780 is None — the author-omitted arm must route through the \
16781 accessor's None-return unchanged (got slots={slots:?})",
16782 );
16783 }
16784
16785 #[test]
16786 fn validate_restart_window_arm_routes_through_accessor() {
16787 // Composition pin: [`Caixa::validate_restart_window`]'s
16788 // shared-codec fold arm must key off [`Caixa::restart_window`],
16789 // not the raw `self.restart_window.as_deref()` field-projection.
16790 // Structurally: (1) `None` → `Ok(())` (the "omit the slot to
16791 // express no reset" canonical shape); (2) a canonical `Some`
16792 // arm (`"60s"`) → `Ok(())`; (3) a codec-rejected `Some` arm
16793 // (`"1.5s"`) → `Err(RestartWindowMalformed { restart_window,
16794 // .. })` carrying the offending raw string verbatim. The three
16795 // arms jointly pin that the validator's raw-string binding is
16796 // the accessor's return, not a peer projection — any future
16797 // silent detour that had the accessor collapse `Some("")` to
16798 // `None` would silently absorb the empty-after-trim refusal
16799 // case at the accessor boundary.
16800 caixa_with_restart_window(None)
16801 .validate_restart_window()
16802 .expect("None :restart-window must validate through the accessor");
16803 caixa_with_restart_window(Some("60s"))
16804 .validate_restart_window()
16805 .expect("canonical :restart-window \"60s\" must validate through the accessor");
16806 let err = caixa_with_restart_window(Some("1.5s"))
16807 .validate_restart_window()
16808 .expect_err("fractional-seconds :restart-window must fail through the accessor");
16809 assert!(
16810 matches!(
16811 err,
16812 ManifestError::RestartWindowMalformed { ref restart_window, .. }
16813 if restart_window == "1.5s"
16814 ),
16815 "validator must carry the offending raw string verbatim \
16816 from the accessor's borrowed &str (got {err:?})",
16817 );
16818 }
16819
16820 #[test]
16821 fn supervisor_view_restart_window_arm_routes_through_accessor() {
16822 // Composition pin: [`Caixa::supervisor_view`]'s
16823 // per-`:restart-window` [`SupervisorSpec`] construction arm
16824 // must key off [`Caixa::restart_window`]'s soft-swallowing
16825 // `.and_then(|s| duration_codec::parse(s).ok())` fold, not the
16826 // raw `self.restart_window.as_deref().and_then(…)` field-fold.
16827 // Structurally: (1) `None` → `SupervisorSpec.restart_window ==
16828 // None` (the "never reset" sentinel); (2) canonical `Some("60s")`
16829 // → `SupervisorSpec.restart_window == Some(Duration::from_secs(60))`
16830 // (the shared codec's canonical parse); (3) codec-rejected
16831 // `Some("1.5s")` → `SupervisorSpec.restart_window == None`
16832 // (the soft-swallow preserving the view's best-effort shape).
16833 let c = caixa_supervisor_with_max_restarts_and_window(None, None);
16834 let view = c.supervisor_view().expect("Supervisor kind has a view");
16835 assert_eq!(
16836 view.restart_window(),
16837 None,
16838 "supervisor_view must project outer None :restart-window \
16839 onto None on the composed SupervisorSpec (never-reset \
16840 sentinel) through the accessor's None-return unchanged",
16841 );
16842
16843 let c = caixa_supervisor_with_max_restarts_and_window(None, Some("60s"));
16844 let view = c.supervisor_view().expect("Supervisor kind has a view");
16845 assert_eq!(
16846 view.restart_window(),
16847 Some(std::time::Duration::from_secs(60)),
16848 "supervisor_view must fold outer Some(\"60s\") through the \
16849 shared duration_codec into Duration::from_secs(60) on the \
16850 composed SupervisorSpec (accessor's Some(&str) → codec \
16851 parse → Some(Duration))",
16852 );
16853
16854 let c = caixa_supervisor_with_max_restarts_and_window(None, Some("1.5s"));
16855 let view = c.supervisor_view().expect("Supervisor kind has a view");
16856 assert_eq!(
16857 view.restart_window(),
16858 None,
16859 "supervisor_view must soft-swallow the shared-codec parse \
16860 failure to None (the view's best-effort shape the sibling \
16861 manifest-level validate_restart_window surfaces as \
16862 RestartWindowMalformed); the accessor's raw-string return \
16863 is the single input every downstream consumer keys off",
16864 );
16865 }
16866
16867 // ── Caixa::upgrade_from — outer top-level &[UpgradeFromEntry] composite-slice accessor ──
16868
16869 fn caixa_with_upgrade_from(upgrade_from: Vec<crate::upgrade::UpgradeFromEntry>) -> Caixa {
16870 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16871 c.upgrade_from = upgrade_from;
16872 c
16873 }
16874
16875 #[test]
16876 fn upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations() {
16877 // The canonical per-`Caixa` `:upgrade-from` M2 typed-slot
16878 // outer-composite `&[UpgradeFromEntry]`-return slice-shape
16879 // pin: [`Caixa::upgrade_from`] must return the `:upgrade-from`
16880 // typed `Vec<UpgradeFromEntry>` verbatim as a
16881 // `&[UpgradeFromEntry]` slice-view over the same backing
16882 // buffer the raw `self.upgrade_from.as_slice()` field access
16883 // borrows from, element-equal across every representative
16884 // fixture in the accept-set — `[]` (the "no hot-upgrade path
16885 // declared" arm every `defcaixa` without an `:upgrade-from`
16886 // block carries; `#[serde(default)]` folds an omitted slot
16887 // onto `Vec::new()`), a canonical single-entry `Restart`
16888 // fixture (the shape most Servicos carry — a single prior
16889 // version with the fallback strategy), a canonical multi-
16890 // entry list carrying every typed instruction variant
16891 // (`LoadModule` / `StateChange` / `SoftPurge` / `Purge` /
16892 // `Restart`), and a past-the-guard sentinel — a duplicate-
16893 // `:from` `[(0.1.0, Restart), (0.1.0, Restart)]` entry pair
16894 // ([`crate::upgrade::validate_upgrade_from`] rejects through
16895 // `DuplicateFrom { from: "0.1.0" }` but the accessor must
16896 // ship the raw slot verbatim so struct-literal fixtures
16897 // continue to expose the duplicate at the accessor boundary).
16898 //
16899 // Pins against a future silent detour that returned an owned
16900 // `Vec<UpgradeFromEntry>` (which would type-check but silently
16901 // clone on every accessor call, breaking the zero-cost
16902 // projection every peer sibling slice accessor carries), a
16903 // `[dup, dup] → [dup]` dedup collapse (which would silently
16904 // absorb the `DuplicateFrom` refusal case at the accessor
16905 // boundary and the [`crate::StandardLayout::verify`] cross-
16906 // entry gate would silently accept a struct-literal `Caixa`
16907 // carrying the drift), a reference to an operator-resolved
16908 // overlay (the future per-cluster `:upgrade-overrides` slot
16909 // — its resolution must land at exactly this accessor body,
16910 // not silently divert the raw slot away from a second
16911 // consumer), or an axis-shuffled projection (a future detour
16912 // that reordered entries through the accessor would silently
16913 // split the paired [`crate::StandardLayout::verify`] per-
16914 // `:upgrade-from` shape gate's traversal input from the peer
16915 // [`crate::render::servico_m2_overlay`] emitter's projection
16916 // input, since the operator's hot-upgrade dispatch matches
16917 // per-`:from` and axis reordering would silently split the
16918 // per-entry script-path existence probe's iteration order
16919 // from the M2 overlay emitter's serialized-entry order).
16920 //
16921 // First outer top-level [`Caixa`] `&[Composite]`-return
16922 // slice accessor pin on the substrate primitive for M2 / M3
16923 // typed-slot vec-carry axes — opens the outer-`Caixa`
16924 // `&[Composite]` composite-slice projection pattern the
16925 // sibling `:children` [`crate::supervisor::ChildSpec`] /
16926 // `:membros` [`crate::aplicacao::Membro`] / `:contratos`
16927 // [`crate::aplicacao::WitContract`] future outer-composite-
16928 // slice pins fold on. Peer of the closed outer-`Caixa`
16929 // scalar `Option<&Composite>` composite-reference family the
16930 // sibling `limits` / `behavior` / `politicas` / `placement`
16931 // / `entrada` `..._returns_..._option_ref_verbatim_across_
16932 // permutations` pins closed (b2bd9d7 → e4128e4) — extends
16933 // the "byte-equal, borrow-shared" outer-accessor discipline
16934 // onto the outer-`Caixa` `&[Composite]` vec-carry altitude.
16935 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
16936 let fixtures: Vec<Vec<UpgradeFromEntry>> = vec![
16937 vec![],
16938 vec![UpgradeFromEntry {
16939 from: "0.0.1".into(),
16940 instructions: vec![UpgradeInstruction::Restart],
16941 }],
16942 vec![
16943 UpgradeFromEntry {
16944 from: "0.0.1".into(),
16945 instructions: vec![
16946 UpgradeInstruction::LoadModule {
16947 module: "demo".into(),
16948 },
16949 UpgradeInstruction::SoftPurge {
16950 module: "demo".into(),
16951 },
16952 ],
16953 },
16954 UpgradeFromEntry {
16955 from: "0.0.2".into(),
16956 instructions: vec![
16957 UpgradeInstruction::StateChange {
16958 script: "servicos/upgrade.lisp".into(),
16959 },
16960 UpgradeInstruction::Purge {
16961 module: "demo".into(),
16962 },
16963 UpgradeInstruction::Restart,
16964 ],
16965 },
16966 ],
16967 vec![
16968 UpgradeFromEntry {
16969 from: "0.1.0".into(),
16970 instructions: vec![UpgradeInstruction::Restart],
16971 },
16972 UpgradeFromEntry {
16973 from: "0.1.0".into(),
16974 instructions: vec![UpgradeInstruction::Restart],
16975 },
16976 ],
16977 ];
16978 for upgrade_from in fixtures {
16979 let c = caixa_with_upgrade_from(upgrade_from.clone());
16980 assert_eq!(
16981 c.upgrade_from(),
16982 upgrade_from.as_slice(),
16983 "Caixa::upgrade_from must return :upgrade-from \
16984 verbatim (got {:?}, expected {upgrade_from:?})",
16985 c.upgrade_from(),
16986 );
16987 assert_eq!(
16988 c.upgrade_from(),
16989 c.upgrade_from.as_slice(),
16990 "Caixa::upgrade_from must element-equal the raw \
16991 `self.upgrade_from.as_slice()` field access across \
16992 every value in the Vec<UpgradeFromEntry> accept-set",
16993 );
16994 assert_eq!(
16995 c.upgrade_from().is_empty(),
16996 c.upgrade_from.is_empty(),
16997 "Caixa::upgrade_from().is_empty() must byte-equal \
16998 self.upgrade_from.is_empty() — a presence-bit drift \
16999 would silently split the paired \
17000 Caixa::declared_servico_slots M2 declared-slot \
17001 enumerator's presence probe from the peer \
17002 crate::render::servico_m2_overlay M2 overlay \
17003 emitter's presence gate",
17004 );
17005 }
17006 }
17007
17008 #[test]
17009 fn declared_servico_slots_upgrade_from_arm_routes_through_accessor() {
17010 // Composition pin: [`Caixa::declared_servico_slots`]'s
17011 // `:upgrade-from` presence-probe arm must key off
17012 // [`Caixa::upgrade_from`], not the raw
17013 // `self.upgrade_from.is_empty()` field-probe. Structurally: a
17014 // `Caixa { upgrade_from: vec![UpgradeFromEntry { from: "0.0.1",
17015 // instructions: vec![Restart] }], .. }` must push
17016 // `M2_AUTHOR_KEY_UPGRADE_FROM` onto the declared-slot list
17017 // (the presence bit is non-empty, so the M2 kind-coherence
17018 // gate must surface the slot as "declared"), and a `Caixa {
17019 // upgrade_from: vec![], .. }` must NOT push the label (the
17020 // "author omitted the slot entirely" arm — the empty-slice
17021 // partition the serde-default folds onto). The pair jointly
17022 // pins the accessor + declared-slot enumerator composition:
17023 // any future silent detour that had the accessor collapse
17024 // `[Restart]` to `[]` (a `.filter(|e| !e.instructions.
17025 // is_empty())` projection) would silently absorb the
17026 // "declared but degenerate" arm at the accessor boundary and
17027 // the [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-
17028 // coherence gate would silently accept a struct-literal
17029 // `Caixa` carrying the drift.
17030 //
17031 // Peer of the sibling
17032 // `declared_servico_slots_limits_arm_routes_through_accessor`
17033 // (b2bd9d7) and
17034 // `declared_servico_slots_behavior_arm_routes_through_accessor`
17035 // (35d8b52) composition pins on the sibling `:limits` /
17036 // `:behavior` outer-`Option<&Composite>` arms — same "the
17037 // enumerator gate must route through the substrate-primitive
17038 // typed dispatch" discipline extended onto the third M2
17039 // Servico-runtime slot axis, closing the enumerator's routing
17040 // invariant on every M2 arm.
17041 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
17042 let c = caixa_with_upgrade_from(vec![UpgradeFromEntry {
17043 from: "0.0.1".into(),
17044 instructions: vec![UpgradeInstruction::Restart],
17045 }]);
17046 let slots = c.declared_servico_slots();
17047 assert!(
17048 slots.contains(&crate::render::M2_AUTHOR_KEY_UPGRADE_FROM),
17049 "declared_servico_slots must push \
17050 M2_AUTHOR_KEY_UPGRADE_FROM when `:upgrade-from` is \
17051 non-empty — the accessor and the enumerator gate must \
17052 route through the same substrate-primitive typed \
17053 dispatch on the outer :upgrade-from presence bit (got \
17054 slots={slots:?})",
17055 );
17056 let c = caixa_with_upgrade_from(vec![]);
17057 let slots = c.declared_servico_slots();
17058 assert!(
17059 !slots.contains(&crate::render::M2_AUTHOR_KEY_UPGRADE_FROM),
17060 "declared_servico_slots must NOT push \
17061 M2_AUTHOR_KEY_UPGRADE_FROM when `:upgrade-from` is \
17062 empty — the author-omitted arm must route through the \
17063 accessor's empty-slice return unchanged (got \
17064 slots={slots:?})",
17065 );
17066 }
17067
17068 #[test]
17069 fn servico_m2_overlay_upgrade_from_arm_routes_through_accessor() {
17070 // Composition pin: [`crate::render::servico_m2_overlay`]'s
17071 // per-`:upgrade-from` M2 overlay emit arm must key off
17072 // [`Caixa::upgrade_from`], not the raw
17073 // `!caixa.upgrade_from.is_empty()` presence gate + the
17074 // `serde_yaml::to_value(&caixa.upgrade_from)` projection.
17075 // Structurally: a `Caixa { upgrade_from: vec![UpgradeFromEntry
17076 // { from: "0.0.1", instructions: vec![Restart] }], .. }` must
17077 // surface the `M2_KEY_UPGRADE_FROM` key with a per-entry
17078 // sequence in the overlay (the emitter fans onto the serde
17079 // slice-serialization), and a `Caixa { upgrade_from: vec![],
17080 // .. }` must omit the key entirely (the empty-slice
17081 // partition — the `!.is_empty()` outer gate elides the key
17082 // when the author omitted the slot). The pair jointly pins
17083 // the accessor + M2 overlay emitter composition: any future
17084 // silent detour that had the accessor return a fresh-cloned
17085 // `Vec<UpgradeFromEntry>` copy would silently break the
17086 // reference-identity pin the peer per-entry
17087 // `serde_yaml::to_value(caixa.upgrade_from())` projection
17088 // reads from — the projection would clone once per accessor
17089 // call instead of borrowing the storage buffer verbatim.
17090 //
17091 // Peer of the sibling
17092 // `servico_m2_overlay_limits_arm_routes_through_accessor`
17093 // (b2bd9d7) and
17094 // `servico_m2_overlay_behavior_arm_routes_through_accessor`
17095 // (35d8b52) composition pins on the sibling `:limits` /
17096 // `:behavior` outer-`Option<&Composite>` arms — same "the
17097 // M2 overlay emitter must route through the substrate-
17098 // primitive typed dispatch" discipline extended onto the
17099 // third M2 Servico-runtime slot axis, closing the overlay
17100 // emitter's routing invariant on every M2 arm.
17101 use crate::render::{M2_KEY_UPGRADE_FROM, servico_m2_overlay};
17102 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
17103 let c = caixa_with_upgrade_from(vec![UpgradeFromEntry {
17104 from: "0.0.1".into(),
17105 instructions: vec![UpgradeInstruction::Restart],
17106 }]);
17107 let overlay = servico_m2_overlay(&c).unwrap();
17108 assert!(
17109 overlay.contains_key(M2_KEY_UPGRADE_FROM),
17110 "servico_m2_overlay must surface M2_KEY_UPGRADE_FROM when \
17111 `:upgrade-from` is non-empty — the accessor and the M2 \
17112 overlay emitter must route through the same substrate- \
17113 primitive typed dispatch on the outer :upgrade-from \
17114 slice (got overlay={overlay:?})",
17115 );
17116 let c = caixa_with_upgrade_from(vec![]);
17117 let overlay = servico_m2_overlay(&c).unwrap();
17118 assert!(
17119 !overlay.contains_key(M2_KEY_UPGRADE_FROM),
17120 "servico_m2_overlay must omit M2_KEY_UPGRADE_FROM when \
17121 `:upgrade-from` is empty — the empty-slice partition \
17122 must route through the accessor's empty-slice return \
17123 unchanged (got overlay={overlay:?})",
17124 );
17125 }
17126
17127 #[test]
17128 fn upgrade_from_projects_slice_by_borrow() {
17129 // The by-borrow pin: [`Caixa::upgrade_from`] returns
17130 // `&[UpgradeFromEntry]` by borrow — the returned slice
17131 // borrows the underlying `Vec<UpgradeFromEntry>` storage of
17132 // the `:upgrade-from` slot and the accessor must not clone
17133 // the backing `Vec` on every call. Peer of the sibling
17134 // outer top-level [`Caixa`] `&[T]`-return by-borrow pins
17135 // (`autores_projects_slice_by_borrow` b5d813f,
17136 // `etiquetas_projects_slice_by_borrow` 78c7d3c,
17137 // `bibliotecas_projects_slice_by_borrow` 8a36c23,
17138 // `exe_projects_slice_by_borrow` 65d9527,
17139 // `servicos_projects_slice_by_borrow` 611f78b,
17140 // `deps_projects_slice_by_borrow` ad34b4e,
17141 // `deps_dev_projects_slice_by_borrow` f7fd81e) on the
17142 // sibling outer top-level [`Caixa`] scalar-element `&[T]`
17143 // axes — extended here to the first outer-`Caixa`
17144 // composite-element `&[Composite]` axis: the accessor's
17145 // returned slice must borrow from `&self` (the returned
17146 // reference's lifetime is tied to `&self`), and calling the
17147 // accessor twice on the same [`Caixa`] must yield slices
17148 // that are pointer-equal (the underlying byte-buffer is the
17149 // storage `Vec`'s allocation, not a fresh copy) as well as
17150 // value-equal (idempotent, no side effects on `&self`).
17151 //
17152 // Pins against a future silent detour that returned an owned
17153 // `Vec<UpgradeFromEntry>` (which would type-check but
17154 // silently clone on every call), a `&Vec<UpgradeFromEntry>`
17155 // return (which would leak the backing `Vec`'s
17156 // grow/push/reserve surface no downstream consumer reaches
17157 // for), or a one-arm-only accessor that returned a
17158 // saturating value on some sentinel input.
17159 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
17160 for upgrade_from in [
17161 vec![],
17162 vec![UpgradeFromEntry {
17163 from: "0.0.1".into(),
17164 instructions: vec![UpgradeInstruction::Restart],
17165 }],
17166 vec![
17167 UpgradeFromEntry {
17168 from: "0.0.1".into(),
17169 instructions: vec![UpgradeInstruction::Restart],
17170 },
17171 UpgradeFromEntry {
17172 from: "0.0.2".into(),
17173 instructions: vec![UpgradeInstruction::SoftPurge {
17174 module: "demo".into(),
17175 }],
17176 },
17177 ],
17178 ] {
17179 let c = caixa_with_upgrade_from(upgrade_from.clone());
17180 let first = c.upgrade_from();
17181 let second = c.upgrade_from();
17182 assert_eq!(
17183 first, second,
17184 "Caixa::upgrade_from must be idempotent — two \
17185 successive calls on the same &self must return the \
17186 same &[UpgradeFromEntry]",
17187 );
17188 assert_eq!(
17189 first.as_ptr(),
17190 second.as_ptr(),
17191 "Caixa::upgrade_from must borrow the underlying \
17192 Vec<UpgradeFromEntry> storage — two successive calls \
17193 must return slices with the same backing pointer (a \
17194 fresh Vec<UpgradeFromEntry> clone would change the \
17195 pointer on every call)",
17196 );
17197 assert_eq!(
17198 first,
17199 upgrade_from.as_slice(),
17200 "Caixa::upgrade_from must return :upgrade-from \
17201 verbatim by borrow — got {first:?}, expected \
17202 {upgrade_from:?}",
17203 );
17204 }
17205 }
17206
17207 // ── Caixa::children — outer top-level &[ChildSpec] composite-slice accessor ──
17208
17209 fn caixa_with_children(children: Vec<crate::supervisor::ChildSpec>) -> Caixa {
17210 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
17211 c.children = children;
17212 c
17213 }
17214
17215 #[test]
17216 fn children_returns_children_slice_verbatim_across_permutations() {
17217 // The canonical per-`Caixa` `:children` M2 supervisor-tree-slot
17218 // outer-composite `&[ChildSpec]`-return slice-shape pin:
17219 // [`Caixa::children`] must return the `:children` typed
17220 // `Vec<ChildSpec>` verbatim as a `&[ChildSpec]` slice-view over
17221 // the same backing buffer the raw `self.children.as_slice()`
17222 // field access borrows from, element-equal across every
17223 // representative fixture in the accept-set — `[]` (the "no
17224 // static children declared" arm every non-`Supervisor`-kind
17225 // `defcaixa` carries by `#[serde(default)]` and every
17226 // `SimpleOneForOne` supervisor carries by cross-slot refusal),
17227 // a canonical single-child `Permanent` fixture (the shape
17228 // most `OneForOne` supervisors carry — a single long-running
17229 // worker child), a canonical multi-child list carrying every
17230 // typed restart-policy variant (`Permanent` / `Transient` /
17231 // `Temporary`), and a past-the-guard sentinel — a duplicate
17232 // `:caixa` `[("w", ...), ("w", ...)]` entry pair
17233 // ([`crate::SupervisorSpec::validate`] rejects through
17234 // `DuplicateChildNome { nome: "w" }` but the accessor must
17235 // ship the raw slot verbatim so struct-literal fixtures
17236 // continue to expose the duplicate at the accessor boundary).
17237 //
17238 // Pins against a future silent detour that returned an owned
17239 // `Vec<ChildSpec>` (which would type-check but silently clone
17240 // on every accessor call, breaking the zero-cost projection
17241 // every peer sibling slice accessor carries), a `[dup, dup] →
17242 // [dup]` dedup collapse (which would silently absorb the
17243 // `DuplicateChildNome` refusal case at the accessor boundary
17244 // and the [`crate::StandardLayout::verify`] cross-child gate
17245 // would silently accept a struct-literal `Caixa` carrying the
17246 // drift), a reference to an operator-resolved overlay (the
17247 // future per-cluster `:children-overrides` slot — its
17248 // resolution must land at exactly this accessor body, not
17249 // silently divert the raw slot away from a second consumer),
17250 // or an axis-shuffled projection (a future detour that
17251 // reordered children through the accessor would silently
17252 // split the paired [`crate::StandardLayout::verify`] per-
17253 // supervisor gate's traversal input from the peer
17254 // [`Self::supervisor_view`] fold-in path's clone-order input,
17255 // since the OTP `RestForOne` restart strategy dispatches on
17256 // declared child order and axis reordering would silently
17257 // split the operator's per-cluster restart-fan-out order
17258 // from the caixa.lisp source-order).
17259 //
17260 // Second outer top-level [`Caixa`] `&[Composite]`-return slice
17261 // accessor pin on the substrate primitive for M2 / M3 typed-
17262 // slot vec-carry axes — folds on the outer-`Caixa`
17263 // `&[Composite]` composite-slice sub-family the sibling
17264 // `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
17265 // (2a1f907) pin opened, peer at the outer altitude of the
17266 // closed inner-`SupervisorSpec` `SupervisorSpec::children`
17267 // (bc92bce) accessor on the same OTP-supervisor static-child-
17268 // list axis.
17269 use crate::supervisor::{ChildSpec, RestartPolicy};
17270 let fixtures: Vec<Vec<ChildSpec>> = vec![
17271 vec![],
17272 vec![ChildSpec {
17273 caixa: "worker".into(),
17274 versao: "^0.1".into(),
17275 restart: RestartPolicy::Permanent,
17276 }],
17277 vec![
17278 ChildSpec {
17279 caixa: "worker-a".into(),
17280 versao: "^0.1".into(),
17281 restart: RestartPolicy::Permanent,
17282 },
17283 ChildSpec {
17284 caixa: "worker-b".into(),
17285 versao: "^0.1".into(),
17286 restart: RestartPolicy::Transient,
17287 },
17288 ChildSpec {
17289 caixa: "worker-c".into(),
17290 versao: "^0.1".into(),
17291 restart: RestartPolicy::Temporary,
17292 },
17293 ],
17294 vec![
17295 ChildSpec {
17296 caixa: "w".into(),
17297 versao: "^0.1".into(),
17298 restart: RestartPolicy::Permanent,
17299 },
17300 ChildSpec {
17301 caixa: "w".into(),
17302 versao: "^0.1".into(),
17303 restart: RestartPolicy::Permanent,
17304 },
17305 ],
17306 ];
17307 for children in fixtures {
17308 let c = caixa_with_children(children.clone());
17309 assert_eq!(
17310 c.children(),
17311 children.as_slice(),
17312 "Caixa::children must return :children verbatim \
17313 (got {:?}, expected {children:?})",
17314 c.children(),
17315 );
17316 assert_eq!(
17317 c.children(),
17318 c.children.as_slice(),
17319 "Caixa::children must element-equal the raw \
17320 `self.children.as_slice()` field access across \
17321 every value in the Vec<ChildSpec> accept-set",
17322 );
17323 assert_eq!(
17324 c.children().is_empty(),
17325 c.children.is_empty(),
17326 "Caixa::children().is_empty() must byte-equal \
17327 self.children.is_empty() — a presence-bit drift \
17328 would silently split the paired \
17329 Caixa::declared_supervisor_slots supervisor-tree \
17330 declared-slot enumerator's presence probe from the \
17331 peer Caixa::supervisor_view typed-view composer's \
17332 fold-in path",
17333 );
17334 }
17335 }
17336
17337 #[test]
17338 fn declared_supervisor_slots_children_arm_routes_through_accessor() {
17339 // Composition pin: [`Caixa::declared_supervisor_slots`]'s
17340 // `:children` presence-probe arm must key off
17341 // [`Caixa::children`], not the raw
17342 // `!self.children.is_empty()` field-probe. Structurally: a
17343 // `Caixa { children: vec![ChildSpec { caixa: "w", versao:
17344 // "^0.1", restart: Permanent }], .. }` must push
17345 // `SUPERVISOR_AUTHOR_KEY_CHILDREN` onto the declared-slot list
17346 // (the presence bit is non-empty, so the supervisor-tree
17347 // kind-coherence gate must surface the slot as "declared"),
17348 // and a `Caixa { children: vec![], .. }` must NOT push the
17349 // label (the "author omitted the slot entirely" arm — the
17350 // empty-slice partition the serde-default folds onto). The
17351 // pair jointly pins the accessor + declared-slot enumerator
17352 // composition: any future silent detour that had the accessor
17353 // collapse `[Permanent]` to `[]` (a `.filter(|c| c.nome() !=
17354 // "__reserved__")` projection) would silently absorb the
17355 // "declared but degenerate" arm at the accessor boundary and
17356 // the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
17357 // kind-coherence gate would silently accept a struct-literal
17358 // `Caixa` carrying the drift.
17359 //
17360 // Peer of the sibling
17361 // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
17362 // (2a1f907) on the M2 `:upgrade-from` composite-slice arm —
17363 // same "the enumerator gate must route through the substrate-
17364 // primitive typed dispatch" discipline extended onto the
17365 // supervisor-tree `:children` composite-slice arm.
17366 use crate::supervisor::{ChildSpec, RestartPolicy};
17367 let c = caixa_with_children(vec![ChildSpec {
17368 caixa: "w".into(),
17369 versao: "^0.1".into(),
17370 restart: RestartPolicy::Permanent,
17371 }]);
17372 let slots = c.declared_supervisor_slots();
17373 assert!(
17374 slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN),
17375 "declared_supervisor_slots must push \
17376 SUPERVISOR_AUTHOR_KEY_CHILDREN when `:children` is \
17377 non-empty — the accessor and the enumerator gate must \
17378 route through the same substrate-primitive typed \
17379 dispatch on the outer :children presence bit (got \
17380 slots={slots:?})",
17381 );
17382 let c = caixa_with_children(vec![]);
17383 let slots = c.declared_supervisor_slots();
17384 assert!(
17385 !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN),
17386 "declared_supervisor_slots must NOT push \
17387 SUPERVISOR_AUTHOR_KEY_CHILDREN when `:children` is \
17388 empty — the author-omitted arm must route through the \
17389 accessor's empty-slice return unchanged (got \
17390 slots={slots:?})",
17391 );
17392 }
17393
17394 #[test]
17395 fn supervisor_view_children_arm_routes_through_accessor() {
17396 // Composition pin: [`Caixa::supervisor_view`]'s per-`:children`
17397 // fold-in arm must key off [`Caixa::children`], not the raw
17398 // `self.children.clone()` field-clone. Structurally: a `Caixa {
17399 // kind: Supervisor, estrategia: Some(OneForOne), children:
17400 // vec![ChildSpec { caixa: "w", .. }], .. }` must fold the
17401 // per-child list through the accessor into the typed
17402 // [`SupervisorSpec`] view's `children` field verbatim — every
17403 // entry the accessor surfaces must land in the view's
17404 // `children` slot in the same order. The pair jointly pins the
17405 // accessor + view-composer composition: any future silent
17406 // detour that had the accessor return a fresh-cloned
17407 // `Vec<ChildSpec>` copy would silently break the reference-
17408 // identity pin the peer `supervisor_view` fold-in path reads
17409 // from — the fold would clone once more per accessor call
17410 // instead of borrowing the storage buffer verbatim once.
17411 //
17412 // Peer of the sibling
17413 // `supervisor_view_kind_gate_routes_through_accessor` (35d8b52-
17414 // family) composition pin on the peer kind-gate arm — same
17415 // "the view composer must route through the substrate-
17416 // primitive typed dispatch" discipline extended onto the
17417 // per-`:children` fold-in arm, closing the supervisor-view
17418 // composer's routing invariant on the composite-slice input.
17419 use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
17420 let mut c = caixa_with_children(vec![
17421 ChildSpec {
17422 caixa: "worker-a".into(),
17423 versao: "^0.1".into(),
17424 restart: RestartPolicy::Permanent,
17425 },
17426 ChildSpec {
17427 caixa: "worker-b".into(),
17428 versao: "^0.1".into(),
17429 restart: RestartPolicy::Transient,
17430 },
17431 ]);
17432 c.kind = crate::CaixaKind::Supervisor;
17433 c.estrategia = Some(RestartStrategy::OneForOne);
17434 let view = c
17435 .supervisor_view()
17436 .expect("Supervisor kind must produce a supervisor_view");
17437 assert_eq!(
17438 view.children(),
17439 c.children(),
17440 "supervisor_view must fold Caixa::children verbatim into \
17441 SupervisorSpec::children — the accessor and the view \
17442 composer must route through the same substrate-primitive \
17443 typed dispatch on the outer :children slice (got view \
17444 children={:?}, expected {:?})",
17445 view.children(),
17446 c.children(),
17447 );
17448 }
17449
17450 #[test]
17451 fn children_projects_slice_by_borrow() {
17452 // The by-borrow pin: [`Caixa::children`] returns
17453 // `&[ChildSpec]` by borrow — the returned slice borrows the
17454 // underlying `Vec<ChildSpec>` storage of the `:children` slot
17455 // and the accessor must not clone the backing `Vec` on every
17456 // call. Peer of the sibling outer top-level [`Caixa`]
17457 // `&[T]`-return by-borrow pins (`autores_projects_slice_by_borrow`
17458 // b5d813f, `etiquetas_projects_slice_by_borrow` 78c7d3c,
17459 // `bibliotecas_projects_slice_by_borrow` 8a36c23,
17460 // `exe_projects_slice_by_borrow` 65d9527,
17461 // `servicos_projects_slice_by_borrow` 611f78b,
17462 // `deps_projects_slice_by_borrow` ad34b4e,
17463 // `deps_dev_projects_slice_by_borrow` f7fd81e,
17464 // `upgrade_from_projects_slice_by_borrow` 2a1f907) on the
17465 // sibling outer top-level [`Caixa`] scalar-element and
17466 // composite-element `&[T]` axes — folds on the outer-`Caixa`
17467 // composite-element `&[Composite]` axis: the accessor's
17468 // returned slice must borrow from `&self` (the returned
17469 // reference's lifetime is tied to `&self`), and calling the
17470 // accessor twice on the same [`Caixa`] must yield slices
17471 // that are pointer-equal (the underlying byte-buffer is the
17472 // storage `Vec`'s allocation, not a fresh copy) as well as
17473 // value-equal (idempotent, no side effects on `&self`).
17474 //
17475 // Pins against a future silent detour that returned an owned
17476 // `Vec<ChildSpec>` (which would type-check but silently clone
17477 // on every call), a `&Vec<ChildSpec>` return (which would leak
17478 // the backing `Vec`'s grow/push/reserve surface no downstream
17479 // consumer reaches for), or a one-arm-only accessor that
17480 // returned a saturating value on some sentinel input.
17481 use crate::supervisor::{ChildSpec, RestartPolicy};
17482 for children in [
17483 vec![],
17484 vec![ChildSpec {
17485 caixa: "w".into(),
17486 versao: "^0.1".into(),
17487 restart: RestartPolicy::Permanent,
17488 }],
17489 vec![
17490 ChildSpec {
17491 caixa: "worker-a".into(),
17492 versao: "^0.1".into(),
17493 restart: RestartPolicy::Permanent,
17494 },
17495 ChildSpec {
17496 caixa: "worker-b".into(),
17497 versao: "^0.1".into(),
17498 restart: RestartPolicy::Transient,
17499 },
17500 ],
17501 ] {
17502 let c = caixa_with_children(children.clone());
17503 let first = c.children();
17504 let second = c.children();
17505 assert_eq!(
17506 first, second,
17507 "Caixa::children must be idempotent — two successive \
17508 calls on the same &self must return the same \
17509 &[ChildSpec]",
17510 );
17511 assert_eq!(
17512 first.as_ptr(),
17513 second.as_ptr(),
17514 "Caixa::children must borrow the underlying \
17515 Vec<ChildSpec> storage — two successive calls must \
17516 return slices with the same backing pointer (a fresh \
17517 Vec<ChildSpec> clone would change the pointer on \
17518 every call)",
17519 );
17520 assert_eq!(
17521 first,
17522 children.as_slice(),
17523 "Caixa::children must return :children verbatim by \
17524 borrow — got {first:?}, expected {children:?}",
17525 );
17526 }
17527 }
17528
17529 // ── Caixa::membros — outer top-level &[Membro] composite-slice accessor ──
17530
17531 fn caixa_aplicacao_with_membros(membros: Vec<crate::aplicacao::Membro>) -> Caixa {
17532 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
17533 c.kind = CaixaKind::Aplicacao;
17534 c.membros = membros;
17535 c
17536 }
17537
17538 #[test]
17539 fn membros_returns_membros_slice_verbatim_across_permutations() {
17540 // The canonical per-`Caixa` `:membros` M3 mesh-slot outer-
17541 // composite `&[Membro]`-return slice-shape pin:
17542 // [`Caixa::membros`] must return the `:membros` typed
17543 // `Vec<Membro>` verbatim as a `&[Membro]` slice-view over the
17544 // same backing buffer the raw `self.membros.as_slice()` field
17545 // access borrows from, element-equal across every
17546 // representative fixture in the accept-set — `[]` (the "no
17547 // members declared" arm every non-`Aplicacao`-kind `defcaixa`
17548 // carries by `#[serde(default)]` and every partially-authored
17549 // Aplicacao carries before the
17550 // [`crate::AplicacaoError::MembrosEmpty`] gate fires), a
17551 // canonical single-member fixture (the shape a minimal
17552 // Aplicacao carries — one Servico wrapping one contained
17553 // computation), a canonical multi-member list carrying three
17554 // distinct entries (the canonical checkout-shape Aplicacao —
17555 // cart / pricing / auth — every canonical example carries), and
17556 // a past-the-guard sentinel — a duplicate `:caixa`
17557 // `[("cart", ...), ("cart", ...)]` entry pair
17558 // ([`crate::AplicacaoSpec::validate`] rejects through
17559 // `DuplicateMembro { nome: "cart" }` but the accessor must ship
17560 // the raw slot verbatim so struct-literal fixtures continue to
17561 // expose the duplicate at the accessor boundary).
17562 //
17563 // Pins against a future silent detour that returned an owned
17564 // `Vec<Membro>` (which would type-check but silently clone on
17565 // every accessor call, breaking the zero-cost projection every
17566 // peer sibling slice accessor carries), a `[dup, dup] → [dup]`
17567 // dedup collapse (which would silently absorb the
17568 // `DuplicateMembro` refusal case at the accessor boundary and
17569 // the [`crate::StandardLayout::verify`] cross-member gate would
17570 // silently accept a struct-literal `Caixa` carrying the drift),
17571 // a reference to an operator-resolved overlay (the future per-
17572 // cluster `:membros-overrides` slot — its resolution must land
17573 // at exactly this accessor body, not silently divert the raw
17574 // slot away from a second consumer), or an axis-shuffled
17575 // projection (a future detour that reordered members through
17576 // the accessor would silently split the paired
17577 // [`crate::StandardLayout::verify`] per-Aplicacao gate's
17578 // traversal input from the peer [`Self::aplicacao_view`] fold-
17579 // in path's clone-order input, since the canonical `:contratos`
17580 // `:de`/`:para` and `:entrada :para` cross-slot refusal probes
17581 // read the member set through the same slice).
17582 //
17583 // Third outer top-level [`Caixa`] `&[Composite]`-return slice
17584 // accessor pin on the substrate primitive for M2 / M3 typed-
17585 // slot vec-carry axes — opens the outer-`Caixa` M3 mesh-slot
17586 // arm of the `&[Composite]` composite-slice sub-family the
17587 // sibling M2 `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
17588 // (2a1f907) and
17589 // `children_returns_children_slice_verbatim_across_permutations`
17590 // (c17b51e) pins opened, peer at the outer altitude of the
17591 // closed inner-[`crate::AplicacaoSpec::membros`] (6c77e36)
17592 // accessor on the same MESH-COMPOSITION per-Aplicacao member-
17593 // list axis.
17594 use crate::aplicacao::Membro;
17595 let fixtures: Vec<Vec<Membro>> = vec![
17596 vec![],
17597 vec![Membro {
17598 caixa: "cart".into(),
17599 versao: "^0.1".into(),
17600 }],
17601 vec![
17602 Membro {
17603 caixa: "cart".into(),
17604 versao: "^0.1".into(),
17605 },
17606 Membro {
17607 caixa: "pricing".into(),
17608 versao: "^0.2".into(),
17609 },
17610 Membro {
17611 caixa: "auth".into(),
17612 versao: "^1.0".into(),
17613 },
17614 ],
17615 vec![
17616 Membro {
17617 caixa: "cart".into(),
17618 versao: "^0.1".into(),
17619 },
17620 Membro {
17621 caixa: "cart".into(),
17622 versao: "^0.1".into(),
17623 },
17624 ],
17625 ];
17626 for membros in fixtures {
17627 let c = caixa_aplicacao_with_membros(membros.clone());
17628 assert_eq!(
17629 c.membros(),
17630 membros.as_slice(),
17631 "Caixa::membros must return :membros verbatim \
17632 (got {:?}, expected {membros:?})",
17633 c.membros(),
17634 );
17635 assert_eq!(
17636 c.membros(),
17637 c.membros.as_slice(),
17638 "Caixa::membros must element-equal the raw \
17639 `self.membros.as_slice()` field access across every \
17640 value in the Vec<Membro> accept-set",
17641 );
17642 assert_eq!(
17643 c.membros().is_empty(),
17644 c.membros.is_empty(),
17645 "Caixa::membros().is_empty() must byte-equal \
17646 self.membros.is_empty() — a presence-bit drift would \
17647 silently split the paired Caixa::declared_mesh_slots \
17648 mesh declared-slot enumerator's presence probe from \
17649 the peer Caixa::aplicacao_view typed-view composer's \
17650 fold-in path",
17651 );
17652 }
17653 }
17654
17655 #[test]
17656 fn declared_mesh_slots_membros_arm_routes_through_accessor() {
17657 // Composition pin: [`Caixa::declared_mesh_slots`]'s `:membros`
17658 // presence-probe arm must key off [`Caixa::membros`], not the
17659 // raw `!self.membros.is_empty()` field-probe. Structurally: a
17660 // `Caixa { membros: vec![Membro { caixa: "cart", versao:
17661 // "^0.1" }], .. }` must push `M3_AUTHOR_KEY_MEMBROS` onto the
17662 // declared-slot list (the presence bit is non-empty, so the
17663 // mesh kind-coherence gate must surface the slot as
17664 // "declared"), and a `Caixa { membros: vec![], .. }` must NOT
17665 // push the label (the "author omitted the slot entirely" arm
17666 // — the empty-slice partition the serde-default folds onto).
17667 // The pair jointly pins the accessor + declared-slot
17668 // enumerator composition: any future silent detour that had
17669 // the accessor collapse `[Membro { .. }]` to `[]` (a
17670 // `.filter(|m| m.nome() != "__reserved__")` projection) would
17671 // silently absorb the "declared but degenerate" arm at the
17672 // accessor boundary and the
17673 // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
17674 // coherence gate would silently accept a struct-literal
17675 // `Caixa` carrying the drift.
17676 //
17677 // Peer of the sibling
17678 // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
17679 // (2a1f907) and
17680 // `declared_supervisor_slots_children_arm_routes_through_accessor`
17681 // (c17b51e) composition pins on the M2 `:upgrade-from` /
17682 // `:children` composite-slice arms — same "the enumerator gate
17683 // must route through the substrate-primitive typed dispatch"
17684 // discipline extended onto the M3 `:membros` composite-slice
17685 // arm, opening the M3 arm of the declared-slot enumerator's
17686 // routing invariant.
17687 use crate::aplicacao::Membro;
17688 let c = caixa_aplicacao_with_membros(vec![Membro {
17689 caixa: "cart".into(),
17690 versao: "^0.1".into(),
17691 }]);
17692 let slots = c.declared_mesh_slots();
17693 assert!(
17694 slots.contains(&crate::render::M3_AUTHOR_KEY_MEMBROS),
17695 "declared_mesh_slots must push M3_AUTHOR_KEY_MEMBROS when \
17696 `:membros` is non-empty — the accessor and the enumerator \
17697 gate must route through the same substrate-primitive \
17698 typed dispatch on the outer :membros presence bit (got \
17699 slots={slots:?})",
17700 );
17701 let c = caixa_aplicacao_with_membros(vec![]);
17702 let slots = c.declared_mesh_slots();
17703 assert!(
17704 !slots.contains(&crate::render::M3_AUTHOR_KEY_MEMBROS),
17705 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_MEMBROS \
17706 when `:membros` is empty — the author-omitted arm must \
17707 route through the accessor's empty-slice return unchanged \
17708 (got slots={slots:?})",
17709 );
17710 }
17711
17712 #[test]
17713 fn aplicacao_view_membros_arm_routes_through_accessor() {
17714 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:membros`
17715 // fold-in arm must key off [`Caixa::membros`], not the raw
17716 // `self.membros.clone()` field-clone. Structurally: a `Caixa {
17717 // kind: Aplicacao, membros: vec![Membro { caixa: "cart", .. },
17718 // Membro { caixa: "pricing", .. }], .. }` must fold the per-
17719 // member list through the accessor into the typed
17720 // [`crate::AplicacaoSpec`] view's `membros` slot verbatim —
17721 // every entry the accessor surfaces must land in the view's
17722 // `membros` slot in the same order. The pair jointly pins the
17723 // accessor + view-composer composition: any future silent
17724 // detour that had the accessor return a fresh-cloned
17725 // `Vec<Membro>` copy would silently break the reference-
17726 // identity pin the peer `aplicacao_view` fold-in path reads
17727 // from — the fold would clone once more per accessor call
17728 // instead of borrowing the storage buffer verbatim once.
17729 //
17730 // Peer of the sibling
17731 // `aplicacao_view_politicas_arm_folds_through_accessor`
17732 // (5d23d29) /
17733 // `aplicacao_view_placement_arm_folds_through_accessor`
17734 // (4fb8074) /
17735 // `aplicacao_view_entrada_arm_folds_through_accessor` (e4128e4)
17736 // composition pins on the M3 `:politicas` / `:placement` /
17737 // `:entrada` outer-`Option<&Composite>` arms — extended here to
17738 // the M3 `:membros` outer-`&[Composite]` composite-slice arm,
17739 // closing the aplicacao-view composer's routing invariant on
17740 // the composite-slice input.
17741 use crate::aplicacao::Membro;
17742 let c = caixa_aplicacao_with_membros(vec![
17743 Membro {
17744 caixa: "cart".into(),
17745 versao: "^0.1".into(),
17746 },
17747 Membro {
17748 caixa: "pricing".into(),
17749 versao: "^0.2".into(),
17750 },
17751 ]);
17752 let view = c
17753 .aplicacao_view()
17754 .expect("Aplicacao kind must produce an aplicacao_view");
17755 assert_eq!(
17756 view.membros(),
17757 c.membros(),
17758 "aplicacao_view must fold Caixa::membros verbatim into \
17759 AplicacaoSpec::membros — the accessor and the view \
17760 composer must route through the same substrate-primitive \
17761 typed dispatch on the outer :membros slice (got view \
17762 membros={:?}, expected {:?})",
17763 view.membros(),
17764 c.membros(),
17765 );
17766 }
17767
17768 #[test]
17769 fn membros_projects_slice_by_borrow() {
17770 // The by-borrow pin: [`Caixa::membros`] returns `&[Membro]` by
17771 // borrow — the returned slice borrows the underlying
17772 // `Vec<Membro>` storage of the `:membros` slot and the
17773 // accessor must not clone the backing `Vec` on every call.
17774 // Peer of the sibling outer top-level [`Caixa`] `&[T]`-return
17775 // by-borrow pins (`autores_projects_slice_by_borrow` b5d813f,
17776 // `etiquetas_projects_slice_by_borrow` 78c7d3c,
17777 // `bibliotecas_projects_slice_by_borrow` 8a36c23,
17778 // `exe_projects_slice_by_borrow` 65d9527,
17779 // `servicos_projects_slice_by_borrow` 611f78b,
17780 // `deps_projects_slice_by_borrow` ad34b4e,
17781 // `deps_dev_projects_slice_by_borrow` f7fd81e,
17782 // `upgrade_from_projects_slice_by_borrow` 2a1f907,
17783 // `children_projects_slice_by_borrow` c17b51e) on the sibling
17784 // outer top-level [`Caixa`] scalar-element and composite-
17785 // element `&[T]` axes — folds on the outer-`Caixa` M3 mesh-
17786 // slot composite-element `&[Composite]` axis: the accessor's
17787 // returned slice must borrow from `&self` (the returned
17788 // reference's lifetime is tied to `&self`), and calling the
17789 // accessor twice on the same [`Caixa`] must yield slices that
17790 // are pointer-equal (the underlying byte-buffer is the storage
17791 // `Vec`'s allocation, not a fresh copy) as well as value-equal
17792 // (idempotent, no side effects on `&self`).
17793 //
17794 // Pins against a future silent detour that returned an owned
17795 // `Vec<Membro>` (which would type-check but silently clone on
17796 // every call), a `&Vec<Membro>` return (which would leak the
17797 // backing `Vec`'s grow/push/reserve surface no downstream
17798 // consumer reaches for), or a one-arm-only accessor that
17799 // returned a saturating value on some sentinel input.
17800 use crate::aplicacao::Membro;
17801 for membros in [
17802 vec![],
17803 vec![Membro {
17804 caixa: "cart".into(),
17805 versao: "^0.1".into(),
17806 }],
17807 vec![
17808 Membro {
17809 caixa: "cart".into(),
17810 versao: "^0.1".into(),
17811 },
17812 Membro {
17813 caixa: "pricing".into(),
17814 versao: "^0.2".into(),
17815 },
17816 ],
17817 ] {
17818 let c = caixa_aplicacao_with_membros(membros.clone());
17819 let first = c.membros();
17820 let second = c.membros();
17821 assert_eq!(
17822 first, second,
17823 "Caixa::membros must be idempotent — two successive \
17824 calls on the same &self must return the same &[Membro]",
17825 );
17826 assert_eq!(
17827 first.as_ptr(),
17828 second.as_ptr(),
17829 "Caixa::membros must borrow the underlying Vec<Membro> \
17830 storage — two successive calls must return slices with \
17831 the same backing pointer (a fresh Vec<Membro> clone \
17832 would change the pointer on every call)",
17833 );
17834 assert_eq!(
17835 first,
17836 membros.as_slice(),
17837 "Caixa::membros must return :membros verbatim by borrow \
17838 — got {first:?}, expected {membros:?}",
17839 );
17840 }
17841 }
17842
17843 // ── Caixa::contratos — outer top-level &[WitContract] composite-slice accessor ──
17844
17845 fn caixa_aplicacao_with_contratos(contratos: Vec<crate::aplicacao::WitContract>) -> Caixa {
17846 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
17847 c.kind = CaixaKind::Aplicacao;
17848 c.contratos = contratos;
17849 c
17850 }
17851
17852 fn contrato_http_for_test(
17853 de: &str,
17854 para: &str,
17855 endpoint: &str,
17856 ) -> crate::aplicacao::WitContract {
17857 crate::aplicacao::WitContract {
17858 de: de.into(),
17859 para: para.into(),
17860 wit: "wasi:http/proxy".into(),
17861 endpoint: Some(endpoint.into()),
17862 subject: None,
17863 slot: None,
17864 }
17865 }
17866
17867 #[test]
17868 fn contratos_returns_contratos_slice_verbatim_across_permutations() {
17869 // The canonical per-`Caixa` `:contratos` M3 mesh-slot outer-
17870 // composite `&[WitContract]`-return slice-shape pin:
17871 // [`Caixa::contratos`] must return the `:contratos` typed
17872 // `Vec<WitContract>` verbatim as a `&[WitContract]` slice-view
17873 // over the same backing buffer the raw
17874 // `self.contratos.as_slice()` field access borrows from,
17875 // element-equal across every representative fixture in the
17876 // accept-set — `[]` (the "no contracts declared" arm every
17877 // non-`Aplicacao`-kind `defcaixa` carries by
17878 // `#[serde(default)]` and every leaf-Aplicacao with a single
17879 // member carries), a canonical single-edge fixture (the
17880 // minimal directed-graph shape: one HTTP-shape `(cart → catalog)`
17881 // edge), and a canonical multi-edge fixture with three distinct
17882 // edges (the checkout-shape Aplicacao's HTTP-fan pattern:
17883 // `(cart → catalog)`, `(cart → pricing)`, `(cart → auth)`).
17884 //
17885 // Pins against a future silent detour that returned an owned
17886 // `Vec<WitContract>` (which would type-check but silently clone
17887 // on every accessor call, breaking the zero-cost projection
17888 // every peer sibling slice accessor carries), an axis-shuffled
17889 // projection (a future detour that reordered edges through the
17890 // accessor would silently split the paired
17891 // [`crate::StandardLayout::verify`] per-Aplicacao gate's
17892 // traversal input from the peer [`Self::aplicacao_view`] fold-
17893 // in path's clone-order input, since every canonical
17894 // `caixa-mesh` renderer's per-`(:de, :para)` adjacency-list
17895 // seed dispatch reads the edge set through the same slice),
17896 // or a reference to an operator-resolved overlay (the future
17897 // per-cluster `:contratos-overrides` slot — its resolution
17898 // must land at exactly this accessor body, not silently divert
17899 // the raw slot away from a second consumer).
17900 //
17901 // Fourth outer top-level [`Caixa`] `&[Composite]`-return slice
17902 // accessor pin on the substrate primitive for M2 / M3 typed-
17903 // slot vec-carry axes — closes the outer-`Caixa`
17904 // `&[Composite]` composite-slice sub-family the sibling M2
17905 // `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
17906 // (2a1f907) and
17907 // `children_returns_children_slice_verbatim_across_permutations`
17908 // (c17b51e) pins opened and the M3
17909 // `membros_returns_membros_slice_verbatim_across_permutations`
17910 // (0f26987) pin folded on, closing the outer-`Caixa` M3 mesh-
17911 // slot arm of the composite-slice sub-family. Peer at the outer
17912 // altitude of the closed inner-
17913 // [`crate::AplicacaoSpec::contratos`] (0dcc926) accessor on the
17914 // same MESH-COMPOSITION per-Aplicacao contract-list axis.
17915 let fixtures: Vec<Vec<crate::aplicacao::WitContract>> = vec![
17916 vec![],
17917 vec![contrato_http_for_test("cart", "catalog", "/items")],
17918 vec![
17919 contrato_http_for_test("cart", "catalog", "/items"),
17920 contrato_http_for_test("cart", "pricing", "/price"),
17921 contrato_http_for_test("cart", "auth", "/whoami"),
17922 ],
17923 ];
17924 for contratos in fixtures {
17925 let c = caixa_aplicacao_with_contratos(contratos.clone());
17926 assert_eq!(
17927 c.contratos(),
17928 contratos.as_slice(),
17929 "Caixa::contratos must return :contratos verbatim \
17930 (got {:?}, expected {contratos:?})",
17931 c.contratos(),
17932 );
17933 assert_eq!(
17934 c.contratos(),
17935 c.contratos.as_slice(),
17936 "Caixa::contratos must element-equal the raw \
17937 `self.contratos.as_slice()` field access across every \
17938 value in the Vec<WitContract> accept-set",
17939 );
17940 assert_eq!(
17941 c.contratos().is_empty(),
17942 c.contratos.is_empty(),
17943 "Caixa::contratos().is_empty() must byte-equal \
17944 self.contratos.is_empty() — a presence-bit drift would \
17945 silently split the paired Caixa::declared_mesh_slots \
17946 mesh declared-slot enumerator's presence probe from \
17947 the peer Caixa::aplicacao_view typed-view composer's \
17948 fold-in path",
17949 );
17950 }
17951 }
17952
17953 #[test]
17954 fn declared_mesh_slots_contratos_arm_routes_through_accessor() {
17955 // Composition pin: [`Caixa::declared_mesh_slots`]'s `:contratos`
17956 // presence-probe arm must key off [`Caixa::contratos`], not the
17957 // raw `!self.contratos.is_empty()` field-probe. Structurally: a
17958 // `Caixa { contratos: vec![WitContract { .. }], .. }` must push
17959 // `M3_AUTHOR_KEY_CONTRATOS` onto the declared-slot list (the
17960 // presence bit is non-empty, so the mesh kind-coherence gate
17961 // must surface the slot as "declared"), and a `Caixa {
17962 // contratos: vec![], .. }` must NOT push the label (the "author
17963 // omitted the slot entirely" arm — the empty-slice partition
17964 // the serde-default folds onto). The pair jointly pins the
17965 // accessor + declared-slot enumerator composition: any future
17966 // silent detour that had the accessor collapse
17967 // `[WitContract { .. }]` to `[]` (a `.filter(|c| c.de() !=
17968 // "__reserved__")` projection) would silently absorb the
17969 // "declared but degenerate" arm at the accessor boundary and
17970 // the [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
17971 // coherence gate would silently accept a struct-literal
17972 // `Caixa` carrying the drift.
17973 //
17974 // Peer of the sibling
17975 // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
17976 // (2a1f907),
17977 // `declared_supervisor_slots_children_arm_routes_through_accessor`
17978 // (c17b51e), and
17979 // `declared_mesh_slots_membros_arm_routes_through_accessor`
17980 // (0f26987) composition pins on the M2 `:upgrade-from` /
17981 // `:children` / M3 `:membros` composite-slice arms — same "the
17982 // enumerator gate must route through the substrate-primitive
17983 // typed dispatch" discipline extended onto the M3 `:contratos`
17984 // composite-slice arm, closing the M3 mesh-slot arm of the
17985 // declared-slot enumerator's routing invariant on the
17986 // composite-slice inputs.
17987 let c = caixa_aplicacao_with_contratos(vec![contrato_http_for_test(
17988 "cart", "catalog", "/items",
17989 )]);
17990 let slots = c.declared_mesh_slots();
17991 assert!(
17992 slots.contains(&crate::render::M3_AUTHOR_KEY_CONTRATOS),
17993 "declared_mesh_slots must push M3_AUTHOR_KEY_CONTRATOS when \
17994 `:contratos` is non-empty — the accessor and the enumerator \
17995 gate must route through the same substrate-primitive \
17996 typed dispatch on the outer :contratos presence bit (got \
17997 slots={slots:?})",
17998 );
17999 let c = caixa_aplicacao_with_contratos(vec![]);
18000 let slots = c.declared_mesh_slots();
18001 assert!(
18002 !slots.contains(&crate::render::M3_AUTHOR_KEY_CONTRATOS),
18003 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_CONTRATOS \
18004 when `:contratos` is empty — the author-omitted arm must \
18005 route through the accessor's empty-slice return unchanged \
18006 (got slots={slots:?})",
18007 );
18008 }
18009
18010 #[test]
18011 fn aplicacao_view_contratos_arm_routes_through_accessor() {
18012 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:contratos`
18013 // fold-in arm must key off [`Caixa::contratos`], not the raw
18014 // `self.contratos.clone()` field-clone. Structurally: a `Caixa
18015 // { kind: Aplicacao, contratos: vec![WitContract { de: "cart",
18016 // .. }, WitContract { de: "pricing", .. }], .. }` must fold the
18017 // per-edge list through the accessor into the typed
18018 // [`crate::AplicacaoSpec`] view's `contratos` slot verbatim —
18019 // every entry the accessor surfaces must land in the view's
18020 // `contratos` slot in the same order. The pair jointly pins
18021 // the accessor + view-composer composition: a future silent
18022 // detour that had the accessor shuffle or drop an edge would
18023 // silently split the paired declared-slot enumerator's
18024 // presence bit from the typed-view composer's edge-list, a
18025 // two-consumer split at the enumerator and the view composer
18026 // far from the source `caixa.lisp`.
18027 //
18028 // Peer of the sibling
18029 // `aplicacao_view_membros_arm_routes_through_accessor`
18030 // (0f26987) composition pin on the M3 `:membros` outer-
18031 // `&[Composite]` composite-slice arm, closing the aplicacao-
18032 // view composer's routing invariant on the composite-slice
18033 // inputs at the outer altitude.
18034 let c = caixa_aplicacao_with_contratos(vec![
18035 contrato_http_for_test("cart", "catalog", "/items"),
18036 contrato_http_for_test("cart", "pricing", "/price"),
18037 ]);
18038 let view = c
18039 .aplicacao_view()
18040 .expect("Aplicacao kind must produce an aplicacao_view");
18041 assert_eq!(
18042 view.contratos(),
18043 c.contratos(),
18044 "aplicacao_view must fold Caixa::contratos verbatim into \
18045 AplicacaoSpec::contratos — the accessor and the view \
18046 composer must route through the same substrate-primitive \
18047 typed dispatch on the outer :contratos slice (got view \
18048 contratos={:?}, expected {:?})",
18049 view.contratos(),
18050 c.contratos(),
18051 );
18052 }
18053
18054 #[test]
18055 fn contratos_projects_slice_by_borrow() {
18056 // The by-borrow pin: [`Caixa::contratos`] returns `&[WitContract]`
18057 // by borrow — the returned slice borrows the underlying
18058 // `Vec<WitContract>` storage of the `:contratos` slot and the
18059 // accessor must not clone the backing `Vec` on every call.
18060 // Peer of the sibling outer top-level [`Caixa`] `&[T]`-return
18061 // by-borrow pins (`autores_projects_slice_by_borrow` b5d813f,
18062 // `etiquetas_projects_slice_by_borrow` 78c7d3c,
18063 // `bibliotecas_projects_slice_by_borrow` 8a36c23,
18064 // `exe_projects_slice_by_borrow` 65d9527,
18065 // `servicos_projects_slice_by_borrow` 611f78b,
18066 // `deps_projects_slice_by_borrow` ad34b4e,
18067 // `deps_dev_projects_slice_by_borrow` f7fd81e,
18068 // `upgrade_from_projects_slice_by_borrow` 2a1f907,
18069 // `children_projects_slice_by_borrow` c17b51e,
18070 // `membros_projects_slice_by_borrow` 0f26987) on the sibling
18071 // outer top-level [`Caixa`] scalar-element and composite-
18072 // element `&[T]` axes — closes the outer-`Caixa` M3 mesh-slot
18073 // composite-element `&[Composite]` axis on the by-borrow pin:
18074 // the accessor's returned slice must borrow from `&self` (the
18075 // returned reference's lifetime is tied to `&self`), and
18076 // calling the accessor twice on the same [`Caixa`] must yield
18077 // slices that are pointer-equal (the underlying byte-buffer is
18078 // the storage `Vec`'s allocation, not a fresh copy) as well as
18079 // value-equal (idempotent, no side effects on `&self`).
18080 //
18081 // Pins against a future silent detour that returned an owned
18082 // `Vec<WitContract>` (which would type-check but silently clone
18083 // on every call), a `&Vec<WitContract>` return (which would
18084 // leak the backing `Vec`'s grow/push/reserve surface no
18085 // downstream consumer reaches for), or a one-arm-only accessor
18086 // that returned a saturating value on some sentinel input.
18087 for contratos in [
18088 vec![],
18089 vec![contrato_http_for_test("cart", "catalog", "/items")],
18090 vec![
18091 contrato_http_for_test("cart", "catalog", "/items"),
18092 contrato_http_for_test("cart", "pricing", "/price"),
18093 ],
18094 ] {
18095 let c = caixa_aplicacao_with_contratos(contratos.clone());
18096 let first = c.contratos();
18097 let second = c.contratos();
18098 assert_eq!(
18099 first, second,
18100 "Caixa::contratos must be idempotent — two successive \
18101 calls on the same &self must return the same \
18102 &[WitContract]",
18103 );
18104 assert_eq!(
18105 first.as_ptr(),
18106 second.as_ptr(),
18107 "Caixa::contratos must borrow the underlying \
18108 Vec<WitContract> storage — two successive calls must \
18109 return slices with the same backing pointer (a fresh \
18110 Vec<WitContract> clone would change the pointer on \
18111 every call)",
18112 );
18113 assert_eq!(
18114 first,
18115 contratos.as_slice(),
18116 "Caixa::contratos must return :contratos verbatim by \
18117 borrow — got {first:?}, expected {contratos:?}",
18118 );
18119 }
18120 }
18121
18122 // ── drift-detection: Caixa top-level multi-word serde-derive-to-const identity ──
18123
18124 #[test]
18125 fn caixa_multi_word_serde_keys_match_lifted_top_level_key_consts() {
18126 // Load-bearing invariant: every multi-word top-level [`Caixa`]
18127 // serde-derived JSON key routes through a lifted `&'static str`
18128 // const. The Rust field names are `snake_case`
18129 // (`deps_dev` / `upgrade_from` / `max_restarts` /
18130 // `restart_window`); [`Caixa`]'s `#[serde(rename_all =
18131 // "camelCase")]` derive attribute maps each to the camelCase
18132 // byte-string the [`Caixa::to_lisp`] round-trip's
18133 // `serde_json::to_value(self)` step lands under before
18134 // `tatara_lisp::domain::json_to_sexp` re-projects the JSON keys
18135 // to the kebab-case `:deps-dev` / `:upgrade-from` /
18136 // `:max-restarts` / `:restart-window` author surface. Serialize
18137 // a fully-populated [`Caixa`] and pin that each canonical
18138 // byte-sequence appears verbatim in the JSON — a future
18139 // accidental `rename_all = "snake_case"` / `"kebab-case"` /
18140 // verbatim-field-name flip at the derive attribute (any of
18141 // which would silently break every [`Caixa::to_lisp`]
18142 // round-trip and the future M4 operator-side manifest ingest's
18143 // `Value::get(<key>)` navigation) surfaces here as a build-time
18144 // test failure at `manifest.rs`, not as an apply-time
18145 // `.get(<stale-canonical-const>)` returning `None` far from the
18146 // derive-attr drift's commit. Same discipline the sibling
18147 // `supervisor_spec_serde_keys_match_lifted_supervisor_key_consts`
18148 // (40cc4e5), `membro_serde_keys_match_lifted_membro_key_consts`
18149 // (ce80ca0), and `upgrade_from_entry_serde_keys_match_lifted_
18150 // m2_upgrade_from_key_consts` (36ffe65) pins established on the
18151 // sibling M2 supervision-tree, M3 [`Membro`] per-entry, and M2
18152 // [`UpgradeFromEntry`] per-entry axes — extended here to the
18153 // enclosing M0 [`Caixa`] top-level axis so the last of the four
18154 // multi-word top-level [`Caixa`] serde-derived JSON keys
18155 // (`depsDev`) joins the substrate's "one canonical byte-string
18156 // per typed serialized-key axis" discipline.
18157 use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
18158 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
18159 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
18160 c.deps_dev = vec![Dep::simple("tatara-check", "^0.1")];
18161 c.upgrade_from = vec![UpgradeFromEntry {
18162 from: "0.0.1".into(),
18163 instructions: vec![UpgradeInstruction::Restart],
18164 }];
18165 c.estrategia = Some(RestartStrategy::OneForOne);
18166 c.max_restarts = Some(3);
18167 c.restart_window = Some("60s".into());
18168 c.children = vec![ChildSpec {
18169 caixa: "child".into(),
18170 versao: "^0.1".into(),
18171 restart: RestartPolicy::Permanent,
18172 }];
18173 let json = serde_json::to_string(&c).unwrap();
18174 for key in [
18175 crate::render::CAIXA_KEY_DEPS_DEV,
18176 crate::render::M2_KEY_UPGRADE_FROM,
18177 crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
18178 crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
18179 ] {
18180 let quoted = format!("\"{key}\"");
18181 assert!(
18182 json.contains("ed),
18183 "serialized Caixa must carry the lifted top-level \
18184 multi-word byte-sequence {quoted} verbatim in the JSON \
18185 emission (got: {json})",
18186 );
18187 }
18188 }
18189
18190 #[test]
18191 fn caixa_top_level_multi_word_key_consts_are_pairwise_distinct() {
18192 // Cross-axis drift-detection pin: a future collapse of the four
18193 // canonical [`Caixa`] top-level multi-word byte-strings onto the
18194 // same value (e.g. an accidental copy-paste flip of
18195 // [`crate::render::CAIXA_KEY_DEPS_DEV`] to also read
18196 // `"upgradeFrom"`) would silently reroute every downstream
18197 // `Value::get(<key>)` probe on one axis onto the sibling axis's
18198 // top-level entry and pass every propagation-probe test that
18199 // expected only the stale axis's value. Peer of the sibling
18200 // four-way distinct pin on the `SUPERVISOR_KEY_*` tetrad
18201 // (40cc4e5) and the two-way pin on `MEMBRO_KEY_*` (ce80ca0).
18202 let all = [
18203 crate::render::CAIXA_KEY_DEPS_DEV,
18204 crate::render::M2_KEY_UPGRADE_FROM,
18205 crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
18206 crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
18207 ];
18208 for (i, a) in all.iter().enumerate() {
18209 for b in all.iter().skip(i + 1) {
18210 assert_ne!(
18211 a, b,
18212 "Caixa top-level multi-word key consts must be \
18213 pairwise-distinct canonical byte-sequences — got \
18214 `{a}` == `{b}`",
18215 );
18216 }
18217 }
18218 }
18219
18220 #[test]
18221 fn caixa_top_level_multi_word_key_consts_are_lower_camel_case_shape() {
18222 // Shape-pin: every [`Caixa`] top-level multi-word key const must
18223 // be a lowerCamelCase byte-sequence (no `snake_case`
18224 // underscores, no `kebab-case` hyphens, no leading colon, no
18225 // `PascalCase` leading capital, no whitespace / dots) — the
18226 // canonical shape the `#[serde(rename_all = "camelCase")]`
18227 // derive produces on [`Caixa`]. A future flip to a
18228 // non-camelCase attribute at the derive surfaces both here
18229 // (this test fails on the stale-constant shape) and at
18230 // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
18231 // (that test fails on the mismatch between const and derive).
18232 // Peer with `membro_key_consts_are_lower_camel_case_shape`
18233 // (ce80ca0) and `supervisor_key_consts_are_lower_camel_case_shape`
18234 // (40cc4e5) on the sibling per-entry / supervisor-tree axes.
18235 for key in [
18236 crate::render::CAIXA_KEY_DEPS_DEV,
18237 crate::render::M2_KEY_UPGRADE_FROM,
18238 crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
18239 crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
18240 ] {
18241 assert!(
18242 !key.is_empty(),
18243 "Caixa top-level multi-word key const must be non-empty \
18244 (got {key:?})"
18245 );
18246 let first = key.chars().next().unwrap();
18247 assert!(
18248 first.is_ascii_lowercase(),
18249 "Caixa top-level multi-word key const must lead with an \
18250 ASCII-lowercase byte (got {key:?}, leads with {first:?})",
18251 );
18252 assert!(
18253 key.chars().all(|c| c.is_ascii_alphanumeric()),
18254 "Caixa top-level multi-word key const must be \
18255 ASCII-alphanumeric only — no `_` / `-` / `:` / `.` / \
18256 whitespace (got {key:?})",
18257 );
18258 }
18259 }
18260
18261 #[test]
18262 fn caixa_key_deps_dev_pins_canonical_camel_case_byte_string() {
18263 // Scalar-value pin: the byte-string the
18264 // [`crate::render::CAIXA_KEY_DEPS_DEV`] const resolves to,
18265 // asserted verbatim. A future rebrand (`depsDev` → `devDeps`
18266 // matching Cargo's verbatim `dev-dependencies` axis, `depsDev`
18267 // → `depsTest` matching a hypothetical per-test-target
18268 // vocabulary flip) lands as an edit to exactly one const AND
18269 // one derive attribute — the sibling
18270 // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
18271 // pin already ties the const to the derive attribute, so a
18272 // rebrand that touches only one side of the pair fails at
18273 // caixa-core build time. Same "scalar-value pin per const"
18274 // discipline the sibling
18275 // `m2_top_level_author_key_consts_pin_canonical_kebab_case_labels`
18276 // (f49c8b0) and `contrato_key_consts_pin_canonical_camel_case_labels`
18277 // (ca463a4) pins carry on the peer M2 / M3 top-level slot axes.
18278 assert_eq!(crate::render::CAIXA_KEY_DEPS_DEV, "depsDev");
18279 }
18280
18281 #[test]
18282 fn caixa_key_deps_pins_canonical_byte_string() {
18283 // Scalar-value pin: the byte-string the
18284 // [`crate::render::CAIXA_KEY_DEPS`] const resolves to, asserted
18285 // verbatim. Peer of `caixa_key_deps_dev_pins_canonical_camel_case_byte_string`
18286 // on the two-list dep-graph serialized-key axis — the sibling
18287 // pin covers the multi-word `deps_dev → depsDev` camelCase
18288 // arm, this pin covers the single-word `deps → deps` no-op arm
18289 // (the [`crate::Caixa::deps`] field name carries no `_`, so the
18290 // `#[serde(rename_all = "camelCase")]` derive is a no-op on this
18291 // axis and the emitted JSON key equals the source-side field
18292 // name byte-for-byte). A future [`crate::Caixa::deps`] field
18293 // rename (`deps` → `dependencies` matching Cargo's verbatim
18294 // `[dependencies]` axis, `deps` → `runtime_deps` matching a
18295 // hypothetical per-runtime-target vocabulary flip) OR an added
18296 // `#[serde(rename = "…")]` explicit override lands as an edit
18297 // to exactly one const AND one derive-attr / field name — the
18298 // sibling `caixa_deps_serde_key_matches_lifted_caixa_key_deps`
18299 // pin ties the const to the emitted JSON key, so a rebrand
18300 // that touches only one side of the pair fails at caixa-core
18301 // build time.
18302 assert_eq!(crate::render::CAIXA_KEY_DEPS, "deps");
18303 }
18304
18305 #[test]
18306 fn caixa_deps_serde_key_matches_lifted_caixa_key_deps() {
18307 // Load-bearing invariant on the single-word `deps` top-level
18308 // axis: the byte-string [`crate::render::CAIXA_KEY_DEPS`] pins
18309 // must appear verbatim in the JSON [`Caixa::to_lisp`]'s
18310 // `serde_json::to_value(self)` step emits. Serialize a
18311 // populated [`Caixa`] whose `:deps` slot carries at least one
18312 // entry (the `#[serde(default)]` attribute on the field emits
18313 // an empty `[]` even without members, but a non-empty vec
18314 // additionally covers the codec's per-`Dep`-entry emission
18315 // path) and pin that `"deps"` appears verbatim in the JSON
18316 // emission — a future accidental `rename_all = "snake_case"` /
18317 // `"kebab-case"` flip at the derive attribute (or an added
18318 // `#[serde(rename = "…")]` explicit override on the field, or
18319 // a Rust field rename) would break every [`Caixa::to_lisp`]
18320 // round-trip and the future M4 operator-side manifest ingest's
18321 // `Value::get(CAIXA_KEY_DEPS)` navigation — surfaces here as a
18322 // build-time test failure at `manifest.rs`, not as an
18323 // apply-time `.get(<stale-canonical-const>)` returning `None`
18324 // far from the drift's commit. Peer of the sibling
18325 // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
18326 // multi-word pin on the same M0 [`Caixa`] top-level
18327 // serialized-key axis, extended here to the single-word arm
18328 // the multi-word test's `rename_all = "camelCase"` sweep can't
18329 // reach (single-word `deps → deps` is a no-op the multi-word
18330 // pin's `\"depsDev\"` / `\"upgradeFrom\"` / `\"maxRestarts\"` /
18331 // `\"restartWindow\"` byte-scan can never observe).
18332 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
18333 c.deps = vec![Dep::simple("caixa-core", "^0.1")];
18334 let json = serde_json::to_string(&c).unwrap();
18335 let quoted = format!("\"{}\"", crate::render::CAIXA_KEY_DEPS);
18336 assert!(
18337 json.contains("ed),
18338 "serialized Caixa must carry the lifted top-level `deps` \
18339 byte-sequence {quoted} verbatim in the JSON emission (got: \
18340 {json})",
18341 );
18342 }
18343
18344 #[test]
18345 fn caixa_dep_graph_two_list_key_consts_are_pairwise_distinct() {
18346 // Cross-axis drift-detection pin on the two-list dep-graph
18347 // renderer-side wire-key axis: a future collapse of the
18348 // canonical [`crate::render::CAIXA_KEY_DEPS`] /
18349 // [`crate::render::CAIXA_KEY_DEPS_DEV`] byte-strings onto the
18350 // same value (e.g. an accidental copy-paste flip of
18351 // `CAIXA_KEY_DEPS_DEV` to also read `"deps"`) would silently
18352 // reroute every downstream `Value::get(<key>)` probe on one
18353 // axis onto the sibling axis's dep-list and pass every
18354 // propagation-probe test that expected only the stale axis's
18355 // value — a dev-only dep would land in the runtime closure at
18356 // publish time, or a runtime dep would be excluded from the
18357 // published lacre. Peer of the sibling four-way distinct pin
18358 // on the top-level multi-word tetrad
18359 // (`caixa_top_level_multi_word_key_consts_are_pairwise_distinct`)
18360 // and the two-way pin on the sibling
18361 // [`DEP_AUTHOR_KEY_DEPS`] / [`DEP_AUTHOR_KEY_DEPS_DEV`]
18362 // author-facing arm (4da6fba's test), extended here to the
18363 // renderer-side wire-key arm of the same two-list dep-graph
18364 // axis so both halves of the "one canonical byte-string per
18365 // typed axis per (author, wire)" grid carry the same
18366 // distinct-ness discipline.
18367 assert_ne!(
18368 crate::render::CAIXA_KEY_DEPS,
18369 crate::render::CAIXA_KEY_DEPS_DEV,
18370 "CAIXA_KEY_DEPS and CAIXA_KEY_DEPS_DEV must be distinct \
18371 canonical byte-sequences on the two-list dep-graph \
18372 renderer-side wire-key axis"
18373 );
18374 }
18375
18376 // ── DepList / Caixa::push_dep pin ────────────────────────────────
18377 //
18378 // The compounding pin: the two-arm closed-set typed enum
18379 // [`crate::dep::DepList`] carries the runtime-closure `:deps`
18380 // (`Prod`) vs dev-only-closure `:deps-dev` (`Dev`) dispatch every
18381 // consumer of the top-level manifest's dep-mutation surface reads
18382 // through, and the typed dispatch [`Caixa::push_dep`] on the
18383 // substrate primitive folds the "select list → check within-list
18384 // dup → push" cascade onto one method call. Prior to this landing
18385 // the two axes lived across two `&'static str` constants
18386 // (`DEP_AUTHOR_KEY_DEPS`, `DEP_AUTHOR_KEY_DEPS_DEV`) with no closed-
18387 // set type carrying the pair; the `feira add` mutation site's
18388 // inline `if self.dev { &mut caixa.deps_dev } else { &mut
18389 // caixa.deps }` dispatch expressed no compile-time link back to
18390 // the substrate primitive, and a future third dep-list axis would
18391 // have silently split at every open-coded mutation site.
18392
18393 #[test]
18394 fn dep_list_as_str_routes_through_lifted_author_key_constants() {
18395 // Every arm returns the same `&'static str` the substrate's
18396 // canonical `DEP_AUTHOR_KEY_DEPS` / `DEP_AUTHOR_KEY_DEPS_DEV`
18397 // constants carry. A future rebrand on either constant reaches
18398 // the enum through one edit; a regression to inline literals
18399 // (e.g. `Prod => ":deps"`) would silently split the diagnostic
18400 // quotes from the wire-format constants every consumer routes
18401 // through and this pin flags it at build time.
18402 assert_eq!(
18403 crate::dep::DepList::Prod.as_str(),
18404 crate::render::DEP_AUTHOR_KEY_DEPS
18405 );
18406 assert_eq!(
18407 crate::dep::DepList::Dev.as_str(),
18408 crate::render::DEP_AUTHOR_KEY_DEPS_DEV
18409 );
18410 }
18411
18412 #[test]
18413 fn dep_list_display_routes_through_as_str() {
18414 // Same as-str-through-Display convergence discipline the
18415 // sibling closed-set typed enums carry — a `format!("{list}")`
18416 // call must land byte-for-byte on the accessor's return so a
18417 // future consumer that formats the enum for a diagnostic line
18418 // reaches the same wire-format constant the wire-format
18419 // producers do.
18420 assert_eq!(
18421 format!("{}", crate::dep::DepList::Prod),
18422 crate::dep::DepList::Prod.as_str()
18423 );
18424 assert_eq!(
18425 format!("{}", crate::dep::DepList::Dev),
18426 crate::dep::DepList::Dev.as_str()
18427 );
18428 }
18429
18430 #[test]
18431 fn dep_list_all_enumerates_every_variant_once() {
18432 // Exhaustive-iteration pin — every arm appears exactly once in
18433 // `ALL`, matching the closed set the compiler enforces on the
18434 // sibling `match self` arms. A future variant addition that
18435 // extends only one method's match without extending `ALL`
18436 // would silently drop the new arm from every consumer that
18437 // iterates the slice.
18438 let variants: &[crate::dep::DepList] = crate::dep::DepList::ALL;
18439 assert!(variants.contains(&crate::dep::DepList::Prod));
18440 assert!(variants.contains(&crate::dep::DepList::Dev));
18441 assert_eq!(variants.len(), 2);
18442 }
18443
18444 #[test]
18445 fn dep_list_from_wire_returns_prod_on_deps_wire_scalar() {
18446 // Reverse projection on the two-list dep-graph axis: the
18447 // author-surface wire tag the sibling `as_str` emitter walks
18448 // for `Prod` (`:deps` via `DEP_AUTHOR_KEY_DEPS`) parses back to
18449 // `Some(DepList::Prod)`. A regression that hand-rolled the
18450 // per-arm match without routing through the lifted
18451 // `DEP_AUTHOR_KEY_DEPS` const would silently disagree on any
18452 // future wire-tag rebrand and this pin flags it at build time.
18453 assert_eq!(
18454 crate::dep::DepList::from_wire(crate::render::DEP_AUTHOR_KEY_DEPS),
18455 Some(crate::dep::DepList::Prod)
18456 );
18457 }
18458
18459 #[test]
18460 fn dep_list_from_wire_returns_dev_on_deps_dev_wire_scalar() {
18461 // Peer of the `Prod`-arm pin on the dev-only axis: the
18462 // author-surface wire tag the sibling `as_str` emitter walks
18463 // for `Dev` (`:deps-dev` via `DEP_AUTHOR_KEY_DEPS_DEV`) parses
18464 // back to `Some(DepList::Dev)`. Same drift-detection posture
18465 // as the peer arm — the sibling method `match` arms are
18466 // compiler-checked exhaustive so a future variant addition
18467 // trips at build time.
18468 assert_eq!(
18469 crate::dep::DepList::from_wire(crate::render::DEP_AUTHOR_KEY_DEPS_DEV),
18470 Some(crate::dep::DepList::Dev)
18471 );
18472 }
18473
18474 #[test]
18475 fn dep_list_from_wire_returns_none_on_unknown_wire_scalar() {
18476 // Every input outside the closed-set arm-string set the
18477 // sibling `as_str` emitter walks lands on the terminal `None`
18478 // fallback — no silent-accept surface. Sweeps a set of
18479 // plausibly-adjacent scalars (unprefixed wire form, PascalCase
18480 // rebrand candidates, foreign wire tags, empty string) so a
18481 // future variant addition that widened one wire form without
18482 // extending the emitter's arm-set would trip the sibling
18483 // round-trip pin below rather than silently accepting the new
18484 // form here.
18485 for candidate in [
18486 "",
18487 "deps",
18488 "deps-dev",
18489 ":deps ",
18490 ":Deps",
18491 ":DEPS",
18492 ":build-dep",
18493 ":tool-dep",
18494 "prod",
18495 "dev",
18496 ] {
18497 assert_eq!(
18498 crate::dep::DepList::from_wire(candidate),
18499 None,
18500 "from_wire({candidate:?}) must return None; every input outside \
18501 the {{DEP_AUTHOR_KEY_DEPS, DEP_AUTHOR_KEY_DEPS_DEV}} accept-set \
18502 the sibling as_str emitter walks lands on the terminal fallback",
18503 );
18504 }
18505 }
18506
18507 #[test]
18508 fn dep_list_round_trips_through_as_str_and_from_wire() {
18509 // Load-bearing round-trip pin: every arm the `ALL` iteration
18510 // exposes survives the `as_str` → `from_wire` composition
18511 // byte-for-byte. Same discipline the sibling closed-set enums
18512 // carry — `CaixaKind` /
18513 // `RestartStrategy` / `RestartPolicy` /
18514 // `PlacementStrategy` — extended onto the two-list dep-graph
18515 // axis. A future variant addition that extends `ALL` +
18516 // `as_str` without extending `from_wire` (or vice versa)
18517 // trips at build time on this iteration because the compiler
18518 // enforces exhaustiveness on the sibling `match self` arms.
18519 for &list in crate::dep::DepList::ALL {
18520 assert_eq!(
18521 crate::dep::DepList::from_wire(list.as_str()),
18522 Some(list),
18523 "DepList::from_wire(as_str({list:?})) must round-trip to Some({list:?}) — \
18524 a silent split between the forward emitter and the reverse parser \
18525 would drift the two halves of the two-list dep-graph axis's typed dispatch",
18526 );
18527 }
18528 }
18529
18530 #[test]
18531 fn push_dep_routes_to_deps_slot_on_prod_arm() {
18532 // The `Prod` arm dispatches to the runtime-closure `:deps`
18533 // slot every downstream lacre-pipeline consumer resolves at
18534 // build time. A future arm that regressed to inline `&mut
18535 // self.deps_dev` on the `Prod` path would silently reroute
18536 // every runtime dep into the dev-only closure at publish time
18537 // — this pin refuses that regression.
18538 let src = Caixa::template("host");
18539 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
18540 let before_deps = caixa.deps().len();
18541 let before_deps_dev = caixa.deps_dev().len();
18542 let dep = Dep {
18543 nome: "caixa-teia".to_string(),
18544 versao: "^0.1".to_string(),
18545 fonte: None,
18546 opcional: false,
18547 caracteristicas: Vec::new(),
18548 };
18549 caixa
18550 .push_dep(crate::dep::DepList::Prod, dep)
18551 .expect("first push into :deps succeeds");
18552 assert_eq!(caixa.deps().len(), before_deps + 1);
18553 assert_eq!(caixa.deps_dev().len(), before_deps_dev);
18554 assert_eq!(caixa.deps().last().unwrap().nome(), "caixa-teia");
18555 }
18556
18557 #[test]
18558 fn push_dep_routes_to_deps_dev_slot_on_dev_arm() {
18559 // Peer of the sibling `Prod`-arm dispatch pin — the `Dev` arm
18560 // must dispatch to the dev-only-closure `:deps-dev` slot every
18561 // downstream test-facing artifact resolver reads. A future
18562 // regression that inverted the two arms would silently route
18563 // every dev-only dep into the runtime closure at publish time
18564 // and this pin catches it before the drift ships.
18565 let src = Caixa::template("host");
18566 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
18567 let dep = Dep {
18568 nome: "tatara-check".to_string(),
18569 versao: "*".to_string(),
18570 fonte: None,
18571 opcional: false,
18572 caracteristicas: Vec::new(),
18573 };
18574 caixa
18575 .push_dep(crate::dep::DepList::Dev, dep)
18576 .expect("first push into :deps-dev succeeds");
18577 assert!(caixa.deps().is_empty());
18578 assert_eq!(caixa.deps_dev().len(), 1);
18579 assert_eq!(caixa.deps_dev().last().unwrap().nome(), "tatara-check");
18580 }
18581
18582 #[test]
18583 fn push_dep_refuses_within_list_duplicate_nome_with_typed_error() {
18584 // Within-list dup check routes through the canonical
18585 // [`DepError::DuplicateNome`] carrier — the substrate's typed
18586 // diagnostic for the same axis [`Caixa::validate_deps`]'s
18587 // parse-time [`crate::render::insert_first_seen`] walk raises
18588 // on. Prior to the lift the mutation site's inline
18589 // `bail!("dep '{}' already declared", …)` string-diagnostic
18590 // path expressed no through-line back to the typed error;
18591 // routing every dep-list refusal through one carrier means an
18592 // author reading a `feira add` refusal and a `feira build`
18593 // refusal reaches for the same corrective surface without
18594 // switching diagnostic idioms.
18595 let src = Caixa::template("host");
18596 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
18597 let dep = Dep {
18598 nome: "caixa-teia".to_string(),
18599 versao: "^0.1".to_string(),
18600 fonte: None,
18601 opcional: false,
18602 caracteristicas: Vec::new(),
18603 };
18604 caixa
18605 .push_dep(crate::dep::DepList::Prod, dep.clone())
18606 .expect("first push succeeds");
18607 let dup = Dep {
18608 nome: "caixa-teia".to_string(),
18609 versao: "^0.2".to_string(),
18610 fonte: None,
18611 opcional: false,
18612 caracteristicas: Vec::new(),
18613 };
18614 let err = caixa
18615 .push_dep(crate::dep::DepList::Prod, dup)
18616 .expect_err("second push with same :nome refuses");
18617 assert_eq!(
18618 err,
18619 DepError::DuplicateNome {
18620 nome: "caixa-teia".to_string(),
18621 list: crate::render::DEP_AUTHOR_KEY_DEPS,
18622 }
18623 );
18624 // The refused mutation must not corrupt the target list —
18625 // exactly one entry lives past the refusal, matching the
18626 // canonical single-source-of-truth invariant `Caixa::deps()`
18627 // carries.
18628 assert_eq!(caixa.deps().len(), 1);
18629 }
18630
18631 #[test]
18632 fn push_dep_refuses_dup_on_dev_list_arm_names_deps_dev_key() {
18633 // Peer of the sibling `Prod`-arm dup-refusal pin — the `Dev`
18634 // arm's refusal must carry `DEP_AUTHOR_KEY_DEPS_DEV` in the
18635 // `list` payload so a future author reading the refusal grep's
18636 // for the correct `:deps-dev` block in their `caixa.lisp`,
18637 // not the sibling `:deps` block the runtime closure resolves.
18638 let src = Caixa::template("host");
18639 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
18640 let dep = Dep {
18641 nome: "tatara-check".to_string(),
18642 versao: "*".to_string(),
18643 fonte: None,
18644 opcional: false,
18645 caracteristicas: Vec::new(),
18646 };
18647 caixa
18648 .push_dep(crate::dep::DepList::Dev, dep.clone())
18649 .expect("first push succeeds");
18650 let err = caixa
18651 .push_dep(crate::dep::DepList::Dev, dep)
18652 .expect_err("second push with same :nome refuses");
18653 assert!(matches!(
18654 err,
18655 DepError::DuplicateNome {
18656 ref nome,
18657 list,
18658 } if nome == "tatara-check"
18659 && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
18660 ));
18661 }
18662
18663 #[test]
18664 fn push_dep_allows_same_nome_across_prod_and_dev_lists() {
18665 // The within-list dup check is scoped to the target arm — a
18666 // caixa may legitimately carry the same `:nome` under both
18667 // `:deps` and `:deps-dev` (though the substrate's peer
18668 // [`crate::Caixa::validate_deps`] walk still refuses the
18669 // shape at parse time; the mutation-site refusal is scoped to
18670 // the mutation-site's list to match the peer parse-time
18671 // per-list [`crate::render::insert_first_seen`] discipline).
18672 // The two arms hold independent seen-sets.
18673 let src = Caixa::template("host");
18674 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
18675 let dep_prod = Dep {
18676 nome: "shared".to_string(),
18677 versao: "^0.1".to_string(),
18678 fonte: None,
18679 opcional: false,
18680 caracteristicas: Vec::new(),
18681 };
18682 let dep_dev = Dep {
18683 nome: "shared".to_string(),
18684 versao: "*".to_string(),
18685 fonte: None,
18686 opcional: false,
18687 caracteristicas: Vec::new(),
18688 };
18689 caixa
18690 .push_dep(crate::dep::DepList::Prod, dep_prod)
18691 .expect("push into :deps succeeds");
18692 caixa
18693 .push_dep(crate::dep::DepList::Dev, dep_dev)
18694 .expect("push same :nome into :deps-dev succeeds");
18695 assert_eq!(caixa.deps().len(), 1);
18696 assert_eq!(caixa.deps_dev().len(), 1);
18697 }
18698
18699 #[test]
18700 fn deps_of_prod_returns_the_deps_slot_verbatim() {
18701 // The `Prod` arm of the typed-dispatch [`Caixa::deps_of`] read
18702 // accessor must project onto the runtime-closure `:deps` slot —
18703 // element-equal and length-equal to the sibling per-slot
18704 // [`Caixa::deps`] accessor's return over every per-caixa fixture.
18705 // A future arm that regressed to `self.deps_dev()` on the `Prod`
18706 // path would silently reroute every downstream typed-dispatch
18707 // walker (the [`Caixa::validate_deps`] per-list
18708 // [`crate::render::insert_first_seen`] dedup walk, any future
18709 // per-axis-parametrised consumer) into the sibling dev-only
18710 // closure and this pin refuses that regression.
18711 let src = Caixa::template("host");
18712 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
18713 assert_eq!(caixa.deps_of(crate::dep::DepList::Prod), caixa.deps());
18714 assert_eq!(caixa.deps_of(crate::dep::DepList::Prod).len(), 0);
18715 let dep = Dep {
18716 nome: "caixa-teia".to_string(),
18717 versao: "^0.1".to_string(),
18718 fonte: None,
18719 opcional: false,
18720 caracteristicas: Vec::new(),
18721 };
18722 caixa
18723 .push_dep(crate::dep::DepList::Prod, dep.clone())
18724 .expect("push into :deps succeeds");
18725 assert_eq!(caixa.deps_of(crate::dep::DepList::Prod), caixa.deps());
18726 assert_eq!(caixa.deps_of(crate::dep::DepList::Prod).len(), 1);
18727 assert_eq!(
18728 caixa.deps_of(crate::dep::DepList::Prod)[0].nome(),
18729 "caixa-teia"
18730 );
18731 }
18732
18733 #[test]
18734 fn deps_of_dev_returns_the_deps_dev_slot_verbatim() {
18735 // Peer of the sibling `Prod`-arm pin — the `Dev` arm of
18736 // [`Caixa::deps_of`] must project onto the dev-only-closure
18737 // `:deps-dev` slot, element-equal and length-equal to the
18738 // sibling per-slot [`Caixa::deps_dev`] accessor's return. A
18739 // future regression that inverted the two arms would silently
18740 // route every dev-list walker onto the runtime closure and this
18741 // pin catches it before the drift ships.
18742 let src = Caixa::template("host");
18743 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
18744 assert_eq!(caixa.deps_of(crate::dep::DepList::Dev), caixa.deps_dev());
18745 assert_eq!(caixa.deps_of(crate::dep::DepList::Dev).len(), 0);
18746 let dep = Dep {
18747 nome: "tatara-check".to_string(),
18748 versao: "*".to_string(),
18749 fonte: None,
18750 opcional: false,
18751 caracteristicas: Vec::new(),
18752 };
18753 caixa
18754 .push_dep(crate::dep::DepList::Dev, dep)
18755 .expect("push into :deps-dev succeeds");
18756 assert_eq!(caixa.deps_of(crate::dep::DepList::Dev), caixa.deps_dev());
18757 assert_eq!(caixa.deps_of(crate::dep::DepList::Dev).len(), 1);
18758 assert_eq!(
18759 caixa.deps_of(crate::dep::DepList::Dev)[0].nome(),
18760 "tatara-check"
18761 );
18762 }
18763
18764 #[test]
18765 fn deps_of_exhaustive_over_dep_list_all_covers_the_two_slots() {
18766 // Composition pin: iterating [`crate::dep::DepList::ALL`] through
18767 // [`Caixa::deps_of`] must land on the same two-slot partition the
18768 // per-slot [`Caixa::deps`] / [`Caixa::deps_dev`] accessors
18769 // expose — the canonical dispatch a future per-axis-parametrised
18770 // walker (a future `feira app graph` per-list dep summary, a
18771 // future M4 per-cluster dev-closure-audit overlay the CR
18772 // materializer resolves per-CR) reads through. Prior to the
18773 // lift the two-block iteration lived open-coded at every walker,
18774 // so a future third dep-list axis (`:deps-build`, per CAIXA-SDLC
18775 // §I) would have had to grow a third block at every consumer.
18776 // A regression that dropped the `Dev` arm from `ALL` would flip
18777 // the collected pairs to `[(":deps", &[])]` alone and this pin
18778 // refuses that shape.
18779 let src = Caixa::template("host");
18780 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
18781 let prod_dep = Dep {
18782 nome: "caixa-teia".to_string(),
18783 versao: "^0.1".to_string(),
18784 fonte: None,
18785 opcional: false,
18786 caracteristicas: Vec::new(),
18787 };
18788 let dev_dep = Dep {
18789 nome: "tatara-check".to_string(),
18790 versao: "*".to_string(),
18791 fonte: None,
18792 opcional: false,
18793 caracteristicas: Vec::new(),
18794 };
18795 caixa
18796 .push_dep(crate::dep::DepList::Prod, prod_dep)
18797 .expect("push into :deps succeeds");
18798 caixa
18799 .push_dep(crate::dep::DepList::Dev, dev_dep)
18800 .expect("push into :deps-dev succeeds");
18801 let collected: Vec<(&'static str, usize, &str)> = crate::dep::DepList::ALL
18802 .iter()
18803 .map(|&list| {
18804 let slice = caixa.deps_of(list);
18805 (list.as_str(), slice.len(), slice[0].nome())
18806 })
18807 .collect();
18808 assert_eq!(
18809 collected,
18810 vec![
18811 (crate::render::DEP_AUTHOR_KEY_DEPS, 1, "caixa-teia"),
18812 (crate::render::DEP_AUTHOR_KEY_DEPS_DEV, 1, "tatara-check"),
18813 ]
18814 );
18815 }
18816
18817 #[test]
18818 fn validate_deps_iterates_through_dep_list_all_via_deps_of() {
18819 // Composition pin: the [`Caixa::validate_deps`] parse-time gate
18820 // must route its per-list [`crate::render::insert_first_seen`]
18821 // dedup walk through [`Caixa::deps_of`] + [`crate::dep::DepList::ALL`]
18822 // rather than the pre-lift open-coded two-block iteration over
18823 // `self.deps()` + `self.deps_dev()`. A regression that dropped
18824 // one arm (e.g. hand-inlining `self.deps()` alone) would silently
18825 // stop refusing within-list dups on the sibling arm; a
18826 // regression that flipped the arm-to-list-key mapping
18827 // (`Dev => DEP_AUTHOR_KEY_DEPS`) would silently mislabel the
18828 // diagnostic surface. Both drifts surface here through a paired
18829 // duplicate-name refusal per arm plus an offending-list-key
18830 // check on the emitted [`DepError::DuplicateNome`] carrier.
18831 for &list in crate::dep::DepList::ALL {
18832 let src = Caixa::template("host");
18833 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
18834 let dup = Dep {
18835 nome: "twin".to_string(),
18836 versao: "^0.1".to_string(),
18837 fonte: None,
18838 opcional: false,
18839 caracteristicas: Vec::new(),
18840 };
18841 match list {
18842 crate::dep::DepList::Prod => {
18843 caixa.deps.push(dup.clone());
18844 caixa.deps.push(dup);
18845 }
18846 crate::dep::DepList::Dev => {
18847 caixa.deps_dev.push(dup.clone());
18848 caixa.deps_dev.push(dup);
18849 }
18850 }
18851 let err = caixa
18852 .validate_deps()
18853 .expect_err("within-list duplicate :nome must refuse");
18854 assert_eq!(
18855 err,
18856 DepError::DuplicateNome {
18857 nome: "twin".to_string(),
18858 list: list.as_str(),
18859 },
18860 "validate_deps on {list} arm must emit \
18861 DepError::DuplicateNome carrying the arm's own \
18862 as_str() diagnostic — the arm-to-list-key mapping \
18863 flowed through DepList::ALL + Caixa::deps_of"
18864 );
18865 }
18866 }
18867
18868 #[test]
18869 fn caixa_licenca_default_pins_canonical_mit_byte() {
18870 // Bridge-arm pin: [`CAIXA_LICENCA_DEFAULT`] resolves to the
18871 // canonical SPDX-`"MIT"` byte today, the same license expression
18872 // every peer substrate-side consumer of the author-omitted
18873 // `:licenca` slot ([`caixa-helm`]'s `build_readme` fallback arm at
18874 // `caixa-helm/src/lib.rs`, the future M4
18875 // `mesh.pleme.io/v1alpha1/Aplicacao` CR materializer's per-CR
18876 // `Chart.yaml annotations["artifacthub.io/license"]` emitter this
18877 // crate's [`Caixa::validate_licenca`] docstring roadmap already
18878 // names as the second consumer) fills into its per-consumer
18879 // README/annotation emit site. Pin the literal here (peer with the
18880 // [`crate::version::DEFAULT_PUBLISH_TAG_PREFIX`] /
18881 // [`crate::version::DEFAULT_GIT_REMOTE`] /
18882 // [`crate::version::DEFAULT_PLEME_GIT_ORG`] canonical-literal pins
18883 // on the sibling lifted-constant surfaces) so a future
18884 // substrate-side license-fallback rebrand surfaces here as a
18885 // coordinated edit-point: the sibling caixa-helm
18886 // `build_readme_license_line_routes_through_lifted_caixa_licenca_default`
18887 // pinning test already pins the equality at the renderer-emit
18888 // axis; this pin closes the second coordinate of the pair by
18889 // anchoring the lifted constant's current byte to the canonical
18890 // CAIXA-SDLC §I license scaffold's documented shape.
18891 assert_eq!(CAIXA_LICENCA_DEFAULT, "MIT");
18892 }
18893}