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 ///
2221 /// Declared `pub const fn` — every operator in the body is already
2222 /// `const`-callable (the [`crate::dep::DepList`] enum is a plain
2223 /// closed-set `#[derive(Copy)]` discriminator so the `match` arms
2224 /// are const-evaluable, and each arm forwards through the sibling
2225 /// `pub const fn` [`Self::deps`] / [`Self::deps_dev`] per-slot
2226 /// slice accessor). Pinned load-bearing by the paired
2227 /// [`caixa_deps_of_is_const_fn`][pin] wrapper test (a
2228 /// `const fn deps_of_via_const_fn(c: &Caixa, l: DepList) -> &[Dep]`
2229 /// that forwards through this accessor) — any future accidental
2230 /// downgrade to non-`const` fails the wrapper at caixa-core build
2231 /// time with E0015 (`cannot call non-const method`), strictly
2232 /// stronger than a runtime `assert!` and side-stepping the
2233 /// destructor-in-const restriction the `Caixa` fixture's owning
2234 /// carriers rule out on the direct-`const _: () = assert!(…)`
2235 /// residence. Peer of the sibling per-`Dep` outer-accessor
2236 /// family's parallel `const`-eval-surface pass and of the outer-
2237 /// `Caixa` slice-return accessor family's earlier pass (231a968)
2238 /// — same "one canonical dispatch per axis, `const`-eval posture
2239 /// pinned at the substrate primitive, thin projections at each
2240 /// consumer" discipline extended onto the outer-`Caixa`
2241 /// typed-dispatch read surface on the [`DepList`]-keyed dep-list
2242 /// axis.
2243 ///
2244 /// [DepList]: crate::dep::DepList
2245 /// [pin]: tests::caixa_deps_of_is_const_fn
2246 #[must_use]
2247 pub const fn deps_of(&self, list: crate::dep::DepList) -> &[Dep] {
2248 match list {
2249 crate::dep::DepList::Prod => self.deps(),
2250 crate::dep::DepList::Dev => self.deps_dev(),
2251 }
2252 }
2253
2254 /// Substrate-canonical per-[`Caixa`] typed-mutation dispatch every
2255 /// consumer that appends to one of the two dep-list axes keys off
2256 /// — routes the `(list: DepList, dep: Dep)` tuple through one typed
2257 /// method on the substrate primitive rather than the prior
2258 /// `feira add`-side open-coded `if self.dev { &mut caixa.deps_dev }
2259 /// else { &mut caixa.deps }` inline dispatch + open-coded
2260 /// `.iter().any(|d| d.nome == …)` dup-check cascade. Refuses the
2261 /// mutation with the canonical typed [`DepError::DuplicateNome`] on
2262 /// a within-list name collision — the same `list: &'static str`
2263 /// diagnostic shape [`Self::validate_deps`]'s per-list
2264 /// [`crate::render::insert_first_seen`] walk raises on the peer
2265 /// parse-time within-list dedup axis, so a future author reading a
2266 /// `feira add` refusal and a `feira build` refusal reaches for the
2267 /// same corrective surface without switching diagnostic idioms.
2268 ///
2269 /// The two-arm [`crate::dep::DepList`] enum is the substrate's
2270 /// closed-set typed carrier for the "runtime-closure `:deps` vs
2271 /// dev-only-closure `:deps-dev`" axis every dep-list consumer
2272 /// dispatches on — the compiler-checked exhaustiveness on the
2273 /// enum's `match` arms is the build-time guarantee that no future
2274 /// per-list mutation-site regresses to a bare-`bool`-flag
2275 /// (`is_dev: bool`) inline dispatch that a future third
2276 /// dep-list axis (a `:deps-build` build-only closure once the
2277 /// substrate grows cross-artifact heterogeneous dep-graphs, per
2278 /// CAIXA-SDLC §I) would silently split at every consumer.
2279 ///
2280 /// Same "one typed dispatch on the substrate primitive, thin
2281 /// projections at each consumer" discipline the sibling per-slot
2282 /// read accessors ([`Self::deps`] ad34b4e, [`Self::deps_dev`],
2283 /// [`Self::nome`] e6b7d97, [`Self::versao`], [`Self::kind`])
2284 /// carry — extended onto the outer-[`Caixa`] typed-mutation surface,
2285 /// the substrate's first typed-mutation dispatch on the top-level
2286 /// manifest. The prior `feira add` open-coded `&mut caixa.deps` /
2287 /// `&mut caixa.deps_dev` inline field-access + `bail!` string-
2288 /// diagnostic path routed no through-line back to the typed slot,
2289 /// so a future extension of either dep-list axis to a richer author
2290 /// surface (a per-cluster override the operator pins through a
2291 /// future `:placement`-scoped dep-list slot the CAIXA-SDLC §I
2292 /// roadmap acknowledges, an M4
2293 /// `mesh.pleme.io/v1alpha1/Caixa` CR materializer's per-CR
2294 /// admission-webhook that normalized the list at admission time)
2295 /// would have had to be threaded through the `feira add` mutation
2296 /// site in lockstep with every read consumer or one path would
2297 /// silently disagree with the other on which list a given dep lands
2298 /// in. Lifting the resolution rule to a typed method on the
2299 /// substrate primitive means every downstream dep-list-mutating
2300 /// consumer of the top-level manifest reaches for exactly one typed
2301 /// dispatch — the resolver's accept-set migrates as a unit on any
2302 /// future axis addition.
2303 ///
2304 /// # Errors
2305 ///
2306 /// Returns [`DepError::DuplicateNome`] with `list = list.as_str()`
2307 /// when another entry in the same list already carries the same
2308 /// `:nome` — the mutation is refused and the caller can surface the
2309 /// typed diagnostic to the author (the `feira add` verb routes the
2310 /// error through `anyhow::Error::from`, which preserves the
2311 /// canonical `#[error(...)]`-templated diagnostic body).
2312 pub fn push_dep(&mut self, list: crate::dep::DepList, dep: Dep) -> Result<(), DepError> {
2313 let target = match list {
2314 crate::dep::DepList::Prod => &mut self.deps,
2315 crate::dep::DepList::Dev => &mut self.deps_dev,
2316 };
2317 if target.iter().any(|d| d.nome() == dep.nome()) {
2318 return Err(DepError::DuplicateNome {
2319 nome: dep.nome().to_string(),
2320 list: list.as_str(),
2321 });
2322 }
2323 target.push(dep);
2324 Ok(())
2325 }
2326
2327 /// Substrate-canonical per-`Caixa` `:limits` M2 typed-slot outer-
2328 /// composite Lunatic-per-process wasm32-sandboxing-composite optional-
2329 /// composite-reference accessor every consumer of the top-level
2330 /// manifest's per-Servico [`LimitsSpec`] outer-composite reader keys
2331 /// off — returns the author-declared `:limits` typed composite
2332 /// verbatim as an `Option<&LimitsSpec>` reference over the same
2333 /// backing storage the raw `self.limits.as_ref()` field access
2334 /// borrows from, with `None` naming the "no `:limits` block
2335 /// authored — every per-axis Lunatic-sandbox cap defers to the
2336 /// wasm-engine-default arm named on the per-axis
2337 /// [`LimitsSpec::memory`] / [`LimitsSpec::fuel`] /
2338 /// [`LimitsSpec::wall_clock`] / [`LimitsSpec::cpu`] scalar-accessor
2339 /// docstrings" partition every downstream Servico-M2-overlay
2340 /// emitter treats as "emit nothing" and the sibling
2341 /// [`crate::StandardLayout::verify`] per-`:limits` shape gate
2342 /// treats as "skip the per-axis
2343 /// [`crate::LimitsError::MemoryZero`] / `MemoryBelowWasm32Page` /
2344 /// `FuelZero` / `WallClockZero` / `CpuZero` refusal cascade".
2345 ///
2346 /// The outer `:limits` slot carries the M2 Servico-runtime typed
2347 /// composite — the load-bearing container of every Lunatic-shaped
2348 /// per-process wasm32-sandbox cap axis every long-running wasm
2349 /// component's runtime dispatches on (INSPIRATIONS §III.1 —
2350 /// Lunatic per-process linear-memory / fuel / wall-clock /
2351 /// millicore cap primitives translated onto pleme-io's typed
2352 /// `:limits :memory` / `:limits :fuel` / `:limits :wall-clock` /
2353 /// `:limits :cpu` sub-slot axes; CAIXA-SDLC §II — the typed-M2
2354 /// slot algebra the wasm-engine + `pleme-computeunit` Helm-library
2355 /// chart both fan on). Every per-`:limits` axis threads through a
2356 /// lifted per-slot accessor on the [`LimitsSpec`] type: the
2357 /// [`LimitsSpec::memory`] wasm32 linear-memory byte-cap scalar
2358 /// accessor, the [`LimitsSpec::fuel`] wasmtime fuel-cap scalar
2359 /// accessor, the [`LimitsSpec::wall_clock`] per-call wall-clock
2360 /// deadline scalar accessor, and the [`LimitsSpec::cpu`]
2361 /// K8s-millicore soft-CPU-share scalar accessor. Every downstream
2362 /// consumer that reaches for a limits axis first passes through
2363 /// this outer accessor onto the composite and then dispatches
2364 /// onto the per-axis accessor — the two-level dispatch means
2365 /// every per-`:limits` reader now routes through a typed dispatch
2366 /// on the substrate primitive at both altitudes.
2367 ///
2368 /// Prior to this lift the `.limits` `Option<LimitsSpec>` composite
2369 /// was accessed inline at three production sites — the
2370 /// [`crate::StandardLayout::verify`] per-`:limits` shape gate's
2371 /// `if let Some(l) = &caixa.limits { … }` traversal head
2372 /// (caixa-core/src/layout.rs:882, which drives the per-axis
2373 /// refusal cascade on the composite: the `LimitsError::MemoryZero`
2374 /// / `MemoryBelowWasm32Page` / `MemoryExceedsWasm32Max` /
2375 /// `FuelZero` / `FuelExceedsMax` / `WallClockZero` /
2376 /// `WallClockExceedsMax` / `CpuZero` / `CpuExceedsMax` refusals
2377 /// [`LimitsSpec::validate`] fans onto), the
2378 /// [`crate::render::servico_m2_overlay`] per-Servico M2 overlay
2379 /// emitter's `if let Some(limits) = &caixa.limits { … }` traversal
2380 /// head (caixa-core/src/render.rs:18504, which drives the
2381 /// `M2_KEY_LIMITS`-keyed `limits.is_empty()`-gated `serde_yaml`
2382 /// projection every `caixa-helm` / `caixa-flux` Servico values-
2383 /// block emitter fans on), and the
2384 /// [`Self::declared_servico_slots`] per-Servico M2 declared-slot-
2385 /// set enumerator's `self.limits.is_some()` presence probe
2386 /// (caixa-core/src/manifest.rs:1788, which drives the
2387 /// `M2_AUTHOR_KEY_LIMITS` kebab-case author-label push every
2388 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
2389 /// gate reads) — three open-coded outer-field accesses that
2390 /// expressed no compile-time link back to the typed slot at the
2391 /// [`Caixa`] altitude. A future extension of the `:limits` outer
2392 /// axis to a richer author surface (a multi-`:limits` list the M4
2393 /// CR materializer resolves per-CR at admission time so a Servico
2394 /// can expose a compute-heavy + IO-heavy limits pair, a per-
2395 /// cluster `:limits-overrides` slot the operator pins so a
2396 /// cluster-specific policy can tighten a caixa-declared cap
2397 /// without re-authoring the `caixa.lisp`, a promotion of the
2398 /// plain `Option<LimitsSpec>` to a richer
2399 /// `{static, dynamic}` partition once the wasm-engine's runtime-
2400 /// resolved dynamic-cap surface lands) would have had to be
2401 /// threaded through all three open-coded copies in lockstep or
2402 /// one consumer would silently disagree with the peers on which
2403 /// limits composite a given Caixa resolves to — the layout gate's
2404 /// per-axis bracket-dispatch seed reading the raw slot while the
2405 /// peer `servico_m2_overlay` emitter read an operator-resolved
2406 /// slot would silently split the build-time sandbox-shape gate
2407 /// from the runtime `ComputeUnit` CR emission gate, a three-
2408 /// consumer split at the layout gate, the M2 overlay emitter, and
2409 /// the declared-slot enumerator far from the source `caixa.lisp`
2410 /// with no field naming the limits-drift root cause. Lifting the
2411 /// resolution rule to a typed method on the substrate primitive
2412 /// means every downstream consumer of the caixa's per-`Caixa`
2413 /// Lunatic-sandboxing outer-composite surface reaches for exactly
2414 /// one typed dispatch — the resolver's accept-set migrates as a
2415 /// unit on any future axis addition.
2416 ///
2417 /// First outer top-level [`Caixa`] `Option<&Composite>`-return
2418 /// composite-reference accessor — opens the outer-`Caixa`
2419 /// `Option<&Composite>` composite-reference projection pattern the
2420 /// sibling per-`Caixa` `:behavior` [`crate::BehaviorSpec`] /
2421 /// `:politicas` [`crate::aplicacao::MeshPolicy`] / `:placement`
2422 /// [`crate::aplicacao::Placement`] / `:entrada`
2423 /// [`crate::aplicacao::Entrada`] future outer-composite lifts
2424 /// fold on. Peer of the M3 mesh-slot outer-composite family the
2425 /// sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
2426 /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2427 /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2428 /// accessors already close on the outer [`crate::AplicacaoSpec`]
2429 /// altitude — extends that "one typed dispatch on the substrate
2430 /// primitive, thin projections at each consumer" discipline onto
2431 /// the outer top-level [`Caixa`] altitude, opening the M2 Servico-
2432 /// runtime slot family's outer-composite axis. Returns
2433 /// `Option<&LimitsSpec>` (not the owning composite by copy or
2434 /// clone) because every downstream consumer of the limits
2435 /// composite treats it as a read-only per-axis dispatch source —
2436 /// the reference-view is the narrowest borrow that supports every
2437 /// present + roadmapped consumer (per-axis accessor dispatch,
2438 /// `.is_empty()`-gated overlay projection, presence-probe early
2439 /// return on the "author-omitted `:limits` ⇒ engine-default
2440 /// applies" partition) without cloning the composite through
2441 /// every consumer's fast path. The `Option` half of the return-
2442 /// type preserves the load-bearing "author-omitted `:limits` ⇒
2443 /// engine-default applies" partition (not a default composite the
2444 /// downstream must reject on emptiness) — the accessor projects
2445 /// the raw `Option<LimitsSpec>` slot's presence bit through the
2446 /// reference-return unchanged. Named `limits()` to match the
2447 /// storage field's name verbatim and the tatara-lisp author-
2448 /// surface term (`:limits`) the field's own docstring already
2449 /// carries.
2450 #[must_use]
2451 pub const fn limits(&self) -> Option<&LimitsSpec> {
2452 self.limits.as_ref()
2453 }
2454
2455 /// Substrate-canonical per-`Caixa` `:behavior` M2 typed-slot outer-
2456 /// composite OTP-`gen_server`-shaped callback-table optional-
2457 /// composite-reference accessor every consumer of the top-level
2458 /// manifest's per-Servico [`BehaviorSpec`] outer-composite reader
2459 /// keys off — returns the author-declared `:behavior` typed
2460 /// composite verbatim as an `Option<&BehaviorSpec>` reference over
2461 /// the same backing storage the raw `self.behavior.as_ref()` field
2462 /// access borrows from, with `None` naming the "no `:behavior`
2463 /// block authored — every per-callback OTP-shaped hook defers to
2464 /// the wasm-engine's runtime default arm named on the per-axis
2465 /// [`BehaviorSpec::on_init`] / [`BehaviorSpec::on_call`] /
2466 /// [`BehaviorSpec::on_cast`] / [`BehaviorSpec::on_info`] /
2467 /// [`BehaviorSpec::on_state_change`] /
2468 /// [`BehaviorSpec::on_terminate`] scalar-accessor docstrings"
2469 /// partition every downstream Servico-M2-overlay emitter treats as
2470 /// "emit nothing" and the sibling [`crate::StandardLayout::verify`]
2471 /// per-`:behavior` shape gate treats as "skip the per-arm
2472 /// [`crate::behavior::BehaviorError`] refusal cascade + the
2473 /// per-callback on-disk `MissingEntry` existence check".
2474 ///
2475 /// The outer `:behavior` slot carries the M2 Servico-runtime typed
2476 /// composite — the load-bearing container of every OTP-shaped
2477 /// per-Servico lifecycle-callback path axis every long-running wasm
2478 /// component's runtime dispatches on (INSPIRATIONS §II.3 — Erlang/
2479 /// OTP `gen_server:init/1` / `handle_call/3` / `handle_cast/2` /
2480 /// `handle_info/2` / `code_change/3` / `terminate/2` primitives
2481 /// translated onto pleme-io's typed `:behavior :on-init` /
2482 /// `:on-call` / `:on-cast` / `:on-info` / `:on-state-change` /
2483 /// `:on-terminate` sub-slot axes; CAIXA-SDLC §II — the typed-M2
2484 /// slot algebra the wasm-engine + `pleme-computeunit` Helm-library
2485 /// chart both fan on). Every per-`:behavior` axis threads through a
2486 /// lifted per-callback accessor on the [`BehaviorSpec`] type
2487 /// (9b4ecde / d66c702 / 156ddbe / 99616ac / 4846cef / 701add7).
2488 /// Every downstream consumer that reaches for a behavior axis
2489 /// first passes through this outer accessor onto the composite
2490 /// and then dispatches onto the per-callback accessor — the
2491 /// two-level dispatch means every per-`:behavior` reader now
2492 /// routes through a typed dispatch on the substrate primitive at
2493 /// both altitudes.
2494 ///
2495 /// Composes cross-slot with the M2 `:upgrade-from` gate: the
2496 /// [`crate::upgrade::validate_upgrade_from_against_behavior`]
2497 /// cross-slot composition gate at [`crate::StandardLayout::verify`]
2498 /// keys the "per-version `:state-change` instruction must have a
2499 /// `:on-state-change` callback" precondition off this accessor's
2500 /// composite (the callback-side counterpart to the
2501 /// `:upgrade-from :instructions :state-change :script` refusal at
2502 /// the appup-side). Threading that gate's traversal input through
2503 /// this accessor closes the cross-slot invariant on the substrate
2504 /// primitive, not on the raw field.
2505 ///
2506 /// Prior to this lift the `.behavior` `Option<BehaviorSpec>`
2507 /// composite was accessed inline at four production sites — the
2508 /// [`crate::StandardLayout::verify`] per-`:behavior` shape gate's
2509 /// `if let Some(b) = &caixa.behavior { … }` traversal head
2510 /// (caixa-core/src/layout.rs:896, which drives the per-arm
2511 /// `BehaviorError` refusal cascade + the per-callback on-disk
2512 /// [`crate::LayoutError::MissingEntry`] existence check under
2513 /// [`crate::render::LAYOUT_MISSING_ENTRY_KIND_BEHAVIOR_CALLBACK`]),
2514 /// the [`crate::upgrade::validate_upgrade_from_against_behavior`]
2515 /// cross-slot composition gate's `caixa.behavior.as_ref()`
2516 /// traversal-input feed (caixa-core/src/layout.rs:1008, which
2517 /// drives the `:state-change` ↔ `:on-state-change` precondition
2518 /// refusal), the [`crate::render::servico_m2_overlay`] per-Servico
2519 /// M2 overlay emitter's `if let Some(behavior) = &caixa.behavior
2520 /// { … }` traversal head (caixa-core/src/render.rs:18513, which
2521 /// drives the `M2_KEY_BEHAVIOR`-keyed `behavior.is_empty()`-gated
2522 /// `serde_yaml` projection every `caixa-helm` / `caixa-flux`
2523 /// Servico values-block emitter fans on), and the
2524 /// [`Self::declared_servico_slots`] per-Servico M2 declared-slot-
2525 /// set enumerator's `self.behavior.is_some()` presence probe
2526 /// (caixa-core/src/manifest.rs:1919, which drives the
2527 /// `M2_AUTHOR_KEY_BEHAVIOR` kebab-case author-label push every
2528 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
2529 /// gate reads) — four open-coded outer-field accesses that
2530 /// expressed no compile-time link back to the typed slot at the
2531 /// [`Caixa`] altitude. A future extension of the `:behavior`
2532 /// outer axis to a richer author surface (a per-callback overlay
2533 /// resolver the operator materializes at admission time so a
2534 /// cluster-specific policy can inject a per-callback tracing
2535 /// interceptor without re-authoring the `caixa.lisp`, a promotion
2536 /// of the plain `Option<BehaviorSpec>` to a richer `{static,
2537 /// dynamic}` partition once a runtime-resolved behavior-swap
2538 /// surface lands, the M4 per-callback middleware chain the
2539 /// caixa-operator's per-Servico admission webhook keys off) would
2540 /// have had to be threaded through all four open-coded copies in
2541 /// lockstep or one consumer would silently disagree with the
2542 /// peers on which behavior composite a given Caixa resolves to —
2543 /// the layout gate's per-callback existence-check seed reading
2544 /// the raw slot while the peer `servico_m2_overlay` emitter read
2545 /// an operator-resolved slot would silently split the build-time
2546 /// callback-shape gate from the runtime `ComputeUnit` CR emission
2547 /// gate from the cross-slot `:state-change` composition gate from
2548 /// the M2 declared-slot enumerator, a four-consumer split far
2549 /// from the source `caixa.lisp` with no field naming the
2550 /// behavior-drift root cause. Lifting the resolution rule to a
2551 /// typed method on the substrate primitive means every downstream
2552 /// consumer of the caixa's per-`Caixa` OTP-callback-table outer-
2553 /// composite surface reaches for exactly one typed dispatch — the
2554 /// resolver's accept-set migrates as a unit on any future axis
2555 /// addition.
2556 ///
2557 /// Second outer top-level [`Caixa`] `Option<&Composite>`-return
2558 /// composite-reference accessor — sibling to the opening
2559 /// [`Self::limits`] (b2bd9d7) accessor on the outer-`Caixa`
2560 /// `Option<&Composite>` composite-reference sub-family, extends
2561 /// the "one typed dispatch on the substrate primitive, thin
2562 /// projections at each consumer" discipline onto the second of
2563 /// the three M2 Servico-runtime slots. The remaining
2564 /// `Option<&Composite>` axes at the outer top-level [`Caixa`]
2565 /// altitude — the M3 mesh-slot family (`:politicas`,
2566 /// `:placement`, `:entrada` — already closed on the inner
2567 /// [`crate::AplicacaoSpec`] altitude via 534dc21 / 9abb8f0 /
2568 /// d32111c) — remain the future sibling lifts on the outer
2569 /// top-level projection. Returns `Option<&BehaviorSpec>` (not
2570 /// the owning composite by copy or clone) because every
2571 /// downstream consumer of the behavior composite treats it as a
2572 /// read-only per-callback dispatch source — the reference-view is
2573 /// the narrowest borrow that supports every present + roadmapped
2574 /// consumer (per-callback accessor dispatch, `.is_empty()`-gated
2575 /// overlay projection, presence-probe early return on the
2576 /// "author-omitted `:behavior` ⇒ runtime-default applies"
2577 /// partition, cross-slot `:state-change` composition input)
2578 /// without cloning the composite through every consumer's fast
2579 /// path. The `Option` half of the return-type preserves the
2580 /// load-bearing "author-omitted `:behavior` ⇒ runtime-default
2581 /// applies" partition (not a default composite the downstream
2582 /// must reject on emptiness) — the accessor projects the raw
2583 /// `Option<BehaviorSpec>` slot's presence bit through the
2584 /// reference-return unchanged. Named `behavior()` to match the
2585 /// storage field's name verbatim and the tatara-lisp author-
2586 /// surface term (`:behavior`) the field's own docstring already
2587 /// carries.
2588 #[must_use]
2589 pub const fn behavior(&self) -> Option<&crate::BehaviorSpec> {
2590 self.behavior.as_ref()
2591 }
2592
2593 /// Substrate-canonical per-`Caixa` `:politicas` M3 mesh-slot outer-
2594 /// composite MESH-COMPOSITION-shaped mesh-policy optional-composite-
2595 /// reference accessor every consumer of the top-level manifest's
2596 /// per-Aplicacao [`crate::aplicacao::MeshPolicy`] outer-composite
2597 /// reader keys off — returns the author-declared `:politicas` typed
2598 /// composite verbatim as an `Option<&MeshPolicy>` reference over the
2599 /// same backing storage the raw `self.politicas.as_ref()` field
2600 /// access borrows from, with `None` naming the "no `:politicas`
2601 /// block authored — every per-axis mesh-policy scalar defers to the
2602 /// cluster-default arm named on the per-axis
2603 /// [`crate::aplicacao::MeshPolicy::timeout`] /
2604 /// [`crate::aplicacao::MeshPolicy::retries`] /
2605 /// [`crate::aplicacao::MeshPolicy::circuit_breaker`] /
2606 /// [`crate::aplicacao::MeshPolicy::mtls_required`] /
2607 /// [`crate::aplicacao::MeshPolicy::rate_limit`] scalar-accessor
2608 /// docstrings" partition every downstream caixa-mesh /
2609 /// caixa-flux / caixa-helm Aplicacao-artifact emitter treats as
2610 /// "emit no per-`:politicas` overlay" and the sibling
2611 /// [`Self::aplicacao_view`] Aplicacao-composition seed folds through
2612 /// the [`crate::aplicacao::MeshPolicy::default`] cluster-default
2613 /// arm.
2614 ///
2615 /// The outer `:politicas` slot carries the M3 mesh-slot per-
2616 /// Aplicacao typed composite — the load-bearing container of every
2617 /// mesh-level policy axis every Cilium NetworkPolicy / Gateway API
2618 /// v1.x HTTPRoute / future M4 per-edge policy overlay emitter fans
2619 /// on (MESH-COMPOSITION §III.2 — the Aplicacao's typed mesh-policy
2620 /// composite; §V — the "no infinite blocking" per-call deadline +
2621 /// "sandboxing-by-default" mTLS-enforcement CSE invariants; §III.3
2622 /// — the typed inter-Servico contrato-edge overlay the per-`(:de,
2623 /// :para)` mesh renderer keys off). Every per-`:politicas` axis
2624 /// threads through a lifted per-slot accessor on the
2625 /// [`crate::aplicacao::MeshPolicy`] type: the
2626 /// [`crate::aplicacao::MeshPolicy::mtls_required`] (c0110f1) Cilium
2627 /// mTLS-enforcement toggle, the
2628 /// [`crate::aplicacao::MeshPolicy::retries`] (bdfb399) transient-
2629 /// failure retry budget, the [`crate::aplicacao::MeshPolicy::timeout`]
2630 /// (7073d0f) Gateway-API per-call deadline, the
2631 /// [`crate::aplicacao::MeshPolicy::circuit_breaker`] (b0e741a)
2632 /// Envoy-outlier-detection composite. Every downstream consumer
2633 /// that reaches for a mesh-policy axis first passes through this
2634 /// outer accessor onto the composite and then dispatches onto the
2635 /// per-axis accessor — the two-level dispatch means every per-
2636 /// `:politicas` reader now routes through a typed dispatch on the
2637 /// substrate primitive at both altitudes.
2638 ///
2639 /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2640 /// seed: the Aplicacao-view builder folds the outer `Option`'s
2641 /// author-omitted arm onto the [`crate::aplicacao::MeshPolicy::default`]
2642 /// cluster-default, so the peer inner [`crate::AplicacaoSpec::politicas`]
2643 /// (534dc21) `&MeshPolicy`-return accessor observes a typed
2644 /// composite whether or not the author declared the outer slot.
2645 /// The outer accessor preserves the "author-omitted vs authored-
2646 /// empty" partition the inner accessor's `is_empty()`-gated
2647 /// renderer overlay collapses — routing the presence bit through
2648 /// this accessor keeps the [`Self::declared_mesh_slots`] M3 kind-
2649 /// coherence enumerator's `M3_AUTHOR_KEY_POLITICAS` push separate
2650 /// from the inner `MeshPolicy::is_empty()`-gated overlay elision.
2651 ///
2652 /// Prior to this lift the `.politicas` `Option<MeshPolicy>`
2653 /// composite was accessed inline at two production sites — the
2654 /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2655 /// `self.politicas.clone().unwrap_or_default()` traversal head
2656 /// (caixa-core/src/manifest.rs:1899, which drives the fold onto
2657 /// the [`crate::aplicacao::MeshPolicy::default`] cluster-default
2658 /// arm the inner [`crate::AplicacaoSpec::politicas`] accessor
2659 /// then observes), and the [`Self::declared_mesh_slots`] M3
2660 /// declared-slot-set enumerator's `self.politicas.is_some()`
2661 /// presence probe (caixa-core/src/manifest.rs:1961, which drives
2662 /// the `M3_AUTHOR_KEY_POLITICAS` kebab-case author-label push
2663 /// every [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
2664 /// coherence gate reads) — two open-coded outer-field accesses
2665 /// that expressed no compile-time link back to the typed slot at
2666 /// the [`Caixa`] altitude. A future extension of the `:politicas`
2667 /// outer axis to a richer author surface (a per-cluster
2668 /// `:politicas-overrides` slot the operator materializes at
2669 /// admission time so a cluster-specific policy can tighten the
2670 /// caixa-declared bound without re-authoring the `caixa.lisp`, a
2671 /// promotion of the plain `Option<MeshPolicy>` to a richer
2672 /// `{static, dynamic}` partition once the M4 per-edge
2673 /// contrato-scoped policy-override surface lands, the M5 traffic-
2674 /// shaping composition the caixa-operator's per-Aplicacao mesh
2675 /// admission webhook keys off) would have had to be threaded
2676 /// through both open-coded copies in lockstep or the Aplicacao-
2677 /// composition seed's default-fold arm would silently disagree
2678 /// with the M3 declared-slot enumerator on which policy composite
2679 /// a given Caixa resolves to — the seed reading an operator-
2680 /// resolved slot while the enumerator's presence probe read the
2681 /// raw slot would silently split the build-time mesh-artifact
2682 /// emission gate from the M3 declared-slot enumerator's kind-
2683 /// coherence gate, a two-consumer split far from the source
2684 /// `caixa.lisp` with no field naming the policy-drift root cause.
2685 /// Lifting the resolution rule to a typed method on the substrate
2686 /// primitive means every downstream consumer of the caixa's per-
2687 /// `Caixa` MESH-COMPOSITION mesh-policy outer-composite surface
2688 /// reaches for exactly one typed dispatch — the resolver's
2689 /// accept-set migrates as a unit on any future axis addition.
2690 ///
2691 /// Third outer top-level [`Caixa`] `Option<&Composite>`-return
2692 /// composite-reference accessor — sibling to the opening
2693 /// [`Self::limits`] (b2bd9d7) and [`Self::behavior`] (35d8b52)
2694 /// accessors on the outer-`Caixa` `Option<&Composite>` composite-
2695 /// reference sub-family, extends the "one typed dispatch on the
2696 /// substrate primitive, thin projections at each consumer"
2697 /// discipline onto the first of the three M3 mesh-slot axes.
2698 /// Peer of the closed inner mesh-slot outer-composite family the
2699 /// sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
2700 /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2701 /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2702 /// accessor pins already close on the inner [`crate::AplicacaoSpec`]
2703 /// altitude — opens the outer top-level [`Caixa`] altitude's M3
2704 /// mesh-slot arm of the composite-reference family the remaining
2705 /// two axes (`:placement`, `:entrada`) fold onto in future
2706 /// sibling lifts. Returns `Option<&MeshPolicy>` (not the owning
2707 /// composite by copy or clone) because every downstream consumer
2708 /// of the mesh-policy composite treats it as a read-only per-axis
2709 /// dispatch source — the reference-view is the narrowest borrow
2710 /// that supports every present + roadmapped consumer (per-axis
2711 /// accessor dispatch, `.is_empty()`-gated overlay projection,
2712 /// presence-probe early return on the "author-omitted `:politicas`
2713 /// ⇒ cluster-default applies" partition, `Aplicacao`-composition
2714 /// seed's default-fold arm) without cloning the composite through
2715 /// every consumer's fast path. The `Option` half of the return-
2716 /// type preserves the load-bearing "author-omitted `:politicas` ⇒
2717 /// cluster-default applies" partition (not a default composite
2718 /// the downstream must reject on emptiness) — the accessor
2719 /// projects the raw `Option<MeshPolicy>` slot's presence bit
2720 /// through the reference-return unchanged. Named `politicas()` to
2721 /// match the storage field's name verbatim and the tatara-lisp
2722 /// author-surface term (`:politicas`) the field's own docstring
2723 /// already carries.
2724 #[must_use]
2725 pub const fn politicas(&self) -> Option<&crate::aplicacao::MeshPolicy> {
2726 self.politicas.as_ref()
2727 }
2728
2729 /// Substrate-canonical per-`Caixa` `:placement` M3 mesh-slot outer-
2730 /// composite MESH-COMPOSITION-shaped distribution optional-composite-
2731 /// reference accessor every consumer of the top-level manifest's
2732 /// per-Aplicacao [`crate::aplicacao::Placement`] outer-composite
2733 /// reader keys off — returns the author-declared `:placement` typed
2734 /// composite verbatim as an `Option<&Placement>` reference over the
2735 /// same backing storage the raw `self.placement.as_ref()` field
2736 /// access borrows from, with `None` naming the "no `:placement`
2737 /// block authored — every per-axis placement scalar defers to the
2738 /// cluster-default arm named on the per-axis
2739 /// [`crate::aplicacao::Placement::estrategia`] /
2740 /// [`crate::aplicacao::Placement::clusters`] /
2741 /// [`crate::aplicacao::Placement::affinity`] /
2742 /// [`crate::aplicacao::Placement::shard_key`] scalar-accessor
2743 /// docstrings" partition every downstream caixa-mesh /
2744 /// caixa-flux / caixa-helm Aplicacao-artifact emitter treats as
2745 /// "emit no per-`:placement` overlay" and the sibling
2746 /// [`Self::aplicacao_view`] Aplicacao-composition seed folds through
2747 /// the [`crate::aplicacao::Placement::default`] cluster-default arm.
2748 ///
2749 /// The outer `:placement` slot carries the M3 mesh-slot per-
2750 /// Aplicacao typed distribution composite — the load-bearing
2751 /// container of every where-does-this-Aplicacao-run axis every
2752 /// caixa-mesh programs.yaml per-cluster distribution overlay /
2753 /// caixa-flux per-Aplicacao GitRepository/HelmRelease fan-out /
2754 /// future M4 per-Aplicacao Akka-style cluster-sharding entity-id
2755 /// resolver emitter fans on (MESH-COMPOSITION §II.4 — the
2756 /// Aplicacao's typed distribution composite; §V CSE invariants —
2757 /// "distribution is a first-class typed composite, not a runtime
2758 /// scheduler hint" the per-axis scalars enforce; §III.3 — the
2759 /// typed inter-Servico contrato-edge overlay the per-cluster
2760 /// mesh renderer keys off). Every per-`:placement` axis threads
2761 /// through a lifted per-slot accessor on the
2762 /// [`crate::aplicacao::Placement`] type: the
2763 /// [`crate::aplicacao::Placement::estrategia`] (921fe1b)
2764 /// MESH-COMPOSITION distribution-strategy scalar, the
2765 /// [`crate::aplicacao::Placement::clusters`] (a6e18d7) per-cluster
2766 /// distribution-target slice, the [`crate::aplicacao::Placement::affinity`]
2767 /// M3-Adaptive-compression-hint optional-scalar, and the
2768 /// [`crate::aplicacao::Placement::shard_key`] (7cd2a28) Akka-cluster-
2769 /// sharding extractor-expression optional-scalar. Every downstream
2770 /// consumer that reaches for a placement axis first passes through
2771 /// this outer accessor onto the composite and then dispatches onto
2772 /// the per-axis accessor — the two-level dispatch means every per-
2773 /// `:placement` reader now routes through a typed dispatch on the
2774 /// substrate primitive at both altitudes.
2775 ///
2776 /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2777 /// seed: the Aplicacao-view builder folds the outer `Option`'s
2778 /// author-omitted arm onto the [`crate::aplicacao::Placement::default`]
2779 /// cluster-default, so the peer inner [`crate::AplicacaoSpec::placement`]
2780 /// (9abb8f0) `&Placement`-return accessor observes a typed composite
2781 /// whether or not the author declared the outer slot. The outer
2782 /// accessor preserves the "author-omitted vs authored-empty" partition
2783 /// the inner accessor collapses at the cluster-default fold —
2784 /// routing the presence bit through this accessor keeps the
2785 /// [`Self::declared_mesh_slots`] M3 kind-coherence enumerator's
2786 /// `M3_AUTHOR_KEY_PLACEMENT` push separate from the inner
2787 /// [`crate::AplicacaoSpec::validate_placement`]-gated overlay
2788 /// dispatch.
2789 ///
2790 /// Prior to this lift the `.placement` `Option<Placement>`
2791 /// composite was accessed inline at two production sites — the
2792 /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2793 /// `self.placement.clone().unwrap_or_default()` traversal head
2794 /// (caixa-core/src/manifest.rs:2036, which drives the fold onto
2795 /// the [`crate::aplicacao::Placement::default`] cluster-default
2796 /// arm the inner [`crate::AplicacaoSpec::placement`] accessor
2797 /// then observes), and the [`Self::declared_mesh_slots`] M3
2798 /// declared-slot-set enumerator's `self.placement.is_some()`
2799 /// presence probe (caixa-core/src/manifest.rs:2100, which drives
2800 /// the `M3_AUTHOR_KEY_PLACEMENT` kebab-case author-label push
2801 /// every [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
2802 /// coherence gate reads) — two open-coded outer-field accesses
2803 /// that expressed no compile-time link back to the typed slot at
2804 /// the [`Caixa`] altitude. A future extension of the `:placement`
2805 /// outer axis to a richer author surface (a per-cluster
2806 /// `:placement-overrides` slot the operator materializes at
2807 /// admission time so a cluster-specific placement can tighten the
2808 /// caixa-declared bound without re-authoring the `caixa.lisp`, a
2809 /// per-tenant placement-alias table the M4
2810 /// `mesh.pleme.io/v1alpha1/Aplicacao` CR materializer resolves
2811 /// per-CR at admission time, a promotion of the plain
2812 /// `Option<Placement>` to a richer `{static, dynamic}` partition
2813 /// once Orleans-style virtual-actor dynamic placement comes into
2814 /// typed scope) would have had to be threaded through both open-
2815 /// coded copies in lockstep or the Aplicacao-composition seed's
2816 /// default-fold arm would silently disagree with the M3 declared-
2817 /// slot enumerator on which distribution composite a given Caixa
2818 /// resolves to — the seed reading an operator-resolved slot while
2819 /// the enumerator's presence probe read the raw slot would
2820 /// silently split the build-time distribution-artifact emission
2821 /// gate from the M3 declared-slot enumerator's kind-coherence
2822 /// gate, a two-consumer split far from the source `caixa.lisp`
2823 /// with no field naming the distribution-drift root cause.
2824 /// Lifting the resolution rule to a typed method on the substrate
2825 /// primitive means every downstream consumer of the caixa's per-
2826 /// `Caixa` MESH-COMPOSITION distribution outer-composite surface
2827 /// reaches for exactly one typed dispatch — the resolver's
2828 /// accept-set migrates as a unit on any future axis addition.
2829 ///
2830 /// Fourth outer top-level [`Caixa`] `Option<&Composite>`-return
2831 /// composite-reference accessor — sibling to the opening
2832 /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) M2-
2833 /// Servico-runtime pair and the peer [`Self::politicas`] (5d23d29)
2834 /// M3-mesh-slot arm on the outer-`Caixa` `Option<&Composite>`
2835 /// composite-reference sub-family, folds on the "one typed
2836 /// dispatch on the substrate primitive, thin projections at each
2837 /// consumer" discipline extended onto the second of the three M3
2838 /// mesh-slot axes. Peer of the closed inner mesh-slot outer-
2839 /// composite family the sibling
2840 /// [`crate::AplicacaoSpec::politicas`] (534dc21) /
2841 /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2842 /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2843 /// accessor pins already close on the inner
2844 /// [`crate::AplicacaoSpec`] altitude — folds on the outer top-
2845 /// level [`Caixa`] altitude's M3 mesh-slot arm the sibling
2846 /// [`Self::politicas`] opened, extending the discipline onto the
2847 /// second of the three M3 mesh-slot axes. The remaining M3
2848 /// mesh-slot axis (`:entrada`) folds onto this accessor's
2849 /// discipline in the final sibling lift, closing the outer top-
2850 /// level [`Caixa`] `Option<&Composite>` M3 mesh-slot sub-family.
2851 /// Returns `Option<&Placement>` (not the owning composite by copy
2852 /// or clone) because every downstream consumer of the placement
2853 /// composite treats it as a read-only per-axis dispatch source —
2854 /// the reference-view is the narrowest borrow that supports every
2855 /// present + roadmapped consumer (per-axis accessor dispatch,
2856 /// serde composite-serialization on the programs.yaml overlay,
2857 /// presence-probe early return on the "author-omitted `:placement`
2858 /// ⇒ cluster-default applies" partition, `Aplicacao`-composition
2859 /// seed's default-fold arm) without cloning the composite through
2860 /// every consumer's fast path. The `Option` half of the return-
2861 /// type preserves the load-bearing "author-omitted `:placement` ⇒
2862 /// cluster-default applies" partition (not a default composite
2863 /// the downstream must reject on emptiness) — the accessor
2864 /// projects the raw `Option<Placement>` slot's presence bit
2865 /// through the reference-return unchanged. Named `placement()` to
2866 /// match the storage field's name verbatim and the tatara-lisp
2867 /// author-surface term (`:placement`) the field's own docstring
2868 /// already carries.
2869 #[must_use]
2870 pub const fn placement(&self) -> Option<&crate::aplicacao::Placement> {
2871 self.placement.as_ref()
2872 }
2873
2874 /// Substrate-canonical per-`Caixa` `:entrada` M3 mesh-slot outer-
2875 /// composite MESH-COMPOSITION-shaped external-gateway optional-
2876 /// composite-reference accessor every consumer of the top-level
2877 /// manifest's per-Aplicacao [`crate::aplicacao::Entrada`] outer-
2878 /// composite reader keys off — returns the author-declared
2879 /// `:entrada` typed composite verbatim as an `Option<&Entrada>`
2880 /// reference over the same backing storage the raw
2881 /// `self.entrada.as_ref()` field access borrows from, with `None`
2882 /// naming the "no `:entrada` block authored — this Aplicacao is
2883 /// cluster-internal, no `Gateway`/`HTTPRoute` fan-out emitted"
2884 /// partition every downstream caixa-mesh Gateway-API artifact
2885 /// emitter treats as "emit no gateway-listener + no `HTTPRoute`
2886 /// backend for this Aplicacao" and the sibling
2887 /// [`Self::aplicacao_view`] Aplicacao-composition seed forwards
2888 /// verbatim (unlike the peer `:politicas` / `:placement` arms,
2889 /// `:entrada` has no cluster-default fold — an omitted `:entrada`
2890 /// stays `None` on the projected [`crate::AplicacaoSpec`] and the
2891 /// peer inner [`crate::AplicacaoSpec::entrada`] accessor observes
2892 /// the same `Option<&Entrada>` presence bit unchanged).
2893 ///
2894 /// The outer `:entrada` slot carries the M3 mesh-slot per-
2895 /// Aplicacao typed external-gateway composite — the load-bearing
2896 /// container of every how-does-the-outside-world-reach-this-
2897 /// Aplicacao axis every caixa-mesh `Gateway`/`HTTPRoute` fan-out
2898 /// emitter fans on (MESH-COMPOSITION §II.5 — the Aplicacao's typed
2899 /// external-entry composite; §V CSE invariants — "the external
2900 /// gateway is a first-class typed composite, not a per-Servico
2901 /// ingress annotation" the per-axis scalars enforce; §III.4 — the
2902 /// typed hostname + backend-Servico pair the per-cluster Gateway-
2903 /// API renderer keys off). Every per-`:entrada` axis threads
2904 /// through a lifted per-slot accessor on the
2905 /// [`crate::aplicacao::Entrada`] type: the
2906 /// [`crate::aplicacao::Entrada::host`] Gateway-API `Listener.hostname`
2907 /// scalar, the [`crate::aplicacao::Entrada::para`] backend-Servico
2908 /// caixa-name scalar, the [`crate::aplicacao::Entrada::paths`]
2909 /// per-rule `HTTPPathMatch` list, the [`crate::aplicacao::Entrada::port`]
2910 /// backend `trigger.service.port` scalar, and the
2911 /// [`crate::aplicacao::Entrada::resolved_paths`] URL-path fallback
2912 /// resolver every HTTPRoute-aware renderer consumes. Every
2913 /// downstream consumer that reaches for an entry axis first passes
2914 /// through this outer accessor onto the composite and then
2915 /// dispatches onto the per-axis accessor — the two-level dispatch
2916 /// means every per-`:entrada` reader now routes through a typed
2917 /// dispatch on the substrate primitive at both altitudes.
2918 ///
2919 /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2920 /// seed: the Aplicacao-view builder forwards the outer `Option`
2921 /// arm verbatim (no default fold — `:entrada` is inherently
2922 /// optional; a cluster-internal Aplicacao has no external gateway
2923 /// at all, not "an external gateway that defaults to nothing"), so
2924 /// the peer inner [`crate::AplicacaoSpec::entrada`] (d32111c)
2925 /// `Option<&Entrada>`-return accessor observes the same presence
2926 /// bit whether or not the author declared the outer slot. Routing
2927 /// the presence bit through this accessor keeps the
2928 /// [`Self::declared_mesh_slots`] M3 kind-coherence enumerator's
2929 /// `M3_AUTHOR_KEY_ENTRADA` push separate from the inner
2930 /// [`crate::AplicacaoSpec::validate_entrada`]-gated
2931 /// hostname/backend/path emission dispatch.
2932 ///
2933 /// Prior to this lift the `.entrada` `Option<Entrada>` composite
2934 /// was accessed inline at two production sites — the
2935 /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2936 /// `self.entrada.clone()` traversal head (caixa-core/src/manifest.rs:2182,
2937 /// which drives the forward onto the peer inner
2938 /// [`crate::AplicacaoSpec::entrada`] accessor the caixa-mesh
2939 /// Gateway-API fan-out then observes), and the
2940 /// [`Self::declared_mesh_slots`] M3 declared-slot-set enumerator's
2941 /// `self.entrada.is_some()` presence probe (caixa-core/src/manifest.rs:2248,
2942 /// which drives the `M3_AUTHOR_KEY_ENTRADA` kebab-case author-
2943 /// label push every [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
2944 /// kind-coherence gate reads) — two open-coded outer-field
2945 /// accesses that expressed no compile-time link back to the typed
2946 /// slot at the [`Caixa`] altitude. A future extension of the
2947 /// `:entrada` outer axis to a richer author surface (a per-cluster
2948 /// `:entrada-overrides` slot the operator materializes at admission
2949 /// time so a cluster-specific hostname can pin the caixa-declared
2950 /// bound without re-authoring the `caixa.lisp`, a per-tenant
2951 /// gateway-alias table the M4 `mesh.pleme.io/v1alpha1/Aplicacao`
2952 /// CR materializer resolves per-CR at admission time, a promotion
2953 /// of the plain `Option<Entrada>` to a richer
2954 /// `{public, private, internal}` partition once Cilium-identity-
2955 /// scoped internal gateways come into typed scope) would have had
2956 /// to be threaded through both open-coded copies in lockstep or the
2957 /// Aplicacao-composition seed's forward arm would silently
2958 /// disagree with the M3 declared-slot enumerator on which external-
2959 /// gateway composite a given Caixa resolves to — the seed reading
2960 /// an operator-resolved slot while the enumerator's presence probe
2961 /// read the raw slot would silently split the build-time gateway-
2962 /// artifact emission gate from the M3 declared-slot enumerator's
2963 /// kind-coherence gate, a two-consumer split far from the source
2964 /// `caixa.lisp` with no field naming the entry-drift root cause.
2965 /// Lifting the resolution rule to a typed method on the substrate
2966 /// primitive means every downstream consumer of the caixa's per-
2967 /// `Caixa` MESH-COMPOSITION external-gateway outer-composite
2968 /// surface reaches for exactly one typed dispatch — the resolver's
2969 /// accept-set migrates as a unit on any future axis addition.
2970 ///
2971 /// Fifth and final outer top-level [`Caixa`] `Option<&Composite>`-
2972 /// return composite-reference accessor — closes the outer-`Caixa`
2973 /// `Option<&Composite>` composite-reference sub-family opened by
2974 /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) on the
2975 /// M2 Servico-runtime arm and extended onto the M3 mesh-slot arm
2976 /// by [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074),
2977 /// folds on the "one typed dispatch on the substrate primitive,
2978 /// thin projections at each consumer" discipline extended onto the
2979 /// third and final M3 mesh-slot axis. Peer of the closed inner
2980 /// mesh-slot outer-composite family the sibling
2981 /// [`crate::AplicacaoSpec::politicas`] (534dc21) /
2982 /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2983 /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2984 /// accessor pins already close on the inner
2985 /// [`crate::AplicacaoSpec`] altitude — this lift closes the mirror
2986 /// sub-family on the outer top-level [`Caixa`] altitude, so both
2987 /// altitudes of the outer-composite reference-return discipline
2988 /// (per-`Caixa` outer-slot presence + per-`AplicacaoSpec` inner-
2989 /// slot presence) now carry the full five-arm accept-set behind a
2990 /// typed dispatch on the substrate primitive. Returns
2991 /// `Option<&Entrada>` (not the owning composite by copy or clone)
2992 /// because every downstream consumer of the entrada composite
2993 /// treats it as a read-only per-axis dispatch source — the
2994 /// reference-view is the narrowest borrow that supports every
2995 /// present + roadmapped consumer (per-axis accessor dispatch,
2996 /// serde composite-serialization on the programs.yaml overlay,
2997 /// presence-probe early return on the "author-omitted `:entrada`
2998 /// ⇒ cluster-internal Aplicacao" partition, `Aplicacao`-composition
2999 /// seed's forward arm) without cloning the composite through every
3000 /// consumer's fast path. The `Option` half of the return-type
3001 /// preserves the load-bearing "author-omitted `:entrada` ⇒
3002 /// cluster-internal Aplicacao" partition (not a default composite
3003 /// the downstream must reject on emptiness — a cluster-internal
3004 /// Aplicacao has no external gateway at all, not "a default gateway
3005 /// that emits nothing"); the accessor projects the raw
3006 /// `Option<Entrada>` slot's presence bit through the reference-
3007 /// return unchanged. Named `entrada()` to match the storage field's
3008 /// name verbatim and the tatara-lisp author-surface term
3009 /// (`:entrada`) the field's own docstring already carries.
3010 #[must_use]
3011 pub const fn entrada(&self) -> Option<&crate::aplicacao::Entrada> {
3012 self.entrada.as_ref()
3013 }
3014
3015 /// Substrate-canonical per-`Caixa` `:ci` slot accessor — returns the
3016 /// author-declared typed CI run (`canteiro_types::CiRun`) verbatim as
3017 /// an `Option<&CiRun>`, borrowed from the typed slot's own
3018 /// `Option<CiRun>` storage. `None` when the slot is absent (every
3019 /// non-`Acao` kind, and an `Acao` caixa that hasn't declared `:ci`
3020 /// yet — the latter is caught by [`crate::LayoutError::MissingCi`],
3021 /// not silently accepted).
3022 ///
3023 /// Named `ci()` to match the storage field's name and the
3024 /// tatara-lisp author surface (`:ci`); mirrors the sibling
3025 /// `Option<&Composite>` accessors on this same `Caixa` altitude
3026 /// ([`Self::limits`], [`Self::behavior`], [`Self::politicas`],
3027 /// [`Self::placement`], [`Self::entrada`]) — one typed dispatch on
3028 /// the substrate primitive rather than an open-coded `self.ci.as_ref()`
3029 /// at every consumer.
3030 #[must_use]
3031 pub const fn ci(&self) -> Option<&canteiro_types::CiRun> {
3032 self.ci.as_ref()
3033 }
3034
3035 /// Substrate-canonical per-`Caixa` `:estrategia` M2 supervisor-tree-
3036 /// slot flat-spread OTP-shaped sibling-restart-strategy discriminant
3037 /// accessor every consumer of the top-level manifest's per-Supervisor
3038 /// restart-strategy axis keys off — returns the author-declared
3039 /// `:estrategia` variant verbatim as an `Option<RestartStrategy>`,
3040 /// `Copy`-projected from the typed slot's own
3041 /// `Option<crate::supervisor::RestartStrategy>` storage. Optional
3042 /// (`:estrategia` is a flat-spread supervisor-only slot every
3043 /// non-`Supervisor`-kind `defcaixa` carries as `None` by
3044 /// `#[serde(default)]`, and every `Supervisor`-kind `defcaixa` may
3045 /// still omit to defer to [`RestartStrategy::default`] —
3046 /// [`RestartStrategy::OneForOne`] — through the [`Self::supervisor_view`]
3047 /// `unwrap_or_default()` fold; a returned `None` degenerates to the
3048 /// [`SupervisorSpec::default`]-inherited strategy without any silent
3049 /// promotion to a fresh explicit variant at the accessor boundary).
3050 ///
3051 /// The `:estrategia` slot carries the M2 typed OTP-shaped sibling-
3052 /// restart-strategy discriminant every substrate-side per-Supervisor
3053 /// dispatch fans on (INSPIRATIONS §II.2 — OTP `supervisor:strategy`
3054 /// closed-set `one_for_one | one_for_all | rest_for_one |
3055 /// simple_one_for_one` algebra translated onto pleme-io's typed
3056 /// [`RestartStrategy`] enum; CAIXA-SDLC §II — the M2 supervisor-tree
3057 /// slot algebra the operator's hierarchical reconciliation scheduler
3058 /// fans on). The slot is *flat-spread* on the outer top-level `Caixa`
3059 /// (per the field-shape docstring at caixa-core/src/manifest.rs — "The
3060 /// supervisor slots are flat on Caixa (vs nested under a
3061 /// `SupervisorSpec` sub-form) to keep tatara-lisp authoring at one
3062 /// level of nesting"), so the accessor's altitude is the outer
3063 /// [`Caixa`] surface rather than the composed [`SupervisorSpec`]
3064 /// altitude the sibling [`crate::supervisor::SupervisorSpec::estrategia`]
3065 /// (eafb619) accessor keys off. The two typed axes — the outer
3066 /// author-surface `Option<RestartStrategy>` on the [`Caixa`] altitude
3067 /// (author-omitted arm carried as `None`) and the inner post-
3068 /// composition `RestartStrategy` on the [`SupervisorSpec`] altitude
3069 /// (`Option` collapsed through the [`Self::supervisor_view`]
3070 /// `unwrap_or_default()` fold) — now share one accessor discipline for
3071 /// the shared substrate concept "the author-declared OTP-shaped
3072 /// sibling-restart-strategy variant that partitions the downstream
3073 /// per-Supervisor renderer's per-arm fan-out"; the outer-altitude
3074 /// `None` arm is the pre-composition presence bit every declared-slot
3075 /// enumerator ([`Self::declared_supervisor_slots`]) reads, and the
3076 /// inner-altitude non-`Option` `RestartStrategy` is the post-
3077 /// composition partition-dispatch input every strategy-arm consumer
3078 /// ([`SupervisorSpec::validate`], the future wasm-operator's per-
3079 /// Supervisor sibling-restart branch, the future M4
3080 /// `mesh.pleme.io/v1alpha1/Supervisor` CR materializer's admission
3081 /// webhook) fans on.
3082 ///
3083 /// Prior to this lift the `.estrategia` field was accessed inline at
3084 /// two production sites in `caixa-core/src/manifest.rs` — the
3085 /// [`Self::declared_supervisor_slots`] `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA`
3086 /// presence-probe arm at `if self.estrategia.is_some()` (which drives
3087 /// the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
3088 /// coherence gate's per-slot label push) and the [`Self::supervisor_view`]
3089 /// `SupervisorSpec` construction site at `estrategia:
3090 /// self.estrategia.unwrap_or_default()` (which composes the flat-
3091 /// spread outer author-surface `Option<RestartStrategy>` onto the
3092 /// inner post-composition [`SupervisorSpec`] `RestartStrategy` field
3093 /// the [`SupervisorSpec::estrategia`] accessor keys off) — two open-
3094 /// coded field-accesses that expressed no compile-time link back to
3095 /// the typed slot. A future extension of the outer `:estrategia` axis
3096 /// to a richer author surface (a per-cluster strategy override the
3097 /// operator pins through a future `:estrategia-overrides` overlay the
3098 /// MESH-COMPOSITION §III.2 supervision-canary roadmap acknowledges,
3099 /// a per-tenant strategy-alias table the M4 CR materializer resolves
3100 /// per-CR, a per-Supervisor dynamic strategy derivation the future
3101 /// adaptive-supervision engine computes from child-failure-history
3102 /// topology, a per-child-cohort strategy split the future
3103 /// `RestForCohort` extension the INSPIRATIONS.md §II.2 Erlang/OTP
3104 /// absorption roadmap acknowledges, a promotion of the plain
3105 /// `Option<RestartStrategy>` to a richer
3106 /// `AuthorDeclaredStrategy { declared, overlay }` newtype once the
3107 /// operator-resolved overlay lands) would have had to be threaded
3108 /// through both open-coded copies in lockstep or the enumerator's
3109 /// presence probe and the composition site's `unwrap_or_default()`
3110 /// fold would silently disagree on which strategy a given [`Caixa`]
3111 /// resolves to (an author's `:estrategia OneForAll` would satisfy
3112 /// the enumerator's presence probe while the composition site
3113 /// silently rendered a stale `OneForOne`, or vice versa). Lifting
3114 /// the resolution rule to a typed method on the substrate primitive
3115 /// means every downstream consumer of the caixa's per-`Caixa` outer-
3116 /// altitude sibling-restart-strategy surface reaches for exactly one
3117 /// typed dispatch — the resolver's accept-set migrates as a unit on
3118 /// any future axis addition.
3119 ///
3120 /// First outer top-level [`Caixa`] `Option<Copy>`-return supervisor-
3121 /// tree-slot flat-spread accessor for M2 supervisor-slot Copy-carry
3122 /// axes — opens the outer-`Caixa` `Option<Copy>` flat-spread
3123 /// projection pattern the sibling per-`Caixa` `:max-restarts`
3124 /// `Option<u32>` and (through the future duration-newtype landing)
3125 /// `:restart-window` `Option<Duration>` future outer-scalar lifts
3126 /// fold on. Peer of the inner-altitude [`crate::supervisor::SupervisorSpec::estrategia`]
3127 /// (eafb619) `Copy`-return sibling-restart-strategy scalar accessor on
3128 /// the post-composition [`SupervisorSpec`] altitude — same "one
3129 /// typed dispatch on the substrate primitive, thin projections at
3130 /// each consumer" discipline extended onto the pre-composition outer
3131 /// author-surface [`Caixa`] altitude for the same OTP-shaped
3132 /// sibling-restart-strategy axis. Peer of the closed outer-`Caixa`
3133 /// `Option<&Composite>` composite-reference family the sibling
3134 /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) /
3135 /// [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074) /
3136 /// [`Self::entrada`] (e4128e4) accessor pins already carry on the
3137 /// outer `Option<&Composite>` altitude — extends the outer-`Caixa`
3138 /// typed-slot accessor discipline onto the flat-spread M2 supervisor-
3139 /// tree `Option<Copy>`-discriminant sub-family the sibling M3
3140 /// [`crate::aplicacao::Placement::estrategia`] (921fe1b)
3141 /// `PlacementStrategy` `Copy`-composite-enum scalar accessor already
3142 /// pins on the inner-altitude per-`:placement` composite. Named
3143 /// `estrategia()` to match the storage field's name and the
3144 /// per-[`SupervisorSpec`] peer [`crate::supervisor::SupervisorSpec::estrategia`]
3145 /// / per-[`crate::aplicacao::Placement`] peer
3146 /// [`crate::aplicacao::Placement::estrategia`] method-name discipline
3147 /// verbatim; the accessor's identity name maps onto the canonical
3148 /// OTP-shape supervision vocabulary the [`RestartStrategy`] enum's
3149 /// docstring already carries.
3150 #[must_use]
3151 pub const fn estrategia(&self) -> Option<crate::supervisor::RestartStrategy> {
3152 self.estrategia
3153 }
3154
3155 /// Substrate-canonical per-`Caixa` `:max-restarts` M2 supervisor-tree-
3156 /// slot flat-spread OTP-`MaxIntensity`-shaped restart-budget-count
3157 /// scalar accessor every consumer of the top-level manifest's per-
3158 /// Supervisor `:max-restarts` restart-budget-count axis keys off —
3159 /// returns the author-declared `:max-restarts` typed `Option<u32>`
3160 /// verbatim, `Copy`-projected from the typed slot's own `Option<u32>`
3161 /// storage (`u32` is `Copy`, so `Option<u32>` is `Copy` and the
3162 /// accessor returns by value; no borrow of `&self` past the call).
3163 /// Optional (`:max-restarts` is a flat-spread supervisor-only slot
3164 /// every non-`Supervisor`-kind `defcaixa` carries as `None` by
3165 /// `#[serde(default)]`, and every `Supervisor`-kind `defcaixa` may
3166 /// still omit to defer to the [`Self::supervisor_view`]
3167 /// `unwrap_or(5)` fold's OTP-canonical `{intensity, 5, 60}` default).
3168 ///
3169 /// The `:max-restarts` slot carries the M2 typed Erlang/OTP-shaped
3170 /// `MaxIntensity` restart-budget count that pairs with the sibling
3171 /// `:restart-window` `Period` to form the `MaxIntensity / Period`
3172 /// restart-intensity ratio the supervisor trips its own escalation on
3173 /// (INSPIRATIONS §II.2 — Erlang/OTP `supervisor` `{intensity, 5, 60}`
3174 /// worker-supervisor default; RUNTIME-PATTERNS §II.2; CAIXA-SDLC §II
3175 /// — the M2 supervisor-tree slot algebra the operator's hierarchical
3176 /// reconciliation scheduler fans on). The slot is *flat-spread* on
3177 /// the outer top-level `Caixa` (per the field-shape docstring at
3178 /// caixa-core/src/manifest.rs — "The supervisor slots are flat on
3179 /// Caixa (vs nested under a `SupervisorSpec` sub-form)"), so the
3180 /// accessor's altitude is the outer [`Caixa`] surface rather than the
3181 /// composed [`SupervisorSpec`] altitude the sibling
3182 /// [`crate::supervisor::SupervisorSpec::max_restarts`] accessor keys
3183 /// off. The two typed axes — the outer author-surface `Option<u32>`
3184 /// on the [`Caixa`] altitude (author-omitted arm carried as `None`)
3185 /// and the inner post-composition `u32` on the [`SupervisorSpec`]
3186 /// altitude (`Option` collapsed through the [`Self::supervisor_view`]
3187 /// `unwrap_or(5)` fold) — now share one accessor discipline for the
3188 /// shared substrate concept "the author-declared OTP-shaped
3189 /// restart-budget count every downstream per-Supervisor consumer's
3190 /// restart-intensity budget-vs-count comparator fans on".
3191 ///
3192 /// Prior to this lift the `.max_restarts` field was accessed inline
3193 /// at two production sites in `caixa-core/src/manifest.rs` — the
3194 /// [`Self::declared_supervisor_slots`] `SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS`
3195 /// presence-probe arm at `if self.max_restarts.is_some()` (which
3196 /// drives the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
3197 /// kind-coherence gate's per-slot label push) and the
3198 /// [`Self::supervisor_view`] `SupervisorSpec` construction site at
3199 /// `max_restarts: self.max_restarts.unwrap_or(5)` (which composes the
3200 /// flat-spread outer author-surface `Option<u32>` onto the inner
3201 /// post-composition [`SupervisorSpec`] `u32` field the
3202 /// [`SupervisorSpec::max_restarts`] accessor keys off) — two open-
3203 /// coded field-accesses that expressed no compile-time link back to
3204 /// the typed slot. A future extension of the outer `:max-restarts`
3205 /// axis to a richer author surface (a per-cluster restart-budget
3206 /// override the operator pins through a future `:max-restarts-overrides`
3207 /// overlay the MESH-COMPOSITION §III.2 supervision-canary roadmap
3208 /// acknowledges, a per-tenant restart-budget-alias table the M4 CR
3209 /// materializer resolves per-CR, a per-Supervisor dynamic restart-
3210 /// budget derivation the future adaptive-supervision engine computes
3211 /// from child-failure-history topology, a promotion of the plain
3212 /// `Option<u32>` count to a richer `{MaxR, MaxT}` per-child-cohort
3213 /// restart-budget-partition once the INSPIRATIONS §II.2 Erlang/OTP
3214 /// per-child-cohort roadmap lands) would have had to be threaded
3215 /// through both open-coded copies in lockstep or the enumerator's
3216 /// presence probe and the composition site's `unwrap_or(5)` fold
3217 /// would silently disagree on which restart-budget a given [`Caixa`]
3218 /// resolves to (an author's `:max-restarts 10` would satisfy the
3219 /// enumerator's presence probe while the composition site silently
3220 /// composed the OTP-canonical `5`, or vice versa). Lifting the
3221 /// resolution rule to a typed method on the substrate primitive means
3222 /// every downstream consumer of the caixa's per-`Caixa` outer-altitude
3223 /// restart-budget-count surface reaches for exactly one typed dispatch
3224 /// — the resolver's accept-set migrates as a unit on any future axis
3225 /// addition.
3226 ///
3227 /// Second outer top-level [`Caixa`] `Option<Copy>`-return supervisor-
3228 /// tree-slot flat-spread accessor for M2 supervisor-slot Copy-carry
3229 /// axes — folds on the outer-`Caixa` `Option<Copy>` flat-spread
3230 /// projection pattern the sibling per-`Caixa`
3231 /// [`Self::estrategia`] (ed04d3c) accessor opened, extends the
3232 /// sub-family onto the sibling `Option<u32>` restart-budget-count arm.
3233 /// Peer of the inner-altitude
3234 /// [`crate::supervisor::SupervisorSpec::max_restarts`] `u32` accessor
3235 /// on the post-composition [`SupervisorSpec`] altitude — same "one
3236 /// typed dispatch on the substrate primitive, thin projections at
3237 /// each consumer" discipline extended onto the pre-composition outer
3238 /// author-surface [`Caixa`] altitude for the same OTP-`MaxIntensity`-
3239 /// shaped restart-budget-count axis. Named `max_restarts()` to match
3240 /// the storage field's name and the per-[`SupervisorSpec`] peer
3241 /// [`crate::supervisor::SupervisorSpec::max_restarts`] method-name
3242 /// discipline verbatim; the accessor's identity maps onto the
3243 /// canonical OTP-shape supervision vocabulary the `:max-restarts`
3244 /// field's docstring already carries.
3245 #[must_use]
3246 pub const fn max_restarts(&self) -> Option<u32> {
3247 self.max_restarts
3248 }
3249
3250 /// Substrate-canonical per-`Caixa` `:restart-window` M2 supervisor-
3251 /// tree-slot flat-spread OTP-`Period`-shaped restart-intensity-
3252 /// denominator raw-duration-string scalar accessor every consumer of
3253 /// the top-level manifest's per-Supervisor `:restart-window` sliding-
3254 /// window axis keys off — returns the author-declared `:restart-window`
3255 /// typed `Option<String>` verbatim as an `Option<&str>`, borrowed
3256 /// from the typed slot's own `Option<String>` storage. `None` when
3257 /// the slot is absent (the canonical "never reset — every restart
3258 /// across the supervisor's lifetime counts against the sibling
3259 /// `:max-restarts` budget" sentinel every non-`Supervisor`-kind
3260 /// `defcaixa` carries by `#[serde(default)]` and every
3261 /// `Supervisor`-kind `defcaixa` may still omit to defer to the
3262 /// [`Self::supervisor_view`] `restart_window: None` composition
3263 /// through the [`crate::supervisor::duration_codec::parse`] soft-
3264 /// swallow `.and_then(|s| … .ok())` fold).
3265 ///
3266 /// The `:restart-window` slot carries the raw M2 typed Erlang/OTP-
3267 /// shaped `Period` sliding-observation-interval duration string that
3268 /// pairs with the sibling `:max-restarts` `MaxIntensity` restart-
3269 /// budget count to form the `MaxIntensity / Period` restart-intensity
3270 /// ratio the supervisor trips its own escalation on (INSPIRATIONS
3271 /// §II.2 — Erlang/OTP `supervisor` `{intensity, 5, 60}` worker-
3272 /// supervisor default; RUNTIME-PATTERNS §II.2). The outer-`Caixa`
3273 /// slot stores the raw duration string (`"60s"`, `"5m"`, `"500ms"`)
3274 /// authored under `:restart-window` — the typed [`SupervisorSpec`]
3275 /// holds an `Option<Duration>` routed through the shared
3276 /// [`crate::supervisor::duration_codec`] via `with = "duration_codec"`
3277 /// — so the outer altitude's accessor returns `Option<&str>` (raw
3278 /// authoring surface) while the inner altitude's
3279 /// [`crate::supervisor::SupervisorSpec::restart_window`] returns
3280 /// `Option<Duration>` (parsed typed surface). The parse-refusal arm
3281 /// is closed by the sibling [`Self::validate_restart_window`] gate
3282 /// that surfaces [`ManifestError::RestartWindowMalformed`] naming
3283 /// the offending value; the view-construction path
3284 /// [`Self::supervisor_view`] soft-swallows the same parse error to
3285 /// `None` to keep the view best-effort.
3286 ///
3287 /// Prior to this lift the `.restart_window` field was accessed inline
3288 /// at three production sites in `caixa-core/src/manifest.rs` — the
3289 /// [`Self::declared_supervisor_slots`]
3290 /// `SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW` presence-probe arm at
3291 /// `if self.restart_window.is_some()` (which drives the
3292 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
3293 /// coherence gate's per-slot label push), the
3294 /// [`Self::validate_restart_window`] `let Some(s) =
3295 /// self.restart_window.as_deref()` empty-and-shape gate binding
3296 /// (which folds the raw string through the shared
3297 /// [`crate::supervisor::duration_codec::parse`] to surface
3298 /// [`ManifestError::RestartWindowMalformed`] naming the offending
3299 /// value), and the [`Self::supervisor_view`] `self.restart_window
3300 /// .as_deref().and_then(…)` view-construction fold (which composes
3301 /// the flat-spread outer author-surface `Option<String>` onto the
3302 /// inner post-composition [`SupervisorSpec`] `Option<Duration>`
3303 /// field the [`SupervisorSpec::restart_window`] accessor keys off) —
3304 /// three open-coded field-accesses that expressed no compile-time
3305 /// link back to the typed slot. A future extension of the outer
3306 /// `:restart-window` axis to a richer author surface (a per-cluster
3307 /// window override, a per-tenant window-alias table, a per-Supervisor
3308 /// dynamic window derivation the future adaptive-supervision engine
3309 /// computes from child-failure-history topology, a promotion of the
3310 /// plain `Option<String>` raw duration to a typed `Option<Duration>`
3311 /// once the future author-surface parser lands at the [`Caixa`]
3312 /// altitude and the raw-string form is retired) would have had to be
3313 /// threaded through every open-coded copy in lockstep or the three
3314 /// consumers would silently disagree on which raw string a given
3315 /// [`Caixa`] resolves to. Lifting the resolution rule to a typed
3316 /// method on the substrate primitive means every downstream consumer
3317 /// of the caixa's per-`Caixa` outer-altitude restart-window raw-
3318 /// string surface reaches for exactly one typed dispatch — the
3319 /// resolver's accept-set migrates as a unit on any future axis
3320 /// addition.
3321 ///
3322 /// Third outer top-level [`Caixa`] supervisor-tree-slot flat-spread
3323 /// accessor — folds on the outer-`Caixa` M2 supervisor-tree flat-
3324 /// spread projection pattern the sibling per-`Caixa`
3325 /// [`Self::estrategia`] (ed04d3c) `Option<Copy>` and
3326 /// [`Self::max_restarts`] `Option<Copy>` accessors opened, extends
3327 /// the sub-family onto the sibling `Option<&str>` raw-duration-
3328 /// string arm (the outer altitude's raw-string form; the inner
3329 /// altitude's parsed [`Duration`] form is the peer
3330 /// [`crate::supervisor::SupervisorSpec::restart_window`] accessor).
3331 /// Peer of the sibling per-`Caixa` `Option<&str>`-return scalar
3332 /// accessors ([`Self::licenca`] / [`Self::repositorio`] /
3333 /// [`Self::descricao`] / [`Self::edicao`]) on the universal-axis
3334 /// outer scalar-projection family the outer-`Caixa` `Option<&str>`
3335 /// sub-family already carries — same "one typed dispatch on the
3336 /// substrate primitive, thin projections at each consumer"
3337 /// discipline extended onto the M2 supervisor-tree flat-spread
3338 /// `Option<&str>` raw-duration-string arm. Named `restart_window()`
3339 /// to match the storage field's name and the per-[`SupervisorSpec`]
3340 /// peer [`crate::supervisor::SupervisorSpec::restart_window`]
3341 /// method-name discipline verbatim; the accessor's identity maps
3342 /// onto the canonical OTP-shape supervision vocabulary the
3343 /// `:restart-window` field's docstring already carries.
3344 #[must_use]
3345 pub const fn restart_window(&self) -> Option<&str> {
3346 match &self.restart_window {
3347 Some(s) => Some(s.as_str()),
3348 None => None,
3349 }
3350 }
3351
3352 /// Substrate-canonical per-`Caixa` `:upgrade-from` M2 typed-slot
3353 /// outer-composite OTP-appup-shaped per-prior-version migration-
3354 /// entry-list slice accessor every consumer of the top-level
3355 /// manifest's per-Servico hot-upgrade-block `&[UpgradeFromEntry]`
3356 /// slice-view keys off — returns the author-declared `:upgrade-from`
3357 /// typed `Vec<UpgradeFromEntry>` verbatim as a
3358 /// `&[UpgradeFromEntry]` slice-view over the same backing buffer
3359 /// the raw `self.upgrade_from.as_slice()` field access borrows
3360 /// from. Empty-slice-carrying (the "no hot-upgrade path declared"
3361 /// arm every `defcaixa` without an `:upgrade-from` block carries;
3362 /// the [`Self::from_lisp`] derive folds an omitted `:upgrade-from`
3363 /// through `#[serde(default)]` to `Vec::new()`, so a `Caixa` past
3364 /// parse definitionally carries a `Vec<UpgradeFromEntry>` slot —
3365 /// possibly empty — and the returned `&[UpgradeFromEntry]`
3366 /// degenerates to an empty slice on that arm without any silent
3367 /// `None` collapse).
3368 ///
3369 /// The outer `:upgrade-from` slot carries the M2 typed OTP-appup
3370 /// migration block — the load-bearing container of every per-
3371 /// prior-`:versao` migration-instruction list the wasm-operator
3372 /// dispatches on at hot-upgrade time (INSPIRATIONS §II.4 — OTP
3373 /// `.appup` per-prior-version `LoadModule | StateChange |
3374 /// SoftPurge | Purge | Restart` instruction algebra translated
3375 /// onto pleme-io's typed `:upgrade-from :from` + `:instructions`
3376 /// entry list; CAIXA-SDLC §II — the typed-M2 slot algebra the
3377 /// operator's hot-upgrade dispatch fans on). Every per-entry axis
3378 /// threads through a lifted per-entry accessor on the
3379 /// [`UpgradeFromEntry`] type: the
3380 /// [`UpgradeFromEntry::prior_versao`] SemVer-shaped previous-
3381 /// version scalar accessor and the
3382 /// [`UpgradeFromEntry::instructions`] `&[UpgradeInstruction]`-
3383 /// return per-entry instruction-list accessor (0137e5a). Every
3384 /// downstream consumer of the hot-upgrade path first passes
3385 /// through this outer accessor onto the slice and then dispatches
3386 /// per-entry through the inner accessors — the two-level dispatch
3387 /// means every per-`:upgrade-from` reader now routes through a
3388 /// typed dispatch on the substrate primitive at both altitudes.
3389 ///
3390 /// Prior to this lift the `.upgrade_from` `Vec<UpgradeFromEntry>`
3391 /// slot was accessed inline at production sites across three
3392 /// files — the [`Self::declared_servico_slots`] M2 declared-slot
3393 /// enumerator's `self.upgrade_from.is_empty()` presence probe
3394 /// (caixa-core/src/manifest.rs, which drives the
3395 /// `M2_AUTHOR_KEY_UPGRADE_FROM` kebab-case author-label push every
3396 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
3397 /// gate reads), the [`crate::StandardLayout::verify`] per-
3398 /// `:upgrade-from` three-stage validation pass (caixa-core/src/
3399 /// layout.rs, which fans onto the
3400 /// [`crate::upgrade::validate_upgrade_from`] per-entry shape +
3401 /// cross-entry duplicate gate, the
3402 /// [`crate::upgrade::validate_upgrade_from_against_versao`]
3403 /// SemVer-precedence cross-slot gate, the
3404 /// [`crate::upgrade::validate_upgrade_from_against_behavior`]
3405 /// `:state-change` ↔ `:on-state-change` cross-slot composition
3406 /// gate, and the per-instruction script-path existence-probe walk
3407 /// that reads each entry's [`UpgradeFromEntry::instructions`] to
3408 /// resolve every declared migration script against the layout
3409 /// root), and the [`crate::render::servico_m2_overlay`] per-
3410 /// Servico M2 overlay emitter's `!caixa.upgrade_from.is_empty()`
3411 /// presence gate + `serde_yaml::to_value(&caixa.upgrade_from)`
3412 /// projection (caixa-core/src/render.rs, which drives the
3413 /// `M2_KEY_UPGRADE_FROM`-keyed `serde_yaml` projection every
3414 /// `caixa-helm` / `caixa-flux` Servico values-block emitter fans
3415 /// on and lands as the ComputeUnit CR's `spec.upgradeFrom` field).
3416 /// A future extension of the outer `:upgrade-from` axis (a per-
3417 /// cluster `:upgrade-overrides` overlay the wasm-engine operator
3418 /// resolves at admission time so a cluster-specific migration
3419 /// policy can tighten a caixa-declared step without re-authoring
3420 /// the `caixa.lisp`, promotion of the plain
3421 /// `Vec<UpgradeFromEntry>` to a richer `{static, dynamic}`
3422 /// partition once runtime-resolved hot-upgrade instructions land,
3423 /// per-entry priority annotation once multi-strategy fan-out
3424 /// lands) would have had to be threaded through all six open-
3425 /// coded copies in lockstep or one consumer would silently
3426 /// disagree with the peers on which upgrade slice a given Caixa
3427 /// resolves to — a six-consumer split at the enumerator, the
3428 /// three-stage validate pass, the script-path probe walk, and the
3429 /// M2 overlay emitter, far from the source `caixa.lisp` with no
3430 /// field naming the upgrade-drift root cause. Lifting the
3431 /// resolution rule to a typed method on the substrate primitive
3432 /// means every downstream consumer of the caixa's per-`Caixa`
3433 /// OTP-appup outer-slice surface reaches for exactly one typed
3434 /// dispatch — the resolver's accept-set migrates as a unit on any
3435 /// future axis addition.
3436 ///
3437 /// First outer top-level [`Caixa`] `&[Composite]`-return slice
3438 /// accessor for M2 / M3 typed-slot vec-carry axes — opens the
3439 /// outer-`Caixa` `&[Composite]` composite-slice projection
3440 /// pattern the sibling `:children`
3441 /// [`crate::supervisor::ChildSpec`] / `:membros`
3442 /// [`crate::aplicacao::Membro`] / `:contratos`
3443 /// [`crate::aplicacao::WitContract`] future outer-composite-slice
3444 /// lifts fold on. Peer of the closed outer-`Caixa` scalar
3445 /// `Option<&Composite>` composite-reference family the sibling
3446 /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) /
3447 /// [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074) /
3448 /// [`Self::entrada`] (e4128e4) accessors closed on the outer
3449 /// `Option<&Composite>` altitude, extended here to the outer-
3450 /// `Caixa` `&[Composite]` vec-carry altitude. Peer at the inner
3451 /// altitude of [`crate::upgrade::UpgradeFromEntry::instructions`]
3452 /// (0137e5a) — same "one typed dispatch on the substrate
3453 /// primitive, thin projections at each consumer" discipline
3454 /// folded onto the outer top-level [`Caixa`] altitude, opening the
3455 /// M2 vec-carry slot family's outer-composite-slice axis. Sibling
3456 /// in shape to the peer outer-`Caixa` `&[Dep]`-return
3457 /// [`Self::deps`] (ad34b4e) / [`Self::deps_dev`] (f7fd81e) and
3458 /// `&[String]`-return [`Self::autores`] (b5d813f) /
3459 /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`]
3460 /// (8a36c23) / [`Self::exe`] (65d9527) / [`Self::servicos`]
3461 /// (611f78b) slice-accessors on the sibling outer-`Caixa` scalar-
3462 /// element vec-carry axes — folds the "outer [`Caixa`] `&[T]`
3463 /// slice" projection pattern onto the sibling M2 typed-composite-
3464 /// element axis (`UpgradeFromEntry` composite, matching the
3465 /// per-inner [`UpgradeFromEntry::instructions`] element type at a
3466 /// different altitude).
3467 ///
3468 /// Returns `&[UpgradeFromEntry]` (not `&Vec<UpgradeFromEntry>`)
3469 /// because every downstream consumer of the hot-upgrade list
3470 /// treats it as a read-only sequence — the slice-view is the
3471 /// narrowest borrow that supports every present + roadmapped
3472 /// consumer (`.iter()`, `.len()`, `.is_empty()`, `serde` slice-
3473 /// serialization through
3474 /// `serde_yaml::to_value(&[UpgradeFromEntry])`) without leaking
3475 /// the backing `Vec`'s grow/push/reserve surface no consumer of
3476 /// the typed view reaches for (the storage-side `Vec` remains
3477 /// reachable through the `pub upgrade_from` field for the
3478 /// mutation-carrying serde round-trip and per-test fixture-
3479 /// mutation paths). Named `upgrade_from()` to match the storage
3480 /// field's `snake_case` name; the kebab-case author-surface tag
3481 /// `:upgrade-from` is the same axis after tatara-lisp's
3482 /// kebab↔snake fold and the accessor's identity maps onto the
3483 /// canonical CAIXA-SDLC §II vocabulary the slot's docstring
3484 /// already carries.
3485 #[must_use]
3486 pub const fn upgrade_from(&self) -> &[UpgradeFromEntry] {
3487 self.upgrade_from.as_slice()
3488 }
3489
3490 /// Substrate-canonical per-`Caixa` `:children` M2 supervisor-tree-
3491 /// slot outer-composite OTP-shaped per-supervisor static-child-list
3492 /// slice accessor every consumer of the top-level manifest's per-
3493 /// Supervisor `&[ChildSpec]` slice-view keys off — returns the
3494 /// author-declared `:children` typed `Vec<crate::supervisor::ChildSpec>`
3495 /// verbatim as a `&[crate::supervisor::ChildSpec]` slice-view over
3496 /// the same backing buffer the raw `self.children.as_slice()` field
3497 /// access borrows from. Empty-slice-carrying (the "no static children
3498 /// declared" arm every non-`Supervisor`-kind `defcaixa` carries by
3499 /// #[serde(default)] and every `SimpleOneForOne` supervisor carries
3500 /// by [`crate::supervisor::SupervisorError::SimpleOneForOneWithStaticChildren`]
3501 /// gate; the returned `&[ChildSpec]` degenerates to an empty slice
3502 /// on those arms without any silent `None` collapse).
3503 ///
3504 /// The outer `:children` slot carries the M2 typed OTP-supervisor
3505 /// static-child list — the load-bearing container of every per-
3506 /// child `{caixa, versao, restart}` triple the wasm-operator's
3507 /// hierarchical reconciler dispatches on at supervisor-tree
3508 /// materialization time (INSPIRATIONS §II.2 — OTP `supervisor:init/1`
3509 /// static-child list translated onto pleme-io's typed
3510 /// [`crate::supervisor::ChildSpec`] entry list; CAIXA-SDLC §II —
3511 /// the typed-M2 slot algebra the operator's per-supervisor fan-out
3512 /// dispatch fans on). Every per-child axis threads through a lifted
3513 /// per-entry accessor on the [`crate::supervisor::ChildSpec`] type:
3514 /// the [`crate::supervisor::ChildSpec::nome`] DNS-1123-label
3515 /// child-caixa-identity scalar accessor, the peer versao SemVer-2
3516 /// version-requirement scalar accessor, and the
3517 /// [`crate::supervisor::ChildSpec::restart`] `Copy`-composite-enum
3518 /// per-child post-exit restart-decision-policy discriminant
3519 /// accessor (dfb4a81). Every downstream consumer of the supervisor-
3520 /// tree path first passes through this outer accessor onto the
3521 /// slice and then dispatches per-child through the inner accessors
3522 /// — the two-level dispatch means every per-`:children` reader now
3523 /// routes through a typed dispatch on the substrate primitive at
3524 /// both altitudes.
3525 ///
3526 /// Prior to this lift the `.children` `Vec<ChildSpec>` slot was
3527 /// accessed inline at three production sites across two files —
3528 /// the [`Self::declared_supervisor_slots`] supervisor-tree
3529 /// declared-slot enumerator's `!self.children.is_empty()` presence
3530 /// probe (caixa-core/src/manifest.rs, which drives the
3531 /// `SUPERVISOR_AUTHOR_KEY_CHILDREN` kebab-case author-label push
3532 /// every [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
3533 /// kind-coherence gate reads), the [`Self::supervisor_view`]
3534 /// per-supervisor typed-view composer's `self.children.clone()`
3535 /// per-child fold-in path (caixa-core/src/manifest.rs, which
3536 /// materializes the typed [`crate::supervisor::SupervisorSpec`]
3537 /// view every [`crate::StandardLayout::verify`] Supervisor-arm gate
3538 /// dispatches on), and the [`crate::StandardLayout::verify`] per-
3539 /// `:children :caixa` self-parent refusal probe's
3540 /// `&caixa.children`-borrowed
3541 /// [`crate::supervisor::validate_no_self_supervision`] input
3542 /// (caixa-core/src/layout.rs, which pins the "no child names the
3543 /// supervisor's own `:nome`" cross-slot coherence gate). A future
3544 /// extension of the outer `:children` axis (a per-cluster
3545 /// `:children-overrides` overlay the wasm-engine operator resolves
3546 /// at admission time so a cluster-specific child-set can tighten
3547 /// a caixa-declared list without re-authoring the `caixa.lisp`,
3548 /// promotion of the plain `Vec<ChildSpec>` to a richer
3549 /// `{static, dynamic}` partition once Erlang/OTP's
3550 /// `simple_one_for_one`-shaped dynamic-child slot lands as a typed
3551 /// axis, per-child priority annotation once multi-strategy fan-out
3552 /// lands) would have had to be threaded through all three open-
3553 /// coded copies in lockstep or one consumer would silently
3554 /// disagree with the peers on which child slice a given Caixa
3555 /// resolves to — the enumerator's presence probe reading the raw
3556 /// slot while the peer view-composer's fold-in path read an
3557 /// operator-resolved slot would silently split the paired
3558 /// declared-slot enumerator and typed-view composition, and the
3559 /// [`crate::supervisor::validate_no_self_supervision`] self-parent
3560 /// refusal probe reading a third borrow would silently drift the
3561 /// cross-slot coherence gate's traversal input from the two peers,
3562 /// a three-consumer split at the enumerator, the view composer,
3563 /// and the self-parent gate far from the source `caixa.lisp` with
3564 /// no field naming the child-set-drift root cause. Lifting the
3565 /// resolution rule to a typed method on the substrate primitive
3566 /// means every downstream consumer of the caixa's per-`Caixa`
3567 /// OTP-supervisor outer-slice surface reaches for exactly one
3568 /// typed dispatch — the resolver's accept-set migrates as a unit
3569 /// on any future axis addition.
3570 ///
3571 /// Second outer top-level [`Caixa`] `&[Composite]`-return slice
3572 /// accessor for M2 / M3 typed-slot vec-carry axes — folds on the
3573 /// outer-`Caixa` `&[Composite]` composite-slice sub-family the
3574 /// sibling [`Self::upgrade_from`] (2a1f907) accessor opened, peer
3575 /// at the outer altitude of the closed inner-`SupervisorSpec`
3576 /// [`crate::SupervisorSpec::children`] (bc92bce) accessor on the
3577 /// same OTP-supervisor static-child-list axis — same "byte-equal,
3578 /// borrow-shared" outer-accessor discipline extended onto the
3579 /// second outer-`Caixa` `&[Composite]` vec-carry axis. Sibling in
3580 /// shape to the peer outer-`Caixa` `&[Dep]`-return [`Self::deps`]
3581 /// (ad34b4e) / [`Self::deps_dev`] (f7fd81e) and `&[String]`-return
3582 /// [`Self::autores`] (b5d813f) / [`Self::etiquetas`] (78c7d3c) /
3583 /// [`Self::bibliotecas`] (8a36c23) / [`Self::exe`] (65d9527) /
3584 /// [`Self::servicos`] (611f78b) slice-accessors on the sibling
3585 /// outer-`Caixa` scalar-element vec-carry axes — folds the "outer
3586 /// [`Caixa`] `&[T]` slice" projection pattern onto the sibling
3587 /// M2 typed-composite-element axis
3588 /// ([`crate::supervisor::ChildSpec`] composite, matching the
3589 /// per-inner [`crate::SupervisorSpec::children`] element type at a
3590 /// different altitude).
3591 ///
3592 /// Returns `&[crate::supervisor::ChildSpec]` (not
3593 /// `&Vec<ChildSpec>`) because every downstream consumer of the
3594 /// child list treats it as a read-only sequence — the slice-view
3595 /// is the narrowest borrow that supports every present +
3596 /// roadmapped consumer (`.iter()`, `.len()`, `.is_empty()`, the
3597 /// [`crate::supervisor::validate_no_self_supervision`] `&[ChildSpec]`
3598 /// input, `serde` slice-serialization) without leaking the backing
3599 /// `Vec`'s grow/push/reserve surface no consumer of the typed view
3600 /// reaches for (the storage-side `Vec` remains reachable through
3601 /// the `pub children` field for the mutation-carrying serde round-
3602 /// trip and per-test fixture-mutation paths, including the
3603 /// [`Self::supervisor_view`] fold-in path that clones the slot
3604 /// into the typed view). Named `children()` to match the storage
3605 /// field's name verbatim and the tatara-lisp author-surface term
3606 /// (`:children`) the field's own docstring already carries; the
3607 /// accessor's identity maps onto the canonical OTP supervision
3608 /// vocabulary the [`Caixa::children`] field's docstring already
3609 /// reaches for ("Static children of a supervisor").
3610 #[must_use]
3611 pub const fn children(&self) -> &[crate::supervisor::ChildSpec] {
3612 self.children.as_slice()
3613 }
3614
3615 /// Substrate-canonical per-`Caixa` `:membros` M3 mesh-slot outer-
3616 /// composite MESH-COMPOSITION-shaped per-Aplicacao member-list slice
3617 /// accessor every consumer of the top-level manifest's per-Aplicacao
3618 /// `&[crate::aplicacao::Membro]` slice-view keys off — returns the
3619 /// author-declared `:membros` typed `Vec<crate::aplicacao::Membro>`
3620 /// verbatim as a `&[crate::aplicacao::Membro]` slice-view over the
3621 /// same backing buffer the raw `self.membros.as_slice()` field access
3622 /// borrows from. Empty-slice-carrying (the "no members declared" arm
3623 /// every non-`Aplicacao`-kind `defcaixa` carries by `#[serde(default)]`
3624 /// and every partially-authored Aplicacao carries before the
3625 /// [`crate::AplicacaoError::MembrosEmpty`] gate fires; the returned
3626 /// `&[Membro]` degenerates to an empty slice on those arms without any
3627 /// silent `None` collapse).
3628 ///
3629 /// The outer `:membros` slot carries the M3 typed MESH-COMPOSITION
3630 /// per-Aplicacao member list — the load-bearing container of every
3631 /// per-member `{caixa, versao}` pair the caixa-mesh renderer's
3632 /// per-Aplicacao program-emission dispatch fans on at mesh-artifact
3633 /// materialization time (MESH-COMPOSITION §III.1 — the typed graph's
3634 /// vertex set the `:contratos` `:de`/`:para` edges resolve against and
3635 /// the `:entrada :para` external-gateway destination validates
3636 /// against; CAIXA-SDLC §II — the typed-M3 slot algebra the operator's
3637 /// per-Aplicacao fan-out dispatch fans on). Every per-member axis
3638 /// threads through a lifted per-entry accessor on the
3639 /// [`crate::aplicacao::Membro`] type: the
3640 /// [`crate::aplicacao::Membro::nome`] DNS-1123-label member-caixa-
3641 /// identity scalar accessor (4a32abf) and the peer
3642 /// [`crate::aplicacao::Membro::versao_requirement`] SemVer-2
3643 /// version-requirement scalar accessor (a40b0e3). Every downstream
3644 /// consumer of the mesh-graph path first passes through this outer
3645 /// accessor onto the slice and then dispatches per-member through
3646 /// the inner accessors — the two-level dispatch means every per-
3647 /// `:membros` reader now routes through a typed dispatch on the
3648 /// substrate primitive at both altitudes.
3649 ///
3650 /// Prior to this lift the `.membros` `Vec<Membro>` slot was accessed
3651 /// inline at three production sites across two files — the
3652 /// [`Self::declared_mesh_slots`] mesh-slot declared-slot
3653 /// enumerator's `!self.membros.is_empty()` presence probe
3654 /// (caixa-core/src/manifest.rs, which drives the
3655 /// `M3_AUTHOR_KEY_MEMBROS` kebab-case author-label push every
3656 /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-coherence
3657 /// gate reads), the [`Self::aplicacao_view`] per-Aplicacao typed-view
3658 /// composer's `self.membros.clone()` per-member fold-in path
3659 /// (caixa-core/src/manifest.rs, which materializes the typed
3660 /// [`crate::aplicacao::AplicacaoSpec`] view every
3661 /// [`crate::StandardLayout::verify`] Aplicacao-arm gate dispatches
3662 /// on), and the [`crate::StandardLayout::verify`] per-`:membros
3663 /// :caixa` self-membership refusal probe's `&caixa.membros`-borrowed
3664 /// [`crate::aplicacao::validate_no_self_membership`] input
3665 /// (caixa-core/src/layout.rs, which pins the "no member names the
3666 /// Aplicacao's own `:nome`" cross-slot coherence gate). A future
3667 /// extension of the outer `:membros` axis (a per-cluster
3668 /// `:membros-overrides` overlay the wasm-engine operator resolves at
3669 /// admission time so a cluster-specific member-set can tighten a
3670 /// caixa-declared list without re-authoring the `caixa.lisp`,
3671 /// promotion of the plain `Vec<Membro>` to a richer
3672 /// `{static, dynamic}` partition once runtime-resolved Aplicacao
3673 /// members land as a typed axis, per-member priority annotation once
3674 /// multi-strategy fan-out lands) would have had to be threaded
3675 /// through all three open-coded copies in lockstep or one consumer
3676 /// would silently disagree with the peers on which member slice a
3677 /// given Caixa resolves to — the enumerator's presence probe reading
3678 /// the raw slot while the peer view-composer's fold-in path read an
3679 /// operator-resolved slot would silently split the paired
3680 /// declared-slot enumerator and typed-view composition, and the
3681 /// [`crate::aplicacao::validate_no_self_membership`] self-membership
3682 /// refusal probe reading a third borrow would silently drift the
3683 /// cross-slot coherence gate's traversal input from the two peers, a
3684 /// three-consumer split at the enumerator, the view composer, and
3685 /// the self-membership gate far from the source `caixa.lisp` with no
3686 /// field naming the member-set-drift root cause. Lifting the
3687 /// resolution rule to a typed method on the substrate primitive
3688 /// means every downstream consumer of the caixa's per-`Caixa`
3689 /// MESH-COMPOSITION outer-slice surface reaches for exactly one
3690 /// typed dispatch — the resolver's accept-set migrates as a unit on
3691 /// any future axis addition.
3692 ///
3693 /// Third outer top-level [`Caixa`] `&[Composite]`-return slice
3694 /// accessor for M2 / M3 typed-slot vec-carry axes — opens the outer-
3695 /// `Caixa` M3 mesh-slot arm of the `&[Composite]` composite-slice
3696 /// sub-family the sibling M2 [`Self::upgrade_from`] (2a1f907) /
3697 /// [`Self::children`] (c17b51e) accessors opened for the M2 vec-carry
3698 /// altitude. Peer at the outer altitude of the closed inner-
3699 /// [`crate::AplicacaoSpec::membros`] (6c77e36) accessor on the same
3700 /// MESH-COMPOSITION per-Aplicacao member-list axis — the two
3701 /// altitudes now share the same "byte-equal, borrow-shared" outer-
3702 /// accessor discipline. Sibling in shape to the peer outer-`Caixa`
3703 /// `&[Dep]`-return [`Self::deps`] (ad34b4e) / [`Self::deps_dev`]
3704 /// (f7fd81e) and `&[String]`-return [`Self::autores`] (b5d813f) /
3705 /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`] (8a36c23) /
3706 /// [`Self::exe`] (65d9527) / [`Self::servicos`] (611f78b) slice-
3707 /// accessors on the sibling outer-`Caixa` scalar-element vec-carry
3708 /// axes — folds the "outer [`Caixa`] `&[T]` slice" projection
3709 /// pattern onto the sibling M3 typed-composite-element axis
3710 /// ([`crate::aplicacao::Membro`] composite, matching the per-inner
3711 /// [`crate::AplicacaoSpec::membros`] element type at a different
3712 /// altitude).
3713 ///
3714 /// Returns `&[crate::aplicacao::Membro]` (not `&Vec<Membro>`)
3715 /// because every downstream consumer of the member list treats it
3716 /// as a read-only sequence — the slice-view is the narrowest borrow
3717 /// that supports every present + roadmapped consumer (`.iter()`,
3718 /// `.len()`, `.is_empty()`, the
3719 /// [`crate::aplicacao::validate_no_self_membership`] `&[Membro]`
3720 /// input, `serde` slice-serialization) without leaking the backing
3721 /// `Vec`'s grow/push/reserve surface no consumer of the typed view
3722 /// reaches for (the storage-side `Vec` remains reachable through the
3723 /// `pub membros` field for the mutation-carrying serde round-trip
3724 /// and per-test fixture-mutation paths, including the
3725 /// [`Self::aplicacao_view`] fold-in path that clones the slot into
3726 /// the typed view). Named `membros()` to match the storage field's
3727 /// name verbatim and the tatara-lisp author-surface term
3728 /// (`:membros`) the field's own docstring already carries; the
3729 /// accessor's identity maps onto the canonical MESH-COMPOSITION
3730 /// vocabulary the [`Caixa::membros`] field's docstring already
3731 /// reaches for ("Member Servicos that make up this Aplicacao").
3732 #[must_use]
3733 pub const fn membros(&self) -> &[crate::aplicacao::Membro] {
3734 self.membros.as_slice()
3735 }
3736
3737 /// Substrate-canonical per-`Caixa` `:contratos` M3 mesh-slot outer-
3738 /// composite MESH-COMPOSITION-shaped per-Aplicacao WIT-typed
3739 /// inter-Servico contract-list slice accessor every consumer of the
3740 /// top-level manifest's per-Aplicacao `&[crate::aplicacao::WitContract]`
3741 /// slice-view keys off — returns the author-declared `:contratos`
3742 /// typed `Vec<crate::aplicacao::WitContract>` verbatim as a
3743 /// `&[crate::aplicacao::WitContract]` slice-view over the same
3744 /// backing buffer the raw `self.contratos.as_slice()` field access
3745 /// borrows from. Empty-slice-carrying (the "no contracts declared"
3746 /// arm every non-`Aplicacao`-kind `defcaixa` carries by
3747 /// `#[serde(default)]` and every leaf Aplicacao carrying only a
3748 /// single member with no inter-Servico edge carries; the returned
3749 /// `&[WitContract]` degenerates to an empty slice on those arms
3750 /// without any silent `None` collapse).
3751 ///
3752 /// The outer `:contratos` slot carries the M3 typed MESH-COMPOSITION
3753 /// per-Aplicacao WIT-typed inter-Servico edge list — the load-bearing
3754 /// container of every per-edge `{de, para, wit, endpoint | subject |
3755 /// slot}` quadruple the caixa-mesh renderer's per-Aplicacao
3756 /// `CiliumNetworkPolicy` fan-out (one L7 policy per edge —
3757 /// MESH-COMPOSITION §III.2 point 2) and per-`(:de, :para)`
3758 /// adjacency-list seed dispatch on at mesh-artifact materialization
3759 /// time (MESH-COMPOSITION §III.1 — the typed graph's edge set the
3760 /// `:membros` vertex set resolves against, closed by the
3761 /// [`crate::AplicacaoError::ContractoUnknownMember`] / cycle-refusal
3762 /// gates in §III.3; CAIXA-SDLC §II — the typed-M3 slot algebra the
3763 /// operator's per-Aplicacao fan-out dispatch fans on). Every
3764 /// per-edge axis threads through a lifted per-entry accessor on the
3765 /// [`crate::aplicacao::WitContract`] type: the peer `de` / `para`
3766 /// DNS-1123-label member-caixa-name endpoint scalar accessors, the
3767 /// [`crate::aplicacao::WitContract::endpoint`] (7020470) HTTP-shape
3768 /// / [`crate::aplicacao::WitContract::subject`] (90de675)
3769 /// NATS-pub-sub-shape / [`crate::aplicacao::WitContract::slot`]
3770 /// (ed22b66) `wasi:keyvalue/store`-shape payload-carrier accessors,
3771 /// and the WIT-world discriminant. Every downstream consumer of the
3772 /// mesh-graph edge path first passes through this outer accessor
3773 /// onto the slice and then dispatches per-contract through the
3774 /// inner accessors — the two-level dispatch means every
3775 /// per-`:contratos` reader now routes through a typed dispatch on
3776 /// the substrate primitive at both altitudes.
3777 ///
3778 /// Prior to this lift the `.contratos` `Vec<WitContract>` slot was
3779 /// accessed inline at two production sites in
3780 /// caixa-core/src/manifest.rs — the [`Self::declared_mesh_slots`]
3781 /// mesh-slot declared-slot enumerator's
3782 /// `!self.contratos.is_empty()` presence probe (which drives the
3783 /// `M3_AUTHOR_KEY_CONTRATOS` kebab-case author-label push every
3784 /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-coherence
3785 /// gate reads) and the [`Self::aplicacao_view`] per-Aplicacao
3786 /// typed-view composer's `self.contratos.clone()` per-contract
3787 /// fold-in path (which materializes the typed
3788 /// [`crate::aplicacao::AplicacaoSpec`] view every
3789 /// [`crate::StandardLayout::verify`] Aplicacao-arm gate and every
3790 /// downstream `caixa-mesh` renderer dispatches on). A future
3791 /// extension of the outer `:contratos` axis (a per-cluster
3792 /// `:contratos-overrides` overlay the wasm-engine operator resolves
3793 /// at admission time so a cluster-specific edge-set can tighten a
3794 /// caixa-declared list without re-authoring the `caixa.lisp`,
3795 /// promotion of the plain `Vec<WitContract>` to a richer
3796 /// `{static, dynamic}` partition once runtime-resolved contract
3797 /// edges land, per-edge policy annotation once the M4 per-edge
3798 /// policy overlay axis lands) would have had to be threaded through
3799 /// both open-coded copies in lockstep or one consumer would
3800 /// silently disagree with the peer on which edge slice a given
3801 /// Caixa resolves to — the enumerator's presence probe reading the
3802 /// raw slot while the peer view-composer's fold-in path read an
3803 /// operator-resolved slot would silently split the paired
3804 /// declared-slot enumerator and typed-view composition, a
3805 /// two-consumer split at the enumerator and the view composer far
3806 /// from the source `caixa.lisp` with no field naming the edge-set-
3807 /// drift root cause. Lifting the resolution rule to a typed method
3808 /// on the substrate primitive means every downstream consumer of
3809 /// the caixa's per-`Caixa` MESH-COMPOSITION outer-slice surface
3810 /// reaches for exactly one typed dispatch — the resolver's
3811 /// accept-set migrates as a unit on any future axis addition.
3812 ///
3813 /// Fourth and final outer top-level [`Caixa`] `&[Composite]`-return
3814 /// slice accessor for M2 / M3 typed-slot vec-carry axes — closes
3815 /// the outer-`Caixa` `&[Composite]` composite-slice sub-family the
3816 /// sibling M2 [`Self::upgrade_from`] (2a1f907) / [`Self::children`]
3817 /// (c17b51e) accessors opened and the M3 [`Self::membros`]
3818 /// (0f26987) accessor folded on, and closes the outer-`Caixa` M3
3819 /// mesh-slot arm of the composite-slice sub-family the sibling
3820 /// [`Self::membros`] accessor opened for the M3 vec-carry altitude.
3821 /// Peer at the outer altitude of the closed inner-
3822 /// [`crate::AplicacaoSpec::contratos`] (0dcc926) accessor on the
3823 /// same MESH-COMPOSITION per-Aplicacao contract-list axis — the two
3824 /// altitudes now share the same "byte-equal, borrow-shared" outer-
3825 /// accessor discipline. Sibling in shape to the peer outer-`Caixa`
3826 /// `&[Dep]`-return [`Self::deps`] (ad34b4e) / [`Self::deps_dev`]
3827 /// (f7fd81e) and `&[String]`-return [`Self::autores`] (b5d813f) /
3828 /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`] (8a36c23) /
3829 /// [`Self::exe`] (65d9527) / [`Self::servicos`] (611f78b) slice-
3830 /// accessors on the sibling outer-`Caixa` scalar-element vec-carry
3831 /// axes — folds the "outer [`Caixa`] `&[T]` slice" projection
3832 /// pattern onto the sibling M3 typed-composite-element axis
3833 /// ([`crate::aplicacao::WitContract`] composite, matching the
3834 /// per-inner [`crate::AplicacaoSpec::contratos`] element type at a
3835 /// different altitude).
3836 ///
3837 /// Returns `&[crate::aplicacao::WitContract]` (not
3838 /// `&Vec<WitContract>`) because every downstream consumer of the
3839 /// contract list treats it as a read-only sequence — the slice-view
3840 /// is the narrowest borrow that supports every present + roadmapped
3841 /// consumer (`.iter()`, `.len()`, `.is_empty()`, per-edge WIT-world
3842 /// discriminant dispatch, `serde` slice-serialization) without
3843 /// leaking the backing `Vec`'s grow/push/reserve surface no
3844 /// consumer of the typed view reaches for (the storage-side `Vec`
3845 /// remains reachable through the `pub contratos` field for the
3846 /// mutation-carrying serde round-trip and per-test fixture-mutation
3847 /// paths, including the [`Self::aplicacao_view`] fold-in path that
3848 /// clones the slot into the typed view). Named `contratos()` to
3849 /// match the storage field's name verbatim and the tatara-lisp
3850 /// author-surface term (`:contratos`) the field's own docstring
3851 /// already carries; the accessor's identity maps onto the canonical
3852 /// MESH-COMPOSITION vocabulary the [`Caixa::contratos`] field's
3853 /// docstring already reaches for ("WIT-typed inter-Servico
3854 /// contracts").
3855 #[must_use]
3856 pub const fn contratos(&self) -> &[crate::aplicacao::WitContract] {
3857 self.contratos.as_slice()
3858 }
3859
3860 /// Compose the Aplicacao-related flat slots into a single typed
3861 /// [`crate::aplicacao::AplicacaoSpec`] for validation +
3862 /// downstream renderer consumption. Returns `None` when the
3863 /// caixa isn't a `:kind Aplicacao`.
3864 #[must_use]
3865 pub fn aplicacao_view(&self) -> Option<crate::aplicacao::AplicacaoSpec> {
3866 if !self.kind().is_aplicacao() {
3867 return None;
3868 }
3869 Some(crate::aplicacao::AplicacaoSpec {
3870 membros: self.membros().to_vec(),
3871 contratos: self.contratos().to_vec(),
3872 politicas: self.politicas().cloned().unwrap_or_default(),
3873 placement: self.placement().cloned().unwrap_or_default(),
3874 entrada: self.entrada().cloned(),
3875 })
3876 }
3877
3878 /// The kebab-case `:slot` tags of every M3 mesh slot this caixa
3879 /// *declares* a value on, in canonical declaration order
3880 /// (`:membros` → `:contratos` → `:politicas` → `:placement` →
3881 /// `:entrada`). A slot counts as declared when its backing field
3882 /// carries a value — a non-empty `Vec`, or a `Some(...)`.
3883 ///
3884 /// The M3 mesh slots compose the typed graph of a `:kind Aplicacao`
3885 /// (MESH-COMPOSITION §III.1). [`Self::aplicacao_view`] only folds
3886 /// them into a validatable [`crate::aplicacao::AplicacaoSpec`] when
3887 /// the kind matches (returns `None` otherwise), and the caixa-mesh /
3888 /// caixa-flux / caixa-helm renderers only emit them for an
3889 /// Aplicacao. On any *other* kind a declared mesh slot is the
3890 /// manifest field's documented "ignored otherwise" (see the
3891 /// `:membros` … `:entrada` field docs): it silently passes
3892 /// [`Caixa::from_lisp`] and then vanishes — never validated, never
3893 /// rendered — far from the source caixa.lisp.
3894 /// [`crate::StandardLayout::verify`] consults this to reject that
3895 /// silent-drop at caixa-build time
3896 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`]), mirroring the
3897 /// `SupervisorOwnsCode` / `AplicacaoOwnsCode` kind-coherence gates:
3898 /// a slot foreign to the kind is a build error, not a silent drop.
3899 ///
3900 /// Lifted as a typed method (rather than an inline disjunction at
3901 /// the verify call site) so the mesh-slot set lives in one place —
3902 /// a future M4 axis added to the Aplicacao surface (per-edge policy
3903 /// overlay, distributed-app takeover config) is one push here, and
3904 /// every consumer reaching for "which mesh slots are set" (the
3905 /// verify gate, a future `feira lint` kind-coherence advisory)
3906 /// inherits the canonical order without rolling its own.
3907 ///
3908 /// Each per-arm kebab-case label is routed through the peer
3909 /// [`crate::M3_AUTHOR_KEY_MEMBROS`] /
3910 /// [`crate::M3_AUTHOR_KEY_CONTRATOS`] /
3911 /// [`crate::M3_AUTHOR_KEY_POLITICAS`] /
3912 /// [`crate::M3_AUTHOR_KEY_PLACEMENT`] /
3913 /// [`crate::M3_AUTHOR_KEY_ENTRADA`] consts declared next to the
3914 /// [`crate::M3_KEY_PLACEMENT`] renderer-side wire-key peer, so both
3915 /// halves of every M3 top-level mesh slot's dual axis (author-facing
3916 /// kebab-case label + renderer-side artifact key) route through one
3917 /// canonical declaration per arm — same discipline the peer
3918 /// [`crate::M2_AUTHOR_KEY_LIMITS`] / [`crate::M2_AUTHOR_KEY_BEHAVIOR`]
3919 /// / [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] top-level M2 slot consts
3920 /// (f49c8b0) establish on the sibling per-Servico M2 top-level slot
3921 /// axis, extended here to close the M3 mesh-slot author-facing-label
3922 /// axis so both altitudes of the typed-slot algebra
3923 /// (per-Servico M2 + per-Aplicacao M3) share the same
3924 /// "one canonical byte-string per arm, next to the axis" discipline.
3925 #[must_use]
3926 pub fn declared_mesh_slots(&self) -> Vec<&'static str> {
3927 let mut slots = Vec::new();
3928 if !self.membros().is_empty() {
3929 slots.push(crate::render::M3_AUTHOR_KEY_MEMBROS);
3930 }
3931 if !self.contratos().is_empty() {
3932 slots.push(crate::render::M3_AUTHOR_KEY_CONTRATOS);
3933 }
3934 if self.politicas().is_some() {
3935 slots.push(crate::render::M3_AUTHOR_KEY_POLITICAS);
3936 }
3937 if self.placement().is_some() {
3938 slots.push(crate::render::M3_AUTHOR_KEY_PLACEMENT);
3939 }
3940 if self.entrada().is_some() {
3941 slots.push(crate::render::M3_AUTHOR_KEY_ENTRADA);
3942 }
3943 slots
3944 }
3945
3946 /// The kebab-case `:slot` tags of every supervisor-tree slot this
3947 /// caixa *declares* a value on, in canonical declaration order
3948 /// (`:estrategia` → `:max-restarts` → `:restart-window` →
3949 /// `:children`). A slot counts as declared when its backing field
3950 /// carries a value — a `Some(...)`, or a non-empty `Vec`.
3951 ///
3952 /// The supervisor-tree slots compose the typed OTP supervisor of a
3953 /// `:kind Supervisor` (INSPIRATIONS §II.2; the `:estrategia` +
3954 /// `:children` field docs above). [`Self::supervisor_view`] only
3955 /// folds them into a validatable [`SupervisorSpec`] when the kind
3956 /// matches (returns `None` otherwise), and the wasm-operator's
3957 /// hierarchical reconciler only consumes them for a Supervisor. On
3958 /// any *other* kind a declared supervisor slot is the manifest
3959 /// field's documented "ignored otherwise" (see the `:estrategia` …
3960 /// `:children` field docs): it silently passes [`Caixa::from_lisp`]
3961 /// and then vanishes — never validated, never reconciled — far from
3962 /// the source caixa.lisp. [`crate::StandardLayout::verify`] consults
3963 /// this to reject that silent-drop at caixa-build time
3964 /// ([`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]), the
3965 /// exact mirror of the [`Self::declared_mesh_slots`] /
3966 /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] gate on the
3967 /// Aplicacao-only slot set: a slot foreign to the kind is a build
3968 /// error, not a silent drop.
3969 #[must_use]
3970 pub fn declared_supervisor_slots(&self) -> Vec<&'static str> {
3971 let mut slots = Vec::new();
3972 if self.estrategia().is_some() {
3973 slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA);
3974 }
3975 if self.max_restarts().is_some() {
3976 slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS);
3977 }
3978 if self.restart_window().is_some() {
3979 slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW);
3980 }
3981 if !self.children().is_empty() {
3982 slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN);
3983 }
3984 slots
3985 }
3986
3987 /// The kebab-case `:slot` tags of every M2 Servico-runtime slot this
3988 /// caixa *declares* a value on, in canonical declaration order
3989 /// (`:limits` → `:behavior` → `:upgrade-from`). A slot counts as
3990 /// declared when its backing field carries a value — a `Some(...)`,
3991 /// or a non-empty `Vec`.
3992 ///
3993 /// The M2 slots configure the runtime of a long-running wasm
3994 /// component, i.e. a `:kind Servico`: `:limits` is Lunatic
3995 /// per-process sandboxing (INSPIRATIONS §III.1), `:behavior` is the
3996 /// OTP `gen_server` callback set (§II.3), `:upgrade-from` is the OTP
3997 /// appup hot-code-reload table (§II.4). The caixa-helm / caixa-flux
3998 /// renderers gate on [`crate::require_kind`]`(_, Servico)` and only
3999 /// emit these slots for a Servico; on any *other* kind a declared M2
4000 /// slot is the manifest field's documented "ignored otherwise": its
4001 /// well-formedness is checked by [`crate::StandardLayout::verify`]
4002 /// but the value is never rendered into a chart / programs.yaml entry
4003 /// — it silently passes [`Caixa::from_lisp`] + `feira build` and then
4004 /// vanishes, far from the source caixa.lisp.
4005 /// [`crate::StandardLayout::verify`] consults this to reject that
4006 /// silent-drop at caixa-build time
4007 /// ([`crate::LayoutError::ServicoSlotsOnNonServico`]), the exact
4008 /// mirror of the [`Self::declared_mesh_slots`] /
4009 /// [`Self::declared_supervisor_slots`] gates on the peer
4010 /// kind-exclusive slot sets: a slot foreign to the kind is a build
4011 /// error, not a silent drop.
4012 ///
4013 /// Each per-arm kebab-case label is routed through the peer
4014 /// [`crate::M2_AUTHOR_KEY_LIMITS`] / [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
4015 /// [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] consts declared next to the
4016 /// [`crate::M2_KEY_LIMITS`] / [`crate::M2_KEY_BEHAVIOR`] /
4017 /// [`crate::M2_KEY_UPGRADE_FROM`] renderer-side wire-key peers, so
4018 /// both halves of the M2 top-level slot's dual axis (author-facing
4019 /// kebab-case label + renderer-side camelCase overlay-container wire
4020 /// key) route through one canonical declaration per arm — same
4021 /// discipline the peer [`crate::M2_BEHAVIOR_AUTHOR_KEY_ON_*`] sub-slot
4022 /// author-label consts (889dc18) establish on the sibling
4023 /// per-callback axis inside the `:behavior` overlay block.
4024 #[must_use]
4025 pub fn declared_servico_slots(&self) -> Vec<&'static str> {
4026 let mut slots = Vec::new();
4027 if self.limits().is_some() {
4028 slots.push(crate::render::M2_AUTHOR_KEY_LIMITS);
4029 }
4030 if self.behavior().is_some() {
4031 slots.push(crate::render::M2_AUTHOR_KEY_BEHAVIOR);
4032 }
4033 if !self.upgrade_from().is_empty() {
4034 slots.push(crate::render::M2_AUTHOR_KEY_UPGRADE_FROM);
4035 }
4036 slots
4037 }
4038
4039 /// The kebab-case `:slot` tags of every code-surface slot this caixa
4040 /// declares a value on that its [`CaixaKind`] doesn't natively own,
4041 /// in canonical declaration order (`:exe` → `:servicos`). A
4042 /// code-surface slot is owned by exactly one kind: `:exe` by
4043 /// [`CaixaKind::Binario`] (the nix-built executable surface), and
4044 /// `:servicos` by [`CaixaKind::Servico`] (the wasm component +
4045 /// `ComputeUnit` daemon surface).
4046 ///
4047 /// Each is silently ignored when declared on the wrong kind: the
4048 /// caixa-helm / caixa-flux / caixa-flake renderers gate on
4049 /// [`crate::require_kind`]`(_, <owning-kind>)`, so on any *other*
4050 /// code-running kind a declared `:exe` / `:servicos` is the manifest
4051 /// field's documented "ignored otherwise" — its path is checked for
4052 /// existence by the layout's `bibliotecas`/`exe`/`servicos` loops
4053 /// (which run after [`Caixa::from_lisp`]), but the value is never
4054 /// rendered into a build target or programs.yaml entry. It silently
4055 /// passes [`Caixa::from_lisp`] + `feira build`, far from the source
4056 /// caixa.lisp, with no field naming which slot is foreign.
4057 ///
4058 /// [`crate::StandardLayout::verify`] consults this to reject that
4059 /// silent-drop at caixa-build time
4060 /// ([`crate::LayoutError::ForeignCodeSlot`]), beside the M2
4061 /// servico-runtime, supervisor-tree, and M3 mesh kind-coherence
4062 /// gates ([`Self::declared_servico_slots`] /
4063 /// [`Self::declared_supervisor_slots`] /
4064 /// [`Self::declared_mesh_slots`]): the fourth kind ↔ slot algebra
4065 /// axis to be closed on the typed surface. The Supervisor /
4066 /// Aplicacao "no code at all" cases ([`crate::LayoutError::SupervisorOwnsCode`]
4067 /// / [`crate::LayoutError::AplicacaoOwnsCode`]) keep their dedicated
4068 /// diagnostics — they fire ahead of this gate on the same `verify`
4069 /// pass, so for Supervisor / Aplicacao the `OwnCode` arm always wins
4070 /// and this method is moot. For Biblioteca / Binario / Servico, this
4071 /// gate fires when a code-running kind declares another code-running
4072 /// kind's exclusive code surface.
4073 ///
4074 /// `:bibliotecas` is deliberately excluded — a Binario or Servico
4075 /// may legitimately ship a `lib/` helper that the underlying
4076 /// substrate (the nix flake for Binario, the wasm component build
4077 /// for Servico) bundles into its build, so the slot's
4078 /// declared-on-wrong-kind cardinality isn't a structural error on
4079 /// either code-running kind. A Biblioteca declaring `:bibliotecas`
4080 /// is the native case (the slot's owning kind). Supervisor /
4081 /// Aplicacao declaring `:bibliotecas` is gated upstream by
4082 /// [`crate::LayoutError::SupervisorOwnsCode`] /
4083 /// [`crate::LayoutError::AplicacaoOwnsCode`].
4084 ///
4085 /// Lifted as a typed method (rather than an inline disjunction at
4086 /// the verify call site) so the foreign-code-slot set lives in one
4087 /// place — a future kind that gains its own code-surface slot is
4088 /// one push here, and every consumer reaching for "which code
4089 /// surfaces are foreign to this kind" (the verify gate, a future
4090 /// `feira lint` kind-coherence advisory, the future `app-operator`'s
4091 /// per-caixa build-target classifier) inherits the canonical order
4092 /// without rolling its own.
4093 #[must_use]
4094 pub fn declared_foreign_code_slots(&self) -> Vec<&'static str> {
4095 let mut slots = Vec::new();
4096 if !self.exe().is_empty() && !self.kind().requires_exe() {
4097 slots.push(":exe");
4098 }
4099 if !self.servicos().is_empty() && !self.kind().requires_servicos() {
4100 slots.push(":servicos");
4101 }
4102 slots
4103 }
4104
4105 /// Validate every entry of `:deps` and `:deps-dev` through
4106 /// [`Dep::validate`] — closing the parity loop with the per-axis
4107 /// `:versao` gates already wired into the typed-graph
4108 /// ([`crate::AplicacaoSpec::validate_membros`] for `:membros`,
4109 /// 9888b13) and typed supervisor tree
4110 /// ([`crate::SupervisorSpec::validate`] for `:children`, b38ff3a).
4111 ///
4112 /// Until this gate landed `:deps :versao` and `:deps-dev :versao`
4113 /// were the only `:versao` axes still untyped past
4114 /// [`Caixa::from_lisp`]: the derive macro stored the requirement
4115 /// as a String without parsing it, so a malformed-but-non-empty
4116 /// requirement (`"^bad-version"`, `"^^0.1"`, `"v0.1"`, `"not-a-req"`)
4117 /// silently passed parse and the `semver::Error` surfaced at
4118 /// lacre-resolve time, far from the source caixa.lisp, with no
4119 /// field naming which `:deps` entry carried the typo. Lifting the
4120 /// gate here makes the four `:versao` typed surfaces (`:deps`,
4121 /// `:deps-dev`, `:membros`, `:children`) structurally equivalent —
4122 /// every requirement string past `validate_deps` is round-trippable
4123 /// through [`crate::parse_requirement`] without re-checking at the
4124 /// resolver layer.
4125 ///
4126 /// Both lists run through the same per-entry validator so a typo
4127 /// in `:deps-dev` surfaces with the same diagnostic as one in
4128 /// `:deps` — neither axis is a second-class citizen of the typed
4129 /// surface.
4130 ///
4131 /// Within each list, [`DepError::DuplicateNome`] closes the
4132 /// set-not-multiset discipline on the `:nome` axis: two entries
4133 /// naming the same caixa carry two `:versao` / `:fonte` / feature
4134 /// triples that the caixa-resolver's lacre pipeline collapses to one
4135 /// via its `HashMap`-keyed-by-`:nome` consumption — the second entry
4136 /// silently overwrites the first at `concrete_versao`-resolve time
4137 /// (the same "second wins / one silently overwrites the other"
4138 /// shape the peer typed-graph duplicate gates already close on every
4139 /// other Vec-shaped authoring surface that keys by name). The
4140 /// duplicate check fires per-list and runs *after* each per-entry
4141 /// [`Dep::validate`] call so a malformed-and-duplicated entry
4142 /// surfaces its narrower per-entry diagnostic
4143 /// ([`DepError::NomeInvalid`], [`DepError::VersaoInvalid`],
4144 /// [`DepError::FonteRepoEmpty`], …) before the cross-entry duplicate
4145 /// diagnostic — the canonical "per-entry shape before cross-entry
4146 /// uniqueness" precedence the peer `:children :caixa`
4147 /// ([`crate::SupervisorSpec::validate`]), `:membros :caixa`
4148 /// ([`crate::AplicacaoSpec::validate_membros`]), `:contratos`
4149 /// ([`crate::AplicacaoSpec::validate`]), `:placement :clusters`
4150 /// ([`crate::AplicacaoSpec::validate_placement`]),
4151 /// `:entrada :paths` ([`crate::AplicacaoSpec::validate`]),
4152 /// `:upgrade-from :from` ([`crate::upgrade::validate_upgrade_from`]),
4153 /// and the within-`:upgrade-from`-entry per-instruction-class
4154 /// singularity gates ([`crate::UpgradeError::DuplicateLoadModule`],
4155 /// [`crate::UpgradeError::DuplicateStateChange`],
4156 /// [`crate::UpgradeError::DuplicateCleanup`]) all establish.
4157 ///
4158 /// Cross-list (`:deps` ↔ `:deps-dev`) coincidence is *not* gated
4159 /// here: Cargo's `[dependencies]` + `[dev-dependencies]` accept the
4160 /// same name in both tables (the dev table's pin overrides the
4161 /// runtime table's pin in test/dev contexts), and caixa's surface
4162 /// mirrors that convention until a deliberate choice retires the
4163 /// override pattern. Only within-list duplicates are structurally
4164 /// incoherent — those are what this gate closes.
4165 ///
4166 /// Compound per-`Caixa` entry gate on the dep-graph axis: folds the
4167 /// two standalone dep-list validators — the per-entry + within-list
4168 /// duplicate-`:nome` walk (the [`Dep::validate`] +
4169 /// [`crate::render::insert_first_seen`] cascade this method opened
4170 /// on) and the cross-slot self-edge gate
4171 /// ([`crate::dep::validate_no_self_dep`]) — onto one substrate
4172 /// primitive on [`Caixa`]. The two arms run in the same canonical
4173 /// order the layout pipeline
4174 /// ([`crate::layout::StandardLayout::verify`], the `feira build`
4175 /// author-time gate) has always sequenced them (per-entry +
4176 /// cross-entry duplicate → cross-slot self-edge), so the fold is
4177 /// byte-for-byte equivalent to the pre-fold two-block cascade at
4178 /// that call site (pinned by the paired
4179 /// `validate_deps_folds_per_entry_arm_matches_gate` /
4180 /// `validate_deps_folds_self_edge_arm_matches_gate` equivalence
4181 /// pins and the `validate_deps_per_entry_arm_fires_before_self_edge_arm`
4182 /// ordering pin). Self-contained on `&self` — resolves its three
4183 /// inputs ([`Self::deps`], [`Self::deps_dev`], [`Self::nome`])
4184 /// through the substrate primitives' own accessor family, the same
4185 /// posture every peer per-slot compound gate
4186 /// ([`crate::AplicacaoSpec::validate_contratos`],
4187 /// [`crate::MeshPolicy::validate`],
4188 /// [`crate::SupervisorSpec::validate_children`],
4189 /// [`Self::validate_upgrade_from`]) already carries.
4190 ///
4191 /// Prior to this lift [`crate::dep::validate_no_self_dep`] lived
4192 /// only open-coded at the layout wire-up site
4193 /// ([`crate::layout::StandardLayout::verify`], caixa-core/src/layout.rs)
4194 /// as a standalone two-arg dispatch immediately after this method's
4195 /// per-entry + cross-entry walk, both wrapped through the same
4196 /// [`crate::LayoutError::DepsViolation`] envelope: every future
4197 /// consumer that wanted to gate the dep-graph as a whole — the
4198 /// deferred `caixa.pleme.io/v1alpha1/Caixa` CR materializer's
4199 /// per-CR admission webhook re-checking `:deps` / `:deps-dev` after
4200 /// a per-entry patch, a future `feira validate --deps` per-caixa
4201 /// admission verb, a per-`:deps` overlay resolver a per-cluster
4202 /// overlay lift would materialize (each the deferred consumer this
4203 /// method's peer [`Self::deps`] / [`Self::deps_dev`] accessors'
4204 /// docstrings already name) — was structurally forced to either
4205 /// re-inline the two-dispatch cascade in lockstep with the layout
4206 /// wire-up (the duplication the PRIME DIRECTIVE names as a bug) or
4207 /// call the whole [`crate::layout::StandardLayout::verify`] pipeline
4208 /// and pay every peer per-Caixa gate to re-check one slot. Post-fold
4209 /// each such consumer reaches the two-arm compound gate through one
4210 /// call on the substrate primitive.
4211 pub fn validate_deps(&self) -> Result<(), DepError> {
4212 for &list in crate::dep::DepList::ALL {
4213 let mut seen = std::collections::HashSet::new();
4214 for dep in self.deps_of(list) {
4215 dep.validate()?;
4216 crate::render::insert_first_seen(&mut seen, dep.nome(), || {
4217 DepError::DuplicateNome {
4218 nome: dep.nome().to_string(),
4219 list: list.as_str(),
4220 }
4221 })?;
4222 }
4223 }
4224 crate::dep::validate_no_self_dep(self.deps(), self.deps_dev(), self.nome())?;
4225 Ok(())
4226 }
4227
4228 /// Reject `:nome` values the K8s apiserver would refuse at admission
4229 /// time. The top-level Caixa identity flows directly into every
4230 /// substrate-side artifact's `metadata.name` axis: the
4231 /// `lareira-<nome>` Helm chart name ([`caixa-helm::lib::chart_name`]),
4232 /// the programs.yaml `name:` entry the `lareira-fleet-programs`
4233 /// aggregator keys ComputeUnit derivation off
4234 /// ([`caixa-flux::lib::programs_yaml_entry`]), the
4235 /// `LABEL_APLICACAO` label value carried on every Aplicacao-owned
4236 /// pod and the per-`:contratos` CiliumNetworkPolicy `metadata.name`
4237 /// (`<aplicacao>-<de>-to-<para>`) and the per-`:entrada`
4238 /// `<aplicacao>-<para>` HTTPRoute `metadata.name`
4239 /// ([`caixa-mesh::lib::cilium_network_policies`],
4240 /// [`caixa-mesh::lib::gateway_routes`]), and the default
4241 /// `lib/<nome>.lisp` / `exe/<nome>` layout paths
4242 /// ([`crate::StandardLayout::verify`]). Each K8s apiserver-side
4243 /// schema enforces the DNS-1123 label rule on admission; a
4244 /// structurally invalid `:nome` (`"MyApp"` — the canonical
4245 /// "I copied the display name verbatim" footgun, `"my_app"` — the
4246 /// Python-/Postgres-leak, `"team.app"` — `:nome` is a single label
4247 /// not a subdomain, `"-app"` / `"app-"` — DNS-1123 boundary
4248 /// violations, `"my app"` — the paste-from-doc footgun, `"café"` —
4249 /// IDN must be pre-encoded as Punycode, the 64-byte UUID-shaped
4250 /// over-cap slug) silently passed [`Caixa::from_lisp`] and the
4251 /// failure surfaced at `kubectl apply` time as a `metadata.name:
4252 /// Invalid value` rejection on whichever derived artifact admitted
4253 /// first, far from the source `caixa.lisp` and without any field
4254 /// naming the offending `:nome`.
4255 ///
4256 /// Thin wrapper around [`crate::render::is_dns_1123_label`] (the
4257 /// substrate-side predicate the per-axis name gates already share:
4258 /// `:membros :caixa` 3f9d7a0, `:placement :clusters` 6cbb900,
4259 /// `:children :caixa` 31bfa43) that maps the shared parser-shaped
4260 /// reason into the [`ManifestError::NomeInvalid`] variant, so the
4261 /// diagnostic is self-locating (the offending `:nome` is named
4262 /// verbatim) and the author can grep their `caixa.lisp` for
4263 /// `:nome "<value>"` and fix it in one edit. Same diagnostic shape
4264 /// every per-axis sibling gate already exposes
4265 /// ([`crate::AplicacaoError::MembroCaixaInvalid`],
4266 /// [`crate::AplicacaoError::PlacementClusterInvalid`],
4267 /// [`crate::SupervisorError::ChildCaixaInvalid`]).
4268 ///
4269 /// Empty `:nome` (which [`Caixa::from_lisp`] does not reject — the
4270 /// derive macro stores the raw String) is gated by the narrower
4271 /// [`ManifestError::NomeEmpty`] arm before the predicate is
4272 /// consulted, mirroring the empty-first cascade every per-axis
4273 /// name gate already uses (e.g. `MembroCaixaEmpty` before
4274 /// `MembroCaixaInvalid`, `EmptyChildName` before `ChildCaixaInvalid`).
4275 pub fn validate_nome(&self) -> Result<(), ManifestError> {
4276 // Routes through the shared
4277 // [`crate::render::require_valid_dns_1123_label`] gate the peer
4278 // name axes each land on so drift between the eight axes'
4279 // accepted DNS-1123-label sets is structurally impossible.
4280 let nome = self.nome();
4281 crate::render::require_valid_dns_1123_label(
4282 nome,
4283 || ManifestError::NomeEmpty,
4284 |reason| ManifestError::NomeInvalid {
4285 nome: nome.to_string(),
4286 reason,
4287 },
4288 )
4289 }
4290
4291 /// Reject `:nome` values whose joint length with the canonical
4292 /// [`crate::LAREIRA_CHART_NAME_PREFIX`] (`"lareira-"`) overflows
4293 /// the K8s DNS-1123 label cap [`crate::DNS_1123_LABEL_MAX_LEN`]
4294 /// (63 bytes). Every per-Servico / per-Aplicacao renderer the
4295 /// substrate carries materializes the caixa's `:nome` through the
4296 /// canonical [`crate::lareira_chart_name`] helper (f7320d7) into a
4297 /// `lareira-<nome>` artifact that lands as a K8s `metadata.name` /
4298 /// Helm chart name / `HelmRelease` `release_name`: `caixa-helm`'s
4299 /// `ChartDir.name` + `Chart.yaml::name`
4300 /// (caixa-helm/src/lib.rs:207), `caixa-flux`'s `cluster_bundle`
4301 /// `HelmRelease` `chart:` slot (caixa-flux/src/lib.rs:329),
4302 /// `caixa-tatara`'s `process_for_aplicacao` `release_name` +
4303 /// `oci://<registry>/lareira-<nome>` chart ref
4304 /// (caixa-tatara/src/lib.rs:124,178). Helm's own `Chart.yaml::name`
4305 /// admission rule strict-parses against DNS-1123-label, the Helm
4306 /// operator's tracking-secret name is derived from `release_name`
4307 /// and is itself DNS-1123-label-bounded, and the rendered chart's
4308 /// K8s object `metadata.name` axes embed the chart name as a
4309 /// prefix — every one fails admission on a > 63-byte chart name.
4310 ///
4311 /// The per-axis [`Self::validate_nome`] gate (6c992f8) already
4312 /// caps `:nome` itself at 63 bytes via [`is_dns_1123_label`], so a
4313 /// `:nome` of 56–63 bytes silently passed validate (the inner
4314 /// DNS-1123 check accepts the bare `:nome`) but produced a
4315 /// `lareira-<nome>` of 64–71 bytes that the apiserver / `helm lint`
4316 /// rejected at admission — far from the source `caixa.lisp`, with
4317 /// no field naming the overflow root cause. The
4318 /// [`lareira_chart_name`] helper's own doc comment
4319 /// (caixa-core/src/render.rs:3198) explicitly deferred the fix:
4320 /// "the M4 admission webhook will pin the joint-length invariant
4321 /// when it lands". This gate lands the invariant at the
4322 /// manifest-validate layer rather than waiting for the apiserver
4323 /// — the same fail-at-the-source posture every peer per-axis
4324 /// value-shape gate (DNS-1123 on `:nome`, SemVer-2 on `:versao`,
4325 /// SPDX-expression-shape on `:licenca`, 4-digit decimal year on
4326 /// `:edicao`, etc.) takes.
4327 ///
4328 /// Thin wrapper around
4329 /// [`crate::render::is_lareira_chart_name_shape`] (the
4330 /// substrate-side predicate that composes [`lareira_chart_name`] +
4331 /// [`is_dns_1123_label`] via the lifted
4332 /// [`crate::LAREIRA_CHART_NAME_NOME_MAX_LEN`] budget); maps the
4333 /// shared parser-shaped reason into the
4334 /// [`ManifestError::NomeChartNameBudgetExceeded`] variant so the
4335 /// diagnostic is self-locating (the offending `:nome` is named
4336 /// verbatim alongside the rendered chart name and the budget) and
4337 /// the author can shorten in one edit. The gate runs across every
4338 /// `:kind` — `:nome` is the substrate-wide identity axis any
4339 /// future renderer the substrate adds can derive a
4340 /// `lareira-<nome>` artifact from, and uniform enforcement closes
4341 /// the drift footgun where a future kind grows a chart-emitting
4342 /// render path while the validate cascade doesn't catch it.
4343 ///
4344 /// Runs *after* [`Self::validate_nome`] so the narrower
4345 /// `NomeEmpty` / `NomeInvalid` shape diagnostics fire first — a
4346 /// structurally-malformed `:nome` (empty, uppercase, underscore,
4347 /// dot, leading/trailing hyphen, Unicode, > 63 bytes) surfaces its
4348 /// specific shape error rather than the chart-name-budget error,
4349 /// preserving the legitimate "well-shaped `:nome` that happens to
4350 /// overflow the joint cap" arm for this gate.
4351 pub fn validate_nome_chart_name_budget(&self) -> Result<(), ManifestError> {
4352 let nome = self.nome();
4353 crate::render::is_lareira_chart_name_shape(nome).map_err(|reason| {
4354 ManifestError::NomeChartNameBudgetExceeded {
4355 nome: nome.to_string(),
4356 reason,
4357 }
4358 })
4359 }
4360
4361 /// Reject `:versao` values that don't parse as [`semver::Version`].
4362 /// The top-level Caixa version flows directly into every
4363 /// substrate-side artifact that carries a "this is which version of
4364 /// the caixa" axis: the `lareira-<nome>` Helm chart's `Chart.yaml`
4365 /// `version:` + `appVersion:` axes ([`caixa-helm::lib`] —
4366 /// SemVer-2-strict at `helm template` / `helm install` time per
4367 /// https://helm.sh/docs/topics/charts/#charts-and-versioning), the
4368 /// `feira publish` Zig-style `v<versao>` git tag
4369 /// ([`caixa-flux::lib::programs_yaml_entry`] / the
4370 /// `caixa-publish.yml` reusable workflow), the programs.yaml entry's
4371 /// `versao:` value the `lareira-fleet-programs` aggregator carries
4372 /// onto each rendered ComputeUnit, the OCI image's `:v<versao>` /
4373 /// `:latest` tags the substrate's `wasi-service-flake` builds with
4374 /// `skopeo push`, the lacre closure's pinned versions
4375 /// ([`caixa-resolver`] keys `concrete_versao`), and the
4376 /// `:upgrade-from :from` references peers in this exact `versao`
4377 /// shape (`semver::Version`, not `VersionReq`). Each consumer
4378 /// expects a strict three-part `MAJOR.MINOR.PATCH` (optionally
4379 /// `-prerelease` and/or `+build`); a structurally invalid `:versao`
4380 /// (`"0.1"` — missing patch, the canonical "I shortened it" footgun;
4381 /// `"v0.1.0"` — the git-tag-shape-leaking-into-versao typo;
4382 /// `"latest"` / `"main"` — the "I confused it with a docker tag"
4383 /// footgun; `"^0.1"` / `"~0.1.2"` — the requirement-shape leaking
4384 /// into the version field a peer `:deps :versao` accepts;
4385 /// `"0.1.0.0"` — the four-part Java/Microsoft convention DNS
4386 /// SemVer-2 forbids) silently passed [`Caixa::from_lisp`] (the
4387 /// derive macro stores the raw String) and the failure surfaced at
4388 /// the *first* downstream consumer that strict-parses it: at
4389 /// `helm install` time as a chart-version rejection, at
4390 /// `feira publish` time as a malformed git tag, at lacre-resolve
4391 /// time as a `semver::Error` not naming the offending caixa, at
4392 /// `feira upgrade --to <versao>` time as an unresolvable
4393 /// `:upgrade-from :from` match — far from the source `caixa.lisp`
4394 /// and without any field naming the offending `:versao`.
4395 ///
4396 /// Thin wrapper around [`semver::Version::parse`] — the same parser
4397 /// [`crate::CaixaVersion::parse`] (the typed `:versao` accessor)
4398 /// and [`crate::UpgradeFromEntry::validate`] (the peer
4399 /// `:upgrade-from :from` axis, 26da2c7) consume. Maps the
4400 /// `semver::Error` reason into the [`ManifestError::VersaoInvalid`]
4401 /// variant, carrying the offending `:versao` verbatim + a
4402 /// parser-shaped reason naming the specific violation, so the
4403 /// diagnostic is self-locating (the author can grep their
4404 /// `caixa.lisp` for `:versao "<value>"` and fix it in one edit).
4405 /// Same diagnostic shape as [`ManifestError::NomeInvalid`]
4406 /// (6c992f8) and [`crate::UpgradeError::FromInvalid`]
4407 /// (b0c8389) on the peer axes. With this gate, the typed `:versao`
4408 /// surfaces — top-level `:versao`, `:upgrade-from :from` — are
4409 /// now structurally equivalent (every value past validate is
4410 /// round-trippable through [`semver::Version::parse`] without
4411 /// re-checking at the renderer, resolver, or operator hot-upgrade
4412 /// layer), peer with the four `:versao` requirement axes (`:deps`,
4413 /// `:deps-dev`, `:membros`, `:children`) the prior commits
4414 /// (2420c44, 9888b13, b38ff3a) wired through `parse_requirement`.
4415 ///
4416 /// Empty `:versao` (which [`Caixa::from_lisp`] does not reject —
4417 /// the derive macro stores the raw String) is gated by the
4418 /// narrower [`ManifestError::VersaoEmpty`] arm before the parser is
4419 /// consulted, mirroring the empty-first cascade every per-axis
4420 /// version gate already uses (e.g. `MembroVersaoEmpty` before
4421 /// `MembroVersaoInvalid`, `EmptyChildVersion` before
4422 /// `ChildVersaoInvalid`, `NomeEmpty` before `NomeInvalid`).
4423 pub fn validate_versao(&self) -> Result<(), ManifestError> {
4424 let versao = self.versao();
4425 if versao.is_empty() {
4426 return Err(ManifestError::VersaoEmpty);
4427 }
4428 semver::Version::parse(versao).map_err(|e| ManifestError::VersaoInvalid {
4429 versao: versao.to_string(),
4430 reason: e.to_string(),
4431 })?;
4432 Ok(())
4433 }
4434
4435 /// Compound per-`Caixa` entry gate on the M2 `:upgrade-from` slot:
4436 /// folds the three [`crate::upgrade`] top-level validators — the
4437 /// per-entry shape + cross-entry duplicate-`:from` gate
4438 /// ([`crate::upgrade::validate_upgrade_from`]), the cross-slot
4439 /// `:from < :versao` SemVer-2 precedence gate
4440 /// ([`crate::upgrade::validate_upgrade_from_against_versao`]), and the
4441 /// cross-slot `:state-change` ↔ `:on-state-change` composition gate
4442 /// ([`crate::upgrade::validate_upgrade_from_against_behavior`]) — onto
4443 /// one substrate primitive on [`Caixa`]. The three dispatches run in
4444 /// the same order the layout pipeline
4445 /// ([`crate::layout::StandardLayout::verify`], the `feira build`
4446 /// author-time gate) has always sequenced them, so the fold is
4447 /// byte-for-byte equivalent to the pre-fold three-block cascade at
4448 /// that call site (pinned by the per-arm
4449 /// `validate_upgrade_from_folds_per_entry_arm_matches_gate` /
4450 /// `_folds_versao_arm_matches_gate` / `_folds_behavior_arm_matches_gate`
4451 /// equivalence pins and by the cross-arm
4452 /// `validate_upgrade_from_per_entry_arm_fires_before_versao_arm` /
4453 /// `_versao_arm_fires_before_behavior_arm` ordering pins).
4454 ///
4455 /// Prior to this lift the three [`crate::upgrade`] top-level validators
4456 /// lived only open-coded at the layout wire-up site
4457 /// ([`crate::layout::StandardLayout::verify`], caixa-core/src/layout.rs),
4458 /// each threaded through the same `self.upgrade_from()` slice and each
4459 /// paired with the same [`crate::LayoutError::UpgradeViolation`]-wrap
4460 /// envelope: every future consumer that wanted to gate `:upgrade-from`
4461 /// as a whole — the deferred `caixa.pleme.io/v1alpha1/Caixa` CR
4462 /// materializer's per-CR admission webhook re-checking `:upgrade-from`
4463 /// after a per-`(:from … :instructions …)` patch, a future `feira
4464 /// validate --upgrade` per-caixa admission verb, a per-`:upgrade-from`
4465 /// overlay resolver a per-cluster overlay lift would materialize —
4466 /// was structurally forced to either re-inline the three-dispatch
4467 /// cascade in lockstep with the layout wire-up (the duplication the
4468 /// PRIME DIRECTIVE names as a bug) or call the whole
4469 /// [`crate::layout::StandardLayout::verify`] pipeline and pay every
4470 /// peer per-Caixa gate to re-check one slot. Post-fold each such
4471 /// consumer reaches the three-arm compound gate through one call on
4472 /// the substrate primitive.
4473 ///
4474 /// The three arms together name one contract with three axes:
4475 ///
4476 /// - **per-entry + cross-entry graph-edge invariant** — every entry's
4477 /// `:from` parses as SemVer-2 and every per-instruction / within-
4478 /// entry ordering / singularity gate on each entry's
4479 /// `:instructions` list passes, and no two entries share the same
4480 /// parsed `:from` (the wasm-operator's OTP appup
4481 /// `release_handler:install_release/1` analog picks at most one
4482 /// matching block per running version — two entries with the same
4483 /// parsed semver are an ambiguous edge in the typed upgrade graph).
4484 /// - **cross-slot reachability invariant** — every entry's `:from`
4485 /// is strictly less than the caixa's own `:versao` under SemVer-2
4486 /// precedence. An entry whose `:from >= :versao` is structurally
4487 /// unreachable by the operator's `:from`-match dispatch (the
4488 /// operator loads the current `:versao` and matches the *running*
4489 /// version against each entry's `:from`; an entry whose `:from >=
4490 /// :versao` is never reached because the operator never runs a
4491 /// version >= the current one that it could then upgrade *to* the
4492 /// current one).
4493 /// - **cross-slot composition invariant** — every entry carrying a
4494 /// `(:state-change …)` instruction has a `:behavior
4495 /// :on-state-change` callback declared on the same caixa. The
4496 /// per-version migration script is the `gen_server:code_change/3`
4497 /// analog and the runtime hook it is delivered through during hot
4498 /// upgrade is the `:on-state-change` callback (the upgrade.rs
4499 /// module doc pins the composition verbatim: "Composes with the
4500 /// `:behavior :on-state-change` callback to deliver state migration
4501 /// during hot upgrades").
4502 ///
4503 /// All three axes must hold together — every consumer's
4504 /// `:upgrade-from` accept-set past this compound gate is the same
4505 /// set the `feira build` author-time gate admits.
4506 ///
4507 /// The per-slot compound entry gate discipline lifted here onto the
4508 /// M2 `:upgrade-from` axis is the sibling of the peer per-kind
4509 /// compound entry gates ([`crate::render::require_supervisor_view`]
4510 /// / [`crate::render::require_aplicacao_view`] /
4511 /// [`crate::render::require_v0_servico_shape`]) that fold every
4512 /// per-kind cascade at the per-kind altitude, and of the peer
4513 /// per-slot compound gates ([`crate::AplicacaoSpec::validate_contratos`],
4514 /// [`crate::MeshPolicy::validate`],
4515 /// [`crate::SupervisorSpec::validate_children`]) that fold every
4516 /// structural axis on their slot onto one substrate primitive.
4517 /// Extended here to the last unlifted compound-cascade wire-up at
4518 /// the layout-pipeline altitude — the three-dispatch M2
4519 /// `:upgrade-from` cascade that lived only open-coded at the layout
4520 /// wire-up site.
4521 ///
4522 /// The per-instruction script-path on-disk existence-probe walk that
4523 /// [`crate::layout::StandardLayout::verify`] runs immediately after
4524 /// this gate (which resolves each entry's `:instructions
4525 /// (:state-change :script)` against the layout root) stays open-coded
4526 /// at the layout wire-up site — that arm needs the filesystem oracle
4527 /// on the [`crate::LayoutInvariants`] trait, not the pure per-Caixa
4528 /// typed-shape surface this compound gate folds. Same posture the
4529 /// peer [`Self::validate_code_paths`] takes on the sibling code-path
4530 /// axes: the typed-shape gate fires on the per-Caixa surface, the
4531 /// on-disk existence check fires on the [`crate::StandardLayout`]
4532 /// surface.
4533 ///
4534 /// # Errors
4535 ///
4536 /// Returns [`crate::UpgradeError::FromInvalid`] /
4537 /// [`crate::UpgradeError::ModuleEmpty`] /
4538 /// [`crate::UpgradeError::ModuleInvalid`] /
4539 /// [`crate::UpgradeError::EmptyScript`] /
4540 /// [`crate::UpgradeError::AbsoluteScript`] /
4541 /// [`crate::UpgradeError::ParentEscapeScript`] /
4542 /// [`crate::UpgradeError::NonLispExtensionScript`] /
4543 /// [`crate::UpgradeError::RestartNotExclusive`] /
4544 /// [`crate::UpgradeError::StateChangeWithoutPriorLoad`] /
4545 /// [`crate::UpgradeError::PurgeWithoutPriorLoad`] /
4546 /// [`crate::UpgradeError::StateChangeAfterCleanup`] /
4547 /// [`crate::UpgradeError::DuplicateLoadModule`] /
4548 /// [`crate::UpgradeError::DuplicateStateChange`] /
4549 /// [`crate::UpgradeError::DuplicateCleanup`] /
4550 /// [`crate::UpgradeError::DuplicateFrom`] on the per-entry +
4551 /// cross-entry axis; [`crate::UpgradeError::FromNotBeforeVersao`] on
4552 /// the cross-slot `:from ↔ :versao` axis;
4553 /// [`crate::UpgradeError::StateChangeWithoutOnStateChangeCallback`]
4554 /// on the cross-slot `:state-change ↔ :on-state-change` axis.
4555 pub fn validate_upgrade_from(&self) -> Result<(), crate::UpgradeError> {
4556 crate::upgrade::validate_upgrade_from(self.upgrade_from())?;
4557 crate::upgrade::validate_upgrade_from_against_versao(self.upgrade_from(), self.versao())?;
4558 crate::upgrade::validate_upgrade_from_against_behavior(
4559 self.upgrade_from(),
4560 self.behavior(),
4561 )?;
4562 Ok(())
4563 }
4564
4565 /// Compound per-`Caixa` entry gate on the M2 `:limits` slot — folds
4566 /// the [`crate::LimitsSpec::validate`] four-axis cascade (`:memory`
4567 /// wasm32 zero-floor / below-page / above-cap / non-page-multiple;
4568 /// `:fuel` zero-floor / cap; `:wall-clock` zero-floor / cap; `:cpu`
4569 /// zero-floor / cap) onto one substrate primitive on [`Caixa`]. The
4570 /// `#[serde(default)]` absent-slot arm (`limits: None`, the
4571 /// canonical "no bound declared — engine-default applies" author
4572 /// shape [`crate::LimitsSpec::is_empty`]'s per-axis `None` cascade
4573 /// reads) is the fold's identity element and passes trivially; the
4574 /// present-slot arm (`limits: Some(l)`) dispatches to
4575 /// [`crate::LimitsSpec::validate`] verbatim, threading its per-axis
4576 /// [`crate::LimitsError`] Display through untouched.
4577 ///
4578 /// Prior to this lift the M2 `:limits` slot lived only wired
4579 /// open-coded at the layout wire-up site
4580 /// ([`crate::layout::StandardLayout::verify`], caixa-core/src/layout.rs),
4581 /// through the `if let Some(l) = caixa.limits() { l.validate() … }`
4582 /// three-line `Option::None → Ok(()) | Some(_) → …` unwrap-and-
4583 /// dispatch pattern paired with the same
4584 /// [`crate::LayoutError::LimitsViolation`]-wrap envelope: every
4585 /// future consumer that wanted to gate `:limits` as a whole — the
4586 /// deferred `caixa.pleme.io/v1alpha1/Caixa` CR materializer's
4587 /// per-CR admission webhook re-checking `:limits` after a per-
4588 /// `{:memory, :fuel, :wall-clock, :cpu}` patch (the exact case the
4589 /// [`Self::limits`] accessor docstring names as the second
4590 /// consumer of the slot), a future `feira validate --limits` per-
4591 /// caixa admission verb, a per-`:limits` overlay resolver a per-
4592 /// cluster `:limits-overrides` overlay lift would materialize — was
4593 /// structurally forced to either re-inline the two-line
4594 /// `Option::None → Ok(()) | Some(_) → …` unwrap-and-dispatch
4595 /// pattern in lockstep with the layout wire-up (the duplication the
4596 /// PRIME DIRECTIVE names as a bug) or call the whole
4597 /// [`crate::layout::StandardLayout::verify`] pipeline and pay every
4598 /// peer per-Caixa gate ([`Self::validate_nome`],
4599 /// [`Self::validate_versao`], [`Self::validate_deps`],
4600 /// [`Self::validate_etiquetas`], [`Self::validate_autores`],
4601 /// [`Self::validate_repositorio`], [`Self::validate_descricao`],
4602 /// [`Self::validate_licenca`], [`Self::validate_edicao`],
4603 /// [`Self::validate_upgrade_from`], [`Self::validate_code_paths`],
4604 /// plus the per-kind `require_supervisor_view` /
4605 /// `require_aplicacao_view` gates, plus the on-disk existence
4606 /// walks) to re-check one slot. Post-lift each such consumer
4607 /// reaches the [`crate::LimitsSpec::validate`] four-axis cascade
4608 /// (and its identity-element on the absent slot) through one call
4609 /// on the substrate primitive.
4610 ///
4611 /// The per-slot compound entry-gate discipline lifted here onto the
4612 /// M2 `:limits` axis is the sibling of the peer per-slot compound
4613 /// gates ([`crate::AplicacaoSpec::validate_contratos`],
4614 /// [`crate::MeshPolicy::validate`],
4615 /// [`crate::SupervisorSpec::validate_children`],
4616 /// [`Self::validate_upgrade_from`], [`Self::validate_deps`]) that
4617 /// fold every structural + cross-slot axis on their slot onto one
4618 /// substrate primitive. Extended here to the M2 `:limits` slot, the
4619 /// first of the two M2 typed slots (`:limits`, `:behavior`) whose
4620 /// per-Caixa compound-gate wire-up still lived open-coded at the
4621 /// layout altitude after the [`Self::validate_upgrade_from`] lift
4622 /// (d6801df) closed the sibling M2 slot's cascade.
4623 ///
4624 /// # Errors
4625 ///
4626 /// Returns every [`crate::LimitsError`] variant on the present-slot
4627 /// arm — verbatim from [`crate::LimitsSpec::validate`]. Passes
4628 /// trivially on the absent-slot arm (`limits: None`, the fold's
4629 /// identity element).
4630 pub fn validate_limits(&self) -> Result<(), crate::LimitsError> {
4631 match self.limits() {
4632 Some(l) => l.validate(),
4633 None => Ok(()),
4634 }
4635 }
4636
4637 /// Compound per-`Caixa` entry gate on the M2 `:behavior` slot's
4638 /// pure typed-shape surface — folds the
4639 /// [`crate::BehaviorSpec::validate`] six-slot value-shape cascade
4640 /// (each declared `:on-init` / `:on-call` / `:on-cast` / `:on-info`
4641 /// / `:on-state-change` / `:on-terminate` callback-path is
4642 /// non-empty / relative / no-`..`-parent-escape / terminating-
4643 /// `.lisp`-extension, routed through the shared
4644 /// [`crate::render::require_sandboxed_lisp_path`] arm-set) onto one
4645 /// substrate primitive on [`Caixa`]. The `#[serde(default)]`
4646 /// absent-slot arm (`behavior: None`, the canonical "no callback
4647 /// declared — the runtime falls back to the wasm-engine's default
4648 /// callback per arm" author shape [`crate::BehaviorSpec::is_empty`]'s
4649 /// per-slot `None` cascade reads) is the fold's identity element
4650 /// and passes trivially; the present-slot arm (`behavior: Some(b)`)
4651 /// dispatches to [`crate::BehaviorSpec::validate`] verbatim,
4652 /// threading its per-slot [`crate::BehaviorError`] Display through
4653 /// untouched.
4654 ///
4655 /// Scope note — the on-disk callback-path existence walk paired
4656 /// with the value-shape gate at
4657 /// [`crate::layout::StandardLayout::verify`] stays open-coded at
4658 /// the layout altitude, because it needs the
4659 /// [`crate::layout::LayoutInvariants`] filesystem oracle
4660 /// ([`crate::layout::LayoutInvariants::exists`]) that the pure
4661 /// per-Caixa typed-shape surface this compound gate folds onto has
4662 /// no reference to. Same posture the peer M2 `:upgrade-from`
4663 /// per-Caixa compound gate ([`Self::validate_upgrade_from`]
4664 /// d6801df) already carries: the pure typed-shape surface folds
4665 /// onto the substrate primitive; the per-instruction script-path
4666 /// existence probe on the paired axis (there `:state-change
4667 /// :script`; here `:on-*`) stays at the layout altitude.
4668 ///
4669 /// Prior to this lift the pure value-shape surface of the M2
4670 /// `:behavior` slot lived only wired open-coded at the layout
4671 /// wire-up site ([`crate::layout::StandardLayout::verify`],
4672 /// caixa-core/src/layout.rs), through the
4673 /// `if let Some(b) = caixa.behavior() { b.validate() … }`
4674 /// unwrap-and-dispatch pattern paired with the same
4675 /// [`crate::LayoutError::BehaviorViolation`]-wrap envelope: every
4676 /// future consumer that wanted to gate the `:behavior` slot's
4677 /// value-shape as a whole — the deferred
4678 /// `caixa.pleme.io/v1alpha1/Caixa` CR materializer's per-CR
4679 /// admission webhook re-checking `:behavior` after a per-`{:on-init,
4680 /// :on-call, :on-cast, :on-info, :on-state-change, :on-terminate}`
4681 /// patch (the exact case the peer `:on-*` accessor docstrings on
4682 /// [`crate::BehaviorSpec`] already name as deferred consumers of
4683 /// the slot), a future `feira validate --behavior` per-caixa
4684 /// admission verb, a per-`:behavior` overlay resolver a future
4685 /// per-cluster callback-overlay lift would materialize — was
4686 /// structurally forced to either re-inline the two-line
4687 /// `Option::None → Ok(()) | Some(_) → …` unwrap-and-dispatch
4688 /// pattern in lockstep with the layout wire-up (the duplication the
4689 /// PRIME DIRECTIVE names as a bug) or call the whole
4690 /// [`crate::layout::StandardLayout::verify`] pipeline and pay every
4691 /// peer per-Caixa gate ([`Self::validate_nome`],
4692 /// [`Self::validate_versao`], [`Self::validate_deps`],
4693 /// [`Self::validate_etiquetas`], [`Self::validate_autores`],
4694 /// [`Self::validate_repositorio`], [`Self::validate_descricao`],
4695 /// [`Self::validate_licenca`], [`Self::validate_edicao`],
4696 /// [`Self::validate_limits`], [`Self::validate_upgrade_from`],
4697 /// [`Self::validate_code_paths`], plus the per-kind
4698 /// `require_supervisor_view` / `require_aplicacao_view` gates, plus
4699 /// the on-disk existence walks) to re-check one slot. Post-lift
4700 /// each such consumer reaches the [`crate::BehaviorSpec::validate`]
4701 /// six-slot cascade (and its identity-element on the absent slot)
4702 /// through one call on the substrate primitive.
4703 ///
4704 /// The per-slot compound entry-gate discipline lifted here onto the
4705 /// M2 `:behavior` axis is the sibling of the peer per-slot compound
4706 /// gates ([`crate::AplicacaoSpec::validate_contratos`],
4707 /// [`crate::MeshPolicy::validate`],
4708 /// [`crate::SupervisorSpec::validate_children`],
4709 /// [`Self::validate_upgrade_from`], [`Self::validate_deps`],
4710 /// [`Self::validate_limits`]) that fold every structural + cross-
4711 /// slot axis on their slot onto one substrate primitive. Extended
4712 /// here to the M2 `:behavior` slot, the last of the four M2 typed
4713 /// slots (`:limits`, `:behavior`, `:upgrade-from`, plus the
4714 /// supervisor-only `:children` peer) whose per-Caixa compound-gate
4715 /// wire-up still lived open-coded at the layout altitude after the
4716 /// [`Self::validate_limits`] lift (baa4688) closed the sibling M2
4717 /// `:limits` slot's cascade. With this lift the "one named per-slot
4718 /// / per-Caixa compound gate per typed slot folding every structural
4719 /// axis on that slot (plus the `Option::None` identity element for
4720 /// the `Option`-shaped slots) onto one substrate primitive"
4721 /// discipline spans every M2 typed slot uniformly, so a reader who
4722 /// has learned any peer M2 gate reads `:behavior` without a per-
4723 /// slot exception carve-out.
4724 ///
4725 /// # Errors
4726 ///
4727 /// Returns every [`crate::BehaviorError`] variant on the present-
4728 /// slot arm — verbatim from [`crate::BehaviorSpec::validate`].
4729 /// Passes trivially on the absent-slot arm (`behavior: None`, the
4730 /// fold's identity element).
4731 pub fn validate_behavior(&self) -> Result<(), crate::BehaviorError> {
4732 match self.behavior() {
4733 Some(b) => b.validate(),
4734 None => Ok(()),
4735 }
4736 }
4737
4738 /// Reject `:restart-window` values the shared
4739 /// [`crate::supervisor::duration_codec::parse`] refuses. The flat
4740 /// `restart_window: Option<String>` slot on [`Caixa`] is stored
4741 /// raw by the derive macro (the typed [`SupervisorSpec`] holds an
4742 /// `Option<Duration>` routed through the shared codec via `with =
4743 /// "duration_codec"`); the inline `Caixa → SupervisorSpec`
4744 /// view-construction path ([`Self::supervisor_view`]) folds the
4745 /// raw string through the same shared codec and soft-swallows the
4746 /// parse error as `None` to keep the view best-effort. Without
4747 /// this gate a malformed `:restart-window` (`"1.5s"` — the
4748 /// fractional-seconds drift class; `"1.0s"` — the decimal-shaped
4749 /// integer drift; `"0.5m"` — the unit-fraction drift; `"+30s"` /
4750 /// `"-30s"` — the leading-sign drift; `"30x"` — the unknown-unit
4751 /// footgun; `"abc"` — pure garbage; `""` — the empty-after-trim
4752 /// edge case) silently produced a `SupervisorSpec` with
4753 /// `restart_window: None`, indistinguishable from the canonical
4754 /// "omit the slot to express no reset" authoring shape — Erlang/OTP's
4755 /// `MaxIntensity / Period` invariant turns into a never-reset
4756 /// supervisor far from the source `caixa.lisp`, with no field
4757 /// naming the offending `:restart-window`. Lifting the gate to a
4758 /// Caixa-level validator mirrors the trajectory of the peer
4759 /// per-axis identity gates ([`Self::validate_nome`] 6c992f8,
4760 /// [`Self::validate_versao`] 1fdaa02, [`Self::validate_deps`]
4761 /// a7f0d8c) and the ABSORPTION-ROADMAP.md M2.2 test pin
4762 /// (line 196: "reject invalid `:restart-window` (non-duration)").
4763 ///
4764 /// Thin wrapper around [`crate::supervisor::duration_codec::parse`]
4765 /// (the shared codec backing `:supervisor :restart-window` as
4766 /// serde-routed on [`SupervisorSpec`], `:politicas :timeout`, and
4767 /// `:politicas :circuit-breaker :window` — all three covered by
4768 /// the integer-magnitude gate 1c55a2a). Maps the codec's parse
4769 /// error verbatim into the [`ManifestError::RestartWindowMalformed`]
4770 /// variant, carrying the offending raw string + a parser-shaped
4771 /// reason naming the canonical authoring form, so the diagnostic
4772 /// is self-locating (the author can grep their `caixa.lisp` for
4773 /// `:restart-window "<value>"` and fix it in one edit) and
4774 /// uniform with every other manifest-level validate diagnostic.
4775 /// With this gate the four `:restart-window`-shaped surfaces (the
4776 /// flat raw string on [`Caixa`], the typed `Option<Duration>` on
4777 /// [`SupervisorSpec`], the two `MeshPolicy` peer durations) are
4778 /// now structurally equivalent — every value past the codec is in
4779 /// one accepted set, by construction.
4780 ///
4781 /// `None` (the canonical "omit the slot to express no reset"
4782 /// shape) is accepted trivially — the gate is a no-op when the
4783 /// author didn't author a window. The empty string is rejected by
4784 /// the shared codec (its digit-only gate refuses an empty
4785 /// magnitude), surfacing the same `RestartWindowMalformed`
4786 /// diagnostic as every other rejected non-canonical shape.
4787 pub fn validate_restart_window(&self) -> Result<(), ManifestError> {
4788 let Some(s) = self.restart_window() else {
4789 return Ok(());
4790 };
4791 crate::supervisor::duration_codec::parse(s)
4792 .map(|_| ())
4793 .map_err(|reason| ManifestError::RestartWindowMalformed {
4794 restart_window: s.to_string(),
4795 reason,
4796 })
4797 }
4798
4799 /// Compound per-`Caixa` entry gate on the Aplicacao-kind mesh-slot
4800 /// family — folds the paired [`crate::AplicacaoSpec::validate`]
4801 /// typed-shape cascade (per-slot gates on `:membros`, `:contratos`,
4802 /// `:entrada`, `:placement`, `:politicas`, in that declared order)
4803 /// plus the cross-slot self-edge gate
4804 /// ([`crate::aplicacao::validate_no_self_membership`], the
4805 /// `:membros :caixa` ≠ `:nome` invariant the typed view cannot
4806 /// enforce on its own because it carries the membros but not the
4807 /// parent `:nome`) onto one substrate primitive on [`Caixa`]. On
4808 /// non-Aplicacao kinds the fold is the identity element — the paired
4809 /// [`Self::aplicacao_view`] accessor returns `None` off the
4810 /// Aplicacao arm (peer with the [`Self::validate_limits`] /
4811 /// [`Self::validate_behavior`] M2 `Option`-arm identity element),
4812 /// so the gate passes trivially without touching the mesh slots.
4813 ///
4814 /// Prior to this lift the paired cascade lived only wired open-coded
4815 /// at the layout wire-up site
4816 /// ([`crate::layout::StandardLayout::verify`], caixa-core/src/layout.rs),
4817 /// as the three-line `let view = caixa.aplicacao_view().expect(...);
4818 /// view.validate() … validate_no_self_membership(...) …` pattern
4819 /// paired with two `.map_err(|err| LayoutError::AplicacaoViolation
4820 /// { caixa, issue })` wraps — every future consumer that wanted to
4821 /// gate the Aplicacao-shape cascade as a whole (the deferred
4822 /// `caixa.pleme.io/v1alpha1/Caixa` CR materializer's per-CR
4823 /// admission webhook re-checking `:membros` / `:contratos` after a
4824 /// per-slot patch, a future `feira validate --aplicacao` per-caixa
4825 /// admission verb, a per-Aplicacao overlay resolver) was structurally
4826 /// forced to either re-inline the two-dispatch cascade in lockstep
4827 /// with the layout wire-up (the duplication the PRIME DIRECTIVE
4828 /// names as a bug) or call the whole
4829 /// [`crate::layout::StandardLayout::verify`] pipeline and pay every
4830 /// peer per-Caixa gate to re-check one slot family. Post-fold each
4831 /// such consumer reaches the two-arm compound gate through one call
4832 /// on the substrate primitive.
4833 ///
4834 /// Peer to the [`crate::render::require_aplicacao_view`] compound
4835 /// entry gate every per-Aplicacao *renderer* routes through
4836 /// (3aefefb folded `validate_no_self_membership` onto the renderer
4837 /// path) — this gate mirrors the same fold on the *layout* path, so
4838 /// the two consumers of the Aplicacao-shape cascade (the author-time
4839 /// gate and every per-Aplicacao renderer) share one substrate
4840 /// primitive rather than two open-coded cascades kept in lockstep.
4841 /// Same lift discipline the peer per-slot compound gates
4842 /// ([`Self::validate_upgrade_from`] d6801df, [`Self::validate_deps`]
4843 /// b5dd55e, [`Self::validate_limits`] baa4688,
4844 /// [`Self::validate_behavior`] 0d2877a) each carry.
4845 ///
4846 /// # Errors
4847 ///
4848 /// Returns every [`crate::AplicacaoError`] variant on the present-
4849 /// kind arm — the typed-shape cascade's per-slot arms first
4850 /// (matching [`crate::AplicacaoSpec::validate`]'s declared order),
4851 /// then the cross-slot self-edge arm
4852 /// ([`crate::AplicacaoError::MembroIsSelfAplicacao`]). Passes
4853 /// trivially on non-Aplicacao kinds (the fold's identity element).
4854 pub fn validate_aplicacao_shape(&self) -> Result<(), crate::AplicacaoError> {
4855 let Some(view) = self.aplicacao_view() else {
4856 return Ok(());
4857 };
4858 view.validate()?;
4859 crate::aplicacao::validate_no_self_membership(self.membros(), self.nome())?;
4860 Ok(())
4861 }
4862
4863 /// Compound per-`Caixa` entry gate on the Supervisor-kind
4864 /// supervision-tree slot family — folds the paired
4865 /// [`crate::SupervisorSpec::validate`] typed-shape cascade
4866 /// (`:estrategia` ↔ `:children` invariants, `:max-restarts` /
4867 /// `:restart-window` bounds, per-child DNS-1123 `:caixa` names,
4868 /// semver-valid `:versao` constraints, the set-not-multiset
4869 /// duplicate-child gate) plus the cross-slot self-edge gate
4870 /// ([`crate::supervisor::validate_no_self_supervision`], the
4871 /// `:children :caixa` ≠ `:nome` invariant the typed view cannot
4872 /// enforce on its own because it carries the children but not the
4873 /// parent `:nome`) onto one substrate primitive on [`Caixa`]. On
4874 /// non-Supervisor kinds the fold is the identity element — the paired
4875 /// [`Self::supervisor_view`] accessor returns `None` off the
4876 /// Supervisor arm (peer with the [`Self::validate_limits`] /
4877 /// [`Self::validate_behavior`] M2 `Option`-arm identity element and
4878 /// the sibling per-Aplicacao [`Self::validate_aplicacao_shape`]),
4879 /// so the gate passes trivially without touching the supervision-tree
4880 /// slots.
4881 ///
4882 /// Prior to this lift the paired cascade lived only wired open-coded
4883 /// at the layout wire-up site
4884 /// ([`crate::layout::StandardLayout::verify`], caixa-core/src/layout.rs),
4885 /// as the three-line `let view = caixa.supervisor_view().expect(...);
4886 /// view.validate() … validate_no_self_supervision(...) …` pattern
4887 /// paired with two `.map_err(|err| LayoutError::SupervisorViolation
4888 /// { caixa, issue })` wraps — every future consumer that wanted to
4889 /// gate the Supervisor-shape cascade as a whole (the wasm-operator's
4890 /// hierarchical reconciliation scheduler re-checking `:children` /
4891 /// `:estrategia` after a per-slot patch, the M4
4892 /// `mesh.pleme.io/v1alpha1/Supervisor` CR materializer's admission
4893 /// webhook, a future `feira validate --supervisor` per-caixa
4894 /// admission verb, a per-Supervisor overlay resolver) was structurally
4895 /// forced to either re-inline the two-dispatch cascade in lockstep
4896 /// with the layout wire-up (the duplication the PRIME DIRECTIVE
4897 /// names as a bug) or call the whole
4898 /// [`crate::layout::StandardLayout::verify`] pipeline and pay every
4899 /// peer per-Caixa gate to re-check one slot family. Post-fold each
4900 /// such consumer reaches the two-arm compound gate through one call
4901 /// on the substrate primitive.
4902 ///
4903 /// Peer to the [`crate::render::require_supervisor_view`] compound
4904 /// entry gate every per-Supervisor *renderer* would route through
4905 /// (which already folds the same `spec.validate()` +
4906 /// `validate_no_self_supervision` two-arm cascade behind its
4907 /// `require_kind` + `validate_restart_window` prelude) — this gate
4908 /// mirrors the same fold on the *layout* path, so the two consumers
4909 /// of the Supervisor-shape cascade (the author-time gate and every
4910 /// per-Supervisor renderer) share one substrate primitive rather
4911 /// than two open-coded cascades kept in lockstep. Same lift
4912 /// discipline the peer per-slot compound gates
4913 /// ([`Self::validate_aplicacao_shape`] 949a7a0,
4914 /// [`Self::validate_upgrade_from`] d6801df,
4915 /// [`Self::validate_deps`] b5dd55e, [`Self::validate_limits`]
4916 /// baa4688, [`Self::validate_behavior`] 0d2877a) each carry.
4917 ///
4918 /// # Errors
4919 ///
4920 /// Returns every [`crate::SupervisorError`] variant on the present-
4921 /// kind arm — the typed-shape cascade's per-slot arms first
4922 /// (matching [`crate::SupervisorSpec::validate`]'s declared order),
4923 /// then the cross-slot self-edge arm
4924 /// ([`crate::SupervisorError::ChildSupervisesSelf`]). Passes
4925 /// trivially on non-Supervisor kinds (the fold's identity element).
4926 pub fn validate_supervisor_shape(&self) -> Result<(), crate::SupervisorError> {
4927 let Some(view) = self.supervisor_view() else {
4928 return Ok(());
4929 };
4930 view.validate()?;
4931 crate::supervisor::validate_no_self_supervision(self.children(), self.nome())?;
4932 Ok(())
4933 }
4934
4935 /// Compound per-`Caixa` entry gate on the Acao-kind `:ci` slot
4936 /// family — folds the [`crate::decompose_ci`] typed decompose gate
4937 /// (`canteiro_types::decompose` refusing every illegal
4938 /// [`canteiro_types::CiRun`] shape: duplicate node name, dependency
4939 /// on an undeclared node, dependency cycle) onto one substrate
4940 /// primitive on [`Caixa`]. On non-`Acao` kinds the fold is the
4941 /// identity element — the paired [`Self::kind`] `is_acao()` guard
4942 /// short-circuits before the decompose gate ever fires (peer with
4943 /// the [`Self::validate_aplicacao_shape`] /
4944 /// [`Self::validate_supervisor_shape`] typed-view identity element
4945 /// and the [`Self::validate_limits`] / [`Self::validate_behavior`]
4946 /// M2 `Option`-arm identity element), so the gate passes trivially
4947 /// without touching the `:ci` slot. An `:kind Acao` caixa with
4948 /// `ci = None` is also an identity-element pass: the presence gate
4949 /// is the sibling axis owned by [`crate::LayoutError::MissingCi`] /
4950 /// [`crate::require_ci`] / [`crate::MissingCiSlot`], not by the
4951 /// decompose gate — a caixa that carries no `:ci` slot has no run
4952 /// to decompose. Same split the peer per-Servico
4953 /// [`crate::LayoutError::ServicoWithoutServicos`] presence gate and
4954 /// per-Binario [`crate::LayoutError::BinarioWithoutExe`] presence
4955 /// gate keep from their sibling per-slot shape gates, so the two
4956 /// axes stay separately diagnosable at the layout altitude.
4957 ///
4958 /// Prior to this lift the decompose gate lived only wired
4959 /// open-coded at the [`caixa_actions::validate`] renderer-side
4960 /// entry gate (routed through the substrate-canonical
4961 /// [`crate::require_acao_view`] compound helper) — the *layout*
4962 /// pipeline ([`crate::layout::StandardLayout::verify`], caixa-core/
4963 /// src/layout.rs) only checked `:ci` *presence* via
4964 /// [`crate::LayoutError::MissingCi`], so a `:kind Acao` caixa
4965 /// carrying a structurally illegal `:ci` (a duplicate node name, a
4966 /// dependency on an undeclared node, a dependency cycle) passed
4967 /// `feira build` cleanly and surfaced the diagnostic only when
4968 /// [`caixa_actions::validate`] later refused it — far from the
4969 /// source `caixa.lisp` on the author-time gate side. Every future
4970 /// consumer that wanted to gate the Acao-shape cascade as a whole
4971 /// (a per-`Acao` CR materializer's admission webhook re-checking
4972 /// `:ci` after a per-node patch, a future `feira validate --acao`
4973 /// per-caixa admission verb, a per-`Acao` overlay resolver
4974 /// rejecting an added / renamed node against a cluster-local
4975 /// snapshot) was structurally forced to either re-inline the
4976 /// decompose dispatch in lockstep with the renderer-side wire-up
4977 /// (the duplication the PRIME DIRECTIVE names as a bug) or call
4978 /// the whole [`caixa_actions::validate`] renderer and pay the
4979 /// per-node accumulation to re-check one slot. Post-fold each such
4980 /// consumer reaches the decompose gate through one call on the
4981 /// substrate primitive.
4982 ///
4983 /// Peer to the [`crate::require_acao_view`] compound entry gate
4984 /// every per-`Acao` *renderer* routes through (which already folds
4985 /// the same `require_ci + decompose_ci` two-arm cascade behind its
4986 /// `require_kind` prelude) — this gate mirrors the same fold on
4987 /// the *layout* path, so the two consumers of the Acao-shape
4988 /// cascade (the author-time gate and every per-`Acao` renderer)
4989 /// share one substrate primitive rather than two open-coded
4990 /// cascades kept in lockstep. Same lift discipline the peer
4991 /// per-kind compound gates ([`Self::validate_aplicacao_shape`]
4992 /// 949a7a0, [`Self::validate_supervisor_shape`] 4c70105,
4993 /// [`Self::validate_upgrade_from`] d6801df, [`Self::validate_deps`]
4994 /// b5dd55e, [`Self::validate_limits`] baa4688,
4995 /// [`Self::validate_behavior`] 0d2877a) each carry. Closes the
4996 /// last per-kind asymmetry: with this lift the four typed
4997 /// named-caixa kinds (`Servico` / `Aplicacao` / `Supervisor` /
4998 /// `Acao`) each carry a compound per-`Caixa` shape gate on the
4999 /// substrate, and the layout pipeline routes through the same one
5000 /// substrate primitive per kind rather than four open-coded
5001 /// cascades.
5002 ///
5003 /// # Errors
5004 ///
5005 /// Returns the [`crate::CiDecomposeFailure`] typed view on the
5006 /// present-slot arm — the caixa's `:nome` alongside the borrowed
5007 /// [`canteiro_types::DecomposeError`] source (`DuplicateNode` /
5008 /// `UnknownDep` / `Cycle`) verbatim, so a consumer that fans on
5009 /// the specific arm reaches for `err.source` directly rather than
5010 /// re-parsing the Display bytes. Passes trivially on non-`Acao`
5011 /// kinds and on `:kind Acao` caixas with absent `:ci` (the fold's
5012 /// two identity-element arms).
5013 pub fn validate_acao_shape(&self) -> Result<(), crate::CiDecomposeFailure> {
5014 if !self.kind().is_acao() {
5015 return Ok(());
5016 }
5017 let Some(ci) = self.ci() else {
5018 return Ok(());
5019 };
5020 crate::render::decompose_ci(self, ci).map(|_| ())
5021 }
5022
5023 /// Reject per-entry values on the three Caixa-level code-surface
5024 /// path lists (`:bibliotecas`, `:exe`, `:servicos`) that the
5025 /// layout checker's `root.join(p)` sandbox would silently subvert.
5026 /// Same three structural footguns the peer
5027 /// [`BehaviorSpec::validate`] (b0c8389) and
5028 /// [`crate::UpgradeInstruction::validate`] `StateChange` arm
5029 /// (26da2c7) already close on the M2 `:behavior :on-*` and
5030 /// `:upgrade-from :state-change :script` axes, here lifted onto
5031 /// the three top-level code-path axes through the shared
5032 /// [`is_sandboxed_relative_path`] predicate:
5033 ///
5034 /// - empty entry (`(:bibliotecas (""))` / `(:exe (""))` /
5035 /// `(:servicos (""))`): `PathBuf::new()` round-trips through
5036 /// [`Path::join`] as the base itself — `root.join("")` ==
5037 /// `root`, so the existence check (`self.exists(&root)`)
5038 /// trivially passes (the project root exists), and the layout
5039 /// silently treats the project root as a biblioteca / exe /
5040 /// servico entry. The `:bibliotecas` loop then hands the root
5041 /// to `tatara_lisp::read` at `feira build` time as if the root
5042 /// directory itself were a Lisp source file — a parse error
5043 /// far from the source `caixa.lisp` with no field naming the
5044 /// offending entry.
5045 /// - absolute path (`(:bibliotecas ("/etc/passwd"))`):
5046 /// [`Path::join`] *replaces* the base when the right-hand side
5047 /// is absolute, so `root.join("/etc/passwd")` resolves to
5048 /// `"/etc/passwd"` and escapes the project sandbox entirely.
5049 /// The existence check then silently consults whatever the
5050 /// escaped path resolves to — for `:bibliotecas`, the layout
5051 /// has no `starts_with`-fence (only `:exe` is fenced under
5052 /// `exe/` and `:servicos` under `servicos/`), so an absolute
5053 /// `:bibliotecas` entry that happens to resolve on disk
5054 /// silently passes. For `:exe` / `:servicos` the fence catches
5055 /// the absolute case downstream as `ExeOutsideDir` /
5056 /// `ServicoOutsideDir` (or `MissingEntry` if the absolute path
5057 /// doesn't exist), but with a downstream-shaped diagnostic
5058 /// that names the resolved escape path rather than the
5059 /// authoring footgun at the source.
5060 /// - parent-escape (`(:bibliotecas ("../sibling/x.lisp"))` /
5061 /// `(:exe ("exe/../../escape.lisp"))`): a [`PathBuf`] with any
5062 /// [`std::path::Component::ParentDir`] anywhere round-trips
5063 /// through [`Path::join`] as a traversal above the caixa root.
5064 /// The `:exe` / `:servicos` `starts_with(<dir>)` fence is
5065 /// *component-aware* (not canonical-path-aware), so
5066 /// `root.join("exe/../../escape.lisp")` `starts_with(exe_dir)`
5067 /// is **true** even though the canonical resolution
5068 /// `{parent of root}/escape.lisp` lives outside the caixa root
5069 /// — the fence silently lets the parent-escape through, and
5070 /// the existence check passes if that escape-target happens
5071 /// to exist. Caught regardless of where the `..` sits
5072 /// (leading, mid-path, trailing) so the gate matches the peer
5073 /// predicate's full coverage.
5074 ///
5075 /// Same `Empty` → `Absolute` → `ParentEscape` arm-ordering every peer
5076 /// `is_sandboxed_relative_path` consumer follows (b0c8389 / 26da2c7);
5077 /// same per-slot diagnostic shape every peer per-axis path-gate
5078 /// exposes (`*Empty { slot }` / `*Absolute { slot, path }` /
5079 /// `*ParentEscape { slot, path }`). Cross-slot precedence is
5080 /// `:bibliotecas` → `:exe` → `:servicos` — the same declaration
5081 /// order [`Caixa::declared_foreign_code_slots`] uses for its
5082 /// canonical foreign-code-slot diagnostic, so a manifest with
5083 /// multiple malformed slots surfaces the lexicographically-earliest
5084 /// slot's diagnostic deterministically.
5085 ///
5086 /// Lifted to the typed surface as a Caixa-level validator (peer
5087 /// of [`Self::validate_nome`] / [`Self::validate_versao`] /
5088 /// [`Self::validate_deps`] / [`Self::validate_restart_window`])
5089 /// and wired into [`crate::StandardLayout::verify`] before the
5090 /// existence-check loops so the diagnostic names the offending
5091 /// slot at the source caixa.lisp rather than reporting a
5092 /// downstream `MissingEntry` / `ExeOutsideDir` /
5093 /// `ServicoOutsideDir` against the resolved sandbox-escape path.
5094 /// The fourth typed code-path surface — every author-supplied
5095 /// path on the manifest — is now structurally accept-shaped
5096 /// past validate, peer with `:behavior :on-*` and
5097 /// `:upgrade-from :state-change :script`.
5098 pub fn validate_code_paths(&self) -> Result<(), ManifestError> {
5099 /// Per-slot file-type contract for the three Caixa-level
5100 /// code-path surfaces (`:bibliotecas`, `:exe`, `:servicos`).
5101 /// Each variant names the predicate the per-entry file-type
5102 /// gate consults; [`Self::None`] opts the slot out of any
5103 /// file-type contract. Lifted as a typed local enum so the
5104 /// per-slot dispatch is exhaustive at the `match` — adding a
5105 /// future axis to the typed-substrate `:` slot set (the
5106 /// future `:assets` resource axis the M5 roadmap names, the
5107 /// future `:nix-flake` derivation axis the caixa-flake
5108 /// emitter consults) lands as one variant + one `match` arm,
5109 /// not a coordinated rewrite of every per-slot bool flag.
5110 ///
5111 /// Peer of the typed-substrate per-slot variant disciplines
5112 /// already established on this surface
5113 /// ([`crate::supervisor::RestartStrategy`] +
5114 /// [`crate::supervisor::RestartPolicy`] on the OTP-shape
5115 /// supervision-tree axis,
5116 /// [`crate::aplicacao::PlacementStrategy`] on the §III.1
5117 /// placement axis, [`crate::aplicacao::WitTarget`] on the
5118 /// `:contratos` payload-target axis): the typed `enum` is
5119 /// the substrate's single source of truth for the per-axis
5120 /// dispatch, and every consumer (the per-arm body here, the
5121 /// future feira-lint per-slot diagnostic renderer, the M4
5122 /// per-axis admission webhook) reaches for the same typed
5123 /// surface rather than re-deriving the partition from inline
5124 /// flag combinations.
5125 enum CodePathFileType {
5126 /// `:exe` — nix-build derivation output, no terminating-
5127 /// extension contract (the canonical `"exe/<name>"`
5128 /// fixtures the layout's `ExeOutsideDir` error message
5129 /// documents carry no extension by convention).
5130 None,
5131 /// `:bibliotecas` — tatara-lisp source files the
5132 /// `feira build` loop reads through `tatara_lisp::read`
5133 /// at parse time. Routes to [`is_lisp_extension`].
5134 LispSource,
5135 /// `:servicos` — ComputeUnit-CR YAML files the
5136 /// caixa-helm / caixa-flux renderers consume through
5137 /// `serde_yaml::from_str`. Routes to
5138 /// [`is_computeunit_yaml_extension`].
5139 ComputeUnitYaml,
5140 }
5141
5142 // The per-slot [`CodePathFileType`] selects which axes carry the
5143 // lifted file-type predicate. `:bibliotecas` is the tatara-lisp
5144 // source axis (the `feira build` loop at
5145 // `caixa-feira/src/cmd/build.rs:33` reads each entry through
5146 // `tatara_lisp::read` at parse time) — the lifted
5147 // [`is_lisp_extension`] predicate gates the `.lisp` extension.
5148 // `:exe` is the nix-built executable surface (per the canonical
5149 // `"exe/<name>"`-shaped fixtures the layout's `ExeOutsideDir`
5150 // error message documents and every in-tree
5151 // `caixa_with_code_paths` positive control uses) — its file-type
5152 // contract is "nix-build derivation output", not a typed source
5153 // file, so [`CodePathFileType::None`] opts the slot out of any
5154 // file-type gate. `:servicos` is the `.computeunit.yaml`
5155 // ComputeUnit-CR axis (the peer caixa-helm / caixa-flux
5156 // renderers consume each entry through `serde_yaml::from_str` as
5157 // a typed `ComputeUnit` CR) — the lifted
5158 // [`is_computeunit_yaml_extension`] predicate gates the compound
5159 // `.computeunit.yaml` suffix. All three axes are surfaced through
5160 // the same iteration so the sandbox-shape + duplicate gates
5161 // apply uniformly; the typed file-type dispatch fires per-slot
5162 // exactly where the downstream consumer's accepted set demands
5163 // it. The third file-type variant ([`ComputeUnitYaml`]) is the
5164 // compounding lift on the peer 64772a9 `:bibliotecas`
5165 // `.lisp`-gate trajectory — the second of the three code-path
5166 // axes to land on a typed compound-suffix gate, with the same
5167 // self-locating per-slot diagnostic shape every peer per-axis
5168 // file-type lift uses (`*NonLispExtension { slot, path }` /
5169 // `*NonComputeUnitYamlExtension { slot, path }`).
5170 for (slot, list, file_type) in [
5171 (
5172 ":bibliotecas",
5173 &self.bibliotecas,
5174 CodePathFileType::LispSource,
5175 ),
5176 (":exe", &self.exe, CodePathFileType::None),
5177 (
5178 ":servicos",
5179 &self.servicos,
5180 CodePathFileType::ComputeUnitYaml,
5181 ),
5182 ] {
5183 // Per-slot set-not-multiset gate on the typed code-path axis.
5184 // Every peer Vec-shaped author-supplied list past validate is
5185 // a set, not a multiset: `:membros :caixa`
5186 // ([`crate::AplicacaoError::MembroDuplicate`]), `:placement
5187 // :clusters` ([`crate::AplicacaoError::PlacementClusterDuplicate`]),
5188 // `:entrada :paths` ([`crate::AplicacaoError::EntradaPathDuplicate`]),
5189 // `:contratos` ([`crate::AplicacaoError::ContratoDuplicate`]),
5190 // `:children :caixa` ([`crate::SupervisorError::DuplicateChild`]),
5191 // `:deps` / `:deps-dev` `:nome` ([`crate::DepError::DuplicateNome`]
5192 // per 359fba5), `:upgrade-from :from` ([`crate::UpgradeError::DuplicateFrom`]),
5193 // `:etiquetas` ([`ManifestError::EtiquetaDuplicate`] per 360a499),
5194 // `:autores` ([`ManifestError::AutorDuplicate`] per 86c769b) —
5195 // the three code-path lists are the last Vec-shaped author-
5196 // supplied slots on the typed Caixa surface still admitting a
5197 // duplicate entry silently. Scope is per-list (`:bibliotecas`
5198 // duplicates are flagged within `:bibliotecas`, not across
5199 // `:bibliotecas` ↔ `:exe`) — the same per-list scope `:deps`
5200 // ↔ `:deps-dev` use (a `:nome` present in both lists is a
5201 // legitimate dev-vs-runtime shape on the dep axis, fenced
5202 // separately by [`crate::dep::validate_no_self_dep`]). On the
5203 // code-path axis a cross-slot collision is structurally
5204 // impossible by the layout's `starts_with(<exe|servicos>_dir)`
5205 // fence — `:exe` and `:servicos` entries are confined to their
5206 // own directory trees, so the only way a string could appear
5207 // on two code-path lists is the (rare, structurally invalid)
5208 // case where `:bibliotecas` carries an `"exe/<x>"` or
5209 // `"servicos/<x>.yaml"`-shaped path.
5210 //
5211 // Without the gate three authoring footguns silently passed:
5212 //
5213 // - `:bibliotecas ("lib/foo.lisp" "lib/foo.lisp")` — the
5214 // canonical copy-paste-the-wrong-file footgun. `feira
5215 // build` (`caixa-feira/src/cmd/build.rs:33`) walks the
5216 // list and re-parses the same file twice, wasting work
5217 // and silently masking the author's intent to declare a
5218 // *second* biblioteca.
5219 // - `:exe ("exe/cli" "exe/cli")` — the same footgun on the
5220 // Binario surface. The future `caixa-flake` `nix flake`
5221 // emitter that materializes each `:exe` entry as a flake
5222 // `packages.<exe-name>` derivation would collide on the
5223 // duplicate package name and surface a flake-eval error
5224 // far from the source `caixa.lisp`.
5225 // - `:servicos ("servicos/x.computeunit.yaml"
5226 // "servicos/x.computeunit.yaml")` — the same footgun on
5227 // the Servico surface. The peer `caixa-helm` / `caixa-flux`
5228 // renderers already refuse `:servicos.len() != 1` with
5229 // the narrower [`UnsupportedServicoCount`] diagnostic, but
5230 // that diagnostic surfaces "too many servicos" without
5231 // naming "duplicate entry" — the typed self-locating
5232 // "which entry is the duplicate" framing only lands at
5233 // this gate.
5234 //
5235 // Same `seen.insert(entry.as_str())` shape every peer per-list
5236 // duplicate gate uses (`:etiquetas` 360a499, `:autores`
5237 // 86c769b, `:deps` 359fba5) and the same "structural shape
5238 // checks fire before the duplicate check on the same entry"
5239 // ordering (a `(:bibliotecas ("" "lib/x.lisp" "lib/x.lisp"))`
5240 // shape surfaces the narrower [`Self::CodePathEmpty`] for the
5241 // empty entry first, not the duplicate on the later pair).
5242 let mut seen = std::collections::HashSet::new();
5243 for entry in list {
5244 let path = Path::new(entry);
5245 match is_sandboxed_relative_path(path) {
5246 Ok(()) => {}
5247 Err(PathShapeViolation::Empty) => {
5248 return Err(ManifestError::CodePathEmpty { slot });
5249 }
5250 Err(PathShapeViolation::Absolute) => {
5251 return Err(ManifestError::CodePathAbsolute {
5252 slot,
5253 path: path.to_path_buf(),
5254 });
5255 }
5256 Err(PathShapeViolation::ParentEscape) => {
5257 return Err(ManifestError::CodePathParentEscape {
5258 slot,
5259 path: path.to_path_buf(),
5260 });
5261 }
5262 }
5263 // The per-slot file-type gate dispatched through the
5264 // typed [`CodePathFileType`] selector above. Each variant
5265 // routes to the lifted predicate the downstream consumer
5266 // demands:
5267 //
5268 // - [`LispSource`] → [`is_lisp_extension`] for
5269 // `:bibliotecas` (the `feira build` loop's
5270 // `tatara_lisp::read` consumer);
5271 // - [`ComputeUnitYaml`] → [`is_computeunit_yaml_extension`]
5272 // for `:servicos` (the caixa-helm / caixa-flux
5273 // `serde_yaml::from_str` consumer's `ComputeUnit` CR
5274 // accepted set);
5275 // - [`None`] for `:exe` — the nix-build derivation-
5276 // output axis has no terminating-extension contract.
5277 //
5278 // Fires after the sandbox-shape arms so a path that is
5279 // *both* sandbox-escaping and wrong-extension surfaces
5280 // the more fundamental sandbox-shape diagnostic first
5281 // (mirrors the peer `EmptyPath` → `AbsolutePath` →
5282 // `ParentEscape` → `NonLispExtension` arm-ordering on
5283 // `:behavior :on-*` c97815a, and `EmptyScript` →
5284 // `AbsoluteScript` → `ParentEscapeScript` →
5285 // `NonLispExtensionScript` on
5286 // `:upgrade-from :state-change :script` 33cc830), and
5287 // before the duplicate gate so the narrower per-entry
5288 // file-type shape dominates the cross-entry uniqueness
5289 // diagnostic (a
5290 // `("servicos/x.yaml" "servicos/x.yaml")` shape on
5291 // `:servicos` surfaces
5292 // `CodePathNonComputeUnitYamlExtension` on the first
5293 // entry rather than `CodePathDuplicate` on the pair —
5294 // peer with the 64772a9 `:bibliotecas`
5295 // `("lib/x.txt" "lib/x.txt")` ordering).
5296 match file_type {
5297 CodePathFileType::None => {}
5298 CodePathFileType::LispSource => {
5299 if !is_lisp_extension(path) {
5300 return Err(ManifestError::CodePathNonLispExtension {
5301 slot,
5302 path: path.to_path_buf(),
5303 });
5304 }
5305 }
5306 CodePathFileType::ComputeUnitYaml => {
5307 if !is_computeunit_yaml_extension(path) {
5308 return Err(ManifestError::CodePathNonComputeUnitYamlExtension {
5309 slot,
5310 path: path.to_path_buf(),
5311 });
5312 }
5313 }
5314 }
5315 crate::render::insert_first_seen(&mut seen, entry.as_str(), || {
5316 ManifestError::CodePathDuplicate {
5317 slot,
5318 path: path.to_path_buf(),
5319 }
5320 })?;
5321 }
5322 }
5323 Ok(())
5324 }
5325
5326 /// Reject `:etiquetas` lists with an empty entry or with two entries
5327 /// agreeing on the same string. `:etiquetas` is the universal
5328 /// registry-search-tag axis on [`Caixa`] (every kind carries the
5329 /// `Vec<String>` slot) and lands verbatim as the Helm chart
5330 /// `Chart.yaml` `keywords:` array on every Servico (caixa-helm's
5331 /// `build_chart_yaml` at `caixa-helm/src/lib.rs:236` folds it through
5332 /// a [`std::collections::BTreeSet`] alongside the four substrate-
5333 /// fixed tags `lareira` / `wasm` / `tatara-lisp` / `caixa-servico`).
5334 /// Two authoring footguns silently passed validate without this gate:
5335 ///
5336 /// - Empty entry (`(:etiquetas (""))` — the canonical paste-from-
5337 /// blank-doc footgun) rendered as `keywords: ["", "caixa-servico",
5338 /// "lareira", "tatara-lisp", "wasm"]` in `Chart.yaml`. Helm's
5339 /// `chart.metadata.keywords` admits the value without a strict
5340 /// parser-side gate, but the empty keyword has no operational
5341 /// meaning — it indexes nothing in the future caixa-registry
5342 /// search axis and clutters the rendered chart with a no-op tag.
5343 /// - Duplicate entries (`(:etiquetas ("demo" "demo"))` — the
5344 /// copy-paste-the-wrong-tag footgun) silently passed validate
5345 /// and were silently dedup'd by caixa-helm's `BTreeSet` collect
5346 /// at chart render — a "second wins / one silently disappears"
5347 /// shape divergent from every peer typed-graph set gate
5348 /// ([`crate::AplicacaoError::MembroDuplicate`] on `:membros`,
5349 /// [`crate::AplicacaoError::PlacementClusterDuplicate`] on
5350 /// `:placement :clusters`, [`crate::AplicacaoError::EntradaPathDuplicate`]
5351 /// on `:entrada :paths`, [`crate::AplicacaoError::ContratoDuplicate`]
5352 /// on `:contratos`, [`crate::DepError::DuplicateNome`] on
5353 /// `:deps` / `:deps-dev` per 359fba5, [`crate::UpgradeError::DuplicateFrom`]
5354 /// on `:upgrade-from`, the per-instruction-class singularity
5355 /// gates [`crate::UpgradeError::DuplicateLoadModule`] /
5356 /// [`crate::UpgradeError::DuplicateStateChange`] /
5357 /// [`crate::UpgradeError::DuplicateCleanup`]). The typed-graph
5358 /// discipline is uniform: every Vec-shaped author-supplied list
5359 /// past validate is set-not-multiset, by construction.
5360 ///
5361 /// Past the empty arm the gate enforces the chart-keyword shape
5362 /// predicate via [`crate::render::is_chart_keyword_shape`]: Cargo's
5363 /// crates.io `[package] keywords` grammar — 1..=20 bytes, starts
5364 /// with an ASCII letter, ASCII alphanumeric / `_` / `-`
5365 /// continuation. Closes the canonical paste-from-doc footguns the
5366 /// bare empty + duplicate arms left open: paste-from-aligned-doc
5367 /// whitespace (`" mesh"`, `"mesh "`), paste-from-multiline-doc
5368 /// newline (`"mesh\nhttp"` — the author pasted a multi-tag block
5369 /// into one entry instead of splitting), paste-from-Windows-CRLF-doc
5370 /// carriage return, CSV-list-separator confusion (`"mesh,http,grpc"`
5371 /// — the author meant three separate list entries), path-separator
5372 /// confusion (`"caixa/servico"`), namespace-suffix (`"http.1"`),
5373 /// leading-digit (`"1foo"`), kebab-leak (`"-foo"`), snake-leak
5374 /// (`"_foo"`), non-ASCII (`"café"`), and paste-from-binary-blob
5375 /// control bytes that would silently land as malformed search tags
5376 /// in the rendered Chart.yaml `keywords:` array and break the
5377 /// Artifact Hub keyword index lookup far from the source caixa.lisp.
5378 /// Mirrors the [`Self::validate_autores`] shape-predicate cascade
5379 /// established on the sibling universal-axis `Vec<String>` surface
5380 /// — the second universal-axis Vec<String> surface to land the
5381 /// empty-first-then-shape-then-duplicate per-entry cascade.
5382 ///
5383 /// Same empty-first cascade discipline every peer per-axis gate
5384 /// uses: the per-entry empty arm fires before the per-entry shape
5385 /// arm fires before the cross-entry duplicate arm, so an
5386 /// `("" "mesh" "mesh")` authoring shape surfaces the narrower
5387 /// [`ManifestError::EtiquetaEmpty`] (the structural "this entry
5388 /// has no value" defect) before either the shape or the duplicate
5389 /// diagnostic. Walks the list in declaration order so the
5390 /// first-collision diagnostic surfaces the lexicographically-
5391 /// earliest offending position, peer with every other duplicate
5392 /// gate on this surface.
5393 ///
5394 /// Universal-axis (every kind carries `:etiquetas`), so wired at the
5395 /// caixa-build gate alongside the peer universal gates
5396 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
5397 /// [`Self::validate_deps`] / [`Self::validate_code_paths`] — before
5398 /// the kind-coherence gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`]
5399 /// / [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
5400 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
5401 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-specific
5402 /// slot sets. The future caixa-registry search axis can reach for
5403 /// `caixa.etiquetas` knowing every entry is a non-empty distinct
5404 /// chart-keyword-shaped string without re-deriving the precondition.
5405 pub fn validate_etiquetas(&self) -> Result<(), ManifestError> {
5406 let mut seen = std::collections::HashSet::new();
5407 for etiqueta in self.etiquetas() {
5408 if etiqueta.is_empty() {
5409 return Err(ManifestError::EtiquetaEmpty);
5410 }
5411 crate::render::is_chart_keyword_shape(etiqueta).map_err(|reason| {
5412 ManifestError::EtiquetaInvalid {
5413 etiqueta: etiqueta.clone(),
5414 reason,
5415 }
5416 })?;
5417 crate::render::insert_first_seen(&mut seen, etiqueta.as_str(), || {
5418 ManifestError::EtiquetaDuplicate {
5419 etiqueta: etiqueta.clone(),
5420 }
5421 })?;
5422 }
5423 Ok(())
5424 }
5425
5426 /// Reject `:autores` lists with an empty entry or with two entries
5427 /// agreeing on the same string. `:autores` is the universal
5428 /// maintainer-axis on [`Caixa`] (every kind carries the
5429 /// `Vec<String>` slot) and lands verbatim as the Helm chart
5430 /// `Chart.yaml` `maintainers:` array on every Servico (caixa-helm's
5431 /// `build_chart_yaml` at `caixa-helm/src/lib.rs:251` maps each entry
5432 /// to a `Maintainer { name, email: None }` without dedup). Two
5433 /// authoring footguns silently passed validate without this gate:
5434 ///
5435 /// - Empty entry (`(:autores (""))` — the canonical paste-from-
5436 /// blank-doc footgun) rendered as
5437 /// `maintainers: [{name: "", email: null}]` in `Chart.yaml`. The
5438 /// empty maintainer name has no operational meaning — it
5439 /// identifies no one in the substrate's authorship index and
5440 /// clutters the rendered chart with a no-op maintainer.
5441 /// - Duplicate entries (`(:autores ("pleme-io" "pleme-io"))` —
5442 /// the copy-paste-the-wrong-author footgun) silently passed
5443 /// validate and rendered as two identical maintainer entries.
5444 /// Unlike the [`Self::validate_etiquetas`] peer (caixa-helm's
5445 /// `BTreeSet`-collect on `:etiquetas` silently dedups the
5446 /// rendered `keywords:` array at chart-render time), the
5447 /// `maintainers:` rendering has *no* dedup — duplicate `:autores`
5448 /// entries stack verbatim in the chart, divergent from every
5449 /// peer typed-graph set gate ([`crate::AplicacaoError::MembroDuplicate`]
5450 /// on `:membros`, [`crate::AplicacaoError::PlacementClusterDuplicate`]
5451 /// on `:placement :clusters`, [`crate::AplicacaoError::EntradaPathDuplicate`]
5452 /// on `:entrada :paths`, [`crate::AplicacaoError::ContratoDuplicate`]
5453 /// on `:contratos`, [`crate::DepError::DuplicateNome`] on
5454 /// `:deps` / `:deps-dev`, [`crate::UpgradeError::DuplicateFrom`]
5455 /// on `:upgrade-from`, [`ManifestError::EtiquetaDuplicate`] on
5456 /// `:etiquetas`).
5457 ///
5458 /// Past the empty arm the gate enforces the chart-maintainer-name
5459 /// shape predicate via [`crate::render::is_chart_maintainer_name_shape`]:
5460 /// the structural single-line printable-UTF-8 floor every realistic
5461 /// Helm chart maintainer name carries — 1..=128 bytes, no leading
5462 /// or trailing whitespace, no ASCII control characters anywhere,
5463 /// Unicode bytes accepted. Closes the canonical paste-from-doc
5464 /// footguns the bare empty + duplicate arms left open:
5465 /// paste-from-aligned-doc whitespace (`" pleme-io"`, `"pleme-io "`),
5466 /// paste-from-multiline-doc newline (`"alice\nbob"` — the author
5467 /// pasted a multi-line block of author records into one `:autores`
5468 /// entry instead of splitting into one entry per author),
5469 /// paste-from-Windows-CRLF-doc carriage return, tab-from-aligned-doc,
5470 /// and the paste-from-binary-blob control bytes that would silently
5471 /// land as YAML-illegal byte sequences in the rendered Chart.yaml
5472 /// `maintainers:` array. Mirrors the shape-predicate cascade
5473 /// [`Self::validate_descricao`] / [`Self::validate_licenca`] /
5474 /// [`Self::validate_edicao`] / [`Self::validate_repositorio`]
5475 /// establish past their own empty arms on the sibling universal-axis
5476 /// `Option<String>` surfaces — the first universal-axis Vec<String>
5477 /// surface to land the empty-first-then-shape-then-duplicate per-entry
5478 /// cascade.
5479 ///
5480 /// Same empty-first cascade discipline every peer per-axis gate
5481 /// uses: the per-entry empty arm fires before the per-entry shape
5482 /// arm before the cross-entry duplicate arm. Walks the list in
5483 /// declaration order so the first-collision diagnostic surfaces the
5484 /// lexicographically-earliest offending position, peer with every
5485 /// other duplicate gate on this surface.
5486 ///
5487 /// Universal-axis (every kind carries `:autores`), so wired at the
5488 /// caixa-build gate alongside the peer universal gates
5489 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
5490 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
5491 /// [`Self::validate_code_paths`] — before the kind-coherence gates
5492 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
5493 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
5494 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
5495 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-specific
5496 /// slot sets.
5497 pub fn validate_autores(&self) -> Result<(), ManifestError> {
5498 let mut seen = std::collections::HashSet::new();
5499 for autor in self.autores() {
5500 if autor.is_empty() {
5501 return Err(ManifestError::AutorEmpty);
5502 }
5503 crate::render::is_chart_maintainer_name_shape(autor).map_err(|reason| {
5504 ManifestError::AutorInvalid {
5505 autor: autor.clone(),
5506 reason,
5507 }
5508 })?;
5509 crate::render::insert_first_seen(&mut seen, autor.as_str(), || {
5510 ManifestError::AutorDuplicate {
5511 autor: autor.clone(),
5512 }
5513 })?;
5514 }
5515 Ok(())
5516 }
5517
5518 /// Reject `:repositorio` values whose shape the shared
5519 /// [`crate::render::is_git_repo_url`] predicate refuses. The flat
5520 /// `repositorio: Option<String>` slot on [`Caixa`] is the
5521 /// universal git-shaped homepage axis every kind carries — the
5522 /// substrate routes the same string through two load-bearing
5523 /// consumers:
5524 ///
5525 /// - [`caixa-helm`] folds it verbatim into the rendered
5526 /// `lareira-<nome>` Helm chart's `Chart.yaml` `home:` field
5527 /// (`build_chart_yaml` at `caixa-helm/src/lib.rs:268`) and into
5528 /// the chart `README.md` `repo = …` interpolation
5529 /// (`caixa-helm/src/lib.rs:359`).
5530 /// - [`caixa-flux`] folds it verbatim into the standalone
5531 /// `ClusterBundleOpts::for_caixa` `git_url:` field
5532 /// (`caixa-flux/src/lib.rs:293`), which becomes the `FluxCD`
5533 /// `GitRepository.spec.url` the cluster's source-controller
5534 /// polls — the load-bearing deploy-time axis.
5535 ///
5536 /// Both consumers use `Option::unwrap_or_else(|| <fallback>)` to
5537 /// substitute a placeholder when the slot is absent (`None` → the
5538 /// fallback fires); a `Some("")` *skips the fallback* and silently
5539 /// passes the empty string through to `Chart.yaml home: ""` /
5540 /// `GitRepository url: ""` — Helm's chart lint and `FluxCD`'s source
5541 /// controller both reject the empty URL far from the source
5542 /// `caixa.lisp`, with no field naming the offending `:repositorio`.
5543 /// Similarly a malformed `:repositorio` (whitespace, control char,
5544 /// missing `:` separator, leading `-`) silently lands in the
5545 /// rendered artifacts and breaks at `git clone` / `helm template`
5546 /// / `flux reconcile` time.
5547 ///
5548 /// Thin wrapper around [`crate::render::is_git_repo_url`] — the
5549 /// same shared predicate the peer [`crate::DepSource::validate`]
5550 /// routes the `:fonte (:tipo git :repo …)` axis through. With this
5551 /// gate the two `git URL`-shaped surfaces on the typed Caixa
5552 /// (`:repositorio` here, `:deps :fonte :repo` peer) are
5553 /// structurally equivalent: every value past validate is
5554 /// guaranteed-acceptable by the predicate's union of constraints
5555 /// (non-empty, length-bounded, no leading `-`, no whitespace, no
5556 /// control chars, ASCII only, no leading `:`, contains a `:`
5557 /// separator). The predicate accepts every documented authoring
5558 /// shape — `github:org/repo` shorthand, `https://host/path`,
5559 /// `ssh://[user@]host/path`, `git://host/path`, `git@host:path`
5560 /// scp-style SSH, `file:///path` — and refuses the canonical
5561 /// paste-from-blank-doc / paste-from-multiline-doc / CLI-arg-
5562 /// injection footguns at validate time. Maps the predicate's
5563 /// `String` reason verbatim into the
5564 /// [`ManifestError::RepositorioInvalid`] variant, carrying the
5565 /// offending value + parser-shaped reason so the diagnostic is
5566 /// self-locating (the author can grep their `caixa.lisp` for
5567 /// `:repositorio "<value>"` and fix it in one edit).
5568 ///
5569 /// `None` (the canonical "omit the slot to express no published
5570 /// homepage" shape) is accepted trivially — the gate is a no-op
5571 /// when the author didn't declare a value. `Some("")` is gated by
5572 /// the narrower [`ManifestError::RepositorioEmpty`] arm before the
5573 /// shape predicate is consulted, mirroring the empty-first cascade
5574 /// every peer per-axis identity gate uses
5575 /// ([`ManifestError::NomeEmpty`] → [`ManifestError::NomeInvalid`],
5576 /// [`ManifestError::VersaoEmpty`] → [`ManifestError::VersaoInvalid`],
5577 /// [`crate::DepError::FonteRepoEmpty`] →
5578 /// [`crate::DepError::FonteRepoInvalid`]).
5579 ///
5580 /// Universal-axis (every kind carries `:repositorio`), so wired at
5581 /// the caixa-build gate alongside the peer universal gates
5582 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
5583 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
5584 /// [`Self::validate_autores`] / [`Self::validate_code_paths`] —
5585 /// before the kind-coherence gates
5586 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
5587 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
5588 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
5589 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
5590 /// specific slot sets.
5591 pub fn validate_repositorio(&self) -> Result<(), ManifestError> {
5592 let Some(s) = self.repositorio() else {
5593 return Ok(());
5594 };
5595 if s.is_empty() {
5596 return Err(ManifestError::RepositorioEmpty);
5597 }
5598 is_git_repo_url(s).map_err(|reason| ManifestError::RepositorioInvalid {
5599 repositorio: s.to_string(),
5600 reason,
5601 })
5602 }
5603
5604 /// Reject `:descricao` values that are the empty string. The flat
5605 /// `descricao: Option<String>` slot on [`Caixa`] is the universal
5606 /// free-form-prose homepage axis every kind carries — the
5607 /// substrate routes the same string through two load-bearing
5608 /// consumers in the [`caixa-helm`] renderer:
5609 ///
5610 /// - `build_chart_yaml` folds it verbatim into the rendered
5611 /// `lareira-<nome>` Helm chart's `Chart.yaml` `description:`
5612 /// field (`caixa-helm/src/lib.rs:232-235`).
5613 /// - `build_readme` folds it verbatim into the rendered chart
5614 /// `README.md` header (`caixa-helm/src/lib.rs:333-336`).
5615 ///
5616 /// Both consumers use `Option::unwrap_or_else(|| <fallback>)` to
5617 /// substitute a `caixa.nome`-derived placeholder when the slot is
5618 /// absent (`None` → the fallback fires); a `Some("")` *skips the
5619 /// fallback* and silently passes the empty string through to
5620 /// `Chart.yaml description: ""` / a blank chart `README.md`
5621 /// header. Helm's chart spec requires a non-empty `description:`
5622 /// field on `apiVersion: v2` charts (`helm lint` surfaces it as
5623 /// `WARNING [chart.metadata.description]: description is required`),
5624 /// so the empty `Some("")` silently lands in the rendered
5625 /// artifacts and breaks at `helm lint` / `helm install` time far
5626 /// from the source `caixa.lisp`, with no field naming the
5627 /// offending `:descricao`.
5628 ///
5629 /// `None` (the canonical "omit the slot to defer to the renderer's
5630 /// `caixa.nome`-derived fallback" shape) is accepted trivially —
5631 /// the gate is a no-op when the author didn't declare a value.
5632 /// `Some("")` is gated by the narrower
5633 /// [`ManifestError::DescricaoEmpty`] arm, mirroring the empty-arm
5634 /// shape every peer per-axis empty gate uses
5635 /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
5636 /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
5637 /// [`ManifestError::RepositorioEmpty`]).
5638 ///
5639 /// Universal-axis (every kind carries `:descricao`), so wired at
5640 /// the caixa-build gate alongside the peer universal gates
5641 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
5642 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
5643 /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
5644 /// [`Self::validate_code_paths`] — before the kind-coherence
5645 /// gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
5646 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
5647 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
5648 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
5649 /// specific slot sets.
5650 ///
5651 /// Past the empty arm the gate enforces the chart-description
5652 /// shape predicate via [`crate::render::is_chart_description_shape`]:
5653 /// the structural single-line UTF-8 floor every realistic chart
5654 /// description in the wild matches — 1..=512 bytes, no leading
5655 /// or trailing whitespace, no ASCII control characters anywhere
5656 /// (`0x00..=0x1F` plus `0x7F` DEL — banning tab, newline,
5657 /// carriage return, and every other control byte), Unicode
5658 /// continuation bytes accepted (the canonical fixtures carry
5659 /// `→` and `—`). Closes the canonical paste-from-doc footguns
5660 /// the bare empty-arm gate left open: paste-from-aligned-doc
5661 /// leading / trailing whitespace (`" Checkout flow."`,
5662 /// `"Checkout flow. "`), paste-from-multiline-doc newline
5663 /// (`"Checkout\nflow."`), paste-from-Windows-CRLF-doc CR
5664 /// (`"Checkout\rflow."`), tab-from-aligned-doc
5665 /// (`"Checkout\tflow."`), and paste-from-binary-blob NUL / BEL /
5666 /// ESC / DEL bytes. Mirrors the shape-predicate cascade
5667 /// [`Self::validate_repositorio`] / [`Self::validate_licenca`] /
5668 /// [`Self::validate_edicao`] establish past their own empty arms
5669 /// on the sibling universal-axis `Option<String>` Caixa-level
5670 /// value-shape surfaces.
5671 ///
5672 /// The empty-first cascade discipline mirrors every peer per-axis
5673 /// identity gate: [`ManifestError::DescricaoEmpty`] runs before
5674 /// [`ManifestError::DescricaoInvalid`], so the narrower empty
5675 /// diagnostic surfaces on `Some("")` rather than the broader
5676 /// shape-predicate diagnostic — peer with how
5677 /// [`ManifestError::LicencaEmpty`] runs before
5678 /// [`ManifestError::LicencaInvalid`],
5679 /// [`ManifestError::EdicaoEmpty`] runs before
5680 /// [`ManifestError::EdicaoInvalid`],
5681 /// [`ManifestError::RepositorioEmpty`] runs before
5682 /// [`ManifestError::RepositorioInvalid`].
5683 pub fn validate_descricao(&self) -> Result<(), ManifestError> {
5684 let Some(s) = self.descricao() else {
5685 return Ok(());
5686 };
5687 if s.is_empty() {
5688 return Err(ManifestError::DescricaoEmpty);
5689 }
5690 crate::render::is_chart_description_shape(s).map_err(|reason| {
5691 ManifestError::DescricaoInvalid {
5692 descricao: s.to_string(),
5693 reason,
5694 }
5695 })?;
5696 Ok(())
5697 }
5698
5699 /// Reject `:licenca` values that are the empty string. The flat
5700 /// `licenca: Option<String>` slot on [`Caixa`] is the universal
5701 /// SPDX-shaped license-expression axis every kind carries — the
5702 /// substrate routes the same string through the [`caixa-helm`]
5703 /// renderer's `build_readme` which folds it verbatim into the
5704 /// rendered `lareira-<nome>` Helm chart's `README.md` `## License`
5705 /// section (`caixa-helm/src/lib.rs:361`) via
5706 /// `caixa.licenca.clone().unwrap_or_else(|| "MIT".into())`. The
5707 /// fallback only fires on `None`; a `Some("")` *skips the
5708 /// fallback* and silently passes the empty string through to a
5709 /// chart `README.md` whose `License` section renders as the bare
5710 /// trailing period (`.\n`) — peer footgun with the
5711 /// `Some("")`-skips-`unwrap_or_else` shape the
5712 /// [`Self::validate_descricao`] and [`Self::validate_repositorio`]
5713 /// gates close on the sibling free-form-prose and git-URL axes.
5714 ///
5715 /// `None` (the canonical "omit the slot to defer to the
5716 /// renderer's `MIT` fallback" shape every existing fixture
5717 /// carries) is accepted trivially — the gate is a no-op when the
5718 /// author didn't declare a value. `Some("")` is gated by the
5719 /// narrower [`ManifestError::LicencaEmpty`] arm, mirroring the
5720 /// empty-arm shape every peer per-axis empty gate uses
5721 /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
5722 /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
5723 /// [`ManifestError::RepositorioEmpty`],
5724 /// [`ManifestError::DescricaoEmpty`]).
5725 ///
5726 /// Universal-axis (every kind carries `:licenca`), so wired at
5727 /// the caixa-build gate alongside the peer universal gates
5728 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
5729 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
5730 /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
5731 /// [`Self::validate_descricao`] / [`Self::validate_code_paths`]
5732 /// — before the kind-coherence gates
5733 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
5734 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
5735 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
5736 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
5737 /// specific slot sets.
5738 ///
5739 /// Past the empty arm the gate enforces the SPDX-expression shape
5740 /// predicate via [`crate::render::is_spdx_expression_shape`]: the
5741 /// structural alphabet floor every realistic SPDX expression in
5742 /// the wild uses — ASCII alphanumeric plus `.`, `-`, `+`, `(`,
5743 /// `)`, `:` (the `DocumentRef-…:LicenseRef-…` separator), and a
5744 /// single ASCII space (token separator). Closes the canonical
5745 /// paste-from-doc footguns the bare empty-arm gate left open:
5746 /// paste-from-doc whitespace (`"MIT "`, `" MIT"`), paste-from-
5747 /// multiline-doc CRLF (`"MIT\n"`), tab-from-aligned-doc
5748 /// (`"MIT\tOR Apache-2.0"`), non-ASCII smart-quote paste,
5749 /// underscore-instead-of-hyphen typo (`"Apache_2.0"`),
5750 /// comma-instead-of-`OR`-keyword colloquial idiom (`"MIT,
5751 /// Apache-2.0"`), slash-dual-license colloquial idiom (`"MIT/
5752 /// Apache-2.0"`), and semicolon-list-separator confusion
5753 /// (`"MIT; Apache-2.0"`). Mirrors the shape-predicate cascade
5754 /// [`Self::validate_repositorio`] / [`Self::validate_edicao`]
5755 /// establish past their own empty arms.
5756 ///
5757 /// The empty-first cascade discipline mirrors every peer per-axis
5758 /// identity gate: [`ManifestError::LicencaEmpty`] runs before
5759 /// [`ManifestError::LicencaInvalid`], so the narrower empty
5760 /// diagnostic surfaces on `Some("")` rather than the broader
5761 /// shape-predicate diagnostic — peer with how
5762 /// [`ManifestError::EdicaoEmpty`] runs before
5763 /// [`ManifestError::EdicaoInvalid`],
5764 /// [`ManifestError::RepositorioEmpty`] runs before
5765 /// [`ManifestError::RepositorioInvalid`].
5766 ///
5767 /// A future tightening on this axis can extend the alphabet
5768 /// floor into a full SPDX expression parser + license-id
5769 /// allowlist (rejecting alphabet-valid values that don't name a
5770 /// real SPDX license identifier — e.g., `"NotAReal"` is
5771 /// alphabet-valid but no `NotAReal` license-id exists). That
5772 /// parser only becomes meaningful past a real SPDX-spec
5773 /// dependency; this gate establishes the structural floor by
5774 /// refusing every non-SPDX-alphabet value at validate time.
5775 pub fn validate_licenca(&self) -> Result<(), ManifestError> {
5776 let Some(s) = self.licenca() else {
5777 return Ok(());
5778 };
5779 if s.is_empty() {
5780 return Err(ManifestError::LicencaEmpty);
5781 }
5782 crate::render::is_spdx_expression_shape(s).map_err(|reason| {
5783 ManifestError::LicencaInvalid {
5784 licenca: s.to_string(),
5785 reason,
5786 }
5787 })?;
5788 Ok(())
5789 }
5790
5791 /// Reject `:edicao` values that are the empty string. The flat
5792 /// `edicao: Option<String>` slot on [`Caixa`] is the universal
5793 /// language-edition axis every kind carries — it determines the
5794 /// tatara-lisp macro surface + compatibility flags the substrate
5795 /// applies when building a caixa, and lands verbatim in the
5796 /// `Caixa::template` author-time scaffold (the canonical
5797 /// `:edicao "2026"` line every `feira init` emits via
5798 /// [`Caixa::template`] at `caixa-core/src/manifest.rs:1193`) and
5799 /// in every renderer-side fixture (`caixa-helm/src/lib.rs:375`,
5800 /// `caixa-flux/src/lib.rs:445`, `caixa-mesh/src/lib.rs:629`,
5801 /// `caixa-core/src/render.rs:2510`) via
5802 /// `edicao: Some("2026".into())`.
5803 ///
5804 /// `None` (the canonical "omit the slot to defer to the
5805 /// substrate's default edition" shape every existing
5806 /// [`caixa-resolver`] integration test fixture carries via
5807 /// `edicao: None` — see `caixa-resolver/tests/git_integration.rs`)
5808 /// is accepted trivially — the gate is a no-op when the author
5809 /// didn't declare a value. `Some("")` is gated by the narrower
5810 /// [`ManifestError::EdicaoEmpty`] arm, mirroring the empty-arm
5811 /// shape every peer per-axis empty gate uses
5812 /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
5813 /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
5814 /// [`ManifestError::RepositorioEmpty`],
5815 /// [`ManifestError::DescricaoEmpty`], [`ManifestError::LicencaEmpty`]).
5816 ///
5817 /// Universal-axis (every kind carries `:edicao`), so wired at
5818 /// the caixa-build gate alongside the peer universal gates
5819 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
5820 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
5821 /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
5822 /// [`Self::validate_descricao`] / [`Self::validate_licenca`] /
5823 /// [`Self::validate_code_paths`] — before the kind-coherence
5824 /// gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
5825 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
5826 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
5827 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
5828 /// specific slot sets.
5829 ///
5830 /// Past the empty arm the gate enforces the canonical year-shape
5831 /// predicate: every documented tatara-lisp edition is a 4-digit
5832 /// ASCII decimal year (`"2026"` is the only edition currently
5833 /// minted; future-introduced siblings will follow the same
5834 /// shape, peer with Cargo's `[package] edition` grammar which
5835 /// every value Cargo has ever accepted matches — `"2015"`,
5836 /// `"2018"`, `"2021"`, `"2024"`). Any value that's not exactly
5837 /// 4 ASCII decimal bytes is rejected with the narrower
5838 /// [`ManifestError::EdicaoInvalid`] arm, mirroring the
5839 /// shape-predicate cascade [`Self::validate_repositorio`]
5840 /// establishes past its own empty arm
5841 /// ([`ManifestError::RepositorioEmpty`] →
5842 /// [`ManifestError::RepositorioInvalid`]). Closes the canonical
5843 /// paste-from-doc footguns the bare empty-arm gate left open:
5844 ///
5845 /// - leading / trailing whitespace from a paste-from-doc
5846 /// (`"2026 "`, `" 2026"`)
5847 /// - control characters / CRLF from a paste-from-multiline-doc
5848 /// (`"2026\n"`)
5849 /// - non-ASCII look-alikes from a fullwidth keyboard
5850 /// (`"2026"`) which would silently land as a non-ASCII
5851 /// string in the rendered caixa.lisp
5852 /// - free-form non-year values (`"x"`, `"latest"`,
5853 /// `"nightly"`) that have no operational meaning on the
5854 /// substrate's build-time edition selector
5855 /// - leading non-digit prefixes (`"v2026"`, `"e2026"`,
5856 /// `"r2026"`) — common version-tag idioms that don't apply
5857 /// to the year-shaped edition axis
5858 /// - decimal-shaped values (`"2026.1"`, `"2026.0"`) — every
5859 /// edition is a year, not a fractional version
5860 /// - wrong-length numeric values (`"26"`, `"202"`, `"20260"`,
5861 /// `"00026"`) that don't name a year
5862 ///
5863 /// `None` (the canonical "omit the slot to defer to the
5864 /// substrate's default edition" shape every existing
5865 /// [`caixa-resolver`] integration test fixture carries via
5866 /// `edicao: None` — see `caixa-resolver/tests/git_integration.rs`)
5867 /// is accepted trivially — the gate is a no-op when the author
5868 /// didn't declare a value. The empty-first cascade discipline
5869 /// mirrors every peer per-axis identity gate:
5870 /// [`ManifestError::EdicaoEmpty`] runs before
5871 /// [`ManifestError::EdicaoInvalid`], so the narrower empty
5872 /// diagnostic surfaces on `Some("")` rather than the broader
5873 /// shape-predicate diagnostic — peer with how
5874 /// [`ManifestError::NomeEmpty`] runs before
5875 /// [`ManifestError::NomeInvalid`],
5876 /// [`ManifestError::VersaoEmpty`] runs before
5877 /// [`ManifestError::VersaoInvalid`],
5878 /// [`ManifestError::RepositorioEmpty`] runs before
5879 /// [`ManifestError::RepositorioInvalid`].
5880 ///
5881 /// A future tightening on this axis can extend the shape
5882 /// predicate into a known-edition allowlist (rejecting
5883 /// year-shaped values that don't name a tatara-lisp edition
5884 /// the substrate actually understands — e.g., `"1999"` is
5885 /// year-shaped but no `1999` edition exists). That allowlist
5886 /// only becomes meaningful past the introduction of a sibling
5887 /// edition to `"2026"`; this gate establishes the structural
5888 /// floor by refusing every non-year-shaped value at validate
5889 /// time.
5890 pub fn validate_edicao(&self) -> Result<(), ManifestError> {
5891 let Some(s) = self.edicao() else {
5892 return Ok(());
5893 };
5894 if s.is_empty() {
5895 return Err(ManifestError::EdicaoEmpty);
5896 }
5897 if s.len() != 4 || !s.bytes().all(|b| b.is_ascii_digit()) {
5898 return Err(ManifestError::EdicaoInvalid {
5899 edicao: s.to_string(),
5900 reason: "must be a 4-digit ASCII decimal year (canonical \"2026\")".to_string(),
5901 });
5902 }
5903 Ok(())
5904 }
5905
5906 /// Compose the supervisor-related flat slots into a single
5907 /// [`SupervisorSpec`] for validation. Returns `None` when the
5908 /// caixa isn't a `:kind Supervisor`.
5909 ///
5910 /// The flat representation in [`Caixa`] keeps tatara-lisp authoring
5911 /// simple (one form, no nested `:supervisor (…)` block); this view
5912 /// is the "typed shape" the operator + supervisor reconciler
5913 /// consume.
5914 #[must_use]
5915 pub fn supervisor_view(&self) -> Option<SupervisorSpec> {
5916 if !self.kind().is_supervisor() {
5917 return None;
5918 }
5919 // Fold through the shared `supervisor::duration_codec::parse`
5920 // — the same parser the serde-routed `with = "duration_codec"`
5921 // on `SupervisorSpec::restart_window`, the `:politicas
5922 // :timeout` codec, and the `:politicas :circuit-breaker
5923 // :window` codec all consume. The prior inline f64-shaped
5924 // duplicate (`parse_window_inline`) admitted every magnitude
5925 // the integer-magnitude gate (1c55a2a) rejects on the three
5926 // serde-routed siblings — `"1.5s"`, `"1.0s"`, `"0.5m"`,
5927 // `"+30s"`, `"-30s"` — and silently dropped malformed input as
5928 // `None` (i.e. "no reset"), divergent from the shared codec's
5929 // integer-magnitude discipline by construction. The fold
5930 // closes the divergence: every value the typed
5931 // `SupervisorSpec` carries past `supervisor_view` is in the
5932 // shared codec's accepted set. The `.ok()` here preserves the
5933 // existing soft-swallow shape on this view-construction path;
5934 // the new [`Caixa::validate_restart_window`] (sibling of
5935 // [`Self::validate_nome`] / [`Self::validate_versao`]) names
5936 // the offending raw string at build time so authoring tools
5937 // (`feira lint`, the future layout-side wire-up) surface a
5938 // self-locating diagnostic instead of a silently dropped
5939 // window.
5940 let restart_window = self
5941 .restart_window()
5942 .and_then(|s| crate::supervisor::duration_codec::parse(s).ok());
5943 Some(SupervisorSpec {
5944 // Route the author-omitted `:estrategia` arm through the
5945 // substrate-canonical
5946 // [`crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT`] typed
5947 // `pub const` rather than the transitively-derived
5948 // [`RestartStrategy::default`] route the prior
5949 // `.unwrap_or_default()` fold reached for — one source of
5950 // truth for the Erlang/OTP `one_for_one` half of Learn You
5951 // Some Erlang's `{one_for_one, intensity, 5, 60}` worker-
5952 // supervisor canonical default that also backs the
5953 // [`crate::supervisor::Default for RestartStrategy`] impl
5954 // and the [`crate::supervisor::Default for SupervisorSpec`]
5955 // impl's struct-literal `estrategia` field, all now routed
5956 // through the same lifted constant. Prior to the lift the
5957 // composition site carried `.unwrap_or_default()` with no
5958 // compile-time link back to the shared OTP-canonical
5959 // default that the peer paired
5960 // `.unwrap_or(SUPERVISOR_MAX_RESTARTS_DEFAULT)` (b698ec0)
5961 // arm on the sibling `:max-restarts` axis routes through —
5962 // so a future rebrand of the OTP-canonical strategy default
5963 // (a widening to `rest_for_one` once the substrate
5964 // discovers startup-order-coupled child cohorts as the more
5965 // common shape, a per-cluster overlay the operator pins
5966 // through the MESH-COMPOSITION §III.2 supervision-canary
5967 // `:estrategia-overrides` roadmap slot) would have had to
5968 // migrate the paired `MaxIntensity` + `Period` halves
5969 // through the lifted constants and the `one_for_one` half
5970 // through a `RestartStrategy::default()` route in lockstep
5971 // or the three halves of the same OTP-canonical default
5972 // would silently drift out of pairing. Byte-parity against
5973 // the lifted constant closes the split. Pinned by
5974 // [`supervisor_view_estrategia_fallback_routes_through_lifted_default`]
5975 // in the tests module.
5976 estrategia: self
5977 .estrategia()
5978 .unwrap_or(crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT),
5979 // Route the author-omitted `:max-restarts` arm through the
5980 // substrate-canonical [`crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT`]
5981 // typed `pub const` rather than the raw `5` literal — one
5982 // source of truth for the Erlang/OTP-canonical
5983 // `{intensity, 5, 60}` `MaxIntensity` default that also
5984 // backs the serde-side wire-format author-omitted arm on
5985 // [`crate::supervisor::SupervisorSpec::max_restarts`] via
5986 // `#[serde(default = "default_max_restarts")]` and the
5987 // [`Default for SupervisorSpec`] impl's struct-literal
5988 // default field. Prior to the lift the composition site
5989 // carried a raw `5` with no compile-time link back to the
5990 // serde-side default, so a future rebrand of the OTP-
5991 // canonical default (a tightening to Elixir's `3`, a
5992 // widening to a per-cluster overlay the operator pins
5993 // through the MESH-COMPOSITION §III.2 supervision-canary
5994 // `:supervisor :max-restarts-overrides` roadmap slot)
5995 // would have had to be threaded through both open-coded
5996 // copies in lockstep or the wire-format author-omitted arm
5997 // and this view-construction author-omitted arm would
5998 // silently disagree on which restart-budget an omitted
5999 // `:max-restarts` resolves to. Pinned by
6000 // [`supervisor_view_max_restarts_fallback_routes_through_lifted_default`]
6001 // in the tests module.
6002 max_restarts: self
6003 .max_restarts()
6004 .unwrap_or(crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT),
6005 restart_window,
6006 children: self.children().to_vec(),
6007 })
6008 }
6009
6010 /// A minimal starter manifest emitted by `feira init`.
6011 #[must_use]
6012 pub fn template(nome: &str) -> String {
6013 format!(
6014 "(defcaixa\n \
6015 :nome {nome:?}\n \
6016 :versao \"0.1.0\"\n \
6017 :kind Biblioteca\n \
6018 :edicao \"2026\"\n \
6019 :descricao \"FIXME — describe this caixa\"\n \
6020 :autores ()\n \
6021 :etiquetas ()\n \
6022 :deps ()\n \
6023 :deps-dev ()\n \
6024 :bibliotecas (\"lib/{nome}.lisp\"))\n"
6025 )
6026 }
6027
6028 /// Serialize to a canonical `caixa.lisp` source — suitable for writing
6029 /// back after mutation (e.g. `feira add`).
6030 ///
6031 /// Goes through serde JSON → canonical Sexp → per-field pretty print.
6032 /// The derive-macro `compile_from_sexp` path is the inverse, so any
6033 /// `Caixa` round-trips through `to_lisp` + `from_lisp`.
6034 #[must_use]
6035 pub fn to_lisp(&self) -> String {
6036 let json = serde_json::to_value(self).expect("Caixa serialize");
6037 let sexp = tatara_lisp::domain::json_to_sexp(&json);
6038 let tatara_lisp::Sexp::List(items) = sexp else {
6039 return format!("(defcaixa {sexp})\n");
6040 };
6041 let mut out = String::from("(defcaixa");
6042 let mut i = 0;
6043 while i + 1 < items.len() {
6044 out.push_str("\n ");
6045 out.push_str(&items[i].to_string());
6046 out.push(' ');
6047 out.push_str(&items[i + 1].to_string());
6048 i += 2;
6049 }
6050 out.push_str(")\n");
6051 out
6052 }
6053}
6054
6055/// Errors raised by top-level [`Caixa`] validators that don't fit
6056/// the per-axis [`DepError`] / [`crate::AplicacaoError`] /
6057/// [`crate::SupervisorError`] / [`crate::LayoutError`] families —
6058/// the Caixa's own identity axes (`:nome`, `:versao`) that flow
6059/// through every substrate-side artifact's `metadata.name` /
6060/// version derivation.
6061///
6062/// A future top-level sum (the M4 `CaixaError` the [`DepError`]
6063/// doc-comment anticipates) can hold one of each per-axis error
6064/// family without reshaping individual diagnostics; this enum is
6065/// the first such per-Caixa-identity family.
6066#[derive(Debug, Error, PartialEq, Eq)]
6067pub enum ManifestError {
6068 #[error(
6069 ":nome is empty (every caixa must name itself; the value flows \
6070 into every K8s artifact's `metadata.name` derivation and into \
6071 the default `lib/<nome>.lisp` / `exe/<nome>` layout paths)"
6072 )]
6073 NomeEmpty,
6074 #[error(
6075 ":nome {nome:?} is not a valid DNS-1123 label: {reason} (the K8s \
6076 apiserver enforces this rule on every `metadata.name` the \
6077 caixa's substrate-side renderers derive from `:nome` — the \
6078 `lareira-<nome>` Helm chart name, the programs.yaml entry \
6079 name, the `LABEL_APLICACAO` label value, the `<aplicacao>-<de>-to-<para>` \
6080 CiliumNetworkPolicy name, the `<aplicacao>-<para>` HTTPRoute \
6081 name; use a lowercase alphanumeric + hyphen identifier like \
6082 `\"checkout\"` or `\"cart-v2\"`)"
6083 )]
6084 NomeInvalid { nome: String, reason: String },
6085 #[error(
6086 ":nome {nome:?} overflows the joint-length budget on the canonical \
6087 `lareira-<nome>` chart-name shape: {reason} (every per-Servico / \
6088 per-Aplicacao renderer the substrate carries — `caixa-helm`'s \
6089 `Chart.yaml::name`, `caixa-flux`'s `cluster_bundle` HelmRelease \
6090 `chart:` slot, `caixa-tatara`'s `release_name` + \
6091 `oci://<registry>/lareira-<nome>` chart ref — derives the same \
6092 joint name through the canonical `lareira_chart_name` helper, and \
6093 Helm's `Chart.yaml::name` admission rule + the K8s apiserver's \
6094 DNS-1123 label cap on every chart-name-derived `metadata.name` \
6095 reject any joint name exceeding 63 bytes; the narrower \
6096 `:nome` shape (`NomeInvalid`) gates the bare-`:nome` budget, this \
6097 arm gates the chart-name budget downstream renderers inherit)"
6098 )]
6099 NomeChartNameBudgetExceeded { nome: String, reason: String },
6100 #[error(
6101 ":versao is empty (every caixa must pin its own version; the value flows \
6102 into the `lareira-<nome>` Helm chart's `Chart.yaml` version + appVersion, \
6103 the `feira publish` `v<versao>` git tag, the OCI image's `:v<versao>` / \
6104 `:latest` tags, the lacre closure's `concrete_versao`, and the \
6105 `:upgrade-from :from` peers — use a SemVer-2 literal like `\"0.1.0\"`)"
6106 )]
6107 VersaoEmpty,
6108 #[error(
6109 ":versao {versao:?} is not a valid SemVer-2 version: {reason} (the substrate \
6110 consumes this string as `semver::Version` — three-part `MAJOR.MINOR.PATCH` \
6111 with optional `-prerelease` and `+build` — across every artifact derived \
6112 from `:versao`: the `lareira-<nome>` Helm chart's `Chart.yaml` version + \
6113 appVersion (Helm SemVer-2-strict), the `feira publish` `v<versao>` git tag, \
6114 the OCI image's `:v<versao>` tag, the lacre closure's `concrete_versao`, \
6115 and the `:upgrade-from :from` peers that match against this exact shape; \
6116 use a literal like `\"0.1.0\"`, `\"0.2.0-rc.1\"`, or `\"1.0.0+build.42\"` — \
6117 not a git-tag-shape like `\"v0.1.0\"`, a docker-tag-shape like `\"latest\"`, \
6118 a requirement-shape like `\"^0.1\"`, or a four-part `\"0.1.0.0\"`)"
6119 )]
6120 VersaoInvalid { versao: String, reason: String },
6121 #[error(
6122 ":restart-window {restart_window:?} is not a valid duration: {reason} (the \
6123 substrate consumes this string through the shared \
6124 `supervisor::duration_codec` — the same parser routed via `with = \
6125 \"duration_codec\"` onto the typed `SupervisorSpec::restart_window`, \
6126 `:politicas :timeout`, and `:politicas :circuit-breaker :window` slots; \
6127 the canonical authoring form is `<integer><unit>` where the unit is one \
6128 of `ms` / `s` / `m` / `h` and the magnitude has no decimal point and no \
6129 leading `+` / `-` sign — e.g. `\"60s\"`, `\"5m\"`, `\"1h\"`, `\"500ms\"`. \
6130 Without this gate a malformed `:restart-window` silently produced a \
6131 supervisor with `restart_window: None` (\"never reset\"), turning OTP's \
6132 `MaxIntensity / Period` invariant into a never-reset supervisor far from \
6133 the source `caixa.lisp`; the gate moves the diagnostic to the manifest \
6134 layer with the offending value named verbatim. Omit the slot entirely to \
6135 express \"no reset\"; carry a positive integer duration to express the \
6136 sliding window)"
6137 )]
6138 RestartWindowMalformed {
6139 restart_window: String,
6140 reason: String,
6141 },
6142 #[error(
6143 "{slot} entry is an empty path string — every {slot} entry must name \
6144 a file relative to the caixa root; omit the entry to omit the file \
6145 (the layout checker's `root.join(\"\")` resolves to the caixa root \
6146 itself, so an empty entry silently aliases the project root as a \
6147 declared {slot} file, then fails downstream at parse / existence \
6148 time with a diagnostic that names the root rather than the offending \
6149 entry)"
6150 )]
6151 CodePathEmpty { slot: &'static str },
6152 #[error(
6153 "{slot} entry {} is an absolute path — entries must be relative to \
6154 the caixa root, since `Path::join` replaces the base with an absolute \
6155 right-hand side and `root.join(\"/abs/...\")` resolves to \"/abs/...\" \
6156 outside the caixa root sandbox; rewrite the entry as a relative path \
6157 under the caixa root (e.g. `\"lib/<name>.lisp\"`, `\"exe/<name>\"`, \
6158 `\"servicos/<name>.computeunit.yaml\"`)",
6159 path.display()
6160 )]
6161 CodePathAbsolute { slot: &'static str, path: PathBuf },
6162 #[error(
6163 "{slot} entry {} contains a `..` component — entries must not traverse \
6164 above the caixa root (the layout's `starts_with(<dir>)` fence on \
6165 `:exe` / `:servicos` is component-aware, not canonical-path-aware, \
6166 so a mid-path `..` silently traverses the sandbox; `:bibliotecas` \
6167 has no such fence, so a leading `..` escapes unconditionally if the \
6168 resolved target happens to exist)",
6169 path.display()
6170 )]
6171 CodePathParentEscape { slot: &'static str, path: PathBuf },
6172 #[error(
6173 "{slot} entry {} does not terminate in the `.lisp` extension — every \
6174 `:bibliotecas` entry is a tatara-lisp source file the `feira build` \
6175 loop reads through `tatara_lisp::read` at parse time, so any other \
6176 extension (`.rs`, `.txt`, `.lisp.bak`) or no-extension shape is \
6177 structurally a parser error far from the source caixa.lisp, with \
6178 no field naming the offending `:bibliotecas` entry. Pin a relative \
6179 path under the caixa root whose terminating extension is \
6180 lowercase-`.lisp` (e.g. `\"lib/<name>.lisp\"`, \
6181 `\"lib/handlers.lisp\"`) — the same file-type contract the peer \
6182 `:behavior :on-*` (c97815a) and `:upgrade-from :state-change :script` \
6183 (33cc830) axes already carry through the same lifted \
6184 `is_lisp_extension` predicate",
6185 path.display()
6186 )]
6187 CodePathNonLispExtension { slot: &'static str, path: PathBuf },
6188 #[error(
6189 "{slot} entry {} does not terminate in the `.computeunit.yaml` \
6190 compound suffix — every `:servicos` entry is a typed `ComputeUnit` \
6191 CR YAML file the peer caixa-helm / caixa-flux renderers consume \
6192 through `serde_yaml::from_str` at chart / FluxCD bundle render \
6193 time, so any other extension (`.yaml`, `.yml`, `.json`, the \
6194 off-by-one-segment `.computeunit-yaml`, the editor-backup \
6195 `.computeunit.yaml.bak`) or no-extension shape is structurally a \
6196 YAML-parser error / `ComputeUnit` schema-mismatch far from the \
6197 source caixa.lisp, with no field naming the offending `:servicos` \
6198 entry. Pin a relative path under the caixa root whose terminating \
6199 compound suffix is lowercase-`.computeunit.yaml` (e.g. \
6200 `\"servicos/<name>.computeunit.yaml\"`, \
6201 `\"servicos/hello-rio.computeunit.yaml\"`) — the same file-type \
6202 contract the sibling `:bibliotecas` axis (64772a9) already carries \
6203 on the tatara-lisp-source axis through the peer lifted \
6204 `is_lisp_extension` predicate, here on the compound-suffix axis \
6205 `Path::extension` can't express on its own through the lifted \
6206 `is_computeunit_yaml_extension` predicate",
6207 path.display()
6208 )]
6209 CodePathNonComputeUnitYamlExtension { slot: &'static str, path: PathBuf },
6210 #[error(
6211 "{slot} entry {} appears more than once (the code-path list is \
6212 a set, not a multiset; every peer Vec-shaped author-supplied \
6213 list past validate is set-not-multiset — `:membros :caixa`, \
6214 `:placement :clusters`, `:entrada :paths`, `:contratos`, \
6215 `:children :caixa`, `:deps` / `:deps-dev` `:nome`, \
6216 `:upgrade-from :from`, `:etiquetas`, `:autores` — and the three \
6217 code-path lists are the last Vec-shaped author-supplied slots on \
6218 the typed Caixa surface still admitting a duplicate entry. \
6219 `:bibliotecas` duplicates re-parse the same file at \
6220 `feira build` time and silently mask the author's intent to \
6221 declare a *second* biblioteca; `:exe` duplicates collide on the \
6222 flake `packages.<name>` derivation key at the future \
6223 `caixa-flake` materializer; `:servicos` duplicates surface as the \
6224 narrower [`caixa-helm`] / [`caixa-flux`] `UnsupportedServicoCount` \
6225 rejection far from the source `caixa.lisp`. Drop the duplicate \
6226 or rename it to the actual second file intended)",
6227 path.display()
6228 )]
6229 CodePathDuplicate { slot: &'static str, path: PathBuf },
6230 #[error(
6231 ":etiquetas entry is empty (every tag must carry a non-empty \
6232 registry-search identifier; the empty entry has no operational \
6233 meaning — it indexes nothing in the future caixa-registry search \
6234 axis and clutters the rendered Helm `Chart.yaml` `keywords:` array \
6235 with a no-op tag; omit the entry to express \"no tag on this \
6236 position\")"
6237 )]
6238 EtiquetaEmpty,
6239 #[error(
6240 ":etiquetas entry {etiqueta:?} appears more than once (the \
6241 registry-search tag set is a set, not a multiset; duplicate \
6242 entries are silently dedup'd by caixa-helm's `BTreeSet` collect \
6243 at chart render — a \"second wins / one silently disappears\" \
6244 shape divergent from every peer typed-graph set gate \
6245 (`:membros :caixa`, `:placement :clusters`, `:entrada :paths`, \
6246 `:contratos`, `:deps :nome`, `:upgrade-from :from`); drop the \
6247 duplicate or rename it to the actual tag intended)"
6248 )]
6249 EtiquetaDuplicate { etiqueta: String },
6250 #[error(
6251 ":etiquetas entry {etiqueta:?} is not a valid chart-keyword shape: \
6252 {reason} (the substrate consumes this string through the shared \
6253 `crate::render::is_chart_keyword_shape` predicate — the same \
6254 Cargo crates.io `[package] keywords` grammar entry shape: 1..=20 \
6255 bytes, starts with an ASCII letter, ASCII alphanumeric / `_` / `-` \
6256 continuation. The canonical authoring shapes are short kebab-case \
6257 identifiers like `\"mesh\"`, `\"wasm\"`, `\"tatara-lisp\"`, \
6258 `\"hello-world\"`, `\"caixa-servico\"`, `\"infrastructure\"`. \
6259 Without this gate a malformed `:etiquetas` entry (paste-from-doc \
6260 leading / trailing whitespace `\" mesh\"` / `\"mesh \"`; \
6261 paste-from-multiline-doc newline `\"mesh\\nhttp\"`; \
6262 paste-from-Windows-CRLF-doc CR; CSV-list-separator confusion \
6263 `\"mesh,http,grpc\"` — the author meant to author three separate \
6264 list entries; path-separator confusion `\"caixa/servico\"`; \
6265 namespace-suffix `\"http.1\"`; leading-digit `\"1foo\"`; \
6266 kebab-leak `\"-foo\"`; snake-leak `\"_foo\"`; non-ASCII \
6267 `\"café\"` — every legitimate search tag is strict ASCII; \
6268 paste-from-binary-blob NUL / BEL / ESC / DEL byte) silently \
6269 passed `from_lisp` + `validate_etiquetas` + \
6270 `StandardLayout::verify` and landed in the rendered \
6271 `lareira-<nome>` Helm chart's `Chart.yaml keywords:` array as a \
6272 malformed search tag — Artifact Hub's keyword index + the future \
6273 caixa-registry's keyword index would either silently drop the \
6274 tag or fail to index it far from the source caixa.lisp; the gate \
6275 moves the diagnostic to the manifest layer with the offending \
6276 value named verbatim)"
6277 )]
6278 EtiquetaInvalid { etiqueta: String, reason: String },
6279 #[error(
6280 ":autores entry is empty (every maintainer must carry a non-empty \
6281 identifier; the empty entry has no operational meaning — it \
6282 identifies no one in the substrate's authorship index and renders \
6283 as `maintainers: [{{name: \"\", email: null}}]` in the Helm chart's \
6284 `Chart.yaml`, a no-op maintainer the substrate cannot route to; \
6285 omit the entry to express \"no maintainer on this position\")"
6286 )]
6287 AutorEmpty,
6288 #[error(
6289 ":autores entry {autor:?} appears more than once (the maintainer \
6290 set is a set, not a multiset; unlike `:etiquetas`, caixa-helm's \
6291 `maintainers:` rendering does *no* dedup — duplicate entries \
6292 stack verbatim in `Chart.yaml` as two identical \
6293 `Maintainer {{ name, email: None }}` records, divergent from every \
6294 peer typed-graph set gate (`:etiquetas`, `:membros :caixa`, \
6295 `:placement :clusters`, `:entrada :paths`, `:contratos`, \
6296 `:deps :nome`, `:upgrade-from :from`); drop the duplicate or \
6297 rename it to the actual author intended)"
6298 )]
6299 AutorDuplicate { autor: String },
6300 #[error(
6301 ":autores entry {autor:?} is not a valid chart-maintainer-name shape: \
6302 {reason} (the substrate consumes this string through the shared \
6303 `crate::render::is_chart_maintainer_name_shape` predicate — the same \
6304 single-line-UTF-8 floor every realistic chart maintainer name carries: \
6305 1..=128 bytes, no leading or trailing whitespace, no ASCII control \
6306 characters anywhere, Unicode bytes accepted. The canonical authoring \
6307 shapes are short single-line identifiers like `\"pleme-io\"`, \
6308 `\"Pleme Contributors\"`, `\"alice <alice@example.com>\"`, \
6309 `\"François Dupont\"`. Without this gate a malformed `:autores` entry \
6310 (paste-from-aligned-doc leading whitespace `\" pleme-io\"` / trailing \
6311 whitespace `\"pleme-io \"`; paste-from-multiline-doc newline \
6312 `\"alice\\nbob\"` — the author pasted a multi-line block of author \
6313 records into one entry instead of splitting into one entry per author; \
6314 paste-from-Windows-CRLF-doc carriage return `\"alice\\rbob\"`; \
6315 tab-from-aligned-doc `\"Pleme\\tContributors\"`; paste-from-binary-blob \
6316 NUL / BEL / ESC / DEL byte) silently passed `from_lisp` + \
6317 `validate_autores` + `StandardLayout::verify` and landed in the \
6318 rendered `lareira-<nome>` Helm chart's `Chart.yaml maintainers:` array \
6319 as a YAML-illegal multi-line scalar or a silently-trimmed whitespace \
6320 round-trip — every chart-aware UI (`helm list`, `helm search`, \
6321 Artifact Hub maintainer index) would render the maintainer name in a \
6322 single-line column far from the source caixa.lisp; the gate moves the \
6323 diagnostic to the manifest layer with the offending value named \
6324 verbatim)"
6325 )]
6326 AutorInvalid { autor: String, reason: String },
6327 #[error(
6328 ":repositorio is the empty string (every published caixa names its \
6329 git source via a non-empty `:repositorio` locator — the value \
6330 flows verbatim into the rendered `lareira-<nome>` Helm chart's \
6331 `Chart.yaml` `home:` field via `caixa-helm` and into the FluxCD \
6332 `GitRepository.spec.url` via `caixa-flux`'s \
6333 `ClusterBundleOpts::for_caixa`; both consumers' \
6334 `Option::unwrap_or_else` fallbacks only fire when the slot is \
6335 `None`, so an empty `Some(\"\")` silently lands as `home: \"\"` / \
6336 `url: \"\"` in the rendered artifacts and breaks at `helm \
6337 template` / FluxCD source-controller reconcile time far from the \
6338 source caixa.lisp; omit the slot entirely to defer to the \
6339 renderer's `https://github.com/pleme-io/<nome>` / \
6340 `caixa.nome`-derived fallback, or carry a canonical authoring \
6341 shape like `\"github:org/repo\"`, `\"https://host/path\"`, \
6342 `\"ssh://[user@]host/path\"`, `\"git@host:path\"`, or \
6343 `\"file:///path\"`)"
6344 )]
6345 RepositorioEmpty,
6346 #[error(
6347 ":repositorio {repositorio:?} is not a valid git repo URL: {reason} \
6348 (the substrate consumes this string through the shared \
6349 `crate::render::is_git_repo_url` predicate — the same parser the \
6350 peer `:deps :fonte (:tipo git :repo …)` axis routes its `:repo` \
6351 value through via `DepSource::validate`; the canonical authoring \
6352 shapes are `\"github:org/repo\"` shorthand, `\"https://host/path\"` \
6353 / `\"ssh://[user@]host/path\"` / `\"git://host/path\"` / \
6354 `\"file:///path\"` URL schemes, or the `\"git@host:path\"` \
6355 scp-style SSH form. Without this gate a malformed `:repositorio` \
6356 (whitespace from a paste-from-doc; control characters / CRLF \
6357 from a paste-from-multiline-doc; a leading `-` from a \
6358 CLI-argument-injection footgun; a missing `:` separator from a \
6359 bare `org/repo` shape git treats as a relative filesystem path) \
6360 silently landed in the rendered `Chart.yaml home:` and the \
6361 FluxCD `GitRepository.spec.url` and broke at `git clone` / \
6362 FluxCD reconcile time far from the source caixa.lisp; the gate \
6363 moves the diagnostic to the manifest layer with the offending \
6364 value named verbatim)"
6365 )]
6366 RepositorioInvalid { repositorio: String, reason: String },
6367 #[error(
6368 ":descricao is the empty string (every published caixa names \
6369 its purpose via a non-empty `:descricao` summary — the value \
6370 flows verbatim into the rendered `lareira-<nome>` Helm \
6371 chart's `Chart.yaml` `description:` field via `caixa-helm`'s \
6372 `build_chart_yaml` and into the chart `README.md` header via \
6373 `build_readme`; both consumers' `Option::unwrap_or_else` \
6374 `caixa.nome`-derived fallbacks only fire when the slot is \
6375 `None`, so an empty `Some(\"\")` silently lands as \
6376 `description: \"\"` / a blank `README.md` header in the \
6377 rendered artifacts and breaks at `helm lint` time \
6378 (`WARNING [chart.metadata.description]: description is \
6379 required` on `apiVersion: v2` charts) far from the source \
6380 caixa.lisp; omit the slot entirely to defer to the \
6381 renderer's `\"Generated chart for caixa Servico <nome>\"` / \
6382 `\"caixa Servico <nome>\"` fallbacks, or carry a non-empty \
6383 summary like `\"Canonical Rust→wasm32-wasip2 caixa \
6384 Servico.\"`)"
6385 )]
6386 DescricaoEmpty,
6387 #[error(
6388 ":descricao {descricao:?} is not a valid chart-description shape: \
6389 {reason} (the substrate consumes this string through the shared \
6390 `crate::render::is_chart_description_shape` predicate — the same \
6391 single-line-UTF-8 floor every realistic chart description carries: \
6392 1..=512 bytes, no leading or trailing whitespace, no ASCII control \
6393 characters anywhere, Unicode prose bytes accepted. The canonical \
6394 authoring shapes are short single-line summaries like `\"Canonical \
6395 Rust→wasm32-wasip2 caixa Servico.\"`, `\"Checkout flow.\"`, \
6396 `\"AWS provider caixa for tatara-lisp\"`. Without this gate a \
6397 malformed `:descricao` (paste-from-aligned-doc leading whitespace \
6398 `\" Checkout flow.\"` / trailing whitespace `\"Checkout flow. \"`; \
6399 paste-from-multiline-doc newline `\"Checkout\\nflow.\"`; \
6400 paste-from-Windows-CRLF-doc carriage return `\"Checkout\\rflow.\"`; \
6401 tab-from-aligned-doc `\"Checkout\\tflow.\"`; paste-from-binary-blob \
6402 NUL / BEL / ESC / DEL byte) silently passed `from_lisp` + \
6403 `validate_descricao` + `StandardLayout::verify` and landed in the \
6404 rendered `lareira-<nome>` Helm chart's `Chart.yaml description:` \
6405 field + `README.md` header paragraph as a YAML-illegal multi-line \
6406 scalar or a silently-trimmed whitespace round-trip — every \
6407 chart-aware UI (`helm list`, `helm search`, Artifact Hub) would \
6408 render the description in a single-line column far from the source \
6409 caixa.lisp; the gate moves the diagnostic to the manifest layer \
6410 with the offending value named verbatim)"
6411 )]
6412 DescricaoInvalid { descricao: String, reason: String },
6413 #[error(
6414 ":licenca is the empty string (every published caixa names \
6415 its license via a non-empty `:licenca` SPDX expression — the \
6416 value flows verbatim into the rendered `lareira-<nome>` Helm \
6417 chart's `README.md` `## License` section via `caixa-helm`'s \
6418 `build_readme` at `caixa-helm/src/lib.rs:361`; the consumer's \
6419 `Option::unwrap_or_else(|| \"MIT\".into())` `MIT` fallback \
6420 only fires when the slot is `None`, so an empty `Some(\"\")` \
6421 silently lands as a bare trailing period in the rendered \
6422 chart `README.md` `License` section far from the source \
6423 caixa.lisp; omit the slot entirely to defer to the \
6424 renderer's `MIT` fallback, or carry a canonical SPDX \
6425 expression like `\"MIT\"`, `\"Apache-2.0\"`, \
6426 `\"Apache-2.0 OR MIT\"`)"
6427 )]
6428 LicencaEmpty,
6429 #[error(
6430 ":licenca {licenca:?} is not a valid SPDX expression shape: {reason} \
6431 (the substrate consumes this string through the shared \
6432 `crate::render::is_spdx_expression_shape` predicate — the same \
6433 alphabet-floor parser every peer per-axis value-shape gate routes \
6434 its value through; the canonical authoring shapes are single \
6435 license identifiers like `\"MIT\"`, `\"Apache-2.0\"`, `\"BSD-3-Clause\"`, \
6436 compound expressions like `\"Apache-2.0 OR MIT\"`, \
6437 `\"MIT AND BSD-3-Clause\"`, `\"(MIT OR Apache-2.0) AND ISC\"`, \
6438 license-with-exception forms like `\"Apache-2.0 WITH LLVM-exception\"`, \
6439 `+`-suffix variants like `\"GPL-2.0+\"`, and user-defined references \
6440 like `\"LicenseRef-MyLicense\"` / \
6441 `\"DocumentRef-doc:LicenseRef-MyLicense\"`. Without this gate a \
6442 malformed `:licenca` (paste-from-doc whitespace `\"MIT \"` / \
6443 `\" MIT\"`; paste-from-multiline-doc CRLF `\"MIT\\n\"`; \
6444 tab-from-aligned-doc `\"MIT\\tOR Apache-2.0\"`; non-ASCII byte from \
6445 a smart-quote paste; underscore-instead-of-hyphen typo \
6446 `\"Apache_2.0\"`; comma-instead-of-`OR`-keyword colloquial idiom \
6447 `\"MIT, Apache-2.0\"`; slash-dual-license colloquial idiom \
6448 `\"MIT/Apache-2.0\"`; semicolon-list-separator confusion \
6449 `\"MIT; Apache-2.0\"`) silently landed in the rendered chart \
6450 `README.md` `## License` section + a future SPDX-aware \
6451 `Chart.yaml license:` emitter would refuse the value at \
6452 `helm lint` time far from the source caixa.lisp; the gate moves \
6453 the diagnostic to the manifest layer with the offending value \
6454 named verbatim)"
6455 )]
6456 LicencaInvalid { licenca: String, reason: String },
6457 #[error(
6458 ":edicao is the empty string (every published caixa names \
6459 its language edition via a non-empty `:edicao` value — the \
6460 edition determines the tatara-lisp macro surface + \
6461 compatibility flags the substrate applies when building \
6462 the caixa; the canonical `Caixa::template` scaffold every \
6463 `feira init` emits carries `:edicao \"2026\"` verbatim and \
6464 every renderer-side fixture (`caixa-helm`, `caixa-flux`, \
6465 `caixa-mesh`) carries `edicao: Some(\"2026\".into())` by \
6466 construction, so an empty `Some(\"\")` silently lands as a \
6467 bare `(:edicao \"\")` line in the rendered `caixa.lisp` and \
6468 a future renderer-side consumer that folds it through \
6469 `Option::unwrap_or_else` will skip the fallback and pass the \
6470 empty edition through to the substrate's build-time edition \
6471 selector far from the source caixa.lisp; omit the slot \
6472 entirely to defer to the substrate's default edition, or \
6473 carry a canonical edition like `\"2026\"`)"
6474 )]
6475 EdicaoEmpty,
6476 #[error(
6477 ":edicao {edicao:?} is not a valid edition: {reason} (every \
6478 documented tatara-lisp edition is a 4-digit ASCII decimal \
6479 year — `\"2026\"` is the only edition currently minted; \
6480 future-introduced siblings will follow the same shape, peer \
6481 with Cargo's `[package] edition` grammar which every value \
6482 Cargo has ever accepted matches: `\"2015\"`, `\"2018\"`, \
6483 `\"2021\"`, `\"2024\"`. Without this gate the canonical \
6484 paste-from-doc footguns silently passed: a trailing space \
6485 (`\"2026 \"`) from a paste-from-doc, a CRLF (`\"2026\\n\"`) \
6486 from a paste-from-multiline-doc, a fullwidth-keyboard \
6487 look-alike (`\"2026\"`), a free-form non-year value \
6488 (`\"x\"`, `\"latest\"`, `\"nightly\"`), a leading non-digit \
6489 version-tag prefix (`\"v2026\"`, `\"e2026\"`), a \
6490 decimal-shaped pseudo-version (`\"2026.1\"`), or a \
6491 wrong-length numeric value (`\"26\"`, `\"202\"`, \
6492 `\"20260\"`) all landed as `(:edicao \"<garbage>\")` in the \
6493 rendered caixa.lisp and broke at the substrate's \
6494 build-time edition selector far from the source caixa.lisp; \
6495 omit the slot entirely to defer to the substrate's default \
6496 edition, or carry a canonical 4-digit ASCII decimal year \
6497 like `\"2026\"`)"
6498 )]
6499 EdicaoInvalid { edicao: String, reason: String },
6500}
6501
6502#[cfg(test)]
6503mod tests {
6504 use super::*;
6505
6506 #[test]
6507 fn template_round_trips() {
6508 let src = Caixa::template("demo");
6509 let c = Caixa::from_lisp(&src).expect("template must parse");
6510 assert_eq!(c.nome, "demo");
6511 assert_eq!(c.versao, "0.1.0");
6512 assert_eq!(c.kind, CaixaKind::Biblioteca);
6513 assert_eq!(c.bibliotecas, vec!["lib/demo.lisp".to_string()]);
6514 assert!(c.deps.is_empty());
6515 assert!(c.deps_dev.is_empty());
6516 }
6517
6518 #[test]
6519 fn caixa_universal_axis_scalar_accessor_pair_is_const_fn() {
6520 // Fail-before-pass-after pin on [`Caixa::nome`] +
6521 // [`Caixa::versao`]'s `const`-eval-surface posture. Each
6522 // accessor projects the top-level manifest's per-`:nome` /
6523 // per-`:versao` [`String`] storage through the `pub const fn`
6524 // [`String::as_str`] (const-stable since Rust 1.87, well within
6525 // the workspace MSRV) — any future accidental downgrade to
6526 // non-`const` fails the corresponding `<name>_via_const_fn`
6527 // wrapper at caixa-core build time with E0015 (`cannot call
6528 // non-const method`), strictly stronger than a runtime
6529 // `assert!`. Sibling of the peer per-M2/M3-slot `String → &str`
6530 // scalar-accessor family pins on the sibling `const`-eval-
6531 // surface passes ([`crate::CaixaVersion::as_str`] at the
6532 // typed-newtype wrapper, [`crate::aplicacao::Membro::nome`] /
6533 // [`crate::aplicacao::Membro::versao_requirement`] at the M3
6534 // membership axis, [`crate::aplicacao::Entrada::hostname`] /
6535 // [`crate::aplicacao::Entrada::destination`] at the M3 ingress
6536 // axis, [`crate::supervisor::ChildSpec::nome`] /
6537 // [`crate::supervisor::ChildSpec::versao_requirement`] at the
6538 // M2 supervisor-tree axis,
6539 // [`crate::upgrade::UpgradeFromEntry::prior_versao`] at the M2
6540 // upgrade axis, [`crate::dep::Dep::nome`] /
6541 // [`crate::dep::Dep::versao_requirement`] at the dep-graph
6542 // axis, and the per-`:contratos`
6543 // [`crate::aplicacao::WitContract::source`] /
6544 // [`crate::aplicacao::WitContract::destination`] /
6545 // [`crate::aplicacao::WitContract::world_ref`] trio the
6546 // sibling pin at 279823b already anchors).
6547 const fn nome_via_const_fn(c: &Caixa) -> &str {
6548 c.nome()
6549 }
6550 const fn versao_via_const_fn(c: &Caixa) -> &str {
6551 c.versao()
6552 }
6553 let src = Caixa::template("demo");
6554 let c = Caixa::from_lisp(&src).expect("template must parse");
6555 assert_eq!(nome_via_const_fn(&c), c.nome());
6556 assert_eq!(versao_via_const_fn(&c), c.versao());
6557 assert_eq!(c.nome(), "demo");
6558 assert_eq!(c.versao(), "0.1.0");
6559 }
6560
6561 #[test]
6562 fn caixa_option_string_scalar_accessor_family_is_const_fn() {
6563 // Fail-before-pass-after pin on the five per-`Caixa`
6564 // `Option<String> → Option<&str>` scalar accessors
6565 // ([`Caixa::licenca`] / [`Caixa::repositorio`] /
6566 // [`Caixa::descricao`] / [`Caixa::edicao`] on the top-level
6567 // manifest's optional universal-axis surface, plus
6568 // [`Caixa::restart_window`] on the M2 supervisor-tree
6569 // per-`SupervisorSpec` peer raw-window-string projection axis).
6570 // Each accessor destructures the typed slot's `Option<String>`
6571 // storage through the `match &self.<field> { Some(s) =>
6572 // Some(s.as_str()), None => None }` shape — routing through
6573 // [`String::as_str`] (const-stable since Rust 1.87, well within
6574 // the workspace MSRV) rather than the non-const
6575 // [`Option::as_deref`] the pre-lift bodies carried — and any
6576 // future accidental downgrade to non-`const` fails the
6577 // corresponding `<name>_via_const_fn` wrapper at caixa-core
6578 // build time with E0015 (`cannot call non-const method`),
6579 // strictly stronger than a runtime `assert!` and strictly
6580 // stronger than a module-scope `const _: () = assert!(…)` pin
6581 // (which cannot be formed on a `&Caixa` fixture because the
6582 // type's `String` / `Option<String>` carriers rule out
6583 // `const`-context value construction; the `const fn` wrapper
6584 // is the load-bearing shape that side-steps the destructor-in-
6585 // const restriction on the value axis while still pinning the
6586 // `const`-fn posture on the callee — mirror of the sibling
6587 // [`caixa_universal_axis_scalar_accessor_pair_is_const_fn`]
6588 // pin's discipline verbatim on the peer non-`Option`
6589 // `String → &str` axis at the same struct).
6590 //
6591 // Peer of the sibling per-M2/M3-slot `Option<String> →
6592 // Option<&str>` accessor family pin
6593 // [`m3_option_string_scalar_accessor_family_is_const_fn`] on
6594 // the M3 mesh-slot atom axes ([`WitContract::endpoint`] /
6595 // [`WitContract::subject`] / [`WitContract::slot`] on the
6596 // per-`:contratos` payload-carrier trio,
6597 // [`Placement::shard_key`] / [`Placement::affinity`] on the
6598 // per-`:placement` optional-scalar pair).
6599 const fn licenca_via_const_fn(c: &Caixa) -> Option<&str> {
6600 c.licenca()
6601 }
6602 const fn repositorio_via_const_fn(c: &Caixa) -> Option<&str> {
6603 c.repositorio()
6604 }
6605 const fn descricao_via_const_fn(c: &Caixa) -> Option<&str> {
6606 c.descricao()
6607 }
6608 const fn edicao_via_const_fn(c: &Caixa) -> Option<&str> {
6609 c.edicao()
6610 }
6611 const fn restart_window_via_const_fn(c: &Caixa) -> Option<&str> {
6612 c.restart_window()
6613 }
6614 // Sweep both the `Some`-carrying arm (author-declared slot,
6615 // the byte-string projection payload) and the `None`-carrying
6616 // arm (author-omitted slot, the default-path projection) on
6617 // every accessor so the `const fn` wrapper family pins each
6618 // axis's canonical two-arm partition through the same const
6619 // dispatch as the runtime path.
6620 let mut c1 = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6621 c1.licenca = Some("MIT".to_string());
6622 c1.repositorio = Some("https://github.com/pleme-io/demo".to_string());
6623 c1.descricao = Some("demo caixa".to_string());
6624 c1.edicao = Some("2024".to_string());
6625 c1.restart_window = Some("60s".to_string());
6626 assert_eq!(licenca_via_const_fn(&c1), c1.licenca());
6627 assert_eq!(repositorio_via_const_fn(&c1), c1.repositorio());
6628 assert_eq!(descricao_via_const_fn(&c1), c1.descricao());
6629 assert_eq!(edicao_via_const_fn(&c1), c1.edicao());
6630 assert_eq!(restart_window_via_const_fn(&c1), c1.restart_window());
6631 assert_eq!(c1.licenca(), Some("MIT"));
6632 assert_eq!(c1.repositorio(), Some("https://github.com/pleme-io/demo"));
6633 assert_eq!(c1.descricao(), Some("demo caixa"));
6634 assert_eq!(c1.edicao(), Some("2024"));
6635 assert_eq!(c1.restart_window(), Some("60s"));
6636 let mut c2 = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6637 c2.licenca = None;
6638 c2.repositorio = None;
6639 c2.descricao = None;
6640 c2.edicao = None;
6641 c2.restart_window = None;
6642 assert_eq!(licenca_via_const_fn(&c2), None);
6643 assert_eq!(repositorio_via_const_fn(&c2), None);
6644 assert_eq!(descricao_via_const_fn(&c2), None);
6645 assert_eq!(edicao_via_const_fn(&c2), None);
6646 assert_eq!(restart_window_via_const_fn(&c2), None);
6647 }
6648
6649 #[test]
6650 fn caixa_outer_copy_return_accessor_pair_is_const_fn() {
6651 // Fail-before-pass-after pin on the two outer-[`Caixa`]
6652 // `Copy`-return accessors — [`Caixa::kind`] on the required
6653 // [`CaixaKind`] enum-discriminant axis and [`Caixa::estrategia`]
6654 // on the M2 supervisor-tree flat-spread `Option<RestartStrategy>`
6655 // axis. Both accessors project a `Copy`-carrier field
6656 // (`CaixaKind: Copy` at caixa-core/src/kind.rs:17,
6657 // `RestartStrategy: Copy` at caixa-core/src/supervisor.rs:33 →
6658 // `Option<RestartStrategy>: Copy`) by value through a bare
6659 // `self.<field>` field-access — no dispatch, no destructor, no
6660 // heap. Any future accidental downgrade to non-`const` fails
6661 // the corresponding `<name>_via_const_fn` wrapper at caixa-core
6662 // build time with E0015 (`cannot call non-const method`),
6663 // strictly stronger than a runtime `assert!` and strictly
6664 // stronger than a module-scope `const _: () = assert!(…)` pin
6665 // (which cannot be formed on a `&Caixa` fixture because the
6666 // type's `String` / `Vec` / `Option<Composite>` carriers rule
6667 // out `const`-context value construction; the `const fn`
6668 // wrapper is the load-bearing shape that side-steps the
6669 // destructor-in-const restriction on the value axis while still
6670 // pinning the `const`-fn posture on the callee — mirror of the
6671 // sibling [`caixa_universal_axis_scalar_accessor_pair_is_const_fn`]
6672 // + [`caixa_option_string_scalar_accessor_family_is_const_fn`]
6673 // pins' discipline verbatim on the peer outer-`Caixa`
6674 // `String → &str` + `Option<String> → Option<&str>` axes at the
6675 // same struct).
6676 //
6677 // Peer of the sibling per-M2/M3-slot `Copy`-return accessor pin
6678 // family on the inner-altitude nested-spec typed-slot
6679 // discriminator axes: [`crate::supervisor::SupervisorSpec::estrategia`]
6680 // + [`crate::supervisor::ChildSpec::restart`] on the M2
6681 // supervisor-tree axis (pinned at 152c868), and
6682 // [`crate::aplicacao::Placement::estrategia`] +
6683 // [`crate::aplicacao::Entrada::port`] on the M3 mesh-slot axis
6684 // (pinned at bafa004) — the outer-`Caixa` altitude is the last
6685 // unlifted altitude for the `Copy`-return-accessor family.
6686 const fn kind_via_const_fn(c: &Caixa) -> CaixaKind {
6687 c.kind()
6688 }
6689 const fn estrategia_via_const_fn(c: &Caixa) -> Option<crate::supervisor::RestartStrategy> {
6690 c.estrategia()
6691 }
6692 // Sweep every arm of both discriminant partitions the accessors
6693 // fan on — every [`CaixaKind`] variant the six-arm required
6694 // discriminant carries (Biblioteca / Binario / Servico /
6695 // Supervisor / Aplicacao / Acao) and both arms of the
6696 // [`Option<RestartStrategy>`] flat-spread supervisor-tree slot
6697 // (`Some(<strategy>)` on an author-declared supervisor and
6698 // `None` on the author-omitted default arm every non-Supervisor
6699 // caixa carries by `#[serde(default)]`) — so the `const fn`
6700 // wrapper family pins the closed-set partition through the
6701 // same const dispatch as the runtime path.
6702 let mut c1 = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6703 c1.kind = CaixaKind::Servico;
6704 c1.estrategia = Some(crate::supervisor::RestartStrategy::OneForAll);
6705 assert_eq!(kind_via_const_fn(&c1), c1.kind());
6706 assert_eq!(estrategia_via_const_fn(&c1), c1.estrategia());
6707 assert_eq!(c1.kind(), CaixaKind::Servico);
6708 assert_eq!(
6709 c1.estrategia(),
6710 Some(crate::supervisor::RestartStrategy::OneForAll)
6711 );
6712 let mut c2 = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6713 c2.kind = CaixaKind::Aplicacao;
6714 c2.estrategia = None;
6715 assert_eq!(kind_via_const_fn(&c2), CaixaKind::Aplicacao);
6716 assert_eq!(estrategia_via_const_fn(&c2), None);
6717 // Anchor the remaining discriminant arms so any future
6718 // reordering of [`CaixaKind`]'s six-variant enum surfaces
6719 // through the wrapper dispatch, not just through the direct
6720 // method call.
6721 for kind in [
6722 CaixaKind::Biblioteca,
6723 CaixaKind::Binario,
6724 CaixaKind::Servico,
6725 CaixaKind::Supervisor,
6726 CaixaKind::Aplicacao,
6727 CaixaKind::Acao,
6728 ] {
6729 let mut c = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6730 c.kind = kind;
6731 assert_eq!(kind_via_const_fn(&c), kind);
6732 }
6733 }
6734
6735 #[test]
6736 fn caixa_outer_string_slice_return_accessor_family_is_const_fn() {
6737 // Fail-before-pass-after pin on the five outer-[`Caixa`]
6738 // `Vec<String> → &[String]` slice-return accessors on the
6739 // universal-axis surface — [`Caixa::autores`] / [`Caixa::etiquetas`]
6740 // / [`Caixa::bibliotecas`] / [`Caixa::exe`] / [`Caixa::servicos`].
6741 // Each body is a bare `self.<field>.as_slice()` dispatch through
6742 // [`Vec::as_slice`] (const-stable since Rust 1.87, well within
6743 // the workspace MSRV). Any future accidental downgrade to
6744 // non-`const` fails the corresponding `<name>_via_const_fn`
6745 // wrapper at caixa-core build time with E0015 (`cannot call
6746 // non-const method`) — mirror of the sibling
6747 // [`caixa_outer_copy_return_accessor_pair_is_const_fn`] pin's
6748 // discipline on the peer outer-`Caixa` `Copy`-return accessor
6749 // axis, and peer of the sibling composite-carrier slice-return
6750 // pin below on the peer outer-`Caixa` composite-slice axis.
6751 const fn autores_via_const_fn(c: &Caixa) -> &[String] {
6752 c.autores()
6753 }
6754 const fn etiquetas_via_const_fn(c: &Caixa) -> &[String] {
6755 c.etiquetas()
6756 }
6757 const fn bibliotecas_via_const_fn(c: &Caixa) -> &[String] {
6758 c.bibliotecas()
6759 }
6760 const fn exe_via_const_fn(c: &Caixa) -> &[String] {
6761 c.exe()
6762 }
6763 const fn servicos_via_const_fn(c: &Caixa) -> &[String] {
6764 c.servicos()
6765 }
6766 // Sweep the empty arm (`autores` / `etiquetas` / `exe` /
6767 // `servicos` — the template's `Vec::new()` default) and the
6768 // populated arm (mutated below) on every accessor so the
6769 // `const fn` wrapper family pins each axis's two-arm partition
6770 // through the same const dispatch as the runtime path.
6771 // [`Caixa::template`] seeds `lib/demo.lisp` into `:bibliotecas`,
6772 // so that arm's "empty" fixture is the populated arm the
6773 // mutation sweep covers.
6774 let c_empty = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6775 assert!(autores_via_const_fn(&c_empty).is_empty());
6776 assert!(etiquetas_via_const_fn(&c_empty).is_empty());
6777 assert!(exe_via_const_fn(&c_empty).is_empty());
6778 assert!(servicos_via_const_fn(&c_empty).is_empty());
6779 let mut c_full = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6780 c_full.autores = vec!["ada".to_string(), "erlang".to_string()];
6781 c_full.etiquetas = vec!["compounding".to_string()];
6782 c_full.bibliotecas = vec!["lib/one.lisp".to_string(), "lib/two.lisp".to_string()];
6783 c_full.exe = vec!["exe/cli.lisp".to_string()];
6784 c_full.servicos = vec!["servicos/one.computeunit.yaml".to_string()];
6785 assert_eq!(autores_via_const_fn(&c_full), c_full.autores());
6786 assert_eq!(autores_via_const_fn(&c_full), &["ada", "erlang"]);
6787 assert_eq!(etiquetas_via_const_fn(&c_full), c_full.etiquetas());
6788 assert_eq!(etiquetas_via_const_fn(&c_full), &["compounding"]);
6789 assert_eq!(bibliotecas_via_const_fn(&c_full), c_full.bibliotecas());
6790 assert_eq!(
6791 bibliotecas_via_const_fn(&c_full),
6792 &["lib/one.lisp", "lib/two.lisp"]
6793 );
6794 assert_eq!(exe_via_const_fn(&c_full), c_full.exe());
6795 assert_eq!(exe_via_const_fn(&c_full), &["exe/cli.lisp"]);
6796 assert_eq!(servicos_via_const_fn(&c_full), c_full.servicos());
6797 assert_eq!(
6798 servicos_via_const_fn(&c_full),
6799 &["servicos/one.computeunit.yaml"]
6800 );
6801 }
6802
6803 #[test]
6804 fn caixa_outer_composite_slice_return_accessor_family_is_const_fn() {
6805 // Fail-before-pass-after pin on the six outer-[`Caixa`] composite-
6806 // carrier `Vec<T> → &[T]` slice-return accessors — [`Caixa::deps`]
6807 // / [`Caixa::deps_dev`] on the dep-graph axis,
6808 // [`Caixa::upgrade_from`] on the M2 appup axis, [`Caixa::children`]
6809 // on the M2 supervisor-tree axis, and [`Caixa::membros`] /
6810 // [`Caixa::contratos`] on the M3 mesh-slot axis. Each body is a
6811 // bare `self.<field>.as_slice()` dispatch through
6812 // [`Vec::as_slice`] (const-stable since Rust 1.87, well within
6813 // the workspace MSRV) — peer of the sibling `String`-payload
6814 // slice-return pin above on the peer outer-`Caixa` universal-
6815 // axis surface, and peer of the sibling inner-composite-
6816 // altitude reference-return pin family
6817 // [`crate::aplicacao::tests::m3_aplicacao_spec_reference_return_accessor_family_is_const_fn`]
6818 // + [`crate::supervisor::tests::supervisor_children_slice_return_accessor_is_const_fn`]
6819 // + [`crate::upgrade::tests::upgrade_from_entry_instructions_slice_return_accessor_is_const_fn`]
6820 // (all pinned at 0b23e0f).
6821 const fn deps_via_const_fn(c: &Caixa) -> &[Dep] {
6822 c.deps()
6823 }
6824 const fn deps_dev_via_const_fn(c: &Caixa) -> &[Dep] {
6825 c.deps_dev()
6826 }
6827 const fn upgrade_from_via_const_fn(c: &Caixa) -> &[UpgradeFromEntry] {
6828 c.upgrade_from()
6829 }
6830 const fn children_via_const_fn(c: &Caixa) -> &[crate::supervisor::ChildSpec] {
6831 c.children()
6832 }
6833 const fn membros_via_const_fn(c: &Caixa) -> &[crate::aplicacao::Membro] {
6834 c.membros()
6835 }
6836 const fn contratos_via_const_fn(c: &Caixa) -> &[crate::aplicacao::WitContract] {
6837 c.contratos()
6838 }
6839 // Empty-arm sweep on all six composite-carrier axes — every
6840 // `Caixa::template` starts with `Vec::new()` on each.
6841 let c_empty = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6842 assert!(deps_via_const_fn(&c_empty).is_empty());
6843 assert!(deps_dev_via_const_fn(&c_empty).is_empty());
6844 assert!(upgrade_from_via_const_fn(&c_empty).is_empty());
6845 assert!(children_via_const_fn(&c_empty).is_empty());
6846 assert!(membros_via_const_fn(&c_empty).is_empty());
6847 assert!(contratos_via_const_fn(&c_empty).is_empty());
6848 // Populate `:membros` / `:contratos` directly via struct literals
6849 // — the parser-side validation path fans on `:kind`-gated cross-
6850 // slot invariants irrelevant to the accessor dispatch under test.
6851 let mut c_full = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6852 c_full.membros = vec![
6853 crate::aplicacao::Membro {
6854 caixa: "demo-a".to_string(),
6855 versao: "^0.1.0".to_string(),
6856 },
6857 crate::aplicacao::Membro {
6858 caixa: "demo-b".to_string(),
6859 versao: "^0.2.0".to_string(),
6860 },
6861 ];
6862 c_full.contratos = vec![crate::aplicacao::WitContract {
6863 de: "demo-a".to_string(),
6864 para: "demo-b".to_string(),
6865 wit: "wasi:http/proxy".to_string(),
6866 endpoint: Some("/edge".to_string()),
6867 subject: None,
6868 slot: None,
6869 }];
6870 assert_eq!(membros_via_const_fn(&c_full), c_full.membros());
6871 assert_eq!(contratos_via_const_fn(&c_full), c_full.contratos());
6872 assert_eq!(membros_via_const_fn(&c_full).len(), 2);
6873 assert_eq!(contratos_via_const_fn(&c_full).len(), 1);
6874 // Alias-borrow check on the four remaining composite-carrier
6875 // slice-return arms — the wrapper's return borrow must alias the
6876 // caller's borrow so any future accessor re-routing that skips
6877 // the storage field surfaces through the assertion.
6878 assert!(std::ptr::eq(deps_via_const_fn(&c_full), c_full.deps()));
6879 assert!(std::ptr::eq(
6880 deps_dev_via_const_fn(&c_full),
6881 c_full.deps_dev()
6882 ));
6883 assert!(std::ptr::eq(
6884 upgrade_from_via_const_fn(&c_full),
6885 c_full.upgrade_from()
6886 ));
6887 assert!(std::ptr::eq(
6888 children_via_const_fn(&c_full),
6889 c_full.children()
6890 ));
6891 }
6892
6893 #[test]
6894 fn caixa_outer_option_composite_reference_return_accessor_family_is_const_fn() {
6895 // Fail-before-pass-after pin on the six outer-[`Caixa`]
6896 // `Option<Composite> → Option<&Composite>` reference-return
6897 // accessors — [`Caixa::limits`] / [`Caixa::behavior`] on the M2
6898 // Servico-runtime typed-slot axis, [`Caixa::politicas`] /
6899 // [`Caixa::placement`] / [`Caixa::entrada`] on the M3 mesh-slot
6900 // axis, and [`Caixa::ci`] on the Acao-kind typed-CI-run axis.
6901 // Each body is a bare `self.<field>.as_ref()` dispatch through
6902 // [`Option::as_ref`] (const-stable since Rust 1.83, well within
6903 // the workspace MSRV of 1.89). Any future accidental downgrade
6904 // to non-`const` fails the corresponding `<name>_via_const_fn`
6905 // wrapper at caixa-core build time with E0015 (`cannot call
6906 // non-const method`), strictly stronger than a runtime `assert!`
6907 // and strictly stronger than a module-scope `const _: () =
6908 // assert!(…)` pin (which cannot be formed on a `&Caixa` fixture
6909 // because the type's `String` / `Vec` / `Option<Composite>`
6910 // carriers rule out `const`-context value construction; the
6911 // `const fn` wrapper is the load-bearing shape that side-steps
6912 // the destructor-in-const restriction on the value axis while
6913 // still pinning the `const`-fn posture on the callee — mirror
6914 // of the sibling
6915 // [`caixa_outer_copy_return_accessor_pair_is_const_fn`] +
6916 // [`caixa_outer_string_slice_return_accessor_family_is_const_fn`] +
6917 // [`caixa_outer_composite_slice_return_accessor_family_is_const_fn`]
6918 // pins' discipline verbatim on the peer outer-`Caixa` axes at
6919 // the same struct).
6920 //
6921 // Closes the outer-`Caixa` `Option<&Composite>` composite-
6922 // reference-return sub-family — the last unlifted altitude on
6923 // the outer-`Caixa` accessor-family const-eval surface after
6924 // the sibling `Copy`-return / universal-axis-`&str` /
6925 // `Option<&str>` / `&[String]` / composite-`&[T]` pins already
6926 // closed the sibling arms at 866d1d5 / 29c5d7e / 0650f64 /
6927 // 231a968 (the last of these pins the `Vec<T> → &[T]`
6928 // composite-slice arm the six accessors here close as their
6929 // `Option<Composite> → Option<&Composite>` peer). Peer of the
6930 // sibling inner-altitude nested-spec composite-reference-return
6931 // pin family — [`crate::AplicacaoSpec::politicas`] /
6932 // [`crate::AplicacaoSpec::placement`] /
6933 // [`crate::AplicacaoSpec::entrada`] on the inner
6934 // [`crate::AplicacaoSpec`] altitude (already `pub const fn`
6935 // per 0b23e0f), and the outer-`Caixa` altitude here now carries
6936 // the same shape so both altitudes of the reference-return
6937 // discipline (per-`Caixa` outer-slot presence + per-
6938 // `AplicacaoSpec` inner-slot presence) route through one typed
6939 // const dispatch on the substrate primitive.
6940 const fn limits_via_const_fn(c: &Caixa) -> Option<&LimitsSpec> {
6941 c.limits()
6942 }
6943 const fn behavior_via_const_fn(c: &Caixa) -> Option<&crate::BehaviorSpec> {
6944 c.behavior()
6945 }
6946 const fn politicas_via_const_fn(c: &Caixa) -> Option<&crate::aplicacao::MeshPolicy> {
6947 c.politicas()
6948 }
6949 const fn placement_via_const_fn(c: &Caixa) -> Option<&crate::aplicacao::Placement> {
6950 c.placement()
6951 }
6952 const fn entrada_via_const_fn(c: &Caixa) -> Option<&crate::aplicacao::Entrada> {
6953 c.entrada()
6954 }
6955 const fn ci_via_const_fn(c: &Caixa) -> Option<&canteiro_types::CiRun> {
6956 c.ci()
6957 }
6958 // Both-arm sweep on every accessor: the `None` author-omitted
6959 // arm (template default — no M2/M3/CI slot declared) and the
6960 // `Some(<composite>)` authored arm (mutated below via struct-
6961 // literal seeds, side-stepping the parser-side `:kind`-gated
6962 // cross-slot invariants irrelevant to the accessor dispatch
6963 // under test). Both arms route through the `const fn` wrapper
6964 // family so the two-arm `Option` partition is pinned through
6965 // the same const dispatch as the runtime path.
6966 let c_empty = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6967 assert!(limits_via_const_fn(&c_empty).is_none());
6968 assert!(behavior_via_const_fn(&c_empty).is_none());
6969 assert!(politicas_via_const_fn(&c_empty).is_none());
6970 assert!(placement_via_const_fn(&c_empty).is_none());
6971 assert!(entrada_via_const_fn(&c_empty).is_none());
6972 assert!(ci_via_const_fn(&c_empty).is_none());
6973 let mut c_full = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
6974 c_full.limits = Some(LimitsSpec::default());
6975 c_full.behavior = Some(crate::BehaviorSpec::default());
6976 c_full.politicas = Some(crate::aplicacao::MeshPolicy::default());
6977 c_full.placement = Some(crate::aplicacao::Placement::default());
6978 c_full.entrada = Some(crate::aplicacao::Entrada {
6979 host: "demo.quero.cloud".to_string(),
6980 para: "demo".to_string(),
6981 paths: Vec::new(),
6982 port: crate::aplicacao::DEFAULT_SERVICO_PORT,
6983 });
6984 c_full.ci = Some(canteiro_types::CiRun {
6985 workspace: "pleme-io".into(),
6986 repo: "caixa".into(),
6987 nodes: vec![],
6988 });
6989 assert!(limits_via_const_fn(&c_full).is_some());
6990 assert!(behavior_via_const_fn(&c_full).is_some());
6991 assert!(politicas_via_const_fn(&c_full).is_some());
6992 assert!(placement_via_const_fn(&c_full).is_some());
6993 assert!(entrada_via_const_fn(&c_full).is_some());
6994 assert!(ci_via_const_fn(&c_full).is_some());
6995 // Alias-borrow check on every arm: the wrapper's inner-`Option`
6996 // reference must alias the caller's borrow so any future accessor
6997 // re-routing that skips the storage field surfaces through the
6998 // assertion.
6999 assert!(std::ptr::eq(
7000 limits_via_const_fn(&c_full).unwrap(),
7001 c_full.limits().unwrap()
7002 ));
7003 assert!(std::ptr::eq(
7004 behavior_via_const_fn(&c_full).unwrap(),
7005 c_full.behavior().unwrap()
7006 ));
7007 assert!(std::ptr::eq(
7008 politicas_via_const_fn(&c_full).unwrap(),
7009 c_full.politicas().unwrap()
7010 ));
7011 assert!(std::ptr::eq(
7012 placement_via_const_fn(&c_full).unwrap(),
7013 c_full.placement().unwrap()
7014 ));
7015 assert!(std::ptr::eq(
7016 entrada_via_const_fn(&c_full).unwrap(),
7017 c_full.entrada().unwrap()
7018 ));
7019 assert!(std::ptr::eq(
7020 ci_via_const_fn(&c_full).unwrap(),
7021 c_full.ci().unwrap()
7022 ));
7023 }
7024
7025 #[test]
7026 fn register_populates_registry() {
7027 Caixa::register().expect("first register call in this test process must succeed");
7028 let kws = tatara_lisp::domain::registered_keywords();
7029 assert!(kws.contains(&"defcaixa"));
7030 }
7031
7032 #[test]
7033 fn to_lisp_round_trips() {
7034 let src = Caixa::template("demo");
7035 let c1 = Caixa::from_lisp(&src).unwrap();
7036 let emitted = c1.to_lisp();
7037 let c2 = Caixa::from_lisp(&emitted).expect("emitted lisp parses back");
7038 assert_eq!(c1, c2);
7039 }
7040
7041 // ── DialetoEstrangeiro carries a single typed axis ────────────────────
7042 //
7043 // The compounding pin: the variant stores only the typed
7044 // [`crate::dialeto::CaixaDialeto`], and every user-facing byte-string
7045 // (canonical keyword, description, consumer) routes through the enum's
7046 // own accessors at Display time. Prior to that closure the variant
7047 // carried each accessor's return value as a stored `&'static str`
7048 // snapshot alongside `dialeto`; a caller could construct the variant
7049 // with a snapshot that drifted from what `dialeto`'s accessors would
7050 // return, and every downstream user-facing projection would silently
7051 // disagree with the classification. Storing only the axis makes the
7052 // drift structurally impossible.
7053
7054 #[test]
7055 fn dialeto_estrangeiro_variant_carries_only_the_typed_dialeto_axis() {
7056 // Single-field construction is the whole compounding shape — a
7057 // future re-introduction of a snapshot field (a `palavra_canonica:
7058 // &'static str`, a stored `descricao:`, a stored `consumidor:`)
7059 // would re-open the drift surface and this construction would fail
7060 // to compile with "missing field" until every snapshot was seeded
7061 // at the call site again. The compile-time guarantee is the
7062 // invariant; the assertion below only witnesses that the
7063 // construction is well-formed after the closure.
7064 let err = LeituraError::DialetoEstrangeiro {
7065 dialeto: crate::dialeto::CaixaDialeto::Molde,
7066 };
7067 assert!(matches!(
7068 err,
7069 LeituraError::DialetoEstrangeiro {
7070 dialeto: crate::dialeto::CaixaDialeto::Molde,
7071 }
7072 ));
7073 }
7074
7075 #[test]
7076 fn dialeto_estrangeiro_display_routes_through_typed_dialeto_accessors() {
7077 // For every foreign-dialect classification the variant surfaces —
7078 // [`crate::dialeto::CaixaDialeto::Molde`] and
7079 // [`crate::dialeto::CaixaDialeto::MoldePosicional`], the two
7080 // variants [`Caixa::from_lisp`] raises this error for — the
7081 // rendered [`std::fmt::Display`] byte-string must interpolate each
7082 // typed accessor's return verbatim. A future re-introduction of a
7083 // stored `&'static str` snapshot alongside `dialeto` that Display
7084 // read instead of the accessor would fail this pin as soon as the
7085 // two disagreed; a future accessor rebrand (a per-dialect
7086 // consumer rename, a canonical-keyword shift once the substrate
7087 // migration named in [`crate::dialeto`] completes) reaches every
7088 // consumer through one typed dispatch and this pin verifies the
7089 // display path is one of them.
7090 for d in [
7091 crate::dialeto::CaixaDialeto::Molde,
7092 crate::dialeto::CaixaDialeto::MoldePosicional,
7093 ] {
7094 let rendered = LeituraError::DialetoEstrangeiro { dialeto: d }.to_string();
7095 assert!(
7096 rendered.contains(d.palavra_canonica()),
7097 "Display must interpolate `dialeto.palavra_canonica()` \
7098 verbatim — a stored snapshot would silently drift from \
7099 the typed accessor. dialect: {d}, rendered: {rendered:?}"
7100 );
7101 assert!(
7102 rendered.contains(d.descricao()),
7103 "Display must interpolate `dialeto.descricao()` verbatim. \
7104 dialect: {d}, rendered: {rendered:?}"
7105 );
7106 assert!(
7107 rendered.contains(d.consumidor()),
7108 "Display must interpolate `dialeto.consumidor()` verbatim. \
7109 dialect: {d}, rendered: {rendered:?}"
7110 );
7111 }
7112 }
7113
7114 #[test]
7115 fn from_lisp_rejects_molde_dialect_via_typed_variant() {
7116 // The end-to-end pin the compounding closure defends: a
7117 // Molde-dialect source lands as [`LeituraError::DialetoEstrangeiro`]
7118 // carrying [`crate::dialeto::CaixaDialeto::Molde`], and the
7119 // rendered Display byte-string names the Molde accessors'
7120 // returns verbatim. Any future path that constructed the variant
7121 // with a mismatched snapshot (a stored `palavra_canonica:
7122 // "defcaixa"` on a `Molde` classification) would land Display
7123 // pointing at `defcaixa` while the typed axis said `Molde` — the
7124 // exact drift the closure removes.
7125 let src = r#"
7126 (defcaixa
7127 :name "x"
7128 :kind :Biblioteca
7129 :ecosystem :rust-single-crate
7130 :package {:name "x" :version "0.1.0"})
7131 "#;
7132 let err = Caixa::from_lisp(src).expect_err("Molde dialect must not parse as Pacote");
7133 match err {
7134 LeituraError::DialetoEstrangeiro { dialeto } => {
7135 assert_eq!(dialeto, crate::dialeto::CaixaDialeto::Molde);
7136 let rendered = LeituraError::DialetoEstrangeiro { dialeto }.to_string();
7137 assert!(rendered.contains(dialeto.palavra_canonica()));
7138 assert!(rendered.contains(dialeto.consumidor()));
7139 assert!(rendered.contains(dialeto.descricao()));
7140 }
7141 other => panic!("expected DialetoEstrangeiro, got {other:?}"),
7142 }
7143 }
7144
7145 #[test]
7146 fn from_lisp_rejects_molde_posicional_dialect_via_typed_variant() {
7147 // Coverage pin for the [`crate::dialeto::CaixaDialeto::MoldePosicional`]
7148 // arm of the [`Caixa::from_lisp`] foreign-dialect gate — the
7149 // positional-arity `defmolde` form written under a `(defcaixa …)`
7150 // head (`(defcaixa todoku-go :kind :Biblioteca :ecosystem :go
7151 // …)`). Pre-lift this arm rode the same `foreign =>` wildcard
7152 // the [`crate::dialeto::CaixaDialeto::Molde`] sibling arm rode,
7153 // so no test exercised the positional-arity path through
7154 // `Caixa::from_lisp` specifically; the sibling
7155 // [`from_lisp_rejects_molde_dialect_via_typed_variant`] only
7156 // covered [`crate::dialeto::CaixaDialeto::Molde`]. Post-lift the
7157 // two arms route through the lifted
7158 // [`crate::dialeto::CaixaDialeto::is_molde_family`] (e9d2315)
7159 // typed predicate — the same predicate the pre-lift `foreign =>`
7160 // wildcard resolved to today — and this pin makes the
7161 // positional-arity arm's byte-shape at the gate explicit rather
7162 // than implied by wildcard-absorption. A future regression that
7163 // silently reordered [`crate::dialeto::CaixaDialeto::is_molde_family`]'s
7164 // arm-set (dropped [`crate::dialeto::CaixaDialeto::MoldePosicional`]
7165 // from the two-arity closure) would fail this pin at caixa-core
7166 // test time rather than surfacing far from the change as a
7167 // `caixa.lisp` carrying a `(defcaixa todoku-go :ecosystem :go
7168 // …)` silently parsing past the derive.
7169 let src = r#"
7170 (defcaixa todoku-go
7171 :kind :Biblioteca
7172 :ecosystem :go
7173 :package {:name "todoku-go" :version "0.3.0"})
7174 "#;
7175 let err =
7176 Caixa::from_lisp(src).expect_err("MoldePosicional dialect must not parse as Pacote");
7177 match err {
7178 LeituraError::DialetoEstrangeiro { dialeto } => {
7179 assert_eq!(
7180 dialeto,
7181 crate::dialeto::CaixaDialeto::MoldePosicional,
7182 "DialetoEstrangeiro must carry the MoldePosicional \
7183 variant verbatim — the positional-arity `defmolde` \
7184 form under a `(defcaixa …)` head is the \
7185 `MoldePosicional` arm's canonical byte-shape"
7186 );
7187 let rendered = LeituraError::DialetoEstrangeiro { dialeto }.to_string();
7188 assert!(
7189 rendered.contains(dialeto.palavra_canonica()),
7190 "Display must interpolate `dialeto.palavra_canonica()` \
7191 verbatim on the MoldePosicional arm; rendered: \
7192 {rendered:?}"
7193 );
7194 assert!(
7195 rendered.contains(dialeto.consumidor()),
7196 "Display must interpolate `dialeto.consumidor()` \
7197 verbatim on the MoldePosicional arm; rendered: \
7198 {rendered:?}"
7199 );
7200 assert!(
7201 rendered.contains(dialeto.descricao()),
7202 "Display must interpolate `dialeto.descricao()` \
7203 verbatim on the MoldePosicional arm; rendered: \
7204 {rendered:?}"
7205 );
7206 }
7207 other => panic!("expected DialetoEstrangeiro, got {other:?}"),
7208 }
7209 }
7210
7211 #[test]
7212 fn from_lisp_dialect_gate_dispatches_through_caixa_dialeto_is_molde_family_predicate() {
7213 // Load-bearing byte-parity pin: for every arm in
7214 // [`crate::dialeto::CaixaDialeto::ALL`], the
7215 // [`Caixa::from_lisp`] foreign-dialect gate's DialetoEstrangeiro
7216 // partition must agree with the lifted
7217 // [`crate::dialeto::CaixaDialeto::is_molde_family`] (e9d2315)
7218 // typed predicate — i.e. from_lisp raises
7219 // [`LeituraError::DialetoEstrangeiro`] carrying `d` iff
7220 // `d.is_molde_family()` returns `true`, and does NOT raise
7221 // [`LeituraError::DialetoEstrangeiro`] on any arm where the
7222 // predicate returns `false` (the arm's source falls through to
7223 // the derive — parses cleanly on
7224 // [`crate::dialeto::CaixaDialeto::Pacote`], surfaces a
7225 // [`LeituraError::Leitura`] on
7226 // [`crate::dialeto::CaixaDialeto::Desconhecido`]).
7227 //
7228 // Pre-lift the gate hand-rolled a three-arm match
7229 // (`Pacote => {}`, `Desconhecido => {}`, `foreign => Err(…)`)
7230 // whose `foreign =>` wildcard expressed no compile-time link
7231 // back to the substrate primitive's arm-family; a future fifth
7232 // dialect the [`crate::dialeto`] module doc's "third dialect"
7233 // hazard actualises would fall silently onto the wildcard
7234 // regardless of whether it belonged to the `defmolde` family or
7235 // to a distinct `defcaixa`-family. Post-lift the partition
7236 // resolves through
7237 // [`crate::dialeto::CaixaDialeto::is_molde_family`]'s single
7238 // typed dispatch, and this pin refuses any future regression
7239 // that silently split the from_lisp partition from the typed
7240 // predicate — the two paths now migrate as one on any future
7241 // arm addition.
7242 //
7243 // Sibling in shape to the peer
7244 // [`crate::dialeto::tests::caixa_dialeto_is_molde_family_agrees_with_palavra_canonica_defmolde_projection`]
7245 // (e9d2315) that pins the same byte-parity between
7246 // [`crate::dialeto::CaixaDialeto::is_molde_family`] and the
7247 // sibling [`crate::dialeto::CaixaDialeto::palavra_canonica`]
7248 // `== "defmolde"` classifier — extends the discipline from the
7249 // two paths within the [`crate::dialeto`] primitive onto the
7250 // third external consumer of the `defmolde`-family partition
7251 // (the [`Caixa::from_lisp`] gate that raises
7252 // [`LeituraError::DialetoEstrangeiro`]).
7253 let fixtures: &[(crate::dialeto::CaixaDialeto, &str)] = &[
7254 (
7255 crate::dialeto::CaixaDialeto::Pacote,
7256 r#"
7257 (defcaixa
7258 :nome "checkout"
7259 :versao "0.1.0"
7260 :kind Biblioteca
7261 :edicao "2026"
7262 :descricao "canonical Pacote source"
7263 :autores ()
7264 :etiquetas ()
7265 :deps ()
7266 :deps-dev ()
7267 :bibliotecas ("lib/checkout.lisp"))
7268 "#,
7269 ),
7270 (
7271 crate::dialeto::CaixaDialeto::Molde,
7272 r#"
7273 (defcaixa
7274 :name "base64"
7275 :kind :Biblioteca
7276 :ecosystem :rust-single-crate
7277 :package {:name "base64" :version "0.22.1"}
7278 :workflows [:auto-release])
7279 "#,
7280 ),
7281 (
7282 crate::dialeto::CaixaDialeto::MoldePosicional,
7283 r#"
7284 (defcaixa todoku-go
7285 :kind :Biblioteca
7286 :ecosystem :go
7287 :package {:name "todoku-go" :version "0.3.0"})
7288 "#,
7289 ),
7290 (
7291 crate::dialeto::CaixaDialeto::Desconhecido,
7292 r#"(defcaixa :licenca "MIT")"#,
7293 ),
7294 ];
7295
7296 // Coverage: every arm in [`crate::dialeto::CaixaDialeto::ALL`]
7297 // must appear in the fixture table so the pin's arm-set stays
7298 // synchronised with the enum's arm-set. Fails at test time if a
7299 // future fifth arm added to [`crate::dialeto::CaixaDialeto`]
7300 // (with a corresponding `is_molde_family` return) forgot to
7301 // extend this fixture table with a canonical source for the new
7302 // arm — the pin cannot cover an arm it has no source for.
7303 for &expected in crate::dialeto::CaixaDialeto::ALL {
7304 assert!(
7305 fixtures.iter().any(|(d, _)| *d == expected),
7306 "fixture table must carry a canonical source for every \
7307 CaixaDialeto arm; missing: {expected:?}"
7308 );
7309 }
7310
7311 for &(expected_dialect, src) in fixtures {
7312 let classified = crate::dialeto::classify(src.trim()).unwrap_or_else(|err| {
7313 panic!(
7314 "fixture source for {expected_dialect:?} must classify \
7315 cleanly, got err: {err:?}"
7316 )
7317 });
7318 assert_eq!(
7319 classified, expected_dialect,
7320 "fixture source for {expected_dialect:?} must classify as \
7321 {expected_dialect:?} (drift here defeats the byte-parity \
7322 pin below — a source labelled for one arm but classifying \
7323 as another would silently satisfy or violate the pin for \
7324 the wrong reason)"
7325 );
7326
7327 let outcome = Caixa::from_lisp(src);
7328 match (expected_dialect.is_molde_family(), &outcome) {
7329 (true, Err(LeituraError::DialetoEstrangeiro { dialeto })) => {
7330 assert_eq!(
7331 *dialeto, expected_dialect,
7332 "DialetoEstrangeiro must carry the same typed arm \
7333 the classifier returned — a drift here would let \
7334 from_lisp raise the error while pointing at the \
7335 wrong dialect (e.g. rejecting a \
7336 MoldePosicional source as Molde). arm: \
7337 {expected_dialect:?}"
7338 );
7339 }
7340 (true, other) => panic!(
7341 "arm {expected_dialect:?} has is_molde_family() = true \
7342 so from_lisp must raise DialetoEstrangeiro carrying \
7343 {expected_dialect:?}; got: {other:?}"
7344 ),
7345 (false, Err(LeituraError::DialetoEstrangeiro { dialeto })) => panic!(
7346 "arm {expected_dialect:?} has is_molde_family() = false \
7347 so from_lisp must NOT raise DialetoEstrangeiro; got \
7348 one carrying: {dialeto:?}. This means the typed \
7349 predicate and the from_lisp partition disagree on \
7350 this arm — exactly the drift this pin refuses."
7351 ),
7352 (false, _) => {
7353 // A non-molde arm's source falls through to the
7354 // derive: Pacote sources parse to Ok(_); Desconhecido
7355 // sources surface as LeituraError::Leitura from the
7356 // derive's own unknown-keyword rejection. Either
7357 // shape is acceptable here — the pin's promise is
7358 // narrower: "no DialetoEstrangeiro on
7359 // is_molde_family() == false".
7360 }
7361 }
7362 }
7363 }
7364
7365 // ── M2 typed-substrate slot tests (limits, behavior, upgrade-from, supervisor) ──
7366
7367 #[test]
7368 fn limits_round_trip_via_json() {
7369 use crate::LimitsSpec;
7370 use std::time::Duration;
7371 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7372 c.limits = Some(LimitsSpec {
7373 memory: Some(64 * 1024 * 1024),
7374 fuel: Some(1_000_000),
7375 wall_clock: Some(Duration::from_secs(30)),
7376 cpu: Some(500),
7377 });
7378 let json = serde_json::to_string(&c).unwrap();
7379 assert!(json.contains("\"limits\""));
7380 assert!(json.contains("\"64MiB\""));
7381 assert!(json.contains("\"30s\""));
7382 assert!(json.contains("\"500m\""));
7383 let back: Caixa = serde_json::from_str(&json).unwrap();
7384 assert_eq!(c.limits, back.limits);
7385 }
7386
7387 #[test]
7388 fn behavior_round_trip_via_json() {
7389 use crate::BehaviorSpec;
7390 use std::path::PathBuf;
7391 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7392 c.behavior = Some(BehaviorSpec {
7393 on_init: Some(PathBuf::from("lib/init.lisp")),
7394 on_call: Some(PathBuf::from("lib/handlers.lisp")),
7395 ..Default::default()
7396 });
7397 let json = serde_json::to_string(&c).unwrap();
7398 let back: Caixa = serde_json::from_str(&json).unwrap();
7399 assert_eq!(c.behavior, back.behavior);
7400 }
7401
7402 #[test]
7403 fn upgrade_from_round_trip_via_json() {
7404 use crate::{UpgradeFromEntry, UpgradeInstruction};
7405 use std::path::PathBuf;
7406 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7407 c.upgrade_from = vec![UpgradeFromEntry {
7408 from: "0.1.0".into(),
7409 instructions: vec![
7410 UpgradeInstruction::LoadModule {
7411 module: "demo".into(),
7412 },
7413 UpgradeInstruction::StateChange {
7414 script: PathBuf::from("lib/migrations/v01-to-v02.lisp"),
7415 },
7416 UpgradeInstruction::SoftPurge {
7417 module: "demo-old".into(),
7418 },
7419 ],
7420 }];
7421 let json = serde_json::to_string(&c).unwrap();
7422 let back: Caixa = serde_json::from_str(&json).unwrap();
7423 assert_eq!(c.upgrade_from, back.upgrade_from);
7424 }
7425
7426 #[test]
7427 fn supervisor_view_returns_typed_shape() {
7428 use crate::{ChildSpec, RestartPolicy, RestartStrategy};
7429 let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
7430 c.kind = CaixaKind::Supervisor;
7431 c.bibliotecas.clear();
7432 c.estrategia = Some(RestartStrategy::OneForOne);
7433 c.max_restarts = Some(5);
7434 c.restart_window = Some("60s".into());
7435 c.children = vec![ChildSpec {
7436 caixa: "worker".into(),
7437 versao: "^0.1".into(),
7438 restart: RestartPolicy::Permanent,
7439 }];
7440 let view = c.supervisor_view().expect("Supervisor kind has a view");
7441 assert_eq!(view.estrategia, RestartStrategy::OneForOne);
7442 assert_eq!(view.max_restarts, 5);
7443 assert_eq!(
7444 view.restart_window,
7445 Some(std::time::Duration::from_secs(60))
7446 );
7447 assert_eq!(view.children.len(), 1);
7448 view.validate().unwrap();
7449 }
7450
7451 #[test]
7452 fn supervisor_view_none_for_non_supervisor_kinds() {
7453 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7454 assert!(c.supervisor_view().is_none());
7455 }
7456
7457 #[test]
7458 fn declared_mesh_slots_empty_for_bare_caixa() {
7459 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7460 assert!(c.declared_mesh_slots().is_empty());
7461 }
7462
7463 #[test]
7464 fn declared_mesh_slots_reports_only_set_slots_in_canonical_order() {
7465 use crate::{Entrada, Membro};
7466 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7467 // Set a non-adjacent pair (:membros + :entrada) to pin that the
7468 // canonical declaration order is preserved regardless of which
7469 // subset is populated.
7470 c.membros = vec![Membro {
7471 caixa: "a".into(),
7472 versao: "^0.1".into(),
7473 }];
7474 c.entrada = Some(Entrada {
7475 host: "x.example.com".into(),
7476 para: "a".into(),
7477 paths: vec![],
7478 port: 8080,
7479 });
7480 assert_eq!(
7481 c.declared_mesh_slots(),
7482 vec![
7483 crate::render::M3_AUTHOR_KEY_MEMBROS,
7484 crate::render::M3_AUTHOR_KEY_ENTRADA,
7485 ]
7486 );
7487 }
7488
7489 #[test]
7490 fn m3_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
7491 // Scalar-value pin: the five author-facing kebab-case labels the
7492 // `(defcaixa … :<slot> (…))` surface admits on the M3 top-level
7493 // mesh slot axis, one arm per typed slot. Mirrors the peer
7494 // scalar-value pin the sibling
7495 // [`crate::M2_AUTHOR_KEY_LIMITS`] /
7496 // [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
7497 // [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] M2 top-level slot consts
7498 // carry (f49c8b0), so both altitudes of the typed-slot algebra
7499 // (per-Servico M2 + per-Aplicacao M3) share the same
7500 // "one canonical byte-string per arm" discipline. A future
7501 // rebrand (`:membros` → `:members`, `:contratos` → `:contracts`,
7502 // `:politicas` → `:policies`, `:placement` → `:distribution`,
7503 // `:entrada` → `:ingress`) lands as an edit to exactly one const,
7504 // and every consumer that reaches for the label picks it up at
7505 // build time rather than at runtime as a downstream mismatch.
7506 assert_eq!(crate::render::M3_AUTHOR_KEY_MEMBROS, ":membros");
7507 assert_eq!(crate::render::M3_AUTHOR_KEY_CONTRATOS, ":contratos");
7508 assert_eq!(crate::render::M3_AUTHOR_KEY_POLITICAS, ":politicas");
7509 assert_eq!(crate::render::M3_AUTHOR_KEY_PLACEMENT, ":placement");
7510 assert_eq!(crate::render::M3_AUTHOR_KEY_ENTRADA, ":entrada");
7511 }
7512
7513 #[test]
7514 fn declared_mesh_slots_route_through_lifted_m3_author_key_consts() {
7515 // Production-through-const pin: the five per-arm labels the
7516 // [`Caixa::declared_mesh_slots`] tagger pushes onto its return
7517 // `Vec` route through the lifted
7518 // [`crate::M3_AUTHOR_KEY_MEMBROS`] /
7519 // [`crate::M3_AUTHOR_KEY_CONTRATOS`] /
7520 // [`crate::M3_AUTHOR_KEY_POLITICAS`] /
7521 // [`crate::M3_AUTHOR_KEY_PLACEMENT`] /
7522 // [`crate::M3_AUTHOR_KEY_ENTRADA`] consts, in canonical
7523 // declaration order. A future re-order or drift at the tagger
7524 // (a rename that reaches the tagger but not the const, or vice
7525 // versa) surfaces here at build time rather than at runtime as
7526 // a [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
7527 // `slots: <stale-kebab-case>` diagnostic far from the rename's
7528 // commit. Mirror of the peer
7529 // [`declared_servico_slots_route_through_lifted_m2_author_key_consts`]
7530 // pin (f49c8b0) on the sibling per-Servico M2 top-level slot
7531 // axis.
7532 use crate::{Entrada, Membro, MeshPolicy, Placement, PlacementStrategy, WitContract};
7533 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7534 c.membros = vec![Membro {
7535 caixa: "a".into(),
7536 versao: "^0.1".into(),
7537 }];
7538 c.contratos = vec![WitContract {
7539 de: "a".into(),
7540 para: "a".into(),
7541 wit: "wasi:http/proxy".into(),
7542 endpoint: Some("/x".into()),
7543 subject: None,
7544 slot: None,
7545 }];
7546 c.politicas = Some(MeshPolicy::default());
7547 c.placement = Some(Placement {
7548 estrategia: PlacementStrategy::Replicated,
7549 clusters: vec!["rio".into()],
7550 affinity: None,
7551 shard_key: None,
7552 });
7553 c.entrada = Some(Entrada {
7554 host: "x.example.com".into(),
7555 para: "a".into(),
7556 paths: vec![],
7557 port: 8080,
7558 });
7559 assert_eq!(
7560 c.declared_mesh_slots(),
7561 vec![
7562 crate::render::M3_AUTHOR_KEY_MEMBROS,
7563 crate::render::M3_AUTHOR_KEY_CONTRATOS,
7564 crate::render::M3_AUTHOR_KEY_POLITICAS,
7565 crate::render::M3_AUTHOR_KEY_PLACEMENT,
7566 crate::render::M3_AUTHOR_KEY_ENTRADA,
7567 ]
7568 );
7569 }
7570
7571 #[test]
7572 fn declared_supervisor_slots_empty_for_bare_caixa() {
7573 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7574 assert!(c.declared_supervisor_slots().is_empty());
7575 }
7576
7577 #[test]
7578 fn declared_supervisor_slots_reports_only_set_slots_in_canonical_order() {
7579 use crate::RestartStrategy;
7580 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7581 // Set a non-adjacent pair (:estrategia + :restart-window) to pin
7582 // that the canonical declaration order is preserved regardless
7583 // of which subset is populated.
7584 c.estrategia = Some(RestartStrategy::OneForOne);
7585 c.restart_window = Some("60s".into());
7586 assert_eq!(
7587 c.declared_supervisor_slots(),
7588 vec![
7589 crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
7590 crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
7591 ]
7592 );
7593 }
7594
7595 #[test]
7596 fn supervisor_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
7597 // Scalar-value pin: the four author-facing kebab-case labels the
7598 // `(defcaixa … :<slot> (…))` surface admits on the Supervisor
7599 // supervision-tree slot axis, one arm per typed slot. Mirrors the
7600 // peer scalar-value pins the sibling
7601 // [`crate::render::M2_AUTHOR_KEY_LIMITS`] /
7602 // [`crate::render::M2_AUTHOR_KEY_BEHAVIOR`] /
7603 // [`crate::render::M2_AUTHOR_KEY_UPGRADE_FROM`] top-level M2 slot
7604 // consts and [`crate::render::M3_AUTHOR_KEY_MEMBROS`] etc.
7605 // top-level M3 slot consts carry, so all three kind-scoped
7606 // typed-slot-family author-facing-label axes route through one
7607 // canonical per-arm declaration. A future rebrand
7608 // (`:estrategia` → `:strategy` for English uniformity,
7609 // `:max-restarts` → `:max-intensity` matching Erlang/OTP's
7610 // `MaxIntensity` name, `:restart-window` → `:period` matching
7611 // OTP's `Period` name, `:children` → `:workers` matching Elixir
7612 // idiom) lands as an edit to exactly one const, and every
7613 // consumer that reaches for the label picks it up at build time
7614 // rather than at runtime as a downstream mismatch.
7615 assert_eq!(
7616 crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
7617 ":estrategia"
7618 );
7619 assert_eq!(
7620 crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS,
7621 ":max-restarts"
7622 );
7623 assert_eq!(
7624 crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
7625 ":restart-window"
7626 );
7627 assert_eq!(crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN, ":children");
7628 }
7629
7630 #[test]
7631 fn declared_supervisor_slots_route_through_lifted_supervisor_author_key_consts() {
7632 // Production-through-const pin: the four per-arm labels the
7633 // [`Caixa::declared_supervisor_slots`] tagger pushes onto its
7634 // return `Vec` route through the lifted
7635 // [`crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA`] /
7636 // [`crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS`] /
7637 // [`crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW`] /
7638 // [`crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN`] consts, in
7639 // canonical declaration order. A future re-order or drift at the
7640 // tagger (a rename that reaches the tagger but not the const, or
7641 // vice versa) surfaces here at build time rather than at runtime
7642 // as a [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
7643 // `slots: <stale-kebab-case>` diagnostic far from the rename's
7644 // commit. Mirror of the peer
7645 // [`declared_servico_slots_route_through_lifted_m2_author_key_consts`]
7646 // (f49c8b0) and
7647 // [`declared_mesh_slots_route_through_lifted_m3_author_key_consts`]
7648 // (882f498) pins on the sibling M2 / M3 top-level slot axes.
7649 use crate::{ChildSpec, RestartPolicy, RestartStrategy};
7650 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7651 c.estrategia = Some(RestartStrategy::OneForOne);
7652 c.max_restarts = Some(5);
7653 c.restart_window = Some("60s".into());
7654 c.children = vec![ChildSpec {
7655 caixa: "worker".into(),
7656 versao: "^0.1".into(),
7657 restart: RestartPolicy::Permanent,
7658 }];
7659 assert_eq!(
7660 c.declared_supervisor_slots(),
7661 vec![
7662 crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
7663 crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS,
7664 crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
7665 crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN,
7666 ]
7667 );
7668 }
7669
7670 #[test]
7671 fn declared_servico_slots_empty_for_bare_caixa() {
7672 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7673 assert!(c.declared_servico_slots().is_empty());
7674 }
7675
7676 #[test]
7677 fn declared_servico_slots_reports_only_set_slots_in_canonical_order() {
7678 use crate::{UpgradeFromEntry, UpgradeInstruction};
7679 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7680 // Set a non-adjacent pair (:limits + :upgrade-from) to pin that
7681 // the canonical declaration order is preserved regardless of
7682 // which subset is populated.
7683 c.limits = Some(crate::LimitsSpec {
7684 fuel: Some(1_000_000),
7685 ..Default::default()
7686 });
7687 c.upgrade_from = vec![UpgradeFromEntry {
7688 from: "0.1.0".into(),
7689 instructions: vec![UpgradeInstruction::Restart],
7690 }];
7691 assert_eq!(
7692 c.declared_servico_slots(),
7693 vec![
7694 crate::render::M2_AUTHOR_KEY_LIMITS,
7695 crate::render::M2_AUTHOR_KEY_UPGRADE_FROM,
7696 ]
7697 );
7698 }
7699
7700 #[test]
7701 fn m2_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
7702 // Scalar-value pin: the three author-facing kebab-case labels
7703 // the `(defcaixa … :<slot> (…))` surface admits on the M2
7704 // top-level slot axis, one arm per typed slot. Mirrors the peer
7705 // scalar-value pin the sibling renderer-side
7706 // [`crate::M2_KEY_LIMITS`] / [`crate::M2_KEY_BEHAVIOR`] /
7707 // [`crate::M2_KEY_UPGRADE_FROM`] camelCase overlay-container
7708 // consts carry, so both halves of the M2 top-level slot dual
7709 // axis (author-facing kebab-case label + renderer-side
7710 // camelCase overlay-container wire key) route through one
7711 // canonical per-arm declaration. A future rebrand
7712 // (`:limits` → `:sandbox` matching Lunatic per-process
7713 // terminology INSPIRATIONS §III.1, `:behavior` → `:gen-server`
7714 // matching Erlang's verbatim name, `:upgrade-from` → `:appup`
7715 // matching Erlang's verbatim appup name) lands as an edit to
7716 // exactly one const, and every consumer that reaches for the
7717 // label picks it up at build time rather than at runtime as a
7718 // downstream mismatch.
7719 assert_eq!(crate::render::M2_AUTHOR_KEY_LIMITS, ":limits");
7720 assert_eq!(crate::render::M2_AUTHOR_KEY_BEHAVIOR, ":behavior");
7721 assert_eq!(crate::render::M2_AUTHOR_KEY_UPGRADE_FROM, ":upgrade-from");
7722 }
7723
7724 #[test]
7725 fn declared_servico_slots_route_through_lifted_m2_author_key_consts() {
7726 // Production-through-const pin: the three per-arm labels the
7727 // [`Caixa::declared_servico_slots`] tagger pushes onto its
7728 // return `Vec` route through the lifted
7729 // [`crate::M2_AUTHOR_KEY_LIMITS`] /
7730 // [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
7731 // [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] consts, in canonical
7732 // declaration order. A future re-order or drift at the tagger
7733 // (a rename that reaches the tagger but not the const, or vice
7734 // versa) surfaces here at build time rather than at runtime as
7735 // a [`crate::LayoutError::ServicoSlotsOnNonServico`]
7736 // `slots: <stale-kebab-case>` diagnostic far from the rename's
7737 // commit. Mirror of the peer
7738 // [`crate::behavior::BehaviorSpec::declared_slots`] production
7739 // tagger pin (889dc18) on the sibling per-callback axis.
7740 use crate::{BehaviorSpec, UpgradeFromEntry, UpgradeInstruction};
7741 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7742 c.limits = Some(crate::LimitsSpec {
7743 fuel: Some(1_000_000),
7744 ..Default::default()
7745 });
7746 c.behavior = Some(BehaviorSpec {
7747 on_init: Some(PathBuf::from("lib/init.lisp")),
7748 ..Default::default()
7749 });
7750 c.upgrade_from = vec![UpgradeFromEntry {
7751 from: "0.1.0".into(),
7752 instructions: vec![UpgradeInstruction::Restart],
7753 }];
7754 assert_eq!(
7755 c.declared_servico_slots(),
7756 vec![
7757 crate::render::M2_AUTHOR_KEY_LIMITS,
7758 crate::render::M2_AUTHOR_KEY_BEHAVIOR,
7759 crate::render::M2_AUTHOR_KEY_UPGRADE_FROM,
7760 ]
7761 );
7762 }
7763
7764 #[test]
7765 fn existing_manifests_unaffected_by_new_optional_slots() {
7766 // Regression test: a caixa.lisp authored before M2 typed slots
7767 // should still parse + serialize cleanly. The bare `defcaixa`
7768 // emitted by `Caixa::template` has none of the new fields.
7769 let src = Caixa::template("legacy");
7770 let c = Caixa::from_lisp(&src).unwrap();
7771 assert!(c.limits.is_none());
7772 assert!(c.behavior.is_none());
7773 assert!(c.upgrade_from.is_empty());
7774 assert!(c.estrategia.is_none());
7775 assert!(c.children.is_empty());
7776
7777 // And to_lisp emits a manifest with the new slots in the
7778 // empty/default state — round-trippable.
7779 let emitted = c.to_lisp();
7780 let back = Caixa::from_lisp(&emitted).unwrap();
7781 assert_eq!(c, back);
7782 }
7783
7784 #[test]
7785 fn validate_deps_accepts_canonical_caixa() {
7786 // Positive control: the bare template — zero deps, zero
7787 // deps_dev — passes the gate trivially. A future axis added to
7788 // `Dep::validate` mustn't regress an empty-deps caixa to a
7789 // build error.
7790 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7791 c.validate_deps().unwrap();
7792 }
7793
7794 #[test]
7795 fn validate_deps_rejects_invalid_versao_in_deps() {
7796 // Fail-before-pass-after pin: a malformed `:deps :versao`
7797 // surfaces at validate_deps() time, not at lacre-resolve time.
7798 // Mirrors `rejects_invalid_membro_versao_requirement` and
7799 // `validate_rejects_invalid_child_versao_requirement` on the
7800 // other two `:versao` axes.
7801 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7802 c.deps = vec![Dep::simple("caixa-teia", "^bad-version")];
7803 let err = c.validate_deps().unwrap_err();
7804 assert!(
7805 matches!(
7806 err,
7807 crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
7808 if nome == "caixa-teia" && versao == "^bad-version"
7809 ),
7810 "got {err:?}"
7811 );
7812 }
7813
7814 #[test]
7815 fn validate_deps_rejects_invalid_versao_in_deps_dev() {
7816 // Parity pin: `:deps-dev` must run through the same per-entry
7817 // validator as `:deps` — a typo in either axis surfaces the
7818 // same diagnostic. Without this leg, `:deps-dev` would be a
7819 // second-class citizen of the typed surface and an author
7820 // could land a build that passes validate_deps but fails at
7821 // `feira lock`-time when the dev-dep is resolved for a test
7822 // build.
7823 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7824 c.deps_dev = vec![Dep::simple("tatara-check", "^^0.1")];
7825 let err = c.validate_deps().unwrap_err();
7826 assert!(
7827 matches!(
7828 err,
7829 crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
7830 if nome == "tatara-check" && versao == "^^0.1"
7831 ),
7832 "got {err:?}"
7833 );
7834 }
7835
7836 #[test]
7837 fn validate_deps_runs_deps_before_deps_dev() {
7838 // Order pin: when both lists carry typos, the `:deps`
7839 // diagnostic surfaces first. The author's mental model is
7840 // "runtime deps are load-bearing; dev deps are scaffolding";
7841 // surfacing the runtime axis first matches that hierarchy.
7842 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7843 c.deps = vec![Dep::simple("runtime-dep", "^bad-runtime")];
7844 c.deps_dev = vec![Dep::simple("dev-dep", "^bad-dev")];
7845 let err = c.validate_deps().unwrap_err();
7846 assert!(
7847 matches!(
7848 err,
7849 crate::dep::DepError::VersaoInvalid { ref nome, .. }
7850 if nome == "runtime-dep"
7851 ),
7852 "expected `:deps` typo to surface first, got {err:?}"
7853 );
7854 }
7855
7856 #[test]
7857 fn validate_deps_accepts_canonical_versao_forms_in_both_lists() {
7858 // Positive control sweep across both lists. Pin every
7859 // canonical Cargo-shaped form so a future tightening of the
7860 // accepted set surfaces here as a test failure (parity with
7861 // `accepts_canonical_membro_versao_forms` and
7862 // `validate_accepts_canonical_child_versao_forms`).
7863 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7864 c.deps = vec![
7865 Dep::simple("caret", "^0.1"),
7866 Dep::simple("tilde", "~0.1.2"),
7867 Dep::simple("exact", "0.1.0"),
7868 Dep::simple("wildcard", "*"),
7869 Dep::simple("multi-range", ">=0.1, <2"),
7870 ];
7871 c.deps_dev = vec![
7872 Dep::simple("dev-caret", "^0.1"),
7873 Dep::simple("dev-wildcard", "*"),
7874 ];
7875 c.validate_deps().unwrap();
7876 }
7877
7878 #[test]
7879 fn validate_deps_diagnostic_carries_offending_dep() {
7880 // Diagnostic-shape pin: the error names the offending entry's
7881 // `:nome` + `:versao` verbatim and carries a non-empty
7882 // `reason` from `semver::VersionReq::parse`, so a `feira lint`
7883 // run can render the diagnostic without re-parsing.
7884 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7885 c.deps = vec![Dep::simple("caixa-teia", "not-a-req")];
7886 let err = c.validate_deps().unwrap_err();
7887 let crate::dep::DepError::VersaoInvalid {
7888 nome,
7889 versao,
7890 reason,
7891 } = err
7892 else {
7893 panic!("expected VersaoInvalid, got other variant");
7894 };
7895 assert_eq!(nome, "caixa-teia");
7896 assert_eq!(versao, "not-a-req");
7897 assert!(
7898 !reason.is_empty(),
7899 "VersaoInvalid `reason` must carry the parser's wording verbatim"
7900 );
7901 }
7902
7903 #[test]
7904 fn validate_deps_rejects_ambiguous_fonte_in_deps_dev() {
7905 // Cross-axis pin: `validate_deps` walks both :deps and
7906 // :deps-dev through `Dep::validate`, and the new fonte gate
7907 // (`:tag` + `:branch` both set — the canonical "pin drift"
7908 // footgun) must surface from the :deps-dev arm with the
7909 // offending entry's :nome named. Pin the :deps-dev arm
7910 // explicitly so a future shortcut that only walks :deps
7911 // surfaces here as a regression.
7912 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7913 c.deps_dev = vec![Dep {
7914 nome: "dev-only".into(),
7915 versao: "^0.1".into(),
7916 fonte: Some(crate::DepSource::Git {
7917 repo: "github:p/x".into(),
7918 tag: Some("v1".into()),
7919 rev: None,
7920 branch: Some("main".into()),
7921 }),
7922 opcional: false,
7923 caracteristicas: vec![],
7924 }];
7925 let err = c.validate_deps().unwrap_err();
7926 let crate::dep::DepError::FontePinAmbiguous { nome, pins } = err else {
7927 panic!("expected FontePinAmbiguous from :deps-dev walk");
7928 };
7929 assert_eq!(nome, "dev-only");
7930 assert!(pins.contains(":tag") && pins.contains(":branch"));
7931 }
7932
7933 #[test]
7934 fn validate_deps_rejects_empty_repo_in_deps() {
7935 // Parity pin on the :deps arm: an empty :repo on the runtime
7936 // deps list surfaces the same FonteRepoEmpty diagnostic the
7937 // dep.rs per-entry tests pin, naming the offending entry.
7938 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7939 c.deps = vec![Dep {
7940 nome: "runtime".into(),
7941 versao: "^0.1".into(),
7942 fonte: Some(crate::DepSource::Git {
7943 repo: String::new(),
7944 tag: Some("v1".into()),
7945 rev: None,
7946 branch: None,
7947 }),
7948 opcional: false,
7949 caracteristicas: vec![],
7950 }];
7951 let err = c.validate_deps().unwrap_err();
7952 assert!(
7953 matches!(
7954 err,
7955 crate::dep::DepError::FonteRepoEmpty { ref nome }
7956 if nome == "runtime"
7957 ),
7958 "got {err:?}"
7959 );
7960 }
7961
7962 // ── validate_deps: within-list :nome set-not-multiset gate ─────────
7963
7964 #[test]
7965 fn validate_deps_rejects_duplicate_nome_in_deps() {
7966 // Fail-before-pass-after pin: two `:deps` entries naming the same
7967 // caixa carry two `:versao` / `:fonte` / feature triples that the
7968 // caixa-resolver's lacre pipeline collapses (the second silently
7969 // overwrites the first at `concrete_versao`-resolve time). The
7970 // gate surfaces the duplicate at validate-time, naming the
7971 // offending caixa + the list, before the resolver-side silent
7972 // drop. Mirrors the peer typed-graph duplicate gates
7973 // (`DuplicateChildCaixa`, `MembroDuplicate`, `DuplicateFrom`, …).
7974 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7975 c.deps = vec![
7976 Dep::simple("caixa-teia", "^0.1"),
7977 Dep::simple("caixa-teia", "^0.2"),
7978 ];
7979 let err = c.validate_deps().unwrap_err();
7980 assert!(
7981 matches!(
7982 err,
7983 crate::dep::DepError::DuplicateNome { ref nome, list }
7984 if nome == "caixa-teia" && list == crate::render::DEP_AUTHOR_KEY_DEPS
7985 ),
7986 "got {err:?}"
7987 );
7988 }
7989
7990 #[test]
7991 fn validate_deps_rejects_duplicate_nome_in_deps_dev() {
7992 // Parity pin: `:deps-dev` runs through the same per-list
7993 // duplicate check as `:deps` — neither axis is a second-class
7994 // citizen of the set-not-multiset discipline.
7995 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7996 c.deps_dev = vec![
7997 Dep::simple("tatara-check", "*"),
7998 Dep::simple("tatara-check", "^0.1"),
7999 ];
8000 let err = c.validate_deps().unwrap_err();
8001 assert!(
8002 matches!(
8003 err,
8004 crate::dep::DepError::DuplicateNome { ref nome, list }
8005 if nome == "tatara-check" && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
8006 ),
8007 "got {err:?}"
8008 );
8009 }
8010
8011 #[test]
8012 fn validate_deps_accepts_cross_list_same_nome() {
8013 // The Cargo `[dependencies]` + `[dev-dependencies]` override
8014 // convention is preserved: a name appearing in *both* lists is
8015 // valid (the dev-pin overrides at test/dev time). Only
8016 // within-list duplicates are structurally incoherent — pin the
8017 // permissive cross-list semantics so a future shortcut that
8018 // collapses the two seen-sets into one surfaces here as a test
8019 // failure.
8020 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8021 c.deps = vec![Dep::simple("caixa-teia", "^0.1")];
8022 c.deps_dev = vec![Dep::simple("caixa-teia", "^0.2")];
8023 c.validate_deps().unwrap();
8024 }
8025
8026 #[test]
8027 fn validate_deps_accepts_distinct_nome_in_both_lists() {
8028 // Positive control: distinct names within each list pass — the
8029 // gate's identity element on the canonical authoring shape.
8030 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8031 c.deps = vec![
8032 Dep::simple("caixa-teia", "^0.1"),
8033 Dep::simple("pleme-mesh", "*"),
8034 ];
8035 c.deps_dev = vec![
8036 Dep::simple("tatara-check", "*"),
8037 Dep::simple("dev-shim", "^0.1"),
8038 ];
8039 c.validate_deps().unwrap();
8040 }
8041
8042 #[test]
8043 fn validate_deps_per_entry_validate_fires_before_duplicate_in_deps() {
8044 // Diagnostic-precedence pin: a malformed `:versao` on the
8045 // duplicating entry surfaces its narrower `VersaoInvalid`
8046 // diagnostic first, before the cross-entry duplicate gate fires
8047 // — the canonical "per-entry shape before cross-entry uniqueness"
8048 // precedence every peer set-not-multiset gate establishes
8049 // (`*_invalid_fires_before_duplicate_check` pins on
8050 // `SupervisorSpec::validate`, `AplicacaoSpec::validate_membros`,
8051 // `validate_upgrade_from`).
8052 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8053 c.deps = vec![
8054 Dep::simple("caixa-teia", "^0.1"),
8055 Dep::simple("caixa-teia", "^bad-version"),
8056 ];
8057 let err = c.validate_deps().unwrap_err();
8058 assert!(
8059 matches!(
8060 err,
8061 crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
8062 if nome == "caixa-teia" && versao == "^bad-version"
8063 ),
8064 "expected VersaoInvalid to surface before DuplicateNome, got {err:?}"
8065 );
8066 }
8067
8068 #[test]
8069 fn validate_deps_duplicate_diagnostic_names_first_collision() {
8070 // First-collision determinism pin: with three entries naming the
8071 // same caixa, the first colliding pair surfaces — not the last.
8072 // Mirrors the peer first-collision posture on every
8073 // duplicate-target gate
8074 // (`validate_upgrade_from_duplicate_diagnostic_names_second_collision`
8075 // — the second entry is the first collision; this gate uses the
8076 // same shape: the second entry's `:nome` lands in the diagnostic
8077 // because `seen.insert(first.nome)` already populated the set).
8078 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8079 c.deps = vec![
8080 Dep::simple("caixa-teia", "^0.1"),
8081 Dep::simple("caixa-teia", "^0.2"),
8082 Dep::simple("caixa-teia", "^0.3"),
8083 ];
8084 let err = c.validate_deps().unwrap_err();
8085 // The diagnostic carries the offending caixa name; the
8086 // implementation surfaces on the *second* entry (the first
8087 // collision), so the test pins the `:nome` value.
8088 assert!(
8089 matches!(
8090 err,
8091 crate::dep::DepError::DuplicateNome { ref nome, list }
8092 if nome == "caixa-teia" && list == crate::render::DEP_AUTHOR_KEY_DEPS
8093 ),
8094 "got {err:?}"
8095 );
8096 }
8097
8098 #[test]
8099 fn validate_deps_duplicate_in_deps_fires_before_duplicate_in_deps_dev() {
8100 // Cross-list precedence pin: when both lists carry duplicates,
8101 // the `:deps` diagnostic surfaces first — same author-mental-
8102 // model ordering the `validate_deps_runs_deps_before_deps_dev`
8103 // pin establishes for malformed `:versao` (runtime axis before
8104 // dev axis).
8105 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8106 c.deps = vec![
8107 Dep::simple("runtime-dep", "^0.1"),
8108 Dep::simple("runtime-dep", "^0.2"),
8109 ];
8110 c.deps_dev = vec![Dep::simple("dev-dep", "*"), Dep::simple("dev-dep", "^0.1")];
8111 let err = c.validate_deps().unwrap_err();
8112 assert!(
8113 matches!(
8114 err,
8115 crate::dep::DepError::DuplicateNome { ref nome, list }
8116 if nome == "runtime-dep" && list == crate::render::DEP_AUTHOR_KEY_DEPS
8117 ),
8118 "expected :deps duplicate to surface before :deps-dev duplicate, got {err:?}"
8119 );
8120 }
8121
8122 #[test]
8123 fn validate_deps_empty_lists_pass_duplicate_gate() {
8124 // Empty-set identity pin: the bare template (zero deps, zero
8125 // deps_dev) passes the duplicate gate as the gate's identity
8126 // element. A future tighten that conflates "empty" with
8127 // "missing" would regress this baseline.
8128 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8129 c.validate_deps().unwrap();
8130 }
8131
8132 #[test]
8133 fn validate_deps_duplicate_diagnostic_carries_list_tag() {
8134 // Diagnostic-shape pin: the `list:` field tags which list the
8135 // duplicate landed in (`:deps` vs `:deps-dev`) verbatim, so a
8136 // `feira lint` run can route the author to the right block in
8137 // their caixa.lisp without re-deriving the list from context.
8138 // Same self-locating shape every peer per-axis diagnostic
8139 // already exposes.
8140 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8141 c.deps_dev = vec![
8142 Dep::simple("dev-thing", "*"),
8143 Dep::simple("dev-thing", "^0.1"),
8144 ];
8145 let err = c.validate_deps().unwrap_err();
8146 let crate::dep::DepError::DuplicateNome { nome, list } = err else {
8147 panic!("expected DuplicateNome from :deps-dev walk");
8148 };
8149 assert_eq!(nome, "dev-thing");
8150 assert_eq!(list, crate::render::DEP_AUTHOR_KEY_DEPS_DEV);
8151 }
8152
8153 // ── validate_deps: per-entry :caracteristicas set-discipline gate ──
8154
8155 #[test]
8156 fn validate_deps_surfaces_caracteristicas_duplicate_in_deps_list() {
8157 // Thread-through pin on `:deps`: the per-entry
8158 // `Dep::validate_caracteristicas` gate fires inside
8159 // `Caixa::validate_deps`'s linear walk, so a malformed feature
8160 // list on any `:deps` entry surfaces as a `DepError` from
8161 // `validate_deps` — the same reachability shape every per-entry
8162 // `Dep::validate` arm threads through. Without this pin a future
8163 // shortcut that skips the per-entry `Dep::validate` call on the
8164 // cross-entry-uniqueness path would mask the within-entry
8165 // `:caracteristicas` gates.
8166 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8167 c.deps = vec![Dep {
8168 nome: "caixa-teia".into(),
8169 versao: "^0.1".into(),
8170 fonte: None,
8171 opcional: false,
8172 caracteristicas: vec!["http".into(), "http".into()],
8173 }];
8174 let err = c.validate_deps().unwrap_err();
8175 let crate::dep::DepError::CaracteristicaDuplicate {
8176 nome,
8177 caracteristica,
8178 } = err
8179 else {
8180 panic!("expected CaracteristicaDuplicate from :deps walk, got {err:?}");
8181 };
8182 assert_eq!(nome, "caixa-teia");
8183 assert_eq!(caracteristica, "http");
8184 }
8185
8186 #[test]
8187 fn validate_deps_surfaces_caracteristicas_empty_in_deps_dev_list() {
8188 // Peer thread-through pin on `:deps-dev`: same reachability as
8189 // the `:deps` arm above, on the dev-only authoring axis. Pins
8190 // that the `validate_deps` walk visits both lists' per-entry
8191 // gates uniformly. The empty-feature arm carries here so both
8192 // new `:caracteristicas` arms are surfaced via at least one
8193 // `validate_deps` thread-through.
8194 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8195 c.deps_dev = vec![Dep {
8196 nome: "caixa-teia".into(),
8197 versao: "^0.1".into(),
8198 fonte: None,
8199 opcional: false,
8200 caracteristicas: vec![String::new()],
8201 }];
8202 let err = c.validate_deps().unwrap_err();
8203 let crate::dep::DepError::CaracteristicaEmpty { nome } = err else {
8204 panic!("expected CaracteristicaEmpty from :deps-dev walk, got {err:?}");
8205 };
8206 assert_eq!(nome, "caixa-teia");
8207 }
8208
8209 #[test]
8210 fn validate_deps_surfaces_caracteristicas_invalid_in_deps_list() {
8211 // Thread-through pin on `:deps`: the per-entry
8212 // `Dep::validate_caracteristicas` value-shape gate (lifted via
8213 // `crate::render::is_cargo_feature_name`) fires inside
8214 // `Caixa::validate_deps`'s linear walk on the `:deps` list, so
8215 // a structurally invalid feature name on any `:deps` entry
8216 // surfaces as `DepError::CaracteristicaInvalid` from
8217 // `validate_deps` — the same reachability shape every per-entry
8218 // `Dep::validate` arm threads through. Without this pin a
8219 // future shortcut that skips the per-entry `Dep::validate` call
8220 // on the cross-entry-uniqueness path would mask the within-
8221 // entry `:caracteristicas` value-shape gate.
8222 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8223 c.deps = vec![Dep {
8224 nome: "caixa-teia".into(),
8225 versao: "^0.1".into(),
8226 fonte: None,
8227 opcional: false,
8228 caracteristicas: vec!["+http".into()],
8229 }];
8230 let err = c.validate_deps().unwrap_err();
8231 let crate::dep::DepError::CaracteristicaInvalid {
8232 nome,
8233 caracteristica,
8234 ..
8235 } = err
8236 else {
8237 panic!("expected CaracteristicaInvalid from :deps walk, got {err:?}");
8238 };
8239 assert_eq!(nome, "caixa-teia");
8240 assert_eq!(caracteristica, "+http");
8241 }
8242
8243 #[test]
8244 fn validate_deps_surfaces_caracteristicas_invalid_in_deps_dev_list() {
8245 // Peer thread-through pin on `:deps-dev`: same reachability as
8246 // the `:deps` arm above, on the dev-only authoring axis. The
8247 // `http/json` shape carries here so the segment-separator
8248 // diagnostic (the canonical Cargo `dep/feat` namespaced-dep
8249 // confusion footgun) is surfaced via the cross-entry walk too —
8250 // pinning that the `:deps-dev` list visits the same per-entry
8251 // value-shape gate as the `:deps` list.
8252 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8253 c.deps_dev = vec![Dep {
8254 nome: "caixa-teia".into(),
8255 versao: "^0.1".into(),
8256 fonte: None,
8257 opcional: false,
8258 caracteristicas: vec!["http/json".into()],
8259 }];
8260 let err = c.validate_deps().unwrap_err();
8261 let crate::dep::DepError::CaracteristicaInvalid {
8262 nome,
8263 caracteristica,
8264 ..
8265 } = err
8266 else {
8267 panic!("expected CaracteristicaInvalid from :deps-dev walk, got {err:?}");
8268 };
8269 assert_eq!(nome, "caixa-teia");
8270 assert_eq!(caracteristica, "http/json");
8271 }
8272
8273 #[test]
8274 fn to_lisp_preserves_deps() {
8275 let src = r#"
8276(defcaixa
8277 :nome "x"
8278 :versao "0.1.0"
8279 :kind Biblioteca
8280 :deps ((:nome "a" :versao "^0.1")
8281 (:nome "b" :versao "*" :fonte (:tipo git :repo "github:o/b" :tag "v1"))))
8282"#;
8283 let c1 = Caixa::from_lisp(src).unwrap();
8284 let emitted = c1.to_lisp();
8285 let c2 = Caixa::from_lisp(&emitted).expect("round trip");
8286 assert_eq!(c1.deps, c2.deps);
8287 }
8288
8289 // ── Caixa::validate_nome — top-level :nome value-shape gate ─────────
8290
8291 fn caixa_with_nome(nome: &str) -> Caixa {
8292 let mut c = Caixa::from_lisp(&Caixa::template("placeholder")).unwrap();
8293 c.nome = nome.to_string();
8294 c
8295 }
8296
8297 #[test]
8298 fn validate_nome_accepts_canonical_template() {
8299 // Positive control: the bare `feira init`-style template's
8300 // `:nome` ("demo") is a canonical DNS-1123 label; the gate must
8301 // not regress this baseline shape. A future tightening of the
8302 // accepted set surfaces here as a test failure first.
8303 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8304 c.validate_nome().unwrap();
8305 }
8306
8307 #[test]
8308 fn validate_nome_accepts_canonical_forms() {
8309 // Positive-set sweep: each realistic caixa-name shape the K8s
8310 // apiserver accepts as a `metadata.name` label must pass —
8311 // single-word, hyphen-joined, version-suffixed, single-char,
8312 // two-char, digit-start (DNS-1123 allows this; the stricter
8313 // DNS-1035 Service-name rule doesn't), version-suffix-bearing.
8314 // Mirrors `accepts_canonical_membro_caixa_forms` (3f9d7a0) on
8315 // the peer member-name axis.
8316 for nome in [
8317 "checkout",
8318 "cart-v2",
8319 "a",
8320 "db",
8321 "3rd-party-shim",
8322 "payment-retry",
8323 "0",
8324 ] {
8325 caixa_with_nome(nome)
8326 .validate_nome()
8327 .unwrap_or_else(|e| panic!("canonical :nome {nome:?} must validate, got {e:?}"));
8328 }
8329 }
8330
8331 #[test]
8332 fn validate_nome_rejects_empty() {
8333 // Fail-before-pass-after pin: `Caixa::from_lisp` does not refuse
8334 // an empty `:nome` (the derive macro stores the raw String);
8335 // the gate's empty arm names the offending axis with a narrower
8336 // diagnostic than the `NomeInvalid` parse arm would emit.
8337 let c = caixa_with_nome("");
8338 let err = c.validate_nome().unwrap_err();
8339 assert_eq!(err, ManifestError::NomeEmpty);
8340 }
8341
8342 #[test]
8343 fn validate_nome_rejects_uppercase() {
8344 // The canonical "I copied the TitleCase display name verbatim"
8345 // footgun. The K8s apiserver rejects `metadata.name: MyApp` at
8346 // admission on every derived artifact (Helm chart, ComputeUnit,
8347 // CNP, HTTPRoute, label values); the gate moves the diagnostic
8348 // to the source `caixa.lisp` and the reason suggests the
8349 // lowercased fix verbatim.
8350 let c = caixa_with_nome("MyApp");
8351 let err = c.validate_nome().unwrap_err();
8352 let ManifestError::NomeInvalid { nome, reason } = err else {
8353 panic!("expected NomeInvalid for uppercase :nome");
8354 };
8355 assert_eq!(nome, "MyApp");
8356 assert!(
8357 reason.contains("uppercase") && reason.contains("myapp"),
8358 "diagnostic must name the violation + the lowercased fix, got {reason:?}"
8359 );
8360 }
8361
8362 #[test]
8363 fn validate_nome_rejects_underscore() {
8364 // The Python-/Postgres-style `snake_case` leak. DNS-1123 forbids
8365 // `_`; the apiserver rejects on admission across every derived
8366 // artifact. Same fixture pinned for `:membros :caixa` (3f9d7a0)
8367 // and `:children :caixa` (31bfa43).
8368 let c = caixa_with_nome("my_app");
8369 let err = c.validate_nome().unwrap_err();
8370 assert!(
8371 matches!(
8372 err,
8373 ManifestError::NomeInvalid { ref nome, ref reason }
8374 if nome == "my_app" && reason.contains('_')
8375 ),
8376 "got {err:?}"
8377 );
8378 }
8379
8380 #[test]
8381 fn validate_nome_rejects_dot() {
8382 // A `:nome` is a single DNS-1123 label, not a subdomain. The
8383 // "I want to namespace with `.`" footgun the gate redirects to
8384 // `-` via the shared predicate's reason wording.
8385 let c = caixa_with_nome("team.app");
8386 let err = c.validate_nome().unwrap_err();
8387 assert!(
8388 matches!(
8389 err,
8390 ManifestError::NomeInvalid { ref nome, ref reason }
8391 if nome == "team.app" && reason.contains('.')
8392 ),
8393 "got {err:?}"
8394 );
8395 }
8396
8397 #[test]
8398 fn validate_nome_rejects_leading_hyphen() {
8399 // DNS-1123 boundary rule: the label must start with an ASCII
8400 // alphanumeric. Pin the leading-`-` arm explicitly.
8401 let c = caixa_with_nome("-app");
8402 let err = c.validate_nome().unwrap_err();
8403 assert!(
8404 matches!(
8405 err,
8406 ManifestError::NomeInvalid { ref nome, .. } if nome == "-app"
8407 ),
8408 "got {err:?}"
8409 );
8410 }
8411
8412 #[test]
8413 fn validate_nome_rejects_trailing_hyphen() {
8414 // Symmetric arm of the boundary rule, pinned separately so a
8415 // future relaxation that only checks the leading position
8416 // surfaces here. Mirrors `rejects_membro_caixa_with_trailing_hyphen`
8417 // and `_with_trailing_hyphen` on the supervisor / aplicacao
8418 // axes.
8419 let c = caixa_with_nome("app-");
8420 let err = c.validate_nome().unwrap_err();
8421 assert!(
8422 matches!(
8423 err,
8424 ManifestError::NomeInvalid { ref nome, .. } if nome == "app-"
8425 ),
8426 "got {err:?}"
8427 );
8428 }
8429
8430 #[test]
8431 fn validate_nome_rejects_unicode() {
8432 // IDN must be pre-encoded as Punycode (`xn--…`); raw Unicode
8433 // bytes are rejected by the K8s apiserver on every name axis.
8434 let c = caixa_with_nome("café");
8435 let err = c.validate_nome().unwrap_err();
8436 assert!(
8437 matches!(
8438 err,
8439 ManifestError::NomeInvalid { ref nome, .. } if nome == "café"
8440 ),
8441 "got {err:?}"
8442 );
8443 }
8444
8445 #[test]
8446 fn validate_nome_rejects_whitespace() {
8447 // The paste-from-sketch / paste-from-spec footgun. Internal
8448 // whitespace is rejected by every K8s name axis.
8449 let c = caixa_with_nome("my app");
8450 let err = c.validate_nome().unwrap_err();
8451 assert!(
8452 matches!(
8453 err,
8454 ManifestError::NomeInvalid { ref nome, .. } if nome == "my app"
8455 ),
8456 "got {err:?}"
8457 );
8458 }
8459
8460 #[test]
8461 fn validate_nome_rejects_too_long() {
8462 // 64-byte boundary pin: the K8s apiserver rejects any
8463 // `metadata.name` over 63 bytes at admission; the diagnostic
8464 // names both the 63-byte cap and the actual length so the
8465 // author can shorten in one edit. Mirrors `_too_long` on the
8466 // peer member-/cluster-/child-name axes.
8467 let over = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN + 1);
8468 let c = caixa_with_nome(&over);
8469 let err = c.validate_nome().unwrap_err();
8470 let ManifestError::NomeInvalid { nome, reason } = err else {
8471 panic!("expected NomeInvalid for over-cap :nome");
8472 };
8473 assert_eq!(nome.len(), crate::DNS_1123_LABEL_MAX_LEN + 1);
8474 assert!(
8475 reason.contains("63") && reason.contains("64"),
8476 "diagnostic must name the cap + actual length, got {reason:?}"
8477 );
8478 }
8479
8480 #[test]
8481 fn nome_max_length_validates() {
8482 // The 63-byte cap exactly — the boundary-accepting case pinned
8483 // alongside `validate_nome_rejects_too_long` so a future cap
8484 // shift surfaces both arms simultaneously. Mirrors
8485 // `membro_caixa_max_length_validates`,
8486 // `placement_cluster_max_length_validates`,
8487 // `child_caixa_max_length_validates`.
8488 let at_cap = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN);
8489 caixa_with_nome(&at_cap).validate_nome().unwrap();
8490 }
8491
8492 #[test]
8493 fn nome_empty_takes_precedence_over_invalid() {
8494 // Order pin: the empty arm fires before the predicate is
8495 // consulted. Empty < invalid in self-locating-ness — the
8496 // narrower `NomeEmpty` diagnostic doesn't carry a useless
8497 // `nome: ""` reference into the parser-shaped reason. Mirrors
8498 // `membro_caixa_empty_takes_precedence_over_invalid` on the
8499 // peer axis (3f9d7a0).
8500 let c = caixa_with_nome("");
8501 assert_eq!(c.validate_nome().unwrap_err(), ManifestError::NomeEmpty);
8502 }
8503
8504 #[test]
8505 fn nome_invalid_diagnostic_carries_offending_nome() {
8506 // Diagnostic-shape pin: the error names the offending `:nome`
8507 // verbatim with a non-empty parser-shaped reason, so a `feira
8508 // lint` run can render the diagnostic without re-parsing.
8509 // Mirrors `membro_caixa_invalid_diagnostic_carries_offending_caixa`.
8510 let c = caixa_with_nome("MyApp");
8511 let err = c.validate_nome().unwrap_err();
8512 let ManifestError::NomeInvalid { nome, reason } = err else {
8513 panic!("expected NomeInvalid variant");
8514 };
8515 assert_eq!(nome, "MyApp");
8516 assert!(
8517 !reason.is_empty(),
8518 "NomeInvalid `reason` must carry the predicate's wording verbatim"
8519 );
8520 }
8521
8522 // ── Caixa::validate_nome_chart_name_budget — joint-length on `:nome` ──
8523 //
8524 // The bare-`:nome` axis [`Caixa::validate_nome`] caps at 63 bytes
8525 // via DNS-1123; this second-axis gate caps the joint
8526 // `lareira-<nome>` chart name at the same 63-byte ceiling. The
8527 // canonical [`crate::lareira_chart_name`] helper's doc comment
8528 // (f7320d7, caixa-core/src/render.rs:3198) explicitly deferred:
8529 // "the M4 admission webhook will pin the joint-length invariant
8530 // when it lands". These tests pin it at the manifest-validate
8531 // layer instead, fail-before-pass-after on the 56-byte boundary.
8532
8533 #[test]
8534 fn validate_nome_chart_name_budget_accepts_canonical_template() {
8535 // Positive control: the bare `feira init`-style template's
8536 // `:nome` ("demo") sits far below the cap; the gate must not
8537 // regress this baseline. Same shape every peer
8538 // value-shape-gate baseline pin uses.
8539 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8540 c.validate_nome_chart_name_budget().unwrap();
8541 }
8542
8543 #[test]
8544 fn validate_nome_chart_name_budget_accepts_canonical_fixtures() {
8545 // Positive-set sweep across the canonical author surface every
8546 // in-tree fixture uses (`hello-rio`, `cart`, `checkout`,
8547 // `worker`, the `checkout-aplicacao` example members, the
8548 // `example-attest` caixa-tatara fixture). Every value sits
8549 // far below the 55-byte per-`:nome` budget. Same shape every
8550 // peer per-axis baseline pin uses.
8551 for nome in [
8552 "hello-rio",
8553 "cart",
8554 "checkout",
8555 "worker",
8556 "example-attest",
8557 "demo",
8558 "a",
8559 ] {
8560 caixa_with_nome(nome)
8561 .validate_nome_chart_name_budget()
8562 .unwrap_or_else(|e| {
8563 panic!("canonical :nome {nome:?} must pass chart-name budget, got {e:?}")
8564 });
8565 }
8566 }
8567
8568 #[test]
8569 fn validate_nome_chart_name_budget_accepts_nome_at_cap() {
8570 // Boundary-accepting case at the 55-byte per-`:nome` budget —
8571 // the joint chart name is exactly 63 bytes, the DNS-1123 label
8572 // cap. Pinned alongside the rejecting-arm test so a future cap
8573 // shift surfaces both arms simultaneously. Mirrors
8574 // `nome_max_length_validates` on the peer bare-`:nome` axis.
8575 let at_cap = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN);
8576 caixa_with_nome(&at_cap)
8577 .validate_nome_chart_name_budget()
8578 .unwrap();
8579 }
8580
8581 #[test]
8582 fn validate_nome_chart_name_budget_rejects_nome_one_over_cap() {
8583 // Fail-before-pass-after pin on the 56-byte boundary: the
8584 // smallest `:nome` length that overflows the joint chart-name
8585 // cap. The inner [`is_dns_1123_label`] gate
8586 // (`Caixa::validate_nome`) accepts it (56 ≤ 63), so prior to
8587 // this gate it silently passed the manifest-validate cascade
8588 // and surfaced as a `helm lint` / apiserver rejection on the
8589 // rendered chart name far from the source `caixa.lisp`, with
8590 // no field naming the overflow. With this gate the diagnostic
8591 // names the offending `:nome` verbatim alongside the rendered
8592 // chart name and the budget, so the author can shorten in one
8593 // edit. Mirrors `validate_nome_rejects_too_long` on the peer
8594 // bare-`:nome` axis.
8595 let over = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
8596 let c = caixa_with_nome(&over);
8597 let err = c.validate_nome_chart_name_budget().unwrap_err();
8598 let ManifestError::NomeChartNameBudgetExceeded { nome, reason } = err else {
8599 panic!("expected NomeChartNameBudgetExceeded for over-budget :nome");
8600 };
8601 assert_eq!(nome.len(), crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
8602 assert_eq!(nome, over);
8603 assert!(
8604 reason.contains("63") && reason.contains("64") && reason.contains("55"),
8605 "diagnostic must name the DNS-1123 cap (63), the actual chart-name length (64), \
8606 and the per-`:nome` budget (55), got {reason:?}"
8607 );
8608 }
8609
8610 #[test]
8611 fn validate_nome_chart_name_budget_rejects_nome_at_bare_dns_cap() {
8612 // The 63-byte `:nome` boundary — passes the bare-`:nome`
8613 // [`is_dns_1123_label`] cap exactly, but produces a 71-byte
8614 // joint chart name that overflows the DNS-1123 label cap
8615 // structurally. The most stringent fail-before-pass-after
8616 // surface: every `:nome` in the 56..=63-byte range passed the
8617 // prior cascade and broke at admission.
8618 let bare_max = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN);
8619 let c = caixa_with_nome(&bare_max);
8620 // The bare-`:nome` gate accepts the 63-byte length.
8621 c.validate_nome().unwrap();
8622 // The new joint-length gate rejects it.
8623 let err = c.validate_nome_chart_name_budget().unwrap_err();
8624 assert!(
8625 matches!(
8626 err,
8627 ManifestError::NomeChartNameBudgetExceeded { ref nome, .. }
8628 if nome.len() == crate::DNS_1123_LABEL_MAX_LEN
8629 ),
8630 "got {err:?}"
8631 );
8632 }
8633
8634 #[test]
8635 fn validate_nome_chart_name_budget_diagnostic_carries_offending_chart_name() {
8636 // Diagnostic-shape pin: the rendered `lareira-<nome>` chart
8637 // name appears verbatim in the diagnostic so the author sees
8638 // exactly the string the apiserver / `helm lint` would have
8639 // rejected — no re-derivation required to grep the source.
8640 // Peer with `nome_invalid_diagnostic_carries_offending_nome`
8641 // on the bare-`:nome` axis.
8642 let over = "x".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 5);
8643 let c = caixa_with_nome(&over);
8644 let err = c.validate_nome_chart_name_budget().unwrap_err();
8645 let ManifestError::NomeChartNameBudgetExceeded { nome, reason } = err else {
8646 panic!("expected NomeChartNameBudgetExceeded variant");
8647 };
8648 assert_eq!(nome, over);
8649 let expected_chart = crate::lareira_chart_name(&over);
8650 assert!(
8651 reason.contains(&expected_chart),
8652 "diagnostic must carry the rendered chart name {expected_chart:?} verbatim, \
8653 got {reason:?}"
8654 );
8655 assert!(
8656 reason.contains("lareira-"),
8657 "diagnostic must name the canonical chart-name prefix verbatim, got {reason:?}"
8658 );
8659 }
8660
8661 #[test]
8662 fn validate_nome_chart_name_budget_runs_after_nome_shape_via_layout_verify() {
8663 // Order pin on the layout cascade: the narrower
8664 // `NomeInvalid` (bare-DNS-1123 shape) fires before the
8665 // joint-length budget. A structurally-malformed `:nome` (here:
8666 // uppercase) surfaces its specific shape error rather than
8667 // the chart-name-budget error, even when the joint length
8668 // would also overflow — the narrower diagnostic is more
8669 // self-locating. Mirrors the cascade-precedence pins peer
8670 // gates already use (e.g. `EntradaParaEmpty` before
8671 // `EntradaParaInvalid`).
8672 let over = "A".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
8673 let c = caixa_with_nome(&over);
8674 // The bare-shape gate fires first.
8675 let err = c.validate_nome().unwrap_err();
8676 assert!(
8677 matches!(err, ManifestError::NomeInvalid { .. }),
8678 "bare-shape gate must fire before chart-name-budget gate; got {err:?}"
8679 );
8680 // And the layout verify cascade surfaces that diagnostic, not
8681 // the budget arm. Inject a path-exists oracle so the cascade
8682 // gets past the manifest-presence check and into the
8683 // value-shape gates.
8684 let layout = crate::StandardLayout::new().with_path_exists(|_| true);
8685 let err = crate::LayoutInvariants::verify(
8686 &layout,
8687 &c,
8688 std::path::Path::new("/tmp/caixa-test-fake-root"),
8689 )
8690 .unwrap_err();
8691 let issue = err.to_string();
8692 assert!(
8693 issue.contains("DNS-1123") || issue.contains("uppercase"),
8694 "layout cascade must surface the bare-DNS-1123 diagnostic on a \
8695 structurally-malformed :nome, not the chart-name-budget diagnostic; got {issue:?}"
8696 );
8697 }
8698
8699 #[test]
8700 fn layout_verify_routes_chart_name_budget_through_nome_violation() {
8701 // Cross-axis envelope pin: the layout cascade wraps both
8702 // bare-`:nome` and joint-length-`:nome` failures through the
8703 // same [`LayoutError::NomeViolation`] envelope, since both
8704 // arms are on the `:nome` axis. The user's diagnostic stays
8705 // self-locating ("which axis"), and a future consumer that
8706 // dispatches on the layout-error variant (e.g. a `feira lint`
8707 // exit-code mapping) sees a single per-axis envelope. The
8708 // wrapped `issue:` carries the full inner diagnostic.
8709 let over = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
8710 let c = caixa_with_nome(&over);
8711 // The bare-shape gate accepts.
8712 c.validate_nome().unwrap();
8713 let layout = crate::StandardLayout::new().with_path_exists(|_| true);
8714 let err = crate::LayoutInvariants::verify(
8715 &layout,
8716 &c,
8717 std::path::Path::new("/tmp/caixa-test-fake-root"),
8718 )
8719 .unwrap_err();
8720 let crate::LayoutError::NomeViolation { caixa, issue } = err else {
8721 panic!("expected LayoutError::NomeViolation, got {err:?}");
8722 };
8723 assert_eq!(caixa, over);
8724 assert!(
8725 issue.contains("lareira-") && issue.contains("63") && issue.contains("55"),
8726 "wrapped issue must carry the joint-length diagnostic verbatim, got {issue:?}"
8727 );
8728 }
8729
8730 // ── Caixa::validate_versao — top-level :versao value-shape gate ─────
8731
8732 fn caixa_with_versao(versao: &str) -> Caixa {
8733 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8734 c.versao = versao.to_string();
8735 c
8736 }
8737
8738 #[test]
8739 fn validate_versao_accepts_canonical_template() {
8740 // Positive control: the bare `feira init`-style template's
8741 // `:versao` ("0.1.0") is a canonical SemVer-2 literal; the gate
8742 // must not regress this baseline shape. A future tightening of
8743 // the accepted set surfaces here as a test failure first.
8744 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8745 c.validate_versao().unwrap();
8746 }
8747
8748 #[test]
8749 fn validate_versao_accepts_canonical_forms() {
8750 // Positive-set sweep: each realistic SemVer-2 shape the
8751 // substrate's downstream consumers accept must pass — bare
8752 // MAJOR.MINOR.PATCH, pre-release tags (`-rc.1`, `-alpha.0`),
8753 // build metadata (`+build.42`), the combined form, and the
8754 // `0.0.0` boundary case. Mirrors `accepts_canonical_forms` on
8755 // the peer `:nome` axis (6c992f8).
8756 for versao in [
8757 "0.1.0",
8758 "0.0.0",
8759 "1.0.0",
8760 "0.2.0-rc.1",
8761 "1.0.0-alpha.0",
8762 "1.0.0+build.42",
8763 "1.0.0-rc.1+build.42",
8764 "10.20.30",
8765 ] {
8766 caixa_with_versao(versao)
8767 .validate_versao()
8768 .unwrap_or_else(|e| {
8769 panic!("canonical :versao {versao:?} must validate, got {e:?}")
8770 });
8771 }
8772 }
8773
8774 #[test]
8775 fn validate_versao_rejects_empty() {
8776 // Fail-before-pass-after pin: `Caixa::from_lisp` does not refuse
8777 // an empty `:versao` (the derive macro stores the raw String);
8778 // the gate's empty arm names the offending axis with a narrower
8779 // diagnostic than the `VersaoInvalid` parse arm would emit.
8780 // Mirrors `validate_nome_rejects_empty` (6c992f8).
8781 let c = caixa_with_versao("");
8782 let err = c.validate_versao().unwrap_err();
8783 assert_eq!(err, ManifestError::VersaoEmpty);
8784 }
8785
8786 #[test]
8787 fn validate_versao_rejects_git_tag_shape() {
8788 // The canonical "I copied the git tag verbatim" footgun —
8789 // `feira publish` *emits* `v<versao>` git tags, so a leaked
8790 // `v0.1.0` in `:versao` would render as `vv0.1.0` and silently
8791 // shift every downstream consumer's version axis. `semver`
8792 // rejects the leading `v` at parse time; the gate moves the
8793 // diagnostic to the source `caixa.lisp`.
8794 let c = caixa_with_versao("v0.1.0");
8795 let err = c.validate_versao().unwrap_err();
8796 let ManifestError::VersaoInvalid { versao, reason } = err else {
8797 panic!("expected VersaoInvalid for git-tag-shape :versao");
8798 };
8799 assert_eq!(versao, "v0.1.0");
8800 assert!(
8801 !reason.is_empty(),
8802 "VersaoInvalid `reason` must carry the parser's wording, got {reason:?}"
8803 );
8804 }
8805
8806 #[test]
8807 fn validate_versao_rejects_missing_patch() {
8808 // The canonical "I shortened it" footgun — SemVer-2 requires
8809 // three parts. Cargo's `version =` field accepts the shortened
8810 // form as a requirement, conflating the two leaks across the
8811 // typed `:deps :versao` vs top-level `:versao` axes; the gate
8812 // pins the top-level axis to the strict three-part shape.
8813 let c = caixa_with_versao("0.1");
8814 let err = c.validate_versao().unwrap_err();
8815 assert!(
8816 matches!(
8817 err,
8818 ManifestError::VersaoInvalid { ref versao, .. } if versao == "0.1"
8819 ),
8820 "got {err:?}"
8821 );
8822 }
8823
8824 #[test]
8825 fn validate_versao_rejects_requirement_shape() {
8826 // The canonical "I leaked a requirement into a version" footgun —
8827 // the typed `:deps :versao` / `:membros :versao` axes accept
8828 // `^0.1` (a `VersionReq`); the top-level `:versao` requires a
8829 // concrete `Version`. Without this gate the two typed surfaces
8830 // would silently overlap, and a top-level `^0.1` would surface
8831 // at `helm install` time as a Chart.yaml version rejection far
8832 // from the source `caixa.lisp`.
8833 let c = caixa_with_versao("^0.1");
8834 let err = c.validate_versao().unwrap_err();
8835 assert!(
8836 matches!(
8837 err,
8838 ManifestError::VersaoInvalid { ref versao, .. } if versao == "^0.1"
8839 ),
8840 "got {err:?}"
8841 );
8842 }
8843
8844 #[test]
8845 fn validate_versao_rejects_docker_tag_shape() {
8846 // The "I confused it with a docker tag" footgun — `latest`,
8847 // `main`, `stable` parse as identifiers, not SemVer-2 versions.
8848 // SemVer rejects at parse time; the gate moves the diagnostic
8849 // to the source `caixa.lisp`.
8850 for bad in ["latest", "main", "stable"] {
8851 let c = caixa_with_versao(bad);
8852 let err = c.validate_versao().unwrap_err();
8853 assert!(
8854 matches!(
8855 err,
8856 ManifestError::VersaoInvalid { ref versao, .. } if versao == bad
8857 ),
8858 "got {err:?} for {bad:?}"
8859 );
8860 }
8861 }
8862
8863 #[test]
8864 fn validate_versao_rejects_four_part_form() {
8865 // The Java/Microsoft "MAJOR.MINOR.PATCH.BUILD" convention
8866 // SemVer-2 forbids. A leak from a non-SemVer ecosystem; the
8867 // semver crate rejects the extra `.0` at parse time.
8868 let c = caixa_with_versao("0.1.0.0");
8869 let err = c.validate_versao().unwrap_err();
8870 assert!(
8871 matches!(
8872 err,
8873 ManifestError::VersaoInvalid { ref versao, .. } if versao == "0.1.0.0"
8874 ),
8875 "got {err:?}"
8876 );
8877 }
8878
8879 #[test]
8880 fn versao_empty_takes_precedence_over_invalid() {
8881 // Order pin: the empty arm fires before the parser is consulted.
8882 // Empty < invalid in self-locating-ness — the narrower
8883 // `VersaoEmpty` diagnostic doesn't carry a useless `versao: ""`
8884 // reference into the parser-shaped reason. Mirrors
8885 // `nome_empty_takes_precedence_over_invalid` (6c992f8) on the
8886 // peer axis.
8887 let c = caixa_with_versao("");
8888 assert_eq!(c.validate_versao().unwrap_err(), ManifestError::VersaoEmpty);
8889 }
8890
8891 #[test]
8892 fn versao_invalid_diagnostic_carries_offending_versao() {
8893 // Diagnostic-shape pin: the error names the offending `:versao`
8894 // verbatim with a non-empty parser-shaped reason, so a `feira
8895 // lint` run can render the diagnostic without re-parsing.
8896 // Mirrors `nome_invalid_diagnostic_carries_offending_nome`.
8897 let c = caixa_with_versao("v0.1.0");
8898 let err = c.validate_versao().unwrap_err();
8899 let ManifestError::VersaoInvalid { versao, reason } = err else {
8900 panic!("expected VersaoInvalid variant");
8901 };
8902 assert_eq!(versao, "v0.1.0");
8903 assert!(
8904 !reason.is_empty(),
8905 "VersaoInvalid `reason` must carry the parser's wording verbatim"
8906 );
8907 }
8908
8909 #[test]
8910 fn validate_versao_accepts_what_upgrade_from_from_accepts() {
8911 // Parity pin: every shape `UpgradeFromEntry::validate` accepts
8912 // for `:upgrade-from :from` must also pass `validate_versao` —
8913 // the two `:versao`-typed surfaces (top-level `:versao`,
8914 // `:upgrade-from :from`) consume the *same* `semver::Version`
8915 // parser, so they must agree on the accepted set. Without this
8916 // pin, a future tightening of one axis could silently diverge
8917 // from the other. Mirrors the `:versao` requirement-axis
8918 // parity (`:deps`/`:deps-dev`/`:membros`/`:children`) the prior
8919 // commits established.
8920 for versao in ["0.1.0", "0.2.0-rc.1", "1.0.0+build.42"] {
8921 // From the canonical UpgradeFromEntry round-trip fixture
8922 // (`upgrade::tests::round_trip_load_module` peers).
8923 let entry = crate::UpgradeFromEntry {
8924 from: versao.to_string(),
8925 instructions: Vec::new(),
8926 };
8927 entry
8928 .validate()
8929 .unwrap_or_else(|e| panic!(":from {versao:?} must validate, got {e:?}"));
8930 caixa_with_versao(versao)
8931 .validate_versao()
8932 .unwrap_or_else(|e| {
8933 panic!(":versao {versao:?} must validate, got {e:?} — peer axis diverges")
8934 });
8935 }
8936 }
8937
8938 // ── Caixa::validate_restart_window — supervisor restart-window
8939 // folds through the shared `supervisor::duration_codec` ────────
8940
8941 fn caixa_with_restart_window(window: Option<&str>) -> Caixa {
8942 let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
8943 c.kind = CaixaKind::Supervisor;
8944 c.restart_window = window.map(str::to_string);
8945 c
8946 }
8947
8948 #[test]
8949 fn validate_restart_window_accepts_none() {
8950 // The canonical "omit the slot to express no reset" shape — a
8951 // `None` raw string is the absence of the typed
8952 // `:restart-window` slot, which is exactly the SupervisorSpec
8953 // "never reset" semantics. The gate must be a no-op here; a
8954 // future tightening that rejected `None` would force every
8955 // supervisor caixa to authoring-time pin a window even when
8956 // the OTP semantics call for none.
8957 caixa_with_restart_window(None)
8958 .validate_restart_window()
8959 .unwrap();
8960 }
8961
8962 #[test]
8963 fn validate_restart_window_accepts_canonical_forms() {
8964 // Positive-set sweep across the canonical authoring units the
8965 // shared `supervisor::duration_codec::parse` accepts —
8966 // matches the codec-side `parse_accepts_integer_canonical_units`
8967 // pin in supervisor::tests so a future codec-side tightening
8968 // surfaces simultaneously on both axes.
8969 for window in ["60s", "5m", "1h", "500ms", "30", "0s"] {
8970 caixa_with_restart_window(Some(window))
8971 .validate_restart_window()
8972 .unwrap_or_else(|e| {
8973 panic!("canonical :restart-window {window:?} must validate, got {e:?}")
8974 });
8975 }
8976 }
8977
8978 #[test]
8979 fn validate_restart_window_rejects_fractional_seconds() {
8980 // Fail-before-pass-after pin: the `"1.5s"` drift class (parses
8981 // as f64 to 1.5 → renders back as `"1500ms"` on first
8982 // serialize). Prior to the fold + this gate, the inline
8983 // `parse_window_inline` accepted f64 magnitudes and silently
8984 // produced a `Duration::from_secs_f64(1.5)`, divergent from
8985 // the shared codec's integer-magnitude discipline on the
8986 // serde-routed siblings. The gate now surfaces a self-locating
8987 // diagnostic at the manifest layer.
8988 let err = caixa_with_restart_window(Some("1.5s"))
8989 .validate_restart_window()
8990 .unwrap_err();
8991 let ManifestError::RestartWindowMalformed {
8992 restart_window,
8993 reason,
8994 } = err
8995 else {
8996 panic!("expected RestartWindowMalformed for fractional seconds");
8997 };
8998 assert_eq!(restart_window, "1.5s");
8999 assert!(
9000 reason.contains("\"1.5\"") && reason.contains("not a non-negative integer"),
9001 "diagnostic must carry shared-codec wording, got {reason:?}"
9002 );
9003 }
9004
9005 #[test]
9006 fn validate_restart_window_rejects_decimal_shaped_integer() {
9007 // The `"1.0s"` class — numerically `1s` exactly, but the
9008 // canonical form is `"1s"` not `"1.0s"`. Decimal-shape leak
9009 // gets the same canonical-form diagnostic.
9010 let err = caixa_with_restart_window(Some("1.0s"))
9011 .validate_restart_window()
9012 .unwrap_err();
9013 assert!(
9014 matches!(
9015 err,
9016 ManifestError::RestartWindowMalformed { ref restart_window, .. }
9017 if restart_window == "1.0s"
9018 ),
9019 "got {err:?}"
9020 );
9021 }
9022
9023 #[test]
9024 fn validate_restart_window_rejects_half_unit_minute() {
9025 // `"0.5m"` is the unit-fraction footgun — author writes a
9026 // human-readable half-minute, the prior inline parser silently
9027 // produced `Duration::from_secs_f64(30.0)` and serde
9028 // re-emitted as `"30s"`, rewriting author intent. The gate
9029 // closes the loop at the manifest layer.
9030 let err = caixa_with_restart_window(Some("0.5m"))
9031 .validate_restart_window()
9032 .unwrap_err();
9033 let ManifestError::RestartWindowMalformed {
9034 restart_window,
9035 reason,
9036 } = err
9037 else {
9038 panic!("expected RestartWindowMalformed");
9039 };
9040 assert_eq!(restart_window, "0.5m");
9041 assert!(
9042 reason.contains("\"30s\""),
9043 "diagnostic must point at the canonical-form remediation, got {reason:?}"
9044 );
9045 }
9046
9047 #[test]
9048 fn validate_restart_window_rejects_leading_sign() {
9049 // `"+30s"` and `"-30s"` both round-tripped through f64 cleanly
9050 // on the prior parser (`+30` parses as `30.0`; `-30` parsed
9051 // and was caught by the `num < 0.0` arm which silently
9052 // returned `None`, dropping the author-supplied window). The
9053 // shared codec's digit-only gate rejects both with a unified
9054 // canonical-form diagnostic; the manifest-layer wrapper names
9055 // the offending value.
9056 for bad in ["+30s", "-30s"] {
9057 let err = caixa_with_restart_window(Some(bad))
9058 .validate_restart_window()
9059 .unwrap_err();
9060 assert!(
9061 matches!(
9062 err,
9063 ManifestError::RestartWindowMalformed { ref restart_window, .. }
9064 if restart_window == bad
9065 ),
9066 "got {err:?} for {bad:?}"
9067 );
9068 }
9069 }
9070
9071 #[test]
9072 fn validate_restart_window_rejects_unknown_unit() {
9073 // `"30x"` — the typo / wrong-unit footgun. The shared codec's
9074 // unit dispatch surfaces an `unknown duration unit` reason;
9075 // the manifest-layer wrapper names the offending value.
9076 let err = caixa_with_restart_window(Some("30x"))
9077 .validate_restart_window()
9078 .unwrap_err();
9079 let ManifestError::RestartWindowMalformed {
9080 restart_window,
9081 reason,
9082 } = err
9083 else {
9084 panic!("expected RestartWindowMalformed for unknown unit");
9085 };
9086 assert_eq!(restart_window, "30x");
9087 assert!(
9088 reason.contains("unknown duration unit"),
9089 "diagnostic must carry shared-codec unit-rejection wording, got {reason:?}"
9090 );
9091 }
9092
9093 #[test]
9094 fn validate_restart_window_rejects_garbage() {
9095 // Pure non-numeric magnitude (`"abc"`) falls through to the
9096 // shared codec's narrower `"bad duration magnitude"` arm. Same
9097 // diagnostic shape as the codec-side
9098 // `parse_garbage_still_falls_through_to_bad_magnitude` pin.
9099 let err = caixa_with_restart_window(Some("abc"))
9100 .validate_restart_window()
9101 .unwrap_err();
9102 let ManifestError::RestartWindowMalformed {
9103 restart_window,
9104 reason,
9105 } = err
9106 else {
9107 panic!("expected RestartWindowMalformed for garbage");
9108 };
9109 assert_eq!(restart_window, "abc");
9110 assert!(
9111 reason.contains("bad duration magnitude"),
9112 "diagnostic must carry shared-codec garbage-rejection wording, got {reason:?}"
9113 );
9114 }
9115
9116 #[test]
9117 fn validate_restart_window_rejects_empty_string() {
9118 // The empty-after-trim edge case — distinct from the `None`
9119 // canonical "omit the slot" shape. The shared codec's
9120 // digit-only gate refuses an empty magnitude; the manifest
9121 // layer names the offending `""` so the author can grep for
9122 // the literal empty value in their `caixa.lisp` and either
9123 // remove the slot (the canonical "no reset" shape) or pin a
9124 // positive duration.
9125 let err = caixa_with_restart_window(Some(""))
9126 .validate_restart_window()
9127 .unwrap_err();
9128 assert!(
9129 matches!(
9130 err,
9131 ManifestError::RestartWindowMalformed { ref restart_window, .. }
9132 if restart_window.is_empty()
9133 ),
9134 "got {err:?}"
9135 );
9136 }
9137
9138 #[test]
9139 fn validate_restart_window_diagnostic_carries_offending_value() {
9140 // Diagnostic-shape pin (peer with
9141 // `nome_invalid_diagnostic_carries_offending_nome` /
9142 // `versao_invalid_diagnostic_carries_offending_versao`): the
9143 // error names the offending raw `:restart-window` verbatim
9144 // with a non-empty shared-codec-shaped reason, so a `feira
9145 // lint` run can render the diagnostic without re-parsing.
9146 let err = caixa_with_restart_window(Some("1.5s"))
9147 .validate_restart_window()
9148 .unwrap_err();
9149 let ManifestError::RestartWindowMalformed {
9150 restart_window,
9151 reason,
9152 } = err
9153 else {
9154 panic!("expected RestartWindowMalformed variant");
9155 };
9156 assert_eq!(restart_window, "1.5s");
9157 assert!(
9158 !reason.is_empty(),
9159 "RestartWindowMalformed `reason` must carry the codec's wording verbatim"
9160 );
9161 }
9162
9163 #[test]
9164 fn supervisor_view_folds_through_shared_codec_on_canonical_form() {
9165 // Behavioral parity pin after the fold (`parse_window_inline`
9166 // deletion): the canonical `"60s"` still produces
9167 // `Duration::from_secs(60)` on the typed view — the fold is
9168 // semantically equivalent to the prior inline parser on the
9169 // accepted set. Mirrors the pre-fold `supervisor_view_returns_typed_shape`
9170 // pin, narrowed to the parser-side contract.
9171 let c = caixa_with_restart_window(Some("60s"));
9172 let view = c.supervisor_view().expect("Supervisor kind has a view");
9173 assert_eq!(
9174 view.restart_window,
9175 Some(std::time::Duration::from_secs(60))
9176 );
9177 }
9178
9179 #[test]
9180 fn supervisor_view_soft_swallows_what_validate_rejects() {
9181 // Parity pin between the view-construction path and the
9182 // manifest-level validator: the same `"1.5s"` that surfaces
9183 // `RestartWindowMalformed` at `validate_restart_window` time
9184 // becomes `restart_window: None` on the typed view (the fold
9185 // preserves the existing best-effort shape of `supervisor_view`).
9186 // The contract is: a layout-verifier / `feira lint` flow that
9187 // cares about the malformed-window axis MUST consult
9188 // `validate_restart_window` — relying solely on the view's
9189 // `None` swallows the diagnostic silently. This pin makes the
9190 // expectation a typed invariant.
9191 let c = caixa_with_restart_window(Some("1.5s"));
9192 let view = c.supervisor_view().expect("Supervisor kind has a view");
9193 assert_eq!(
9194 view.restart_window, None,
9195 "view-construction path soft-swallows the parse error to None"
9196 );
9197 // And the manifest-level validator does NOT soft-swallow:
9198 assert!(
9199 matches!(
9200 c.validate_restart_window().unwrap_err(),
9201 ManifestError::RestartWindowMalformed { ref restart_window, .. }
9202 if restart_window == "1.5s"
9203 ),
9204 "validator must surface the offending value",
9205 );
9206 }
9207
9208 // ── validate_code_paths — per-entry shape on :bibliotecas / :exe / :servicos ──
9209
9210 fn caixa_with_code_paths(bibliotecas: Vec<&str>, exe: Vec<&str>, servicos: Vec<&str>) -> Caixa {
9211 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9212 c.bibliotecas = bibliotecas.into_iter().map(String::from).collect();
9213 c.exe = exe.into_iter().map(String::from).collect();
9214 c.servicos = servicos.into_iter().map(String::from).collect();
9215 c
9216 }
9217
9218 #[test]
9219 fn validate_code_paths_accepts_canonical_template() {
9220 // The bare `Caixa::template` shape is the gate's identity element
9221 // on the canonical authoring shape — `:bibliotecas
9222 // ("lib/demo.lisp")` + empty `:exe` + empty `:servicos`. Pins
9223 // that the gate is non-disruptive against every existing caixa.
9224 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9225 c.validate_code_paths().unwrap();
9226 }
9227
9228 #[test]
9229 fn validate_code_paths_accepts_explicit_relative_paths_on_every_slot() {
9230 // Positive control sweep: a canonical-shaped path on every slot
9231 // passes. Mirrors the peer
9232 // `behavior::validate_every_slot_relative_is_ok` pin.
9233 let c = caixa_with_code_paths(
9234 vec!["lib/demo.lisp", "lib/helpers.lisp"],
9235 vec!["exe/demo", "exe/tool"],
9236 vec!["servicos/demo.computeunit.yaml"],
9237 );
9238 c.validate_code_paths().unwrap();
9239 }
9240
9241 #[test]
9242 fn validate_code_paths_accepts_all_empty_lists() {
9243 // The empty-list identity element: every Caixa with no declared
9244 // code paths trivially passes (Supervisor / Aplicacao kinds rely
9245 // on this — the OwnCode gate already rejected them before the
9246 // path-shape gate runs in the layout, but the validator itself
9247 // must accept the empty shape).
9248 let c = caixa_with_code_paths(vec![], vec![], vec![]);
9249 c.validate_code_paths().unwrap();
9250 }
9251
9252 #[test]
9253 fn validate_code_paths_rejects_empty_bibliotecas_entry() {
9254 let c = caixa_with_code_paths(vec![""], vec![], vec![]);
9255 let err = c.validate_code_paths().unwrap_err();
9256 assert!(
9257 matches!(
9258 err,
9259 ManifestError::CodePathEmpty {
9260 slot: ":bibliotecas"
9261 }
9262 ),
9263 "got {err:?}",
9264 );
9265 }
9266
9267 #[test]
9268 fn validate_code_paths_rejects_empty_exe_entry() {
9269 let c = caixa_with_code_paths(vec![], vec![""], vec![]);
9270 let err = c.validate_code_paths().unwrap_err();
9271 assert!(
9272 matches!(err, ManifestError::CodePathEmpty { slot: ":exe" }),
9273 "got {err:?}",
9274 );
9275 }
9276
9277 #[test]
9278 fn validate_code_paths_rejects_empty_servicos_entry() {
9279 let c = caixa_with_code_paths(vec![], vec![], vec![""]);
9280 let err = c.validate_code_paths().unwrap_err();
9281 assert!(
9282 matches!(err, ManifestError::CodePathEmpty { slot: ":servicos" }),
9283 "got {err:?}",
9284 );
9285 }
9286
9287 #[test]
9288 fn validate_code_paths_rejects_absolute_bibliotecas_entry() {
9289 // `:bibliotecas` has no `starts_with(<dir>)` fence downstream,
9290 // so an absolute path that resolves on disk silently passes the
9291 // layout's existence check — the canonical sandbox-escape on
9292 // the biblioteca axis.
9293 let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
9294 let err = c.validate_code_paths().unwrap_err();
9295 let ManifestError::CodePathAbsolute { slot, path } = err else {
9296 panic!("expected CodePathAbsolute, got {err:?}");
9297 };
9298 assert_eq!(slot, ":bibliotecas");
9299 assert_eq!(path, PathBuf::from("/etc/passwd"));
9300 }
9301
9302 #[test]
9303 fn validate_code_paths_rejects_absolute_exe_entry() {
9304 let c = caixa_with_code_paths(vec![], vec!["/usr/bin/env"], vec![]);
9305 let err = c.validate_code_paths().unwrap_err();
9306 let ManifestError::CodePathAbsolute { slot, path } = err else {
9307 panic!("expected CodePathAbsolute, got {err:?}");
9308 };
9309 assert_eq!(slot, ":exe");
9310 assert_eq!(path, PathBuf::from("/usr/bin/env"));
9311 }
9312
9313 #[test]
9314 fn validate_code_paths_rejects_absolute_servicos_entry() {
9315 let c = caixa_with_code_paths(vec![], vec![], vec!["/var/servicos/x.yaml"]);
9316 let err = c.validate_code_paths().unwrap_err();
9317 let ManifestError::CodePathAbsolute { slot, path } = err else {
9318 panic!("expected CodePathAbsolute, got {err:?}");
9319 };
9320 assert_eq!(slot, ":servicos");
9321 assert_eq!(path, PathBuf::from("/var/servicos/x.yaml"));
9322 }
9323
9324 #[test]
9325 fn validate_code_paths_rejects_parent_escape_bibliotecas_leading() {
9326 // Canonical "I want a lib from a sibling caixa" footgun on the
9327 // biblioteca axis. `:bibliotecas` has no `starts_with` fence
9328 // downstream, so a leading `..` traverses to the parent of the
9329 // caixa root with no diagnostic at layout time if the resolved
9330 // target exists.
9331 let c = caixa_with_code_paths(vec!["../sibling/x.lisp"], vec![], vec![]);
9332 let err = c.validate_code_paths().unwrap_err();
9333 let ManifestError::CodePathParentEscape { slot, path } = err else {
9334 panic!("expected CodePathParentEscape, got {err:?}");
9335 };
9336 assert_eq!(slot, ":bibliotecas");
9337 assert_eq!(path, PathBuf::from("../sibling/x.lisp"));
9338 }
9339
9340 #[test]
9341 fn validate_code_paths_rejects_parent_escape_exe_mid_path() {
9342 // Mid-path `..` defeats the layout's component-aware
9343 // `starts_with(exe_dir)` fence — `root.join("exe/../../escape")`
9344 // `starts_with(<root>/exe)` is true, but the canonical resolution
9345 // lives outside the caixa root. Caught regardless of where the
9346 // `..` sits — mirrors the peer
9347 // `behavior::validate_rejects_parent_escape_mid_path` pin.
9348 let c = caixa_with_code_paths(vec![], vec!["exe/../../escape"], vec![]);
9349 let err = c.validate_code_paths().unwrap_err();
9350 let ManifestError::CodePathParentEscape { slot, path } = err else {
9351 panic!("expected CodePathParentEscape, got {err:?}");
9352 };
9353 assert_eq!(slot, ":exe");
9354 assert_eq!(path, PathBuf::from("exe/../../escape"));
9355 }
9356
9357 #[test]
9358 fn validate_code_paths_rejects_parent_escape_servicos_trailing() {
9359 let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/foo/../../escape.yaml"]);
9360 let err = c.validate_code_paths().unwrap_err();
9361 let ManifestError::CodePathParentEscape { slot, path } = err else {
9362 panic!("expected CodePathParentEscape, got {err:?}");
9363 };
9364 assert_eq!(slot, ":servicos");
9365 assert_eq!(path, PathBuf::from("servicos/foo/../../escape.yaml"));
9366 }
9367
9368 #[test]
9369 fn validate_code_paths_cross_slot_precedence_bibliotecas_before_exe_before_servicos() {
9370 // Cross-slot precedence pin: `:bibliotecas` → `:exe` →
9371 // `:servicos`. A manifest with malformed entries on all three
9372 // surfaces surfaces the `:bibliotecas` defect first, mirroring
9373 // the canonical declaration order
9374 // `Caixa::declared_foreign_code_slots` already establishes for
9375 // the foreign-code-slot diagnostic.
9376 let c = caixa_with_code_paths(vec![""], vec![""], vec![""]);
9377 let err = c.validate_code_paths().unwrap_err();
9378 assert!(
9379 matches!(
9380 err,
9381 ManifestError::CodePathEmpty {
9382 slot: ":bibliotecas"
9383 }
9384 ),
9385 "got {err:?}",
9386 );
9387 }
9388
9389 #[test]
9390 fn validate_code_paths_within_slot_precedence_empty_before_absolute_before_parent_escape() {
9391 // Within-slot precedence pin: empty → absolute → parent-escape,
9392 // matching the [`PathShapeViolation`] arm-ordering every peer
9393 // `is_sandboxed_relative_path` caller follows (b0c8389
9394 // BehaviorSpec, 26da2c7 UpgradeInstruction::StateChange). A
9395 // `:bibliotecas` list whose first entry is empty *and* whose
9396 // later entries are absolute/parent-escape surfaces the empty
9397 // arm first, on the lexicographically-earliest offending entry.
9398 let c = caixa_with_code_paths(vec!["", "/etc/passwd", "../escape.lisp"], vec![], vec![]);
9399 let err = c.validate_code_paths().unwrap_err();
9400 assert!(
9401 matches!(
9402 err,
9403 ManifestError::CodePathEmpty {
9404 slot: ":bibliotecas"
9405 }
9406 ),
9407 "got {err:?}",
9408 );
9409 }
9410
9411 #[test]
9412 fn validate_code_paths_first_offender_per_slot_wins() {
9413 // Within a single slot, the first declaration-order offender
9414 // surfaces — pins that the gate is left-to-right deterministic
9415 // (peer of every `*_first_collision_*` pin on duplicate gates).
9416 let c = caixa_with_code_paths(
9417 vec!["lib/ok.lisp", "/etc/escape", "../also-escape"],
9418 vec![],
9419 vec![],
9420 );
9421 let err = c.validate_code_paths().unwrap_err();
9422 let ManifestError::CodePathAbsolute { slot, path } = err else {
9423 panic!("expected CodePathAbsolute, got {err:?}");
9424 };
9425 assert_eq!(slot, ":bibliotecas");
9426 assert_eq!(path, PathBuf::from("/etc/escape"));
9427 }
9428
9429 #[test]
9430 fn validate_code_paths_diagnostic_carries_offending_slot_and_path() {
9431 // Diagnostic-shape pin (peer with
9432 // `nome_invalid_diagnostic_carries_offending_nome` /
9433 // `versao_invalid_diagnostic_carries_offending_versao`): the
9434 // error's Display surfaces both the offending `:slot` tag and
9435 // the offending path verbatim, so a `feira lint` run can render
9436 // the diagnostic without re-parsing.
9437 let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
9438 let rendered = c.validate_code_paths().unwrap_err().to_string();
9439 assert!(
9440 rendered.contains(":bibliotecas"),
9441 "diagnostic must name the offending slot: {rendered}",
9442 );
9443 assert!(
9444 rendered.contains("/etc/passwd"),
9445 "diagnostic must quote the offending path: {rendered}",
9446 );
9447 }
9448
9449 #[test]
9450 fn validate_code_paths_rejects_duplicate_bibliotecas_entry() {
9451 // Canonical copy-paste-the-wrong-file footgun on the biblioteca
9452 // axis. Without the gate `feira build` re-parses the same lib
9453 // twice, wasting work and silently masking the author's intent
9454 // to declare a *second* biblioteca.
9455 let c = caixa_with_code_paths(vec!["lib/demo.lisp", "lib/demo.lisp"], vec![], vec![]);
9456 let err = c.validate_code_paths().unwrap_err();
9457 let ManifestError::CodePathDuplicate { slot, path } = err else {
9458 panic!("expected CodePathDuplicate, got {err:?}");
9459 };
9460 assert_eq!(slot, ":bibliotecas");
9461 assert_eq!(path, PathBuf::from("lib/demo.lisp"));
9462 }
9463
9464 #[test]
9465 fn validate_code_paths_rejects_duplicate_exe_entry() {
9466 // Same footgun on the Binario surface. The future `caixa-flake`
9467 // emitter that materializes each `:exe` entry as a flake
9468 // `packages.<name>` derivation would collide on the duplicate
9469 // package key — surfaced here at the typed-validate layer with a
9470 // self-locating diagnostic instead.
9471 let c = caixa_with_code_paths(vec![], vec!["exe/cli", "exe/cli"], vec![]);
9472 let err = c.validate_code_paths().unwrap_err();
9473 let ManifestError::CodePathDuplicate { slot, path } = err else {
9474 panic!("expected CodePathDuplicate, got {err:?}");
9475 };
9476 assert_eq!(slot, ":exe");
9477 assert_eq!(path, PathBuf::from("exe/cli"));
9478 }
9479
9480 #[test]
9481 fn validate_code_paths_rejects_duplicate_servicos_entry() {
9482 // Same footgun on the Servico surface. The peer caixa-helm /
9483 // caixa-flux renderers refuse `:servicos.len() != 1` with the
9484 // narrower `UnsupportedServicoCount` diagnostic, but that
9485 // diagnostic surfaces "too many servicos" without naming
9486 // "duplicate entry" — the typed self-locating framing only lands
9487 // at this gate.
9488 let c = caixa_with_code_paths(
9489 vec![],
9490 vec![],
9491 vec![
9492 "servicos/demo.computeunit.yaml",
9493 "servicos/demo.computeunit.yaml",
9494 ],
9495 );
9496 let err = c.validate_code_paths().unwrap_err();
9497 let ManifestError::CodePathDuplicate { slot, path } = err else {
9498 panic!("expected CodePathDuplicate, got {err:?}");
9499 };
9500 assert_eq!(slot, ":servicos");
9501 assert_eq!(path, PathBuf::from("servicos/demo.computeunit.yaml"));
9502 }
9503
9504 #[test]
9505 fn validate_code_paths_accepts_same_path_across_slots() {
9506 // Per-list scope pin: a `:bibliotecas` entry that happens to
9507 // collide with an `:exe` or `:servicos` entry as a *string* is
9508 // not a duplicate by this gate (each list gets its own HashSet),
9509 // mirroring the peer `:deps` ↔ `:deps-dev` per-list scope
9510 // (a `:nome` present in both lists is a legitimate dev-vs-runtime
9511 // shape on the dep axis). The structural `starts_with(<exe |
9512 // servicos>_dir)` fence at layout time prevents the realistic
9513 // cross-slot collision case from existing on disk, but the gate's
9514 // per-list scope is correct independent of that downstream fence.
9515 let c = caixa_with_code_paths(
9516 vec!["lib/x.lisp"],
9517 vec!["exe/x"],
9518 vec!["servicos/x.computeunit.yaml"],
9519 );
9520 c.validate_code_paths().unwrap();
9521 }
9522
9523 #[test]
9524 fn validate_code_paths_duplicate_fires_after_structural_checks_on_same_slot() {
9525 // Within-slot ordering pin: structural defects (empty / absolute
9526 // / parent-escape) fire before the duplicate gate on the same
9527 // slot. A `:bibliotecas ("" "lib/x.lisp" "lib/x.lisp")` shape
9528 // surfaces the narrower `CodePathEmpty` for the empty entry
9529 // first, not the duplicate on the later pair — same arm-ordering
9530 // every peer per-list duplicate gate uses (`:etiquetas` 360a499,
9531 // `:autores` 86c769b, `:deps` 359fba5).
9532 let c = caixa_with_code_paths(vec!["", "lib/x.lisp", "lib/x.lisp"], vec![], vec![]);
9533 let err = c.validate_code_paths().unwrap_err();
9534 assert!(
9535 matches!(
9536 err,
9537 ManifestError::CodePathEmpty {
9538 slot: ":bibliotecas"
9539 }
9540 ),
9541 "got {err:?}",
9542 );
9543 }
9544
9545 #[test]
9546 fn validate_code_paths_duplicate_in_bibliotecas_fires_before_duplicate_in_exe() {
9547 // Cross-slot ordering pin on the duplicate arm: `:bibliotecas`
9548 // duplicates surface before `:exe` duplicates, matching the
9549 // canonical `:bibliotecas` → `:exe` → `:servicos` declaration
9550 // order every peer per-slot diagnostic on this surface follows.
9551 let c = caixa_with_code_paths(
9552 vec!["lib/x.lisp", "lib/x.lisp"],
9553 vec!["exe/y", "exe/y"],
9554 vec![],
9555 );
9556 let err = c.validate_code_paths().unwrap_err();
9557 let ManifestError::CodePathDuplicate { slot, path } = err else {
9558 panic!("expected CodePathDuplicate, got {err:?}");
9559 };
9560 assert_eq!(slot, ":bibliotecas");
9561 assert_eq!(path, PathBuf::from("lib/x.lisp"));
9562 }
9563
9564 #[test]
9565 fn validate_code_paths_duplicate_diagnostic_carries_offending_slot_and_path() {
9566 // Diagnostic-shape pin (peer with
9567 // `validate_code_paths_diagnostic_carries_offending_slot_and_path`
9568 // on the structural arm): the duplicate-arm Display surfaces both
9569 // the offending `:slot` tag and the offending path verbatim, so a
9570 // `feira lint` run can render the diagnostic without re-parsing.
9571 let c = caixa_with_code_paths(
9572 vec![],
9573 vec![],
9574 vec![
9575 "servicos/demo.computeunit.yaml",
9576 "servicos/demo.computeunit.yaml",
9577 ],
9578 );
9579 let rendered = c.validate_code_paths().unwrap_err().to_string();
9580 assert!(
9581 rendered.contains(":servicos"),
9582 "diagnostic must name the offending slot: {rendered}",
9583 );
9584 assert!(
9585 rendered.contains("servicos/demo.computeunit.yaml"),
9586 "diagnostic must quote the offending path: {rendered}",
9587 );
9588 }
9589
9590 // ── validate_code_paths — `.lisp` extension gate on :bibliotecas ──
9591 //
9592 // The lifted [`crate::render::is_lisp_extension`] predicate (33cc830)
9593 // now gates `:bibliotecas` entries on the tatara-lisp-source file-type
9594 // contract. The `feira build` loop (`caixa-feira/src/cmd/build.rs:33`)
9595 // reads every declared `:bibliotecas` entry through `tatara_lisp::read`
9596 // at parse time — the same downstream consumer the peer `:behavior
9597 // :on-*` (c97815a, [`crate::BehaviorError::NonLispExtension`]) and
9598 // `:upgrade-from :state-change :script` (33cc830,
9599 // [`crate::UpgradeError::NonLispExtensionScript`]) axes route through.
9600 // `:exe` and `:servicos` are deliberately excluded — `:exe` is the
9601 // nix-built executable surface (`"exe/<name>"` shape per the canonical
9602 // [`crate::LayoutError::ExeOutsideDir`] error message and every
9603 // in-tree `caixa_with_code_paths` positive control), and `:servicos`
9604 // is the `.computeunit.yaml` ComputeUnit-CR axis.
9605
9606 #[test]
9607 fn validate_code_paths_rejects_no_extension_bibliotecas_entry() {
9608 // Canonical "I dragged the wrong file from the workspace tree"
9609 // footgun on the biblioteca axis. Without the gate `feira build`
9610 // hands the extensionless path to `tatara_lisp::read` and fails
9611 // with a parser-shaped diagnostic far from the source caixa.lisp,
9612 // with no field naming the offending `:bibliotecas` entry.
9613 for relpath in ["lib/demo", "demo", "lib/handlers/inner"] {
9614 let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
9615 let err = c.validate_code_paths().unwrap_err();
9616 let ManifestError::CodePathNonLispExtension { slot, path } = err else {
9617 panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
9618 };
9619 assert_eq!(slot, ":bibliotecas");
9620 assert_eq!(path, PathBuf::from(relpath));
9621 }
9622 }
9623
9624 #[test]
9625 fn validate_code_paths_rejects_wrong_extension_bibliotecas_entry() {
9626 // Wrong-extension sweep across common authoring footguns. Same
9627 // sweep posture as the peer
9628 // `behavior::validate_rejects_wrong_extension` (c97815a) and
9629 // `upgrade::tests::state_change_rejects_wrong_extension_script`
9630 // (33cc830) cases.
9631 for relpath in [
9632 "lib/demo.rs",
9633 "lib/demo.txt",
9634 "lib/demo.md",
9635 "lib/demo.json",
9636 "lib/demo.yaml",
9637 "lib/demo.toml",
9638 "lib/demo.lisp.bak",
9639 "lib/demo.lispx",
9640 "lib/demo.lis",
9641 ] {
9642 let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
9643 let err = c.validate_code_paths().unwrap_err();
9644 let ManifestError::CodePathNonLispExtension { slot, path } = err else {
9645 panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
9646 };
9647 assert_eq!(slot, ":bibliotecas");
9648 assert_eq!(path, PathBuf::from(relpath));
9649 }
9650 }
9651
9652 #[test]
9653 fn validate_code_paths_rejects_case_folded_extension_bibliotecas_entry() {
9654 // Case-sensitivity sweep — pins the strict lowercase `.lisp`
9655 // contract. An uppercase `.LISP` shape that the layout's existence
9656 // check would (case-insensitively, on case-insensitive volumes)
9657 // match the on-disk file still mismatches the canonical form the
9658 // codec emits, breaking the THEORY.md §V.2.7 render-determinism
9659 // contract. Mirrors the peer
9660 // `behavior::validate_rejects_case_folded_extension` (c97815a) and
9661 // `upgrade::tests::state_change_rejects_case_folded_extension_script`
9662 // (33cc830) sweeps.
9663 for relpath in [
9664 "lib/demo.LISP",
9665 "lib/demo.Lisp",
9666 "lib/demo.LiSp",
9667 "lib/demo.lISP",
9668 ] {
9669 let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
9670 let err = c.validate_code_paths().unwrap_err();
9671 let ManifestError::CodePathNonLispExtension { slot, path } = err else {
9672 panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
9673 };
9674 assert_eq!(slot, ":bibliotecas");
9675 assert_eq!(path, PathBuf::from(relpath));
9676 }
9677 }
9678
9679 #[test]
9680 fn validate_code_paths_accepts_canonical_lisp_shapes() {
9681 // Positive-control sweep through every canonical authoring shape
9682 // every in-tree fixture and the `Caixa::template` scaffold use.
9683 // Mirrors the peer `behavior::validate_accepts_canonical_lisp_paths`
9684 // (c97815a) and the lifted predicate's own
9685 // `is_lisp_extension_accepts_canonical_shapes` sweep in render.rs
9686 // (33cc830).
9687 for relpath in [
9688 "lib/demo.lisp",
9689 "lib/handlers.lisp",
9690 "lib/migrations/v01-to-v02.lisp",
9691 "demo.lisp",
9692 "a.lisp",
9693 "./lib/demo.lisp",
9694 "lib/./handlers.lisp",
9695 "lib/migrations/v.0.1.lisp",
9696 ] {
9697 let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
9698 c.validate_code_paths()
9699 .unwrap_or_else(|e| panic!("canonical shape {relpath:?} must pass, got {e:?}"));
9700 }
9701 }
9702
9703 #[test]
9704 fn validate_code_paths_non_lisp_extension_does_not_fire_on_exe_or_servicos() {
9705 // The file-type gate is per-slot — only `:bibliotecas` carries the
9706 // tatara-lisp-source contract. An extensionless `:exe` entry
9707 // (`exe/demo`) and a `.computeunit.yaml` `:servicos` entry are the
9708 // canonical shapes every in-tree fixture uses, and must continue
9709 // to pass validate. Pins that a future tightening that broadens
9710 // the `.lisp` gate to either axis surfaces as a test failure
9711 // rather than as a silent breaking change to existing valid
9712 // manifests.
9713 let c = caixa_with_code_paths(
9714 vec![],
9715 vec!["exe/demo", "exe/tool"],
9716 vec!["servicos/demo.computeunit.yaml"],
9717 );
9718 c.validate_code_paths().unwrap();
9719 }
9720
9721 #[test]
9722 fn validate_code_paths_sandbox_shape_arms_precede_non_lisp_extension() {
9723 // Cross-arm precedence pin: a `:bibliotecas` entry that is *both*
9724 // sandbox-escaping and non-`.lisp` surfaces the more fundamental
9725 // sandbox-shape diagnostic first (the `.lisp` remediation would
9726 // be misleading when the offending path can never resolve under
9727 // the caixa root anyway). Mirrors the peer
9728 // `EmptyPath` → `AbsolutePath` → `ParentEscape` → `NonLispExtension`
9729 // ordering on `:behavior :on-*` (c97815a) and `EmptyScript` →
9730 // `AbsoluteScript` → `ParentEscapeScript` → `NonLispExtensionScript`
9731 // on `:upgrade-from :state-change :script` (33cc830).
9732 //
9733 // Empty wins (the strictly-smaller-scope structural arm).
9734 let c = caixa_with_code_paths(vec![""], vec![], vec![]);
9735 assert!(
9736 matches!(
9737 c.validate_code_paths().unwrap_err(),
9738 ManifestError::CodePathEmpty {
9739 slot: ":bibliotecas"
9740 }
9741 ),
9742 "empty must win over non-lisp-extension",
9743 );
9744 // Absolute wins (the path can't resolve under the caixa root).
9745 let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
9746 let err = c.validate_code_paths().unwrap_err();
9747 let ManifestError::CodePathAbsolute { slot, .. } = err else {
9748 panic!("absolute must win over non-lisp-extension, got {err:?}");
9749 };
9750 assert_eq!(slot, ":bibliotecas");
9751 // ParentEscape wins (the path escapes the caixa root).
9752 let c = caixa_with_code_paths(vec!["../sibling/x.txt"], vec![], vec![]);
9753 let err = c.validate_code_paths().unwrap_err();
9754 let ManifestError::CodePathParentEscape { slot, .. } = err else {
9755 panic!("parent-escape must win over non-lisp-extension, got {err:?}");
9756 };
9757 assert_eq!(slot, ":bibliotecas");
9758 }
9759
9760 #[test]
9761 fn validate_code_paths_non_lisp_extension_precedes_duplicate() {
9762 // Within-slot precedence pin: the per-entry file-type shape gate
9763 // fires before the cross-entry duplicate gate, so the narrower
9764 // structural defect dominates the uniqueness diagnostic. A
9765 // `("lib/x.txt" "lib/x.txt")` shape surfaces
9766 // `CodePathNonLispExtension` on the first entry rather than
9767 // `CodePathDuplicate` on the pair — same posture every per-entry
9768 // shape-gate-precedes-duplicate cascade follows on this surface
9769 // (the empty / absolute / parent-escape arms already precede the
9770 // duplicate arm; the lifted file-type arm joins that set).
9771 let c = caixa_with_code_paths(vec!["lib/x.txt", "lib/x.txt"], vec![], vec![]);
9772 let err = c.validate_code_paths().unwrap_err();
9773 let ManifestError::CodePathNonLispExtension { slot, path } = err else {
9774 panic!("expected CodePathNonLispExtension, got {err:?}");
9775 };
9776 assert_eq!(slot, ":bibliotecas");
9777 assert_eq!(path, PathBuf::from("lib/x.txt"));
9778 }
9779
9780 #[test]
9781 fn validate_code_paths_non_lisp_extension_diagnostic_carries_offending_slot_and_path() {
9782 // Diagnostic-shape pin (peer with
9783 // `validate_code_paths_diagnostic_carries_offending_slot_and_path`
9784 // on the sandbox-shape arms and
9785 // `validate_code_paths_duplicate_diagnostic_carries_offending_slot_and_path`
9786 // on the duplicate arm): the file-type-arm Display surfaces both
9787 // the offending `:slot` tag, the offending path verbatim, and the
9788 // expected `.lisp` extension named in the remediation text, so a
9789 // `feira lint` run can render the diagnostic without re-parsing.
9790 let c = caixa_with_code_paths(vec!["lib/demo.rs"], vec![], vec![]);
9791 let rendered = c.validate_code_paths().unwrap_err().to_string();
9792 assert!(
9793 rendered.contains(":bibliotecas"),
9794 "diagnostic must name the offending slot: {rendered}",
9795 );
9796 assert!(
9797 rendered.contains("lib/demo.rs"),
9798 "diagnostic must quote the offending path: {rendered}",
9799 );
9800 assert!(
9801 rendered.contains(".lisp"),
9802 "diagnostic must name the expected extension: {rendered}",
9803 );
9804 }
9805
9806 // ── validate_code_paths — `.computeunit.yaml` compound-suffix gate on :servicos ──
9807 //
9808 // The lifted [`crate::render::is_computeunit_yaml_extension`] predicate
9809 // now gates `:servicos` entries on the ComputeUnit-CR YAML file-type
9810 // contract. The peer caixa-helm / caixa-flux renderers consume each
9811 // `:servicos` entry through `serde_yaml::from_str` as a typed
9812 // `ComputeUnit` CR — same downstream-consumer-shape lift as the peer
9813 // `:bibliotecas` `.lisp` gate (64772a9), here on the compound-suffix
9814 // axis `Path::extension` can't express on its own.
9815
9816 #[test]
9817 fn validate_code_paths_rejects_no_extension_servicos_entry() {
9818 // Canonical "I dragged the wrong file from the workspace tree"
9819 // footgun on the Servico axis. Without the gate the peer
9820 // caixa-helm / caixa-flux renderers hand the extensionless path
9821 // to `serde_yaml::from_str` and fail with a parser-shaped
9822 // diagnostic far from the source caixa.lisp, with no field
9823 // naming the offending `:servicos` entry.
9824 for relpath in ["servicos/demo", "demo", "servicos/sub/nested"] {
9825 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
9826 let err = c.validate_code_paths().unwrap_err();
9827 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
9828 panic!(
9829 "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
9830 got {err:?}"
9831 );
9832 };
9833 assert_eq!(slot, ":servicos");
9834 assert_eq!(path, PathBuf::from(relpath));
9835 }
9836 }
9837
9838 #[test]
9839 fn validate_code_paths_rejects_wrong_extension_servicos_entry() {
9840 // Wrong-extension sweep across common authoring footguns on the
9841 // Servico axis. Bare `.yaml` is the canonical "I forgot the
9842 // `.computeunit` segment" typo; the off-by-one-segment shapes
9843 // (`.computeunit-yaml` / `.computeunit_yaml`) silently pass the
9844 // bare `Path::extension` view but mismatch the typed compound
9845 // suffix the renderers' `serde_yaml::from_str` consumer demands.
9846 // Same sweep-posture as the peer
9847 // `validate_code_paths_rejects_wrong_extension_bibliotecas_entry`
9848 // (64772a9) on the sibling tatara-lisp-source axis.
9849 for relpath in [
9850 "servicos/demo.yaml",
9851 "servicos/demo.yml",
9852 "servicos/demo.json",
9853 "servicos/demo.toml",
9854 "servicos/demo.txt",
9855 "servicos/demo.computeunit.yaml.bak",
9856 "servicos/demo.computeunit.yam",
9857 "servicos/demo.computeunit",
9858 "servicos/demo-computeunit.yaml",
9859 "servicos/demo_computeunit.yaml",
9860 ] {
9861 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
9862 let err = c.validate_code_paths().unwrap_err();
9863 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
9864 panic!(
9865 "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
9866 got {err:?}"
9867 );
9868 };
9869 assert_eq!(slot, ":servicos");
9870 assert_eq!(path, PathBuf::from(relpath));
9871 }
9872 }
9873
9874 #[test]
9875 fn validate_code_paths_rejects_case_folded_extension_servicos_entry() {
9876 // Case-sensitivity sweep — pins the strict lowercase
9877 // `.computeunit.yaml` contract. A case-folded shape that the
9878 // layout's existence check would (case-insensitively, on
9879 // case-insensitive volumes) match the on-disk file still
9880 // mismatches the canonical form the codec emits, breaking the
9881 // THEORY.md §V.2.7 render-determinism contract. Mirrors the peer
9882 // `validate_code_paths_rejects_case_folded_extension_bibliotecas_entry`
9883 // (64772a9) sweep on the sibling tatara-lisp-source axis.
9884 for relpath in [
9885 "servicos/demo.ComputeUnit.yaml",
9886 "servicos/demo.COMPUTEUNIT.yaml",
9887 "servicos/demo.computeunit.YAML",
9888 "servicos/demo.computeunit.Yaml",
9889 "servicos/demo.COMPUTEUNIT.YAML",
9890 ] {
9891 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
9892 let err = c.validate_code_paths().unwrap_err();
9893 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
9894 panic!(
9895 "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
9896 got {err:?}"
9897 );
9898 };
9899 assert_eq!(slot, ":servicos");
9900 assert_eq!(path, PathBuf::from(relpath));
9901 }
9902 }
9903
9904 #[test]
9905 fn validate_code_paths_rejects_empty_stem_servicos_entry() {
9906 // Degenerate hidden-file shape: a file name exactly equal to the
9907 // suffix (`.computeunit.yaml` — no stem preceding the suffix) is
9908 // the structural "Servico declared with no identity" footgun.
9909 // The substrate identifies each ComputeUnit by the file-stem
9910 // segment that precedes `.computeunit.yaml` (the rendered
9911 // `lareira-<stem>` Helm chart, the per-Servico `metadata.name`,
9912 // the M3 `:contratos` membership lookup), so an empty stem
9913 // leaves the Servico unidentifiable. Pinned at the typed-axis
9914 // level so a future regression that drops the `name.len() >
9915 // SUFFIX.len()` bound at the predicate surfaces here, not
9916 // piecemeal as a `lareira-` chart-name collision at render time.
9917 for relpath in ["servicos/.computeunit.yaml"] {
9918 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
9919 let err = c.validate_code_paths().unwrap_err();
9920 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
9921 panic!(
9922 "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
9923 got {err:?}"
9924 );
9925 };
9926 assert_eq!(slot, ":servicos");
9927 assert_eq!(path, PathBuf::from(relpath));
9928 }
9929 }
9930
9931 #[test]
9932 fn validate_code_paths_accepts_canonical_computeunit_yaml_shapes() {
9933 // Positive-control sweep through every canonical authoring shape
9934 // every in-tree fixture and the `Caixa::template` scaffold use.
9935 // Mirrors the peer
9936 // `validate_code_paths_accepts_canonical_lisp_shapes` (64772a9)
9937 // and the lifted predicate's own
9938 // `computeunit_yaml_extension_accepts_canonical_shapes` sweep in
9939 // render.rs.
9940 for relpath in [
9941 "servicos/demo.computeunit.yaml",
9942 "servicos/hello-rio.computeunit.yaml",
9943 "servicos/my-service.computeunit.yaml",
9944 "servicos/a.computeunit.yaml",
9945 "./servicos/demo.computeunit.yaml",
9946 "servicos/./demo.computeunit.yaml",
9947 "servicos/sub/nested.computeunit.yaml",
9948 "servicos/v0.1.computeunit.yaml",
9949 ] {
9950 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
9951 c.validate_code_paths()
9952 .unwrap_or_else(|e| panic!("canonical shape {relpath:?} must pass, got {e:?}"));
9953 }
9954 }
9955
9956 #[test]
9957 fn validate_code_paths_non_computeunit_yaml_extension_does_not_fire_on_bibliotecas_or_exe() {
9958 // The file-type gate is per-slot — only `:servicos` carries the
9959 // ComputeUnit-CR YAML contract. A canonical `.lisp` `:bibliotecas`
9960 // entry and an extensionless `:exe` entry are the canonical
9961 // shapes every in-tree fixture uses, and must continue to pass
9962 // validate. Peer of
9963 // `validate_code_paths_non_lisp_extension_does_not_fire_on_exe_or_servicos`
9964 // (64772a9) — together pin that the typed
9965 // [`CodePathFileType`] dispatch is exhaustively per-slot, with no
9966 // cross-axis leakage in either direction.
9967 let c = caixa_with_code_paths(
9968 vec!["lib/demo.lisp"],
9969 vec!["exe/demo", "exe/tool"],
9970 vec!["servicos/demo.computeunit.yaml"],
9971 );
9972 c.validate_code_paths().unwrap();
9973 }
9974
9975 #[test]
9976 fn validate_code_paths_sandbox_shape_arms_precede_non_computeunit_yaml_extension() {
9977 // Cross-arm precedence pin: a `:servicos` entry that is *both*
9978 // sandbox-escaping and wrong-extension surfaces the more
9979 // fundamental sandbox-shape diagnostic first (the
9980 // `.computeunit.yaml` remediation would be misleading when the
9981 // offending path can never resolve under the caixa root
9982 // anyway). Mirrors the peer
9983 // `validate_code_paths_sandbox_shape_arms_precede_non_lisp_extension`
9984 // (64772a9) ordering on the sibling `:bibliotecas` axis and the
9985 // peer `EmptyPath` → `AbsolutePath` → `ParentEscape` →
9986 // `NonComputeUnitYamlExtension` arm-ordering the dispatch
9987 // table establishes.
9988 //
9989 // Empty wins (the strictly-smaller-scope structural arm).
9990 let c = caixa_with_code_paths(vec![], vec![], vec![""]);
9991 assert!(
9992 matches!(
9993 c.validate_code_paths().unwrap_err(),
9994 ManifestError::CodePathEmpty { slot: ":servicos" }
9995 ),
9996 "empty must win over non-computeunit-yaml-extension",
9997 );
9998 // Absolute wins (the path can't resolve under the caixa root).
9999 let c = caixa_with_code_paths(vec![], vec![], vec!["/etc/foo.yaml"]);
10000 let err = c.validate_code_paths().unwrap_err();
10001 let ManifestError::CodePathAbsolute { slot, .. } = err else {
10002 panic!("absolute must win over non-computeunit-yaml-extension, got {err:?}");
10003 };
10004 assert_eq!(slot, ":servicos");
10005 // ParentEscape wins (the path escapes the caixa root).
10006 let c = caixa_with_code_paths(vec![], vec![], vec!["../sibling/x.yaml"]);
10007 let err = c.validate_code_paths().unwrap_err();
10008 let ManifestError::CodePathParentEscape { slot, .. } = err else {
10009 panic!("parent-escape must win over non-computeunit-yaml-extension, got {err:?}");
10010 };
10011 assert_eq!(slot, ":servicos");
10012 }
10013
10014 #[test]
10015 fn validate_code_paths_non_computeunit_yaml_extension_precedes_duplicate() {
10016 // Within-slot precedence pin: the per-entry file-type shape gate
10017 // fires before the cross-entry duplicate gate, so the narrower
10018 // structural defect dominates the uniqueness diagnostic. A
10019 // `("servicos/x.yaml" "servicos/x.yaml")` shape surfaces
10020 // `CodePathNonComputeUnitYamlExtension` on the first entry
10021 // rather than `CodePathDuplicate` on the pair — same posture
10022 // every per-entry shape-gate-precedes-duplicate cascade follows
10023 // on this surface, peer of the 64772a9 `:bibliotecas`
10024 // `("lib/x.txt" "lib/x.txt")` ordering.
10025 let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/x.yaml", "servicos/x.yaml"]);
10026 let err = c.validate_code_paths().unwrap_err();
10027 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
10028 panic!("expected CodePathNonComputeUnitYamlExtension, got {err:?}");
10029 };
10030 assert_eq!(slot, ":servicos");
10031 assert_eq!(path, PathBuf::from("servicos/x.yaml"));
10032 }
10033
10034 #[test]
10035 fn validate_code_paths_non_computeunit_yaml_extension_diagnostic_carries_offending_slot_and_path()
10036 {
10037 // Diagnostic-shape pin (peer with
10038 // `validate_code_paths_non_lisp_extension_diagnostic_carries_offending_slot_and_path`
10039 // on the sibling tatara-lisp-source axis): the file-type-arm
10040 // Display surfaces both the offending `:slot` tag, the
10041 // offending path verbatim, and the expected
10042 // `.computeunit.yaml` compound suffix named in the remediation
10043 // text, so a `feira lint` run can render the diagnostic without
10044 // re-parsing.
10045 let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/demo.yaml"]);
10046 let rendered = c.validate_code_paths().unwrap_err().to_string();
10047 assert!(
10048 rendered.contains(":servicos"),
10049 "diagnostic must name the offending slot: {rendered}",
10050 );
10051 assert!(
10052 rendered.contains("servicos/demo.yaml"),
10053 "diagnostic must quote the offending path: {rendered}",
10054 );
10055 assert!(
10056 rendered.contains(".computeunit.yaml"),
10057 "diagnostic must name the expected compound suffix: {rendered}",
10058 );
10059 }
10060
10061 // ── validate_etiquetas — universal-axis registry-search-tag shape ──
10062
10063 fn caixa_with_etiquetas(etiquetas: Vec<&str>) -> Caixa {
10064 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10065 c.etiquetas = etiquetas.into_iter().map(String::from).collect();
10066 c
10067 }
10068
10069 #[test]
10070 fn validate_etiquetas_accepts_empty_list() {
10071 // The empty-list identity: every caixa with no declared tags
10072 // trivially passes — `Caixa::template` emits `:etiquetas ()`,
10073 // so the gate is non-disruptive against every existing manifest.
10074 let c = caixa_with_etiquetas(vec![]);
10075 c.validate_etiquetas().unwrap();
10076 }
10077
10078 #[test]
10079 fn validate_etiquetas_accepts_canonical_forms() {
10080 // Positive control sweep: a canonical-shaped non-empty distinct
10081 // tag list passes, mirroring the example checkout-aplicacao
10082 // (`:etiquetas ("example" "aplicacao" "mesh" "ecommerce" "demo")`)
10083 // and the hello-rio fixture (`("hello-world" "wasm" "rust")`).
10084 let c = caixa_with_etiquetas(vec!["example", "aplicacao", "mesh", "ecommerce", "demo"]);
10085 c.validate_etiquetas().unwrap();
10086 }
10087
10088 #[test]
10089 fn validate_etiquetas_rejects_empty_entry() {
10090 // Canonical paste-from-blank-doc footgun. Without the gate the
10091 // empty entry rendered as `keywords: [""]` in `Chart.yaml`, a
10092 // no-op tag indexing nothing in the future caixa-registry.
10093 let c = caixa_with_etiquetas(vec![""]);
10094 let err = c.validate_etiquetas().unwrap_err();
10095 assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
10096 }
10097
10098 #[test]
10099 fn validate_etiquetas_rejects_duplicate_entry() {
10100 // Canonical copy-paste-the-wrong-tag footgun. Without the gate
10101 // the duplicate was silently dedup'd by caixa-helm's BTreeSet
10102 // collect at chart render — a "second wins / one silently
10103 // disappears" shape divergent from every peer typed-graph set
10104 // gate. The duplicate-arm names the offending tag verbatim.
10105 let c = caixa_with_etiquetas(vec!["demo", "demo"]);
10106 let err = c.validate_etiquetas().unwrap_err();
10107 let ManifestError::EtiquetaDuplicate { etiqueta } = err else {
10108 panic!("expected EtiquetaDuplicate, got {err:?}");
10109 };
10110 assert_eq!(etiqueta, "demo");
10111 }
10112
10113 #[test]
10114 fn validate_etiquetas_empty_takes_precedence_over_duplicate() {
10115 // Empty-first cascade pin: `("" "demo" "demo")` surfaces
10116 // `EtiquetaEmpty` not `EtiquetaDuplicate` — the narrower
10117 // structural "this entry has no value" defect dominates the
10118 // cross-entry uniqueness diagnostic. Mirrors the peer
10119 // empty-before-duplicate cascades on `:caracteristicas`
10120 // (`CaracteristicaEmpty` before `CaracteristicaDuplicate`,
10121 // fc3b4d5) and `:membros :caixa` (`MembroCaixaEmpty` before
10122 // `MembroDuplicate`).
10123 let c = caixa_with_etiquetas(vec!["", "demo", "demo"]);
10124 let err = c.validate_etiquetas().unwrap_err();
10125 assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
10126 }
10127
10128 #[test]
10129 fn validate_etiquetas_duplicate_reports_first_collision() {
10130 // First-collision pin: `("a" "b" "a" "b")` surfaces the `"a"`
10131 // duplicate (the lexicographically-earliest offending position
10132 // — the second `"a"` at index 2 collides with the first `"a"`
10133 // at index 0), not the later `"b"` collision at index 3,
10134 // peer with every other first-collision diagnostic posture on
10135 // this surface (`validate_load_singularity_reports_first_collision`,
10136 // `validate_cleanup_singularity_reports_first_collision`).
10137 let c = caixa_with_etiquetas(vec!["a", "b", "a", "b"]);
10138 let err = c.validate_etiquetas().unwrap_err();
10139 let ManifestError::EtiquetaDuplicate { etiqueta } = err else {
10140 panic!("expected EtiquetaDuplicate, got {err:?}");
10141 };
10142 assert_eq!(etiqueta, "a");
10143 }
10144
10145 #[test]
10146 fn validate_etiquetas_case_sensitive() {
10147 // Case-sensitivity pin: `("Foo" "foo")` is two distinct entries,
10148 // mirroring the peer `:membros :caixa` / `:children :caixa`
10149 // exact-string-match discipline. The shape gate this routine
10150 // landed (`is_chart_keyword_shape`, Cargo crates.io keyword
10151 // grammar) accepts mixed case — crates.io's keyword rule is
10152 // "case-insensitive" at the index layer but admits mixed case
10153 // at the entry layer (the canonical Helm chart `keywords:`
10154 // shape is lowercase by convention, but the grammar admits
10155 // uppercase). Case-sensitivity at the duplicate-set layer
10156 // remains structural — two distinct strings are two distinct
10157 // entries.
10158 let c = caixa_with_etiquetas(vec!["Foo", "foo"]);
10159 c.validate_etiquetas().unwrap();
10160 }
10161
10162 #[test]
10163 fn validate_etiquetas_diagnostic_carries_offending_tag() {
10164 // Diagnostic-shape pin (peer with
10165 // `validate_code_paths_diagnostic_carries_offending_slot_and_path`):
10166 // the error's Display surfaces the offending tag verbatim, so a
10167 // `feira lint` run can render the diagnostic without re-parsing
10168 // and the author can grep their caixa.lisp for the offending
10169 // value.
10170 let c = caixa_with_etiquetas(vec!["demo", "demo"]);
10171 let rendered = c.validate_etiquetas().unwrap_err().to_string();
10172 assert!(
10173 rendered.contains(":etiquetas"),
10174 "diagnostic must name the offending slot: {rendered}",
10175 );
10176 assert!(
10177 rendered.contains("demo"),
10178 "diagnostic must quote the offending tag: {rendered}",
10179 );
10180 }
10181
10182 #[test]
10183 fn validate_etiquetas_rejects_leading_whitespace_entry() {
10184 // Canonical paste-from-aligned-doc footgun. Without the shape
10185 // gate `" mesh"` silently passed validate and landed as a
10186 // YAML plain-style scalar with leading whitespace in the
10187 // rendered Chart.yaml `keywords:` array — every YAML 1.2
10188 // dumper trims leading whitespace from plain-style scalars,
10189 // so the authored space round-tripped inconsistently back
10190 // through `caixa.lisp`. Mirrors the peer
10191 // `validate_autores_rejects_leading_whitespace_entry`.
10192 let c = caixa_with_etiquetas(vec![" mesh"]);
10193 let err = c.validate_etiquetas().unwrap_err();
10194 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
10195 panic!("expected EtiquetaInvalid, got {err:?}");
10196 };
10197 assert_eq!(etiqueta, " mesh");
10198 assert!(reason.contains("whitespace"), "got: {reason}");
10199 }
10200
10201 #[test]
10202 fn validate_etiquetas_rejects_embedded_newline_entry() {
10203 // Canonical paste-from-multiline-doc footgun — the author
10204 // pasted a multi-tag block into one `:etiquetas` entry
10205 // instead of splitting into one entry per tag. Without the
10206 // shape gate `"mesh\nhttp"` silently passed validate and
10207 // landed as a YAML-illegal multi-line scalar in the rendered
10208 // Chart.yaml `keywords:` array.
10209 let c = caixa_with_etiquetas(vec!["mesh\nhttp"]);
10210 let err = c.validate_etiquetas().unwrap_err();
10211 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
10212 panic!("expected EtiquetaInvalid, got {err:?}");
10213 };
10214 assert_eq!(etiqueta, "mesh\nhttp");
10215 assert!(reason.contains("newline"), "got: {reason}");
10216 }
10217
10218 #[test]
10219 fn validate_etiquetas_rejects_embedded_comma_entry() {
10220 // Canonical CSV-list-separator-confusion footgun: the author
10221 // confused the CSV-style separator convention with the
10222 // `:etiquetas` list grammar. Without the shape gate
10223 // `"mesh,http,grpc"` silently passed validate and landed as a
10224 // single malformed search tag in the rendered Chart.yaml
10225 // `keywords:` array — Artifact Hub's keyword index would
10226 // either silently drop the tag or index it as
10227 // `mesh,http,grpc` instead of three separate tags.
10228 let c = caixa_with_etiquetas(vec!["mesh,http,grpc"]);
10229 let err = c.validate_etiquetas().unwrap_err();
10230 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
10231 panic!("expected EtiquetaInvalid, got {err:?}");
10232 };
10233 assert_eq!(etiqueta, "mesh,http,grpc");
10234 assert!(reason.contains('`'), "got: {reason}");
10235 assert!(reason.contains(','), "got: {reason}");
10236 }
10237
10238 #[test]
10239 fn validate_etiquetas_rejects_embedded_slash_entry() {
10240 // Canonical path-separator-confusion footgun: the author
10241 // confused namespace-path notation with the keyword grammar.
10242 let c = caixa_with_etiquetas(vec!["caixa/servico"]);
10243 let err = c.validate_etiquetas().unwrap_err();
10244 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
10245 panic!("expected EtiquetaInvalid, got {err:?}");
10246 };
10247 assert_eq!(etiqueta, "caixa/servico");
10248 assert!(reason.contains('/'), "got: {reason}");
10249 }
10250
10251 #[test]
10252 fn validate_etiquetas_rejects_leading_digit_entry() {
10253 // Canonical paste-from-numbered-list footgun: the author
10254 // copied `1. mesh` from a numbered doc and the `1` leaked
10255 // into the tag.
10256 let c = caixa_with_etiquetas(vec!["1mesh"]);
10257 let err = c.validate_etiquetas().unwrap_err();
10258 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
10259 panic!("expected EtiquetaInvalid, got {err:?}");
10260 };
10261 assert_eq!(etiqueta, "1mesh");
10262 assert!(reason.contains("digit"), "got: {reason}");
10263 }
10264
10265 #[test]
10266 fn validate_etiquetas_rejects_leading_hyphen_entry() {
10267 // Canonical kebab-leak footgun.
10268 let c = caixa_with_etiquetas(vec!["-foo"]);
10269 let err = c.validate_etiquetas().unwrap_err();
10270 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
10271 panic!("expected EtiquetaInvalid, got {err:?}");
10272 };
10273 assert_eq!(etiqueta, "-foo");
10274 assert!(reason.contains('-'), "got: {reason}");
10275 }
10276
10277 #[test]
10278 fn validate_etiquetas_rejects_non_ascii_entry() {
10279 // Canonical paste-from-Unicode-doc footgun. Every legitimate
10280 // search tag is strict ASCII; raw non-ASCII silently
10281 // round-trips inconsistently across NFC/NFD normalization on
10282 // APFS / case-folding filesystems and breaks the Artifact Hub
10283 // keyword search index lookup.
10284 let c = caixa_with_etiquetas(vec!["café"]);
10285 let err = c.validate_etiquetas().unwrap_err();
10286 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
10287 panic!("expected EtiquetaInvalid, got {err:?}");
10288 };
10289 assert_eq!(etiqueta, "café");
10290 assert!(reason.contains("non-ASCII"), "got: {reason}");
10291 }
10292
10293 #[test]
10294 fn validate_etiquetas_rejects_period_entry() {
10295 // Canonical namespace-confusion / version-suffix footgun
10296 // (`"http.1"` / `"v1.0"`): Cargo's crates.io keyword grammar
10297 // excludes `.` from the continuation set even though the
10298 // sibling `:caracteristicas` axis (Cargo's feature-name
10299 // grammar) admits it. Tighter than the sibling axis, peer
10300 // with Cargo's own crates.io keyword shape.
10301 let c = caixa_with_etiquetas(vec!["http.1"]);
10302 let err = c.validate_etiquetas().unwrap_err();
10303 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
10304 panic!("expected EtiquetaInvalid, got {err:?}");
10305 };
10306 assert_eq!(etiqueta, "http.1");
10307 assert!(reason.contains('.'), "got: {reason}");
10308 }
10309
10310 #[test]
10311 fn validate_etiquetas_empty_takes_precedence_over_shape() {
10312 // Per-entry empty-first cascade pin: an entry that is both
10313 // empty *and* shape-invalid surfaces `EtiquetaEmpty` (the
10314 // narrower "this entry has no value" structural defect
10315 // dominates the broader shape-predicate diagnostic). The
10316 // empty arm fires before the shape predicate is consulted,
10317 // mirroring the peer `validate_autores_empty_takes_precedence_over_shape`
10318 // cascade established on the sibling universal-axis Vec<String>
10319 // surface.
10320 let c = caixa_with_etiquetas(vec![""]);
10321 let err = c.validate_etiquetas().unwrap_err();
10322 assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
10323 }
10324
10325 #[test]
10326 fn validate_etiquetas_shape_takes_precedence_over_duplicate() {
10327 // Per-entry shape-before-cross-entry-duplicate cascade pin: an
10328 // entry that is malformed surfaces `EtiquetaInvalid` even when
10329 // a later entry would have collided on duplicate. The
10330 // per-entry shape arm fires inside the same loop iteration as
10331 // the empty arm, before the seen-set insert at end-of-iteration
10332 // — structural per-entry defects dominate the cross-entry
10333 // uniqueness diagnostic. Mirrors the peer
10334 // `validate_autores_shape_takes_precedence_over_duplicate`.
10335 let c = caixa_with_etiquetas(vec!["mesh\nhttp", "mesh\nhttp"]);
10336 let err = c.validate_etiquetas().unwrap_err();
10337 assert!(
10338 matches!(err, ManifestError::EtiquetaInvalid { .. }),
10339 "got {err:?}",
10340 );
10341 }
10342
10343 #[test]
10344 fn validate_etiquetas_invalid_diagnostic_names_offending_slot_and_value() {
10345 // Diagnostic-shape pin on the new shape arm (peer with
10346 // `validate_autores_invalid_diagnostic_names_offending_slot_and_value`):
10347 // the rendered Display surfaces both the offending slot name
10348 // and the offending value verbatim, so a `feira lint` run
10349 // points the author at the exact `:etiquetas` entry to fix.
10350 let c = caixa_with_etiquetas(vec!["mesh\nhttp"]);
10351 let rendered = c.validate_etiquetas().unwrap_err().to_string();
10352 assert!(
10353 rendered.contains(":etiquetas"),
10354 "diagnostic must name the offending slot: {rendered}",
10355 );
10356 assert!(
10357 rendered.contains("mesh\\nhttp"),
10358 "diagnostic must quote the offending value (debug-escaped): {rendered}",
10359 );
10360 }
10361
10362 #[test]
10363 fn validate_etiquetas_rejects_at_21_byte_boundary() {
10364 // The 20-byte cap pin — boundary-exceeding case rejected,
10365 // boundary-accepting case passes. Mirrors the peer
10366 // `chart_keyword_shape_rejects_at_21_byte_boundary` substrate-
10367 // side pin, surfaced at the per-axis caller so the cap
10368 // propagates through validate end-to-end. Constructed as a
10369 // single all-`a` token so only the cap arm fires.
10370 let max_ok = "a".repeat(20);
10371 let c = caixa_with_etiquetas(vec![max_ok.as_str()]);
10372 c.validate_etiquetas().unwrap();
10373 let too_long = "a".repeat(21);
10374 let c = caixa_with_etiquetas(vec![too_long.as_str()]);
10375 let err = c.validate_etiquetas().unwrap_err();
10376 let ManifestError::EtiquetaInvalid { reason, .. } = err else {
10377 panic!("expected EtiquetaInvalid, got {err:?}");
10378 };
10379 assert!(reason.contains("20"), "got: {reason}");
10380 assert!(reason.contains("21"), "got: {reason}");
10381 }
10382
10383 #[test]
10384 fn validate_etiquetas_accepts_canonical_shaped_forms() {
10385 // Positive control sweep: every canonical-shaped tag from the
10386 // hello-rio / checkout-aplicacao / pangea-tatara-akeyless
10387 // example fixtures plus the substrate-fixed tags caixa-helm
10388 // unions in at chart render. Drift between this list and the
10389 // substrate-side `chart_keyword_shape_accepts_canonical_forms`
10390 // sweep surfaces here — one source of truth for the rule.
10391 let c = caixa_with_etiquetas(vec![
10392 "example",
10393 "aplicacao",
10394 "mesh",
10395 "ecommerce",
10396 "demo",
10397 "infrastructure",
10398 "aws",
10399 "akeyless",
10400 "pangea-native",
10401 "hello-world",
10402 "wasm",
10403 "rust",
10404 "tatara-lisp",
10405 "caixa-servico",
10406 "lareira",
10407 ]);
10408 c.validate_etiquetas().unwrap();
10409 }
10410
10411 // ── validate_autores — universal-axis maintainer shape ────────────
10412
10413 fn caixa_with_autores(autores: Vec<&str>) -> Caixa {
10414 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10415 c.autores = autores.into_iter().map(String::from).collect();
10416 c
10417 }
10418
10419 #[test]
10420 fn validate_autores_accepts_empty_list() {
10421 // The empty-list identity: `Caixa::template` emits `:autores ()`,
10422 // so the gate is non-disruptive against every existing manifest.
10423 let c = caixa_with_autores(vec![]);
10424 c.validate_autores().unwrap();
10425 }
10426
10427 #[test]
10428 fn validate_autores_accepts_canonical_forms() {
10429 // Positive control sweep: every canonical-shaped non-empty
10430 // distinct maintainer list passes — the hello-rio / checkout-
10431 // aplicacao fixtures' `:autores ("pleme-io")` shape, plus the
10432 // multi-author shape downstream packaging surfaces emit.
10433 let c = caixa_with_autores(vec!["pleme-io"]);
10434 c.validate_autores().unwrap();
10435 let c = caixa_with_autores(vec!["alice <alice@example.com>", "bob <bob@example.com>"]);
10436 c.validate_autores().unwrap();
10437 }
10438
10439 #[test]
10440 fn validate_autores_rejects_empty_entry() {
10441 // Canonical paste-from-blank-doc footgun. Without the gate the
10442 // empty entry rendered as `maintainers: [{name: "", email: null}]`
10443 // in `Chart.yaml`, a no-op maintainer the substrate cannot route
10444 // to.
10445 let c = caixa_with_autores(vec![""]);
10446 let err = c.validate_autores().unwrap_err();
10447 assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
10448 }
10449
10450 #[test]
10451 fn validate_autores_rejects_duplicate_entry() {
10452 // Canonical copy-paste-the-wrong-author footgun. Unlike the
10453 // `:etiquetas` peer (caixa-helm's `BTreeSet` collect silently
10454 // dedups the rendered `keywords:` array), the `maintainers:`
10455 // rendering has *no* dedup — duplicates stack verbatim. The
10456 // duplicate-arm names the offending author verbatim.
10457 let c = caixa_with_autores(vec!["pleme-io", "pleme-io"]);
10458 let err = c.validate_autores().unwrap_err();
10459 let ManifestError::AutorDuplicate { autor } = err else {
10460 panic!("expected AutorDuplicate, got {err:?}");
10461 };
10462 assert_eq!(autor, "pleme-io");
10463 }
10464
10465 #[test]
10466 fn validate_autores_empty_takes_precedence_over_duplicate() {
10467 // Empty-first cascade pin: `("" "pleme-io" "pleme-io")` surfaces
10468 // `AutorEmpty` not `AutorDuplicate` — the narrower structural
10469 // "this entry has no value" defect dominates the cross-entry
10470 // uniqueness diagnostic. Mirrors the peer empty-before-duplicate
10471 // cascades on `:etiquetas` (`EtiquetaEmpty` before
10472 // `EtiquetaDuplicate`, 360a499), `:caracteristicas`
10473 // (`CaracteristicaEmpty` before `CaracteristicaDuplicate`,
10474 // fc3b4d5), and `:membros :caixa` (`MembroCaixaEmpty` before
10475 // `MembroDuplicate`).
10476 let c = caixa_with_autores(vec!["", "pleme-io", "pleme-io"]);
10477 let err = c.validate_autores().unwrap_err();
10478 assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
10479 }
10480
10481 #[test]
10482 fn validate_autores_duplicate_reports_first_collision() {
10483 // First-collision pin: `("a" "b" "a" "b")` surfaces the `"a"`
10484 // duplicate (the lexicographically-earliest offending position
10485 // — the second `"a"` at index 2 collides with the first `"a"`
10486 // at index 0), not the later `"b"` collision at index 3,
10487 // peer with every other first-collision diagnostic posture on
10488 // this surface.
10489 let c = caixa_with_autores(vec!["a", "b", "a", "b"]);
10490 let err = c.validate_autores().unwrap_err();
10491 let ManifestError::AutorDuplicate { autor } = err else {
10492 panic!("expected AutorDuplicate, got {err:?}");
10493 };
10494 assert_eq!(autor, "a");
10495 }
10496
10497 #[test]
10498 fn validate_autores_case_sensitive() {
10499 // Case-sensitivity pin: `("Pleme-io" "pleme-io")` is two distinct
10500 // entries, mirroring the peer `:etiquetas` / `:membros :caixa`
10501 // / `:children :caixa` exact-string-match discipline.
10502 let c = caixa_with_autores(vec!["Pleme-io", "pleme-io"]);
10503 c.validate_autores().unwrap();
10504 }
10505
10506 #[test]
10507 fn validate_autores_diagnostic_carries_offending_author() {
10508 // Diagnostic-shape pin (peer with
10509 // `validate_etiquetas_diagnostic_carries_offending_tag`): the
10510 // error's Display surfaces the offending author verbatim, so a
10511 // `feira lint` run can render the diagnostic without re-parsing
10512 // and the author can grep their caixa.lisp for the offending
10513 // value.
10514 let c = caixa_with_autores(vec!["pleme-io", "pleme-io"]);
10515 let rendered = c.validate_autores().unwrap_err().to_string();
10516 assert!(
10517 rendered.contains(":autores"),
10518 "diagnostic must name the offending slot: {rendered}",
10519 );
10520 assert!(
10521 rendered.contains("pleme-io"),
10522 "diagnostic must quote the offending author: {rendered}",
10523 );
10524 }
10525
10526 #[test]
10527 fn validate_autores_rejects_leading_whitespace_entry() {
10528 // Canonical paste-from-aligned-doc footgun. Without the shape
10529 // gate `" pleme-io"` silently passed validate and landed as a
10530 // YAML plain-style scalar with leading whitespace in the
10531 // rendered Chart.yaml `maintainers:` array — every YAML 1.2
10532 // dumper trims leading whitespace from plain-style scalars, so
10533 // the authored space round-tripped inconsistently back through
10534 // `caixa.lisp`. Mirrors the peer
10535 // `validate_descricao_rejects_leading_whitespace`.
10536 let c = caixa_with_autores(vec![" pleme-io"]);
10537 let err = c.validate_autores().unwrap_err();
10538 let ManifestError::AutorInvalid { autor, reason } = err else {
10539 panic!("expected AutorInvalid, got {err:?}");
10540 };
10541 assert_eq!(autor, " pleme-io");
10542 assert!(reason.contains("whitespace"), "got: {reason}");
10543 }
10544
10545 #[test]
10546 fn validate_autores_rejects_trailing_whitespace_entry() {
10547 // Canonical paste-from-doc footgun.
10548 let c = caixa_with_autores(vec!["pleme-io "]);
10549 let err = c.validate_autores().unwrap_err();
10550 let ManifestError::AutorInvalid { autor, reason } = err else {
10551 panic!("expected AutorInvalid, got {err:?}");
10552 };
10553 assert_eq!(autor, "pleme-io ");
10554 assert!(reason.contains("whitespace"), "got: {reason}");
10555 }
10556
10557 #[test]
10558 fn validate_autores_rejects_embedded_newline_entry() {
10559 // Canonical paste-from-multiline-doc footgun — the author
10560 // pasted a multi-line block of author records into one
10561 // `:autores` entry instead of splitting into one entry per
10562 // author. Without the shape gate `"alice\nbob"` silently
10563 // passed validate and landed as a YAML-illegal multi-line
10564 // scalar in the rendered Chart.yaml `maintainers:` array.
10565 let c = caixa_with_autores(vec!["alice\nbob"]);
10566 let err = c.validate_autores().unwrap_err();
10567 let ManifestError::AutorInvalid { autor, reason } = err else {
10568 panic!("expected AutorInvalid, got {err:?}");
10569 };
10570 assert_eq!(autor, "alice\nbob");
10571 assert!(reason.contains("newline"), "got: {reason}");
10572 }
10573
10574 #[test]
10575 fn validate_autores_rejects_embedded_carriage_return_entry() {
10576 // Canonical paste-from-Windows-CRLF-doc footgun.
10577 let c = caixa_with_autores(vec!["alice\rbob"]);
10578 let err = c.validate_autores().unwrap_err();
10579 let ManifestError::AutorInvalid { autor, reason } = err else {
10580 panic!("expected AutorInvalid, got {err:?}");
10581 };
10582 assert_eq!(autor, "alice\rbob");
10583 assert!(reason.contains("carriage return"), "got: {reason}");
10584 }
10585
10586 #[test]
10587 fn validate_autores_rejects_embedded_tab_entry() {
10588 // Canonical tab-from-aligned-doc footgun.
10589 let c = caixa_with_autores(vec!["Pleme\tContributors"]);
10590 let err = c.validate_autores().unwrap_err();
10591 let ManifestError::AutorInvalid { autor, reason } = err else {
10592 panic!("expected AutorInvalid, got {err:?}");
10593 };
10594 assert_eq!(autor, "Pleme\tContributors");
10595 assert!(reason.contains("tab"), "got: {reason}");
10596 }
10597
10598 #[test]
10599 fn validate_autores_rejects_embedded_control_bytes_entry() {
10600 // Paste-from-binary-blob footguns: NUL, BEL, ESC, DEL all
10601 // surface the same control-byte arm.
10602 for entry in [
10603 "alice\x00bob",
10604 "alice\x07bob",
10605 "alice\x1bbob",
10606 "alice\x7fbob",
10607 ] {
10608 let c = caixa_with_autores(vec![entry]);
10609 let err = c.validate_autores().unwrap_err();
10610 let ManifestError::AutorInvalid { autor, reason } = err else {
10611 panic!("expected AutorInvalid for {entry:?}, got {err:?}");
10612 };
10613 assert_eq!(autor, entry);
10614 assert!(
10615 reason.contains("control character"),
10616 "{entry:?} reason: {reason}",
10617 );
10618 }
10619 }
10620
10621 #[test]
10622 fn validate_autores_accepts_unicode_entry() {
10623 // Unicode positive control: realistic maintainer names carry
10624 // Unicode (`François`, `日本語`, `naïve`). The predicate must
10625 // round-trip Unicode losslessly, peer with the
10626 // `chart_maintainer_name_shape_accepts_unicode` substrate-side
10627 // sweep.
10628 let c = caixa_with_autores(vec![
10629 "François Dupont",
10630 "日本語の名前",
10631 "naïve <naive@example.com>",
10632 ]);
10633 c.validate_autores().unwrap();
10634 }
10635
10636 #[test]
10637 fn validate_autores_empty_takes_precedence_over_shape() {
10638 // Per-entry empty-first cascade pin: an entry that is both
10639 // empty *and* shape-invalid surfaces `AutorEmpty` (the narrower
10640 // "this entry has no value" structural defect dominates the
10641 // broader shape-predicate diagnostic). The empty arm fires
10642 // before the shape predicate is consulted, mirroring the peer
10643 // `validate_repositorio_empty_takes_precedence_over_shape`
10644 // cascade on the universal `Option<String>` siblings — and now
10645 // established on the Vec<String> per-entry surface.
10646 let c = caixa_with_autores(vec![""]);
10647 let err = c.validate_autores().unwrap_err();
10648 assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
10649 }
10650
10651 #[test]
10652 fn validate_autores_shape_takes_precedence_over_duplicate() {
10653 // Per-entry shape-before-cross-entry-duplicate cascade pin: an
10654 // entry that is malformed surfaces `AutorInvalid` even when a
10655 // later entry would have collided on duplicate. The per-entry
10656 // shape arm fires inside the same loop iteration as the empty
10657 // arm, before the seen-set insert at end-of-iteration —
10658 // structural per-entry defects dominate the cross-entry
10659 // uniqueness diagnostic.
10660 let c = caixa_with_autores(vec!["alice\nbob", "alice\nbob"]);
10661 let err = c.validate_autores().unwrap_err();
10662 assert!(
10663 matches!(err, ManifestError::AutorInvalid { .. }),
10664 "got {err:?}",
10665 );
10666 }
10667
10668 #[test]
10669 fn validate_autores_invalid_diagnostic_names_offending_slot_and_value() {
10670 // Diagnostic-shape pin on the new shape arm (peer with
10671 // `validate_descricao_invalid_diagnostic_carries_offending_value`):
10672 // the rendered Display surfaces both the offending slot name
10673 // and the offending value verbatim, so a `feira lint` run
10674 // points the author at the exact `:autores` entry to fix.
10675 let c = caixa_with_autores(vec!["alice\nbob"]);
10676 let rendered = c.validate_autores().unwrap_err().to_string();
10677 assert!(
10678 rendered.contains(":autores"),
10679 "diagnostic must name the offending slot: {rendered}",
10680 );
10681 assert!(
10682 rendered.contains("alice\\nbob"),
10683 "diagnostic must quote the offending value (debug-escaped): {rendered}",
10684 );
10685 }
10686
10687 #[test]
10688 fn validate_autores_rejects_at_129_byte_boundary() {
10689 // The 128-byte cap pin — boundary-exceeding case rejected,
10690 // boundary-accepting case passes. Mirrors the peer
10691 // `chart_maintainer_name_shape_rejects_at_129_byte_boundary`
10692 // substrate-side pin, surfaced at the per-axis caller so the
10693 // cap propagates through validate end-to-end. Constructed as
10694 // a single all-`a` token so only the cap arm fires.
10695 let max_ok = "a".repeat(128);
10696 let c = caixa_with_autores(vec![max_ok.as_str()]);
10697 c.validate_autores().unwrap();
10698 let too_long = "a".repeat(129);
10699 let c = caixa_with_autores(vec![too_long.as_str()]);
10700 let err = c.validate_autores().unwrap_err();
10701 let ManifestError::AutorInvalid { reason, .. } = err else {
10702 panic!("expected AutorInvalid, got {err:?}");
10703 };
10704 assert!(reason.contains("128"), "got: {reason}");
10705 assert!(reason.contains("129"), "got: {reason}");
10706 }
10707
10708 // ── validate_repositorio — universal-axis git-repo-URL shape ──────
10709
10710 fn caixa_with_repositorio(repositorio: Option<&str>) -> Caixa {
10711 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10712 c.repositorio = repositorio.map(String::from);
10713 c
10714 }
10715
10716 #[test]
10717 fn validate_repositorio_accepts_none() {
10718 // The omit-the-slot identity: `:repositorio` is optional. The
10719 // gate is a no-op when the author didn't declare a value —
10720 // every caixa without a `:repositorio` line trivially passes,
10721 // and the substrate-side renderers fall back to their
10722 // documented placeholder (`caixa-helm`'s `home: None`,
10723 // `caixa-flux`'s `https://github.com/pleme-io/<nome>` derived
10724 // URL). Mirrors the peer `validate_restart_window_accepts_none`
10725 // posture on the other `Option<String>` Caixa slot.
10726 let c = caixa_with_repositorio(None);
10727 c.validate_repositorio().unwrap();
10728 }
10729
10730 #[test]
10731 fn validate_repositorio_accepts_canonical_forms() {
10732 // Positive control sweep across every documented `:repositorio`
10733 // authoring shape — the same union the shared
10734 // `crate::render::is_git_repo_url` predicate accepts and the
10735 // peer `:deps :fonte :repo` axis already routes through.
10736 // Covers the `github:` shorthand (the canonical pleme-io
10737 // convention used in the `:repositorio` field of every
10738 // manifest fixture across `caixa-helm` / `caixa-mesh` and the
10739 // `examples/`), the `https://…` URL the README quickstart uses,
10740 // the `ssh://`, `git://`, `git@host:path` scp-style SSH, and
10741 // `file://` URL schemes the shared predicate documents.
10742 for repo in [
10743 "github:pleme-io/hello-rio",
10744 "github:pleme-io/checkout",
10745 "https://github.com/pleme-io/hello-rio",
10746 "ssh://git@github.com/pleme-io/hello-rio.git",
10747 "git://github.com/pleme-io/hello-rio.git",
10748 "git@github.com:pleme-io/hello-rio.git",
10749 "file:///srv/pleme/hello-rio",
10750 ] {
10751 let c = caixa_with_repositorio(Some(repo));
10752 c.validate_repositorio()
10753 .unwrap_or_else(|err| panic!("canonical {repo:?} must pass: {err:?}"));
10754 }
10755 }
10756
10757 #[test]
10758 fn validate_repositorio_rejects_empty_some() {
10759 // Canonical paste-from-blank-doc footgun. The narrower
10760 // [`ManifestError::RepositorioEmpty`] arm fires before the
10761 // shape predicate is consulted, mirroring the empty-first
10762 // cascade every peer per-axis identity gate uses
10763 // (`NomeEmpty` → `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid`,
10764 // `FonteRepoEmpty` → `FonteRepoInvalid`). Without this gate
10765 // the empty `Some("")` silently passed the renderer's
10766 // `Option::unwrap_or_else(|| <fallback>)` (which only fires
10767 // on `None`) and landed as `home: ""` in `Chart.yaml` /
10768 // `url: ""` in the FluxCD `GitRepository`.
10769 let c = caixa_with_repositorio(Some(""));
10770 let err = c.validate_repositorio().unwrap_err();
10771 assert!(
10772 matches!(err, ManifestError::RepositorioEmpty),
10773 "got {err:?}",
10774 );
10775 }
10776
10777 #[test]
10778 fn validate_repositorio_rejects_whitespace() {
10779 // Paste-from-doc whitespace footgun. The shared
10780 // `is_git_repo_url` predicate refuses any whitespace byte; a
10781 // trailing space in a `:repositorio` value silently broke
10782 // `git clone '<value> '` at clone time. The diagnostic names
10783 // the offending value verbatim.
10784 let c = caixa_with_repositorio(Some("github:pleme-io/hello-rio "));
10785 let err = c.validate_repositorio().unwrap_err();
10786 let ManifestError::RepositorioInvalid { repositorio, .. } = err else {
10787 panic!("expected RepositorioInvalid, got {err:?}");
10788 };
10789 assert_eq!(repositorio, "github:pleme-io/hello-rio ");
10790 }
10791
10792 #[test]
10793 fn validate_repositorio_rejects_control_char() {
10794 // Paste-from-multiline-doc CRLF footgun — control characters
10795 // at the URL boundary are a class of subprocess-arg injection
10796 // and break git's URL parser at every porcelain entry point.
10797 let c = caixa_with_repositorio(Some("https://example.com/repo\n"));
10798 let err = c.validate_repositorio().unwrap_err();
10799 assert!(
10800 matches!(err, ManifestError::RepositorioInvalid { .. }),
10801 "got {err:?}",
10802 );
10803 }
10804
10805 #[test]
10806 fn validate_repositorio_rejects_leading_dash() {
10807 // Canonical CLI-argument-injection footgun: `git clone <repo>`
10808 // interprets a leading `-` as a CLI flag, so a
10809 // `-upload-pack=…` value escapes the subprocess argument
10810 // boundary. The shared predicate refuses every leading-`-`
10811 // shape at validate time.
10812 let c = caixa_with_repositorio(Some("-upload-pack=evil"));
10813 let err = c.validate_repositorio().unwrap_err();
10814 assert!(
10815 matches!(err, ManifestError::RepositorioInvalid { .. }),
10816 "got {err:?}",
10817 );
10818 }
10819
10820 #[test]
10821 fn validate_repositorio_rejects_missing_colon_separator() {
10822 // The bare `org/repo` ambiguity footgun — `git clone` reads
10823 // a no-`:` form as a relative filesystem path rather than the
10824 // GitHub-shorthand expansion the author probably intended.
10825 // The shared predicate refuses every shape without a `:`
10826 // separator.
10827 let c = caixa_with_repositorio(Some("pleme-io/hello-rio"));
10828 let err = c.validate_repositorio().unwrap_err();
10829 assert!(
10830 matches!(err, ManifestError::RepositorioInvalid { .. }),
10831 "got {err:?}",
10832 );
10833 }
10834
10835 #[test]
10836 fn validate_repositorio_rejects_fragment_anchor() {
10837 // Paste-from-browser-address-bar footgun on the
10838 // `:repositorio` axis — an author copies a GitHub permalink
10839 // to a README section / line-permalink and forgets to trim
10840 // the `#fragment` tail. The shared `is_git_repo_url`
10841 // predicate refuses the byte at the URL-grammar layer
10842 // (libcurl strips the fragment before opening the
10843 // transport, so the byte rides verbatim into the rendered
10844 // `Chart.yaml` `home:` and FluxCD `GitRepository` `url:`
10845 // fields but is silently dropped on the wire — two
10846 // manifest variants whose values differ only in their
10847 // fragment anchor lock to two distinct rendered artifacts
10848 // for the byte-identical clone, defeating the THEORY.md
10849 // §V.2 render-determinism contract on the `:repositorio`
10850 // axis the peer `:fonte :repo` axis already closes).
10851 let c = caixa_with_repositorio(Some("https://github.com/pleme-io/hello-rio#readme"));
10852 let err = c.validate_repositorio().unwrap_err();
10853 let ManifestError::RepositorioInvalid {
10854 repositorio,
10855 reason,
10856 } = err
10857 else {
10858 panic!("expected RepositorioInvalid, got {err:?}");
10859 };
10860 assert_eq!(repositorio, "https://github.com/pleme-io/hello-rio#readme");
10861 assert!(
10862 reason.contains("must not contain `#`"),
10863 "reason must surface the fragment-`#` arm, got {reason:?}"
10864 );
10865 }
10866
10867 #[test]
10868 fn validate_repositorio_rejects_query_string() {
10869 // Paste-from-browser-address-bar footgun on the
10870 // `:repositorio` axis (peer with the a68f818 fragment-`#`
10871 // arm on the same axis). An author copies a GitHub tab
10872 // deep-link out of the address bar and forgets to trim
10873 // the `?tab=…` query tail. The shared `is_git_repo_url`
10874 // predicate refuses the byte at the URL-grammar layer
10875 // (GitHub / GitLab / Bitbucket silently ignore the
10876 // `?query` tail and serve the same repo regardless, so
10877 // the byte rides verbatim into the rendered `Chart.yaml`
10878 // `home:` and FluxCD `GitRepository` `url:` fields but
10879 // is silently masked at the wire — two manifest variants
10880 // whose values differ only in their query tail lock to
10881 // two distinct rendered artifacts for the byte-identical
10882 // clone, defeating the THEORY.md §V.2 render-determinism
10883 // contract on the `:repositorio` axis the peer `:fonte
10884 // :repo` axis already closes).
10885 let c = caixa_with_repositorio(Some(
10886 "https://github.com/pleme-io/hello-rio?tab=readme-ov-file",
10887 ));
10888 let err = c.validate_repositorio().unwrap_err();
10889 let ManifestError::RepositorioInvalid {
10890 repositorio,
10891 reason,
10892 } = err
10893 else {
10894 panic!("expected RepositorioInvalid, got {err:?}");
10895 };
10896 assert_eq!(
10897 repositorio,
10898 "https://github.com/pleme-io/hello-rio?tab=readme-ov-file"
10899 );
10900 assert!(
10901 reason.contains("must not contain `?`"),
10902 "reason must surface the query-`?` arm, got {reason:?}"
10903 );
10904 }
10905
10906 #[test]
10907 fn validate_repositorio_rejects_embedded_backslash() {
10908 // Windows-file-path-confusion footgun on the `:repositorio`
10909 // axis (peer with the prior fragment-`#` / query-`?` arms on
10910 // the same axis, and peer with the new dep-level `:fonte :repo`
10911 // backslash arm on the URL-grammar trajectory). An author
10912 // pastes a Windows Explorer address-bar `file:///C:\Users\me\
10913 // hello-rio` into the `:repositorio` slot, expecting the
10914 // `lareira-<nome>` chart's `home:` field and the FluxCD
10915 // `GitRepository` `url:` field to render the canonical local
10916 // file-URI. The shared `is_git_repo_url` predicate refuses
10917 // the byte at the URL-grammar layer (libcurl silently
10918 // translates `\` → `/` on some platforms and refuses it on
10919 // others, so the byte rides verbatim into the rendered
10920 // artifacts but is silently rewritten or rejected at the wire
10921 // — two manifest variants whose values differ only in
10922 // backslash-vs-forward-slash lock to two distinct rendered
10923 // artifacts for the byte-identical clone, defeating the
10924 // THEORY.md §V.2 render-determinism contract on the
10925 // `:repositorio` axis the peer `:fonte :repo` axis already
10926 // closes).
10927 let c = caixa_with_repositorio(Some("file:///C:\\Users\\me\\hello-rio"));
10928 let err = c.validate_repositorio().unwrap_err();
10929 let ManifestError::RepositorioInvalid {
10930 repositorio,
10931 reason,
10932 } = err
10933 else {
10934 panic!("expected RepositorioInvalid, got {err:?}");
10935 };
10936 assert_eq!(repositorio, "file:///C:\\Users\\me\\hello-rio");
10937 assert!(
10938 reason.contains("must not contain `\\`"),
10939 "reason must surface the backslash-`\\` arm, got {reason:?}"
10940 );
10941 }
10942
10943 #[test]
10944 fn validate_repositorio_rejects_uri_template_placeholder() {
10945 // URI Template (RFC 6570) placeholder footgun on the
10946 // `:repositorio` axis (peer with the prior fragment-`#` /
10947 // query-`?` / backslash-`\` arms on the same axis, and peer
10948 // with the new dep-level `:fonte :repo` `{` / `}` arm on the
10949 // URL-grammar trajectory). An author pastes a quick-start
10950 // README snippet / OpenAPI `servers:` URL / Helm chart
10951 // `home:` template carrying unresolved `{org}` / `{repo}`
10952 // placeholders into the `:repositorio` slot, expecting the
10953 // substrate to resolve the placeholder downstream. The
10954 // shared `is_git_repo_url` predicate refuses the byte at the
10955 // URL-grammar layer (libcurl percent-encodes `{` / `}` to
10956 // `%7B` / `%7D` on the wire, so the byte round-trips
10957 // inconsistently between the rendered `Chart.yaml home:` /
10958 // FluxCD `GitRepository url:` and the resolver's `git clone`
10959 // invocation, defeating the THEORY.md §V.2 render-
10960 // determinism contract on the `:repositorio` axis the peer
10961 // `:fonte :repo` axis already closes; every git porcelain
10962 // entry-point additionally fetches a nonexistent literal-
10963 // `{placeholder}`-named path far from the source caixa.lisp).
10964 let c = caixa_with_repositorio(Some("https://github.com/{org}/hello-rio"));
10965 let err = c.validate_repositorio().unwrap_err();
10966 let ManifestError::RepositorioInvalid {
10967 repositorio,
10968 reason,
10969 } = err
10970 else {
10971 panic!("expected RepositorioInvalid, got {err:?}");
10972 };
10973 assert_eq!(repositorio, "https://github.com/{org}/hello-rio");
10974 assert!(
10975 reason.contains("must not contain `{`"),
10976 "reason must surface the open-brace `{{` arm, got {reason:?}"
10977 );
10978 assert!(
10979 reason.contains("URI Template") || reason.contains("RFC 6570"),
10980 "reason must name the RFC 6570 URI Template grammar, got {reason:?}"
10981 );
10982 }
10983
10984 #[test]
10985 fn validate_repositorio_empty_takes_precedence_over_shape() {
10986 // Empty-first cascade pin: the empty `Some("")` surfaces the
10987 // narrower `RepositorioEmpty` not the shape-predicate-wrapped
10988 // `RepositorioInvalid`, mirroring the peer
10989 // `NomeEmpty` → `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid`,
10990 // `FonteRepoEmpty` → `FonteRepoInvalid` cascades. The shared
10991 // `is_git_repo_url` predicate also rejects the empty input
10992 // (defensively, with its own `"must not be empty"` reason),
10993 // but the manifest-layer empty arm runs first to surface the
10994 // narrower diagnostic verbatim.
10995 let c = caixa_with_repositorio(Some(""));
10996 let err = c.validate_repositorio().unwrap_err();
10997 assert!(
10998 matches!(err, ManifestError::RepositorioEmpty),
10999 "got {err:?}",
11000 );
11001 }
11002
11003 #[test]
11004 fn validate_repositorio_diagnostic_carries_offending_value() {
11005 // Diagnostic-shape pin (peer with
11006 // `validate_autores_diagnostic_carries_offending_author`): the
11007 // error's Display surfaces the offending value + slot name
11008 // verbatim, so a `feira lint` run can render the diagnostic
11009 // without re-parsing and the author can grep their caixa.lisp
11010 // for the offending `:repositorio` value.
11011 let c = caixa_with_repositorio(Some("pleme-io/hello-rio"));
11012 let rendered = c.validate_repositorio().unwrap_err().to_string();
11013 assert!(
11014 rendered.contains(":repositorio"),
11015 "diagnostic must name the offending slot: {rendered}",
11016 );
11017 assert!(
11018 rendered.contains("pleme-io/hello-rio"),
11019 "diagnostic must quote the offending value: {rendered}",
11020 );
11021 }
11022
11023 // ── validate_descricao — universal-axis Chart.yaml description shape ──
11024
11025 fn caixa_with_descricao(descricao: Option<&str>) -> Caixa {
11026 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
11027 c.descricao = descricao.map(String::from);
11028 c
11029 }
11030
11031 #[test]
11032 fn validate_descricao_accepts_none() {
11033 // The omit-the-slot identity: `:descricao` is optional. The
11034 // gate is a no-op when the author didn't declare a value —
11035 // every caixa without a `:descricao` line trivially passes,
11036 // and the substrate-side renderers fall back to their
11037 // documented `caixa.nome`-derived placeholder. Mirrors the
11038 // peer `validate_repositorio_accepts_none` posture on the
11039 // sibling `Option<String>` Caixa slot.
11040 let c = caixa_with_descricao(None);
11041 c.validate_descricao().unwrap();
11042 }
11043
11044 #[test]
11045 fn validate_descricao_accepts_canonical_summary() {
11046 // Positive control: the canonical pleme-io descricao shape —
11047 // a short free-form prose summary — passes the gate. Covers
11048 // the fixture shapes the `caixa-helm` / `caixa-flux` /
11049 // `caixa-mesh` test fixtures use (`"Canonical Rust→wasm32-
11050 // wasip2 caixa Servico."`, `"Checkout flow."`).
11051 for desc in [
11052 "Canonical Rust→wasm32-wasip2 caixa Servico.",
11053 "Checkout flow.",
11054 "AWS provider caixa for tatara-lisp",
11055 "FIXME — describe this caixa",
11056 "x",
11057 ] {
11058 let c = caixa_with_descricao(Some(desc));
11059 c.validate_descricao()
11060 .unwrap_or_else(|err| panic!("canonical {desc:?} must pass: {err:?}"));
11061 }
11062 }
11063
11064 #[test]
11065 fn validate_descricao_rejects_empty_some() {
11066 // Canonical paste-from-blank-doc footgun. Without this gate
11067 // the empty `Some("")` silently passed the renderer's
11068 // `Option::unwrap_or_else(|| <fallback>)` (which only fires
11069 // on `None`) and landed as `description: ""` in `Chart.yaml`
11070 // and a blank `README.md` header. Mirrors the peer
11071 // [`ManifestError::RepositorioEmpty`] empty-arm on the
11072 // sibling `Option<String>` Caixa slot.
11073 let c = caixa_with_descricao(Some(""));
11074 let err = c.validate_descricao().unwrap_err();
11075 assert!(matches!(err, ManifestError::DescricaoEmpty), "got {err:?}",);
11076 }
11077
11078 #[test]
11079 fn validate_descricao_rejects_leading_whitespace() {
11080 // Paste-from-aligned-doc footgun: a leading ASCII space the
11081 // bare empty-arm gate accepted, the shape predicate now
11082 // refuses. The diagnostic carries the offending value
11083 // verbatim (with the leading space preserved) so the author
11084 // can grep their caixa.lisp for the exact `:descricao` line
11085 // and fix the round-trip-inconsistent leading whitespace.
11086 // Mirrors the peer
11087 // `validate_licenca_rejects_leading_whitespace` arm on the
11088 // sibling `:licenca` axis.
11089 let c = caixa_with_descricao(Some(" Checkout flow."));
11090 let err = c.validate_descricao().unwrap_err();
11091 let ManifestError::DescricaoInvalid { descricao, reason } = err else {
11092 panic!("expected DescricaoInvalid, got {err:?}");
11093 };
11094 assert_eq!(descricao, " Checkout flow.");
11095 assert!(reason.contains("whitespace"), "got: {reason:?}");
11096 }
11097
11098 #[test]
11099 fn validate_descricao_rejects_trailing_whitespace() {
11100 // Paste-from-doc footgun: a trailing ASCII space the bare
11101 // empty-arm gate accepted, the shape predicate now refuses.
11102 let c = caixa_with_descricao(Some("Checkout flow. "));
11103 let err = c.validate_descricao().unwrap_err();
11104 let ManifestError::DescricaoInvalid { descricao, reason } = err else {
11105 panic!("expected DescricaoInvalid, got {err:?}");
11106 };
11107 assert_eq!(descricao, "Checkout flow. ");
11108 assert!(reason.contains("whitespace"), "got: {reason:?}");
11109 }
11110
11111 #[test]
11112 fn validate_descricao_rejects_embedded_newline() {
11113 // Paste-from-multiline-doc footgun: an embedded LF the bare
11114 // empty-arm gate accepted, the shape predicate now refuses.
11115 // Without this gate the embedded newline silently landed in
11116 // the rendered Chart.yaml as a multi-line YAML block scalar,
11117 // and every chart-aware UI (`helm list`, `helm search`,
11118 // Artifact Hub) renders the description in a single-line
11119 // column so the embedded newline is silently dropped at
11120 // every downstream consumer.
11121 let c = caixa_with_descricao(Some("Checkout\nflow."));
11122 let err = c.validate_descricao().unwrap_err();
11123 assert!(
11124 matches!(err, ManifestError::DescricaoInvalid { .. }),
11125 "got {err:?}",
11126 );
11127 assert!(err.to_string().contains("newline"), "got {err}");
11128 }
11129
11130 #[test]
11131 fn validate_descricao_rejects_embedded_carriage_return() {
11132 // Paste-from-Windows-CRLF-doc footgun.
11133 let c = caixa_with_descricao(Some("Checkout\rflow."));
11134 let err = c.validate_descricao().unwrap_err();
11135 assert!(
11136 matches!(err, ManifestError::DescricaoInvalid { .. }),
11137 "got {err:?}",
11138 );
11139 assert!(err.to_string().contains("carriage return"), "got {err}");
11140 }
11141
11142 #[test]
11143 fn validate_descricao_rejects_embedded_tab() {
11144 // Tab-from-aligned-doc footgun.
11145 let c = caixa_with_descricao(Some("Checkout\tflow."));
11146 let err = c.validate_descricao().unwrap_err();
11147 assert!(
11148 matches!(err, ManifestError::DescricaoInvalid { .. }),
11149 "got {err:?}",
11150 );
11151 assert!(err.to_string().contains("tab"), "got {err}");
11152 }
11153
11154 #[test]
11155 fn validate_descricao_rejects_embedded_control_bytes() {
11156 // Paste-from-binary-blob footgun: every other control byte
11157 // (NUL, BEL, ESC, DEL) is refused at validate time. Mirrors
11158 // the peer SPDX-expression control-byte arm.
11159 for s in [
11160 "Checkout\x00flow.",
11161 "Checkout\x07flow.",
11162 "Checkout\x1bflow.",
11163 "Checkout\x7fflow.",
11164 ] {
11165 let c = caixa_with_descricao(Some(s));
11166 let err = c.validate_descricao().unwrap_err();
11167 assert!(
11168 matches!(err, ManifestError::DescricaoInvalid { .. }),
11169 "{s:?} got {err:?}",
11170 );
11171 assert!(
11172 err.to_string().contains("control character"),
11173 "{s:?} got {err}",
11174 );
11175 }
11176 }
11177
11178 #[test]
11179 fn validate_descricao_accepts_unicode_prose() {
11180 // Positive control: Unicode prose is accepted — the
11181 // canonical fixtures carry `→` (U+2192) and `—` (U+2014),
11182 // and `Caixa::template`'s `"FIXME — describe this caixa"`
11183 // scaffold every `feira init` emits must continue to pass.
11184 for s in [
11185 "Canonical Rust→wasm32-wasip2 caixa Servico.",
11186 "FIXME — describe this caixa",
11187 "Caixa pour le projet tâche",
11188 "日本語の説明",
11189 ] {
11190 let c = caixa_with_descricao(Some(s));
11191 c.validate_descricao()
11192 .unwrap_or_else(|err| panic!("Unicode {s:?} must pass: {err:?}"));
11193 }
11194 }
11195
11196 #[test]
11197 fn validate_descricao_empty_takes_precedence_over_shape() {
11198 // Cascade pin: a `Some("")` surfaces the narrower
11199 // `DescricaoEmpty` arm, not the broader `DescricaoInvalid`
11200 // shape-predicate arm. Mirrors the peer
11201 // `validate_licenca_empty_takes_precedence_over_shape` pin
11202 // on the sibling `:licenca` axis.
11203 let c = caixa_with_descricao(Some(""));
11204 let err = c.validate_descricao().unwrap_err();
11205 assert!(matches!(err, ManifestError::DescricaoEmpty), "got {err:?}",);
11206 }
11207
11208 #[test]
11209 fn validate_descricao_invalid_diagnostic_carries_offending_value_and_slot() {
11210 // Diagnostic-shape pin: the error's Display surfaces both
11211 // the `:descricao` slot name and the offending value
11212 // verbatim, so a `feira lint` run can render the diagnostic
11213 // without re-parsing and the author can grep their caixa.lisp
11214 // for the offending `:descricao` line. Mirrors the peer
11215 // `validate_licenca_invalid_diagnostic_carries_offending_value_and_slot`
11216 // pin (ee2e888) on the sibling `:licenca` axis.
11217 // The `{descricao:?}` Debug format escapes embedded control
11218 // bytes; the quoted offending value surfaces as
11219 // `"Checkout\nflow."` (literal backslash-n) in the rendered
11220 // diagnostic. The author can grep their caixa.lisp for the
11221 // literal `Checkout` summary prefix.
11222 let c = caixa_with_descricao(Some("Checkout\nflow."));
11223 let rendered = c.validate_descricao().unwrap_err().to_string();
11224 assert!(
11225 rendered.contains(":descricao"),
11226 "diagnostic must name the offending slot: {rendered}",
11227 );
11228 assert!(
11229 rendered.contains("Checkout\\nflow."),
11230 "diagnostic must quote the offending value (debug-escaped): {rendered}",
11231 );
11232 }
11233
11234 #[test]
11235 fn validate_descricao_template_passes() {
11236 // Round-trip pin: the bare `Caixa::template` shape carries
11237 // `:descricao "FIXME — describe this caixa"` (a non-empty
11238 // sentinel), so the template-derived Caixa passes the gate by
11239 // construction. A future template-shape change that omits or
11240 // empties `:descricao` would surface here as a regression.
11241 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
11242 c.validate_descricao().unwrap();
11243 }
11244
11245 #[test]
11246 fn validate_descricao_diagnostic_names_offending_slot() {
11247 // Diagnostic-shape pin (peer with
11248 // `validate_repositorio_diagnostic_carries_offending_value`):
11249 // the error's Display surfaces the `:descricao` slot name
11250 // verbatim, so a `feira lint` run can render the diagnostic
11251 // without re-parsing and the author can grep their caixa.lisp
11252 // for the offending `:descricao` line.
11253 let c = caixa_with_descricao(Some(""));
11254 let rendered = c.validate_descricao().unwrap_err().to_string();
11255 assert!(
11256 rendered.contains(":descricao"),
11257 "diagnostic must name the offending slot: {rendered}",
11258 );
11259 }
11260
11261 // ── validate_licenca — universal-axis chart README license shape ──
11262
11263 fn caixa_with_licenca(licenca: Option<&str>) -> Caixa {
11264 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
11265 c.licenca = licenca.map(String::from);
11266 c
11267 }
11268
11269 #[test]
11270 fn validate_licenca_accepts_none() {
11271 // The omit-the-slot identity: `:licenca` is optional. The
11272 // gate is a no-op when the author didn't declare a value —
11273 // every caixa without a `:licenca` line trivially passes,
11274 // and the substrate-side `caixa-helm` renderer falls back to
11275 // the documented `"MIT"` placeholder. Mirrors the peer
11276 // `validate_descricao_accepts_none` posture on the sibling
11277 // `Option<String>` Caixa slot.
11278 let c = caixa_with_licenca(None);
11279 c.validate_licenca().unwrap();
11280 }
11281
11282 #[test]
11283 fn validate_licenca_accepts_canonical_expressions() {
11284 // Positive control: every canonical SPDX expression shape
11285 // pleme-io carries in its existing fixtures + the canonical
11286 // SPDX dual-license / with-exception / `+`-suffix / grouped /
11287 // user-defined-reference shapes all pass the gate. Covers
11288 // the single-license, `OR`-compound, `AND`-compound,
11289 // `WITH`-exception, parenthesis-grouped, `+`-suffix, and
11290 // `LicenseRef-` / `DocumentRef-:LicenseRef-` shapes — every
11291 // production the SPDX 2.1 expression grammar admits that
11292 // sits within the alphabet floor the
11293 // `is_spdx_expression_shape` predicate enforces.
11294 for lic in [
11295 "MIT",
11296 "Apache-2.0",
11297 "Apache-2.0 OR MIT",
11298 "Apache-2.0 AND MIT",
11299 "BSD-3-Clause",
11300 "MPL-2.0",
11301 "GPL-3.0-or-later",
11302 "GPL-2.0+",
11303 "Apache-2.0 WITH LLVM-exception",
11304 "(MIT OR Apache-2.0) AND BSD-3-Clause",
11305 "(MIT OR Apache-2.0) AND BSD-3-Clause AND ISC",
11306 "LicenseRef-MyLicense",
11307 "DocumentRef-spdx-tool:LicenseRef-MIT-Style",
11308 "x",
11309 ] {
11310 let c = caixa_with_licenca(Some(lic));
11311 c.validate_licenca()
11312 .unwrap_or_else(|err| panic!("canonical {lic:?} must pass: {err:?}"));
11313 }
11314 }
11315
11316 #[test]
11317 fn validate_licenca_rejects_trailing_whitespace() {
11318 // Paste-from-doc whitespace footgun. A trailing space in the
11319 // `:licenca` value would silently break a downstream SPDX
11320 // parser that splits on exact `AND` / `OR` / `WITH` keyword
11321 // boundaries. The shape predicate refuses every trailing
11322 // whitespace byte by construction. Peer with
11323 // `validate_repositorio_rejects_whitespace` and
11324 // `validate_edicao_rejects_trailing_whitespace`.
11325 let c = caixa_with_licenca(Some("MIT "));
11326 let err = c.validate_licenca().unwrap_err();
11327 let ManifestError::LicencaInvalid { licenca, .. } = err else {
11328 panic!("expected LicencaInvalid, got {err:?}");
11329 };
11330 assert_eq!(licenca, "MIT ");
11331 }
11332
11333 #[test]
11334 fn validate_licenca_rejects_leading_whitespace() {
11335 // Symmetric paste-from-doc whitespace footgun on the leading
11336 // boundary — the gate refuses every shape that starts with a
11337 // space byte by construction. Peer with
11338 // `validate_edicao_rejects_leading_whitespace`.
11339 let c = caixa_with_licenca(Some(" MIT"));
11340 let err = c.validate_licenca().unwrap_err();
11341 assert!(
11342 matches!(err, ManifestError::LicencaInvalid { .. }),
11343 "got {err:?}",
11344 );
11345 }
11346
11347 #[test]
11348 fn validate_licenca_rejects_control_char() {
11349 // Paste-from-multiline-doc CRLF footgun — control characters
11350 // at the value boundary land as a malformed line in the
11351 // rendered chart `README.md` `## License` section. Peer with
11352 // `validate_repositorio_rejects_control_char` and
11353 // `validate_edicao_rejects_control_char`.
11354 for lic in ["MIT\n", "MIT\r\n", "MIT\rApache-2.0"] {
11355 let c = caixa_with_licenca(Some(lic));
11356 let err = c.validate_licenca().unwrap_err();
11357 assert!(
11358 matches!(err, ManifestError::LicencaInvalid { .. }),
11359 "expected LicencaInvalid on {lic:?}, got {err:?}",
11360 );
11361 }
11362 }
11363
11364 #[test]
11365 fn validate_licenca_rejects_tab() {
11366 // Tab-from-aligned-doc footgun — SPDX expressions use a
11367 // single ASCII space between tokens; a tab breaks every
11368 // downstream SPDX parser that splits on exact `" "`
11369 // boundaries.
11370 let c = caixa_with_licenca(Some("MIT\tOR Apache-2.0"));
11371 let err = c.validate_licenca().unwrap_err();
11372 assert!(
11373 matches!(err, ManifestError::LicencaInvalid { .. }),
11374 "got {err:?}",
11375 );
11376 }
11377
11378 #[test]
11379 fn validate_licenca_rejects_non_ascii() {
11380 // Smart-quote / non-ASCII paste footgun — SPDX identifiers
11381 // are ASCII per the `idstring = 1*(ALPHA / DIGIT / "-" /
11382 // ".")` production. The shape predicate refuses every
11383 // non-ASCII byte by construction; peer with
11384 // `validate_edicao_rejects_non_ascii_lookalike`.
11385 for lic in ["MIT\u{a0}OR Apache-2.0", "MIT\u{2013}1.0", "Café-1.0"] {
11386 let c = caixa_with_licenca(Some(lic));
11387 let err = c.validate_licenca().unwrap_err();
11388 assert!(
11389 matches!(err, ManifestError::LicencaInvalid { .. }),
11390 "expected LicencaInvalid on {lic:?}, got {err:?}",
11391 );
11392 }
11393 }
11394
11395 #[test]
11396 fn validate_licenca_rejects_underscore() {
11397 // Underscore-instead-of-hyphen typo footgun — `Apache_2.0` /
11398 // `MIT_Style` / `BSD_3_Clause` are familiar shapes from
11399 // snake-case identifier conventions that don't apply to the
11400 // SPDX `idstring` grammar (which admits only `ALPHA / DIGIT /
11401 // "-" / "."`). The shape predicate refuses every underscore
11402 // byte by construction.
11403 for lic in ["Apache_2.0", "MIT_Style", "BSD_3_Clause"] {
11404 let c = caixa_with_licenca(Some(lic));
11405 let err = c.validate_licenca().unwrap_err();
11406 assert!(
11407 matches!(err, ManifestError::LicencaInvalid { .. }),
11408 "expected LicencaInvalid on {lic:?}, got {err:?}",
11409 );
11410 }
11411 }
11412
11413 #[test]
11414 fn validate_licenca_rejects_comma_separator() {
11415 // Comma-instead-of-`OR`-keyword colloquial idiom footgun —
11416 // SPDX expressions compose multiple licenses via `AND` / `OR`
11417 // keywords, not the comma separator. The shape predicate
11418 // refuses every comma byte by construction.
11419 for lic in ["MIT, Apache-2.0", "MIT,Apache-2.0"] {
11420 let c = caixa_with_licenca(Some(lic));
11421 let err = c.validate_licenca().unwrap_err();
11422 assert!(
11423 matches!(err, ManifestError::LicencaInvalid { .. }),
11424 "expected LicencaInvalid on {lic:?}, got {err:?}",
11425 );
11426 }
11427 }
11428
11429 #[test]
11430 fn validate_licenca_rejects_slash_dual_license() {
11431 // Slash-dual-license colloquial idiom footgun — the
11432 // `MIT/Apache-2.0` shape is common in Cargo's pre-SPDX
11433 // `package.license` field but non-SPDX; the SPDX equivalent
11434 // is `MIT OR Apache-2.0`. The shape predicate refuses every
11435 // forward-slash byte by construction.
11436 for lic in ["MIT/Apache-2.0", "MIT/BSD-3-Clause"] {
11437 let c = caixa_with_licenca(Some(lic));
11438 let err = c.validate_licenca().unwrap_err();
11439 assert!(
11440 matches!(err, ManifestError::LicencaInvalid { .. }),
11441 "expected LicencaInvalid on {lic:?}, got {err:?}",
11442 );
11443 }
11444 }
11445
11446 #[test]
11447 fn validate_licenca_rejects_semicolon_separator() {
11448 // Semicolon-list-separator confusion footgun — adjacent to
11449 // the comma-separator idiom, every list-separator-belongs-
11450 // to-list-grammar confusion lands here.
11451 let c = caixa_with_licenca(Some("MIT; Apache-2.0"));
11452 let err = c.validate_licenca().unwrap_err();
11453 assert!(
11454 matches!(err, ManifestError::LicencaInvalid { .. }),
11455 "got {err:?}",
11456 );
11457 }
11458
11459 #[test]
11460 fn validate_licenca_empty_takes_precedence_over_shape() {
11461 // Empty-first cascade pin: the empty `Some("")` surfaces the
11462 // narrower `LicencaEmpty` not the shape-predicate-wrapped
11463 // `LicencaInvalid`, mirroring the peer
11464 // `validate_edicao_empty_takes_precedence_over_shape` and
11465 // `validate_repositorio_empty_takes_precedence_over_shape`
11466 // (`RepositorioEmpty` → `RepositorioInvalid`), `NomeEmpty` →
11467 // `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid` cascades.
11468 // The shape predicate also refuses the empty input
11469 // (defensively — `"must not be empty"`), but the manifest-
11470 // layer empty arm runs first to surface the narrower
11471 // diagnostic verbatim.
11472 let c = caixa_with_licenca(Some(""));
11473 let err = c.validate_licenca().unwrap_err();
11474 assert!(matches!(err, ManifestError::LicencaEmpty), "got {err:?}",);
11475 }
11476
11477 #[test]
11478 fn validate_licenca_invalid_diagnostic_carries_offending_value() {
11479 // Diagnostic-shape pin on the shape-predicate arm (peer with
11480 // `validate_edicao_invalid_diagnostic_carries_offending_value`
11481 // and `validate_repositorio_diagnostic_carries_offending_value`):
11482 // the error's Display surfaces the offending value + slot
11483 // name verbatim, so a `feira lint` run can render the
11484 // diagnostic without re-parsing and the author can grep
11485 // their caixa.lisp for the offending `:licenca` value.
11486 let c = caixa_with_licenca(Some("Apache_2.0"));
11487 let rendered = c.validate_licenca().unwrap_err().to_string();
11488 assert!(
11489 rendered.contains(":licenca"),
11490 "diagnostic must name the offending slot: {rendered}",
11491 );
11492 assert!(
11493 rendered.contains("Apache_2.0"),
11494 "diagnostic must quote the offending value: {rendered}",
11495 );
11496 }
11497
11498 #[test]
11499 fn validate_licenca_rejects_empty_some() {
11500 // Canonical paste-from-blank-doc footgun. Without this gate
11501 // the empty `Some("")` silently passed the renderer's
11502 // `Option::unwrap_or_else(|| "MIT".into())` (which only
11503 // fires on `None`) and landed as a bare trailing period in
11504 // the rendered chart `README.md` `## License` section.
11505 // Mirrors the peer [`ManifestError::DescricaoEmpty`] empty-
11506 // arm on the sibling `Option<String>` Caixa slot.
11507 let c = caixa_with_licenca(Some(""));
11508 let err = c.validate_licenca().unwrap_err();
11509 assert!(matches!(err, ManifestError::LicencaEmpty), "got {err:?}",);
11510 }
11511
11512 #[test]
11513 fn validate_licenca_template_passes() {
11514 // Round-trip pin: the bare `Caixa::template` shape (whether
11515 // it carries `:licenca` or omits it) passes the gate by
11516 // construction. A future template-shape change that
11517 // introduced `(:licenca "")` would surface here as a
11518 // regression. Mirrors the peer
11519 // `validate_descricao_template_passes` pin.
11520 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
11521 c.validate_licenca().unwrap();
11522 }
11523
11524 #[test]
11525 fn validate_licenca_diagnostic_names_offending_slot() {
11526 // Diagnostic-shape pin (peer with
11527 // `validate_descricao_diagnostic_names_offending_slot`):
11528 // the error's Display surfaces the `:licenca` slot name
11529 // verbatim, so a `feira lint` run can render the diagnostic
11530 // without re-parsing and the author can grep their caixa.lisp
11531 // for the offending `:licenca` line.
11532 let c = caixa_with_licenca(Some(""));
11533 let rendered = c.validate_licenca().unwrap_err().to_string();
11534 assert!(
11535 rendered.contains(":licenca"),
11536 "diagnostic must name the offending slot: {rendered}",
11537 );
11538 }
11539
11540 // ── Caixa::licenca — outer top-level Option<&str> scalar accessor ──
11541
11542 #[test]
11543 fn licenca_returns_licenca_byte_string_verbatim_across_permutations() {
11544 // The canonical per-`Caixa` `:licenca` SPDX-expression scalar
11545 // pin: [`Caixa::licenca`] must return the `:licenca` typed
11546 // byte-string verbatim as an `Option<&str>`, byte-equal to the
11547 // raw `self.licenca.as_deref()` access across every
11548 // representative value in the accept-set — `None` (the "omit
11549 // the slot to defer to the caixa-helm renderer's `MIT`
11550 // fallback" arm every existing fixture without a `:licenca`
11551 // line carries), `Some("")` (a past-the-guard sentinel that
11552 // pins the accessor doesn't perform a silent
11553 // `Some("") → None` collapse on the empty arm — validate
11554 // rejects `Some("")` through `LicencaEmpty` but the accessor
11555 // must ship the raw slot verbatim so a validate-time gate
11556 // regression surfaces at the caixa-helm emit boundary rather
11557 // than being silently absorbed into the fallback), `Some("MIT")`
11558 // (the canonical single-license shape every `feira init`
11559 // template scaffolds), `Some("Apache-2.0 OR MIT")` (the
11560 // canonical `OR`-compound shape the peer
11561 // `validate_licenca_accepts_canonical_expressions` positive
11562 // sweep exercises), `Some("(MIT OR Apache-2.0) AND
11563 // BSD-3-Clause")` (the canonical parenthesis-grouped shape),
11564 // `Some("MIT ")` / `Some(" MIT")` / `Some("MIT\n")` /
11565 // `Some("Apache_2.0")` / `Some("MIT,Apache-2.0")` (past-the-
11566 // guard sentinels — validate rejects each through
11567 // `LicencaInvalid` but the accessor must ship the raw slot
11568 // verbatim).
11569 //
11570 // First outer top-level [`Caixa`] `Option<&str>`-return scalar
11571 // accessor pin on the substrate primitive — opens the "outer
11572 // [`Caixa`] `Option<&str>` scalar" projection pattern the
11573 // sibling per-`Caixa` `:descricao` / `:repositorio` / `:edicao`
11574 // future lifts fold on. Sibling in shape to the peer per-`:placement`
11575 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
11576 // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
11577 // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
11578 // axes, extended onto the outer top-level [`Caixa`] universal-
11579 // axis surface. Pins against a future silent detour that
11580 // returned an owned `Option<String>` (which would type-check
11581 // but silently allocate on every accessor call, breaking the
11582 // zero-cost projection every peer sibling accessor carries), a
11583 // `Some("") → None` collapse (which would silently absorb the
11584 // `LicencaEmpty` refusal case at the accessor boundary and the
11585 // caixa-helm emit path would silently fall back to `"MIT"` on
11586 // a struct-literal `Caixa { licenca: Some(""), .. }`), or a
11587 // `None → Some("MIT")` collapse (which would silently reify
11588 // the caixa-helm renderer's `"MIT"` fallback at the accessor
11589 // boundary and every downstream consumer keying off the
11590 // `Option::is_none()` discriminator would lose the "author
11591 // omitted the slot" signal).
11592 for licenca in [
11593 None,
11594 Some(""),
11595 Some("MIT"),
11596 Some("Apache-2.0 OR MIT"),
11597 Some("(MIT OR Apache-2.0) AND BSD-3-Clause"),
11598 Some("MIT "),
11599 Some(" MIT"),
11600 Some("MIT\n"),
11601 Some("Apache_2.0"),
11602 Some("MIT,Apache-2.0"),
11603 ] {
11604 let c = caixa_with_licenca(licenca);
11605 assert_eq!(
11606 c.licenca(),
11607 licenca,
11608 "Caixa::licenca must return :licenca verbatim (got {:?}, \
11609 expected {licenca:?})",
11610 c.licenca(),
11611 );
11612 assert_eq!(
11613 c.licenca(),
11614 c.licenca.as_deref(),
11615 "Caixa::licenca must byte-equal the raw \
11616 `self.licenca.as_deref()` field access across every \
11617 value in the Option<&str> accept-set",
11618 );
11619 }
11620 }
11621
11622 #[test]
11623 fn validate_licenca_empty_arm_routes_through_accessor() {
11624 // Composition pin: [`Caixa::validate_licenca`]'s empty-arm gate
11625 // must key off [`Caixa::licenca`], not the raw
11626 // `self.licenca.as_deref()` field access. Structurally: a
11627 // `Caixa { licenca: Some(""), .. }` must surface the
11628 // `LicencaEmpty` refusal exactly, and a
11629 // `Caixa { licenca: Some("MIT"), .. }` (the canonical
11630 // single-license form) must pass validate. The pair jointly
11631 // pins the accessor + validate-gate composition: any future
11632 // silent detour that had the accessor return `None` on the
11633 // empty arm (a `.filter(|s| !s.is_empty())` collapse) would
11634 // silently absorb the `LicencaEmpty` refusal at the accessor
11635 // boundary and the validate gate would accept a struct-literal
11636 // `Caixa { licenca: Some(""), .. }` — the composition pin
11637 // catches that at caixa-core build time.
11638 //
11639 // Peer of the per-`:politicas :circuit-breaker`
11640 // [`crate::aplicacao::CircuitBreaker::max_failures`] (3a74062)
11641 // accessor-composition pin
11642 // (`validate_politicas_max_failures_zero_floor_arm_routes_through_accessor`)
11643 // on the sibling per-M3-mesh-slot required-`u32` axis — same
11644 // "the validate / shape-gate predicate must route through the
11645 // substrate-primitive typed dispatch" discipline extended onto
11646 // the outer top-level [`Caixa`] universal-axis
11647 // `Option<&str>`-composition surface.
11648 let c = caixa_with_licenca(Some(""));
11649 assert!(
11650 matches!(c.validate_licenca(), Err(ManifestError::LicencaEmpty)),
11651 "validate_licenca must reject licenca == Some(\"\") with \
11652 LicencaEmpty — the accessor and the validate gate must \
11653 route through the same substrate-primitive typed dispatch \
11654 on the :licenca empty arm",
11655 );
11656 let c = caixa_with_licenca(Some("MIT"));
11657 assert!(
11658 c.validate_licenca().is_ok(),
11659 "validate_licenca must accept licenca == Some(\"MIT\") \
11660 (the canonical single-license SPDX shape)",
11661 );
11662 }
11663
11664 #[test]
11665 fn licenca_projects_option_str_by_borrow() {
11666 // The by-borrow pin: [`Caixa::licenca`] returns
11667 // `Option<&str>` by borrow — the `&str` borrows the underlying
11668 // `String` storage of the `Option<String>` slot and the
11669 // accessor must not allocate a fresh `String` on every call.
11670 // Peer of the per-`:placement`
11671 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
11672 // borrow pin on the peer per-M3-mesh-slot
11673 // `Option<&str>`-return axis, extended onto the outer top-
11674 // level [`Caixa`] universal-axis `Option<&str>` shape — the
11675 // accessor's returned `&str` must borrow from `&self` (the
11676 // returned reference's lifetime is tied to `&self`), and
11677 // calling the accessor twice on the same [`Caixa`] must yield
11678 // the same `Option<&str>` verbatim (idempotent, no side
11679 // effects on `&self`).
11680 //
11681 // Pins against a future silent detour that returned an owned
11682 // `Option<String>` (which would type-check but silently
11683 // allocate on every call, breaking the zero-cost projection
11684 // every peer sibling accessor carries), or a one-arm-only
11685 // accessor that returned a saturating value on some sentinel
11686 // input (breaking the pass-through invariant the sibling
11687 // required-scalar accessors carry).
11688 for licenca in [None, Some(""), Some("MIT"), Some("Apache-2.0 OR MIT")] {
11689 let c = caixa_with_licenca(licenca);
11690 let first = c.licenca();
11691 let second = c.licenca();
11692 assert_eq!(
11693 first, second,
11694 "Caixa::licenca must be idempotent — two successive \
11695 calls on the same &self must return the same \
11696 Option<&str>",
11697 );
11698 assert_eq!(
11699 first, licenca,
11700 "Caixa::licenca must return :licenca verbatim by \
11701 borrow — got {first:?}, expected {licenca:?}",
11702 );
11703 }
11704 }
11705
11706 // ── Caixa::repositorio — outer top-level Option<&str> scalar accessor ──
11707
11708 #[test]
11709 fn repositorio_returns_repositorio_byte_string_verbatim_across_permutations() {
11710 // The canonical per-`Caixa` `:repositorio` git-repo-URL scalar
11711 // pin: [`Caixa::repositorio`] must return the `:repositorio`
11712 // typed byte-string verbatim as an `Option<&str>`, byte-equal
11713 // to the raw `self.repositorio.as_deref()` access across every
11714 // representative value in the accept-set — `None` (the "omit
11715 // the slot to defer to the per-renderer placeholder" arm every
11716 // existing fixture without a `:repositorio` line carries),
11717 // `Some("")` (a past-the-guard sentinel that pins the accessor
11718 // doesn't perform a silent `Some("") → None` collapse on the
11719 // empty arm — validate rejects `Some("")` through
11720 // `RepositorioEmpty` but the accessor must ship the raw slot
11721 // verbatim so a validate-time gate regression surfaces at the
11722 // caixa-helm / caixa-flux emit boundary rather than being
11723 // silently absorbed into the per-renderer fallback),
11724 // `Some("github:pleme-io/hello-rio")` (the canonical `github:`
11725 // shorthand every existing manifest fixture across
11726 // `caixa-helm` / `caixa-mesh` and the `examples/` uses),
11727 // `Some("https://github.com/pleme-io/checkout")` (the canonical
11728 // `https://` URL the README quickstart uses),
11729 // `Some("ssh://git@github.com/pleme-io/checkout.git")` /
11730 // `Some("git://github.com/pleme-io/checkout.git")` /
11731 // `Some("git@github.com:pleme-io/checkout.git")` /
11732 // `Some("file:///opt/mirrors/pleme-io/checkout")` (every non-
11733 // github scheme the shared `is_git_repo_url` predicate
11734 // documents), and five past-the-guard sentinels for the
11735 // `RepositorioInvalid` refusal cases (`Some("pleme-io/checkout")`
11736 // missing-colon, `Some("-upload-pack=evil")` leading-dash, /
11737 // `Some("github:pleme-io/checkout?ref=main")` query-string, /
11738 // `Some("github:pleme-io/checkout#main")` fragment-anchor, /
11739 // `Some("github:pleme-io/{tpl}")` URI-template-placeholder — the
11740 // sentinels pin the accessor doesn't silently absorb the
11741 // refusal cases into a fallback).
11742 //
11743 // Second outer top-level [`Caixa`] `Option<&str>`-return scalar
11744 // accessor pin on the substrate primitive — sibling of the peer
11745 // [`Caixa::licenca`] (6d5bc28) pin
11746 // (`licenca_returns_licenca_byte_string_verbatim_across_permutations`)
11747 // that opened the "outer [`Caixa`] `Option<&str>` scalar"
11748 // projection pin pattern this pin folds on. Sibling in shape to
11749 // the peer per-`:placement`
11750 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
11751 // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
11752 // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
11753 // axes, extended onto the outer top-level [`Caixa`] universal-
11754 // axis surface. Pins against a future silent detour that
11755 // returned an owned `Option<String>` (which would type-check
11756 // but silently allocate on every accessor call, breaking the
11757 // zero-cost projection every peer sibling accessor carries), a
11758 // `Some("") → None` collapse (which would silently absorb the
11759 // `RepositorioEmpty` refusal case at the accessor boundary and
11760 // the caixa-helm `Chart.yaml` `home:` fold would silently
11761 // render a `home: null` / omitted field on a struct-literal
11762 // `Caixa { repositorio: Some(""), .. }`), or a
11763 // `None → Some(<default>)` collapse (which would silently reify
11764 // the per-renderer fallback at the accessor boundary and every
11765 // downstream consumer keying off the `Option::is_none()`
11766 // discriminator would lose the "author omitted the slot"
11767 // signal).
11768 for repositorio in [
11769 None,
11770 Some(""),
11771 Some("github:pleme-io/hello-rio"),
11772 Some("https://github.com/pleme-io/checkout"),
11773 Some("ssh://git@github.com/pleme-io/checkout.git"),
11774 Some("git://github.com/pleme-io/checkout.git"),
11775 Some("git@github.com:pleme-io/checkout.git"),
11776 Some("file:///opt/mirrors/pleme-io/checkout"),
11777 Some("pleme-io/checkout"),
11778 Some("-upload-pack=evil"),
11779 Some("github:pleme-io/checkout?ref=main"),
11780 Some("github:pleme-io/checkout#main"),
11781 Some("github:pleme-io/{tpl}"),
11782 ] {
11783 let c = caixa_with_repositorio(repositorio);
11784 assert_eq!(
11785 c.repositorio(),
11786 repositorio,
11787 "Caixa::repositorio must return :repositorio verbatim \
11788 (got {:?}, expected {repositorio:?})",
11789 c.repositorio(),
11790 );
11791 assert_eq!(
11792 c.repositorio(),
11793 c.repositorio.as_deref(),
11794 "Caixa::repositorio must byte-equal the raw \
11795 `self.repositorio.as_deref()` field access across every \
11796 value in the Option<&str> accept-set",
11797 );
11798 }
11799 }
11800
11801 #[test]
11802 fn validate_repositorio_empty_arm_routes_through_accessor() {
11803 // Composition pin: [`Caixa::validate_repositorio`]'s empty-arm
11804 // gate must key off [`Caixa::repositorio`], not the raw
11805 // `self.repositorio.as_deref()` field access. Structurally: a
11806 // `Caixa { repositorio: Some(""), .. }` must surface the
11807 // `RepositorioEmpty` refusal exactly, and a
11808 // `Caixa { repositorio: Some("github:pleme-io/hello-rio"), .. }`
11809 // (the canonical `github:` shorthand form) must pass validate.
11810 // The pair jointly pins the accessor + validate-gate
11811 // composition: any future silent detour that had the accessor
11812 // return `None` on the empty arm (a `.filter(|s| !s.is_empty())`
11813 // collapse) would silently absorb the `RepositorioEmpty` refusal
11814 // at the accessor boundary and the validate gate would accept a
11815 // struct-literal `Caixa { repositorio: Some(""), .. }` — the
11816 // composition pin catches that at caixa-core build time.
11817 //
11818 // Peer of the [`Caixa::licenca`] (6d5bc28)
11819 // `validate_licenca_empty_arm_routes_through_accessor`
11820 // composition pin on the sibling outer top-level [`Caixa`]
11821 // `Option<&str>` universal-axis surface — same "the validate /
11822 // shape-gate predicate must route through the substrate-
11823 // primitive typed dispatch" discipline extended onto the second
11824 // outer top-level [`Caixa`] universal-axis `Option<&str>`-
11825 // composition surface.
11826 let c = caixa_with_repositorio(Some(""));
11827 assert!(
11828 matches!(
11829 c.validate_repositorio(),
11830 Err(ManifestError::RepositorioEmpty),
11831 ),
11832 "validate_repositorio must reject repositorio == Some(\"\") \
11833 with RepositorioEmpty — the accessor and the validate gate \
11834 must route through the same substrate-primitive typed \
11835 dispatch on the :repositorio empty arm",
11836 );
11837 let c = caixa_with_repositorio(Some("github:pleme-io/hello-rio"));
11838 assert!(
11839 c.validate_repositorio().is_ok(),
11840 "validate_repositorio must accept repositorio == \
11841 Some(\"github:pleme-io/hello-rio\") (the canonical \
11842 `github:` shorthand git-repo-URL shape)",
11843 );
11844 }
11845
11846 #[test]
11847 fn repositorio_projects_option_str_by_borrow() {
11848 // The by-borrow pin: [`Caixa::repositorio`] returns
11849 // `Option<&str>` by borrow — the `&str` borrows the underlying
11850 // `String` storage of the `Option<String>` slot and the
11851 // accessor must not allocate a fresh `String` on every call.
11852 // Peer of the per-`:placement`
11853 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) and the
11854 // [`Caixa::licenca`] (6d5bc28) by-borrow pins on the peer
11855 // `Option<&str>`-return axes, extended onto the second outer
11856 // top-level [`Caixa`] universal-axis `Option<&str>` shape —
11857 // the accessor's returned `&str` must borrow from `&self` (the
11858 // returned reference's lifetime is tied to `&self`), and
11859 // calling the accessor twice on the same [`Caixa`] must yield
11860 // the same `Option<&str>` verbatim (idempotent, no side effects
11861 // on `&self`).
11862 //
11863 // Pins against a future silent detour that returned an owned
11864 // `Option<String>` (which would type-check but silently
11865 // allocate on every call, breaking the zero-cost projection
11866 // every peer sibling accessor carries), or a one-arm-only
11867 // accessor that returned a saturating value on some sentinel
11868 // input (breaking the pass-through invariant the sibling
11869 // required-scalar accessors carry).
11870 for repositorio in [
11871 None,
11872 Some(""),
11873 Some("github:pleme-io/hello-rio"),
11874 Some("https://github.com/pleme-io/checkout"),
11875 ] {
11876 let c = caixa_with_repositorio(repositorio);
11877 let first = c.repositorio();
11878 let second = c.repositorio();
11879 assert_eq!(
11880 first, second,
11881 "Caixa::repositorio must be idempotent — two successive \
11882 calls on the same &self must return the same \
11883 Option<&str>",
11884 );
11885 assert_eq!(
11886 first, repositorio,
11887 "Caixa::repositorio must return :repositorio verbatim by \
11888 borrow — got {first:?}, expected {repositorio:?}",
11889 );
11890 }
11891 }
11892
11893 // ── Caixa::canonical_git_url — resolved-git-URL composer ──────────
11894
11895 #[test]
11896 fn canonical_git_url_returns_repositorio_verbatim_on_some_arm() {
11897 // Fail-before-pass-after pin: [`Caixa::canonical_git_url`] must
11898 // return the author-declared `:repositorio` byte-string verbatim
11899 // on the `Some` arm — no scheme rewrite, no trailing-slash
11900 // canonicalization, no `github:` → `https://github.com/`
11901 // desugaring. The resolved-URL composer is the projection of
11902 // the raw [`Caixa::repositorio`] `Option<&str>` accessor onto
11903 // the `String`-return arity every substrate-side field-fill
11904 // consumer keys off; on the `Some` arm the projection is
11905 // `str::to_owned` verbatim, so every accept-set value the
11906 // sibling `repositorio_returns_repositorio_byte_string_verbatim_
11907 // across_permutations` pin covers (`https://…`, `github:…`,
11908 // `ssh://…`, `git://…`, `git@…`, `file://…`, and the past-the-
11909 // guard sentinel `pleme-io/…`) must survive the accessor
11910 // byte-equal. Pins against a future silent detour that rewrote
11911 // the `github:` shorthand to the `https://github.com/` full URL
11912 // at the accessor boundary (which would silently split the
11913 // resolved-URL surface from the raw [`Caixa::repositorio`]
11914 // accessor's documented pass-through invariant), or a trailing-
11915 // slash normalization (which would silently break the
11916 // FluxCD `GitRepository` `spec.url` byte-exact match every
11917 // downstream consumer keys the source-controller reconcile off).
11918 for repositorio in [
11919 "github:pleme-io/hello-rio",
11920 "https://github.com/pleme-io/checkout",
11921 "ssh://git@github.com/pleme-io/checkout.git",
11922 "git://github.com/pleme-io/checkout.git",
11923 "git@github.com:pleme-io/checkout.git",
11924 "file:///opt/mirrors/pleme-io/checkout",
11925 ] {
11926 let c = caixa_with_repositorio(Some(repositorio));
11927 assert_eq!(
11928 c.canonical_git_url(),
11929 repositorio,
11930 "Caixa::canonical_git_url on the Some arm must return \
11931 :repositorio verbatim (got {:?}, expected {repositorio:?})",
11932 c.canonical_git_url(),
11933 );
11934 }
11935 }
11936
11937 #[test]
11938 fn canonical_git_url_falls_back_to_pleme_org_url_on_none_arm() {
11939 // Fail-before-pass-after pin: [`Caixa::canonical_git_url`] on the
11940 // `None` arm must emit the substrate's canonical pleme-org github
11941 // URL derived from `caixa.nome()` — `https://github.com/<org>/
11942 // <nome>` with `<org>` bound to [`crate::DEFAULT_PLEME_GIT_ORG`]
11943 // and `<nome>` bound to the typed [`Caixa::nome`] accessor. This
11944 // is the exact byte-image of the prior inline
11945 // [`caixa-flux::ClusterBundleOpts::for_caixa`] `git_url`
11946 // composer at caixa-flux/src/lib.rs:2080 that every prior caller
11947 // re-derived open-coded. Pins against a future silent detour
11948 // that migrated the `<org>` segment to a different constant (a
11949 // fork rebranding that split off a new
11950 // `DEFAULT_PLEME_GIT_ORG_MIRROR` const the accessor would need
11951 // to migrate onto), a scheme change (`https://` → `git://` or
11952 // `ssh://`), or a per-`Caixa` `.canonical_git_url_prefix`
11953 // override (which would break the substrate-wide single-source-
11954 // of-truth guarantee this method encodes).
11955 let c = caixa_with_repositorio(None);
11956 let expected = format!(
11957 "https://github.com/{org}/{nome}",
11958 org = crate::DEFAULT_PLEME_GIT_ORG,
11959 nome = c.nome(),
11960 );
11961 assert_eq!(
11962 c.canonical_git_url(),
11963 expected,
11964 "Caixa::canonical_git_url on the None arm must fold through \
11965 the substrate's canonical pleme-org github URL fallback \
11966 `https://github.com/<DEFAULT_PLEME_GIT_ORG>/<nome>` — got \
11967 {:?}, expected {expected:?}",
11968 c.canonical_git_url(),
11969 );
11970 }
11971
11972 #[test]
11973 fn canonical_git_url_byte_matches_manual_composition() {
11974 // Byte-parity pin: [`Caixa::canonical_git_url`] must render
11975 // byte-identically to the manual open-coded
11976 // `caixa.repositorio().map(str::to_owned).unwrap_or_else(||
11977 // format!("https://github.com/{org}/{nome}", ...))` composition
11978 // every prior substrate-side caller re-derived. Guards the
11979 // paired-site convergence just applied at caixa-flux's
11980 // [`ClusterBundleOpts::for_caixa`] `git_url` composer (which
11981 // now routes through this accessor): a future implementation of
11982 // this method that reordered the format arguments, swapped the
11983 // `<org>` constant for a different one, or interposed a
11984 // canonicalization pass on the `Some` arm surfaces here as a
11985 // caixa-core build-time test failure rather than as a downstream
11986 // FluxCD `GitRepository` reconcile mismatch far from this
11987 // method's source.
11988 for repositorio in [
11989 None,
11990 Some("github:pleme-io/hello-rio"),
11991 Some("https://github.com/pleme-io/checkout"),
11992 Some("ssh://git@github.com/pleme-io/checkout.git"),
11993 ] {
11994 let c = caixa_with_repositorio(repositorio);
11995 let manual = c.repositorio().map_or_else(
11996 || {
11997 format!(
11998 "https://github.com/{org}/{nome}",
11999 org = crate::DEFAULT_PLEME_GIT_ORG,
12000 nome = c.nome(),
12001 )
12002 },
12003 str::to_owned,
12004 );
12005 assert_eq!(
12006 c.canonical_git_url(),
12007 manual,
12008 "Caixa::canonical_git_url must byte-equal the manual \
12009 open-coded `repositorio().map(str::to_owned)\
12010 .unwrap_or_else(|| format!(...))` composition across \
12011 every representative :repositorio input — got {:?}, \
12012 expected {manual:?}",
12013 c.canonical_git_url(),
12014 );
12015 }
12016 }
12017
12018 // ── Caixa::publish_tag — resolved-publish-tag composer ───────────
12019
12020 #[test]
12021 fn publish_tag_composes_prefix_and_versao_on_all_shapes() {
12022 // Fail-before-pass-after pin: [`Caixa::publish_tag`] must compose
12023 // [`crate::DEFAULT_PUBLISH_TAG_PREFIX`] against the caixa's typed
12024 // [`Caixa::versao`] byte-string across every SemVer-2 shape the
12025 // sibling [`validate_versao_accepts_canonical_forms`] positive-set
12026 // sweep documents — bare MAJOR.MINOR.PATCH, pre-release tags
12027 // (`-rc.1`), build metadata (`+build.42`), the combined form, and
12028 // the `0.0.0` boundary case. Every accept-set value the peer
12029 // validate gate lets through must survive the resolved-tag
12030 // projection byte-equal.
12031 for versao in [
12032 "0.1.0",
12033 "0.0.0",
12034 "1.0.0",
12035 "1.2.3-rc.1",
12036 "1.2.3+build.42",
12037 "1.2.3-rc.1+build.42",
12038 ] {
12039 let c = caixa_with_versao(versao);
12040 let expected = format!(
12041 "{prefix}{versao}",
12042 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
12043 );
12044 assert_eq!(
12045 c.publish_tag(),
12046 expected,
12047 "Caixa::publish_tag must compose \
12048 DEFAULT_PUBLISH_TAG_PREFIX ({prefix:?}) against \
12049 :versao ({versao:?}) verbatim — got {got:?}, \
12050 expected {expected:?}",
12051 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
12052 got = c.publish_tag(),
12053 );
12054 }
12055 }
12056
12057 #[test]
12058 fn publish_tag_starts_with_default_publish_tag_prefix() {
12059 // Prefix-shape pin: every [`Caixa::publish_tag`] emission must
12060 // begin with the canonical [`crate::DEFAULT_PUBLISH_TAG_PREFIX`]
12061 // byte-string on every input, guarding a hypothetical future
12062 // implementation that migrated the prefix segment to an inline
12063 // literal (`"v"`) that would silently drift from any rebrand of
12064 // the lifted constant. Peer to the sibling caixa-flux
12065 // `cluster_bundle_default_git_tag_uses_lifted_caixa_core_prefix`
12066 // test which pins the same prefix invariant at the reader-side
12067 // `GitRefSpec::Tag` emit site.
12068 for versao in ["0.0.0", "0.1.0", "1.2.3-rc.1", "9.9.9+build.1"] {
12069 let c = caixa_with_versao(versao);
12070 let tag = c.publish_tag();
12071 assert!(
12072 tag.starts_with(crate::DEFAULT_PUBLISH_TAG_PREFIX),
12073 "Caixa::publish_tag emission {tag:?} must start with \
12074 the lifted crate::DEFAULT_PUBLISH_TAG_PREFIX \
12075 ({prefix:?})",
12076 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
12077 );
12078 }
12079 }
12080
12081 #[test]
12082 fn publish_tag_byte_matches_manual_composition() {
12083 // Byte-parity pin: [`Caixa::publish_tag`] must render byte-
12084 // identically to the manual open-coded
12085 // `format!("{prefix}{versao}", prefix =
12086 // caixa_core::DEFAULT_PUBLISH_TAG_PREFIX, versao =
12087 // caixa.versao())` composition every prior substrate-side
12088 // caller re-derived. Guards the paired-site convergence just
12089 // applied at caixa-flux's [`ClusterBundleOpts::for_caixa`]
12090 // `git_ref` composer (which now routes through this accessor):
12091 // a future implementation of this method that reordered the
12092 // format arguments, swapped the `<prefix>` constant for a
12093 // different one, or interposed a canonicalization pass on the
12094 // `:versao` axis surfaces here as a caixa-core build-time test
12095 // failure rather than as a downstream FluxCD `GitRepository`
12096 // reconcile mismatch far from this method's source.
12097 for versao in [
12098 "0.1.0",
12099 "0.0.0",
12100 "1.2.3-rc.1",
12101 "1.2.3+build.42",
12102 "1.2.3-rc.1+build.42",
12103 ] {
12104 let c = caixa_with_versao(versao);
12105 let manual = format!(
12106 "{prefix}{versao}",
12107 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
12108 versao = c.versao(),
12109 );
12110 assert_eq!(
12111 c.publish_tag(),
12112 manual,
12113 "Caixa::publish_tag must byte-equal the manual \
12114 open-coded `format!(\"{{prefix}}{{versao}}\", ...)` \
12115 composition across every representative :versao input \
12116 — got {got:?}, expected {manual:?}",
12117 got = c.publish_tag(),
12118 );
12119 }
12120 }
12121
12122 // ── Caixa::lareira_chart_name — resolved-chart-name composer ─────
12123
12124 #[test]
12125 fn lareira_chart_name_composes_prefix_and_nome_on_all_shapes() {
12126 // Fail-before-pass-after pin: [`Caixa::lareira_chart_name`] must
12127 // compose [`crate::LAREIRA_CHART_NAME_PREFIX`] against the caixa's
12128 // typed [`Caixa::nome`] byte-string across every DNS-1123 shape
12129 // the sibling [`validate_nome_accepts_canonical_forms`] positive-
12130 // set sweep documents — single-word, hyphen-joined, version-
12131 // suffixed, single-char, two-char, digit-start, retry-suffixed.
12132 // Every accept-set value the peer validate gate lets through must
12133 // survive the resolved-chart-name projection byte-equal.
12134 for nome in [
12135 "checkout",
12136 "cart-v2",
12137 "a",
12138 "db",
12139 "3rd-party-shim",
12140 "payment-retry",
12141 "0",
12142 ] {
12143 let c = caixa_with_nome(nome);
12144 let expected = format!("{prefix}{nome}", prefix = crate::LAREIRA_CHART_NAME_PREFIX);
12145 assert_eq!(
12146 c.lareira_chart_name(),
12147 expected,
12148 "Caixa::lareira_chart_name must compose \
12149 LAREIRA_CHART_NAME_PREFIX ({prefix:?}) against \
12150 :nome ({nome:?}) verbatim — got {got:?}, \
12151 expected {expected:?}",
12152 prefix = crate::LAREIRA_CHART_NAME_PREFIX,
12153 got = c.lareira_chart_name(),
12154 );
12155 }
12156 }
12157
12158 #[test]
12159 fn lareira_chart_name_starts_with_lifted_prefix() {
12160 // Prefix-shape pin: every [`Caixa::lareira_chart_name`] emission
12161 // must begin with the canonical
12162 // [`crate::LAREIRA_CHART_NAME_PREFIX`] byte-string on every
12163 // input, guarding a hypothetical future implementation that
12164 // migrated the prefix segment to an inline literal (`"lareira-"`)
12165 // that would silently drift from any rebrand of the lifted
12166 // constant. Peer to the sibling
12167 // [`publish_tag_starts_with_default_publish_tag_prefix`] pin on
12168 // the co-resident resolved-publish-tag composer's prefix axis.
12169 for nome in ["checkout", "cart", "a", "payment-retry", "0"] {
12170 let c = caixa_with_nome(nome);
12171 let chart = c.lareira_chart_name();
12172 assert!(
12173 chart.starts_with(crate::LAREIRA_CHART_NAME_PREFIX),
12174 "Caixa::lareira_chart_name emission {chart:?} must start \
12175 with the lifted crate::LAREIRA_CHART_NAME_PREFIX \
12176 ({prefix:?})",
12177 prefix = crate::LAREIRA_CHART_NAME_PREFIX,
12178 );
12179 }
12180 }
12181
12182 #[test]
12183 fn lareira_chart_name_byte_matches_canonical_helper_composition() {
12184 // Byte-parity pin: [`Caixa::lareira_chart_name`] must render
12185 // byte-identically to the manual open-coded
12186 // `caixa_core::lareira_chart_name(caixa.nome())` two-step
12187 // composition every prior substrate-side caller re-derived.
12188 // Guards the paired-site convergence just applied at caixa-helm's
12189 // [`render_chart_for_servico_with`] `ChartDir.name` composer,
12190 // caixa-flux's [`cluster_bundle`] per-CR `chart_name` binding,
12191 // and caixa-tatara's [`process_for_aplicacao`] `release_name`
12192 // composer (all of which now route through this accessor): a
12193 // future implementation of this method that reordered the
12194 // composition arguments, swapped the `<prefix>` constant for a
12195 // different one, or interposed a canonicalization pass on the
12196 // `:nome` axis surfaces here as a caixa-core build-time test
12197 // failure rather than as a downstream Helm chart-render / FluxCD
12198 // reconcile / tatara Process-CR mismatch far from this method's
12199 // source.
12200 for nome in [
12201 "checkout",
12202 "cart-v2",
12203 "a",
12204 "db",
12205 "3rd-party-shim",
12206 "payment-retry",
12207 ] {
12208 let c = caixa_with_nome(nome);
12209 let manual = crate::lareira_chart_name(c.nome());
12210 assert_eq!(
12211 c.lareira_chart_name(),
12212 manual,
12213 "Caixa::lareira_chart_name must byte-equal the manual \
12214 open-coded `caixa_core::lareira_chart_name(caixa.nome())` \
12215 composition across every representative :nome input — \
12216 got {got:?}, expected {manual:?}",
12217 got = c.lareira_chart_name(),
12218 );
12219 }
12220 }
12221
12222 // ── Caixa::oci_chart_ref — resolved-OCI-chart-ref composer ────────
12223
12224 #[test]
12225 fn oci_chart_ref_composes_scheme_and_lareira_chart_name_on_all_shapes() {
12226 // Fail-before-pass-after pin: [`Caixa::oci_chart_ref`] must
12227 // compose [`crate::OCI_SCHEME_PREFIX`] + the caller-supplied
12228 // `registry` + [`crate::lareira_chart_name`]-of-[`Caixa::nome`]
12229 // across the full paired `(registry, :nome)` accept-set — every
12230 // representative registry the substrate-side emitters carry
12231 // (`ghcr.io/pleme-io/charts`, the canonical CAIXA-SDLC §II
12232 // ArtifactHub-tier registry; `ghcr.io/pleme-io`, the bare-org
12233 // arm the sibling `oci_chart_ref_pins_byte_shape_against_prior_
12234 // inline_format` render-side pin exercises; `registry.example.
12235 // com`, an off-org shape; `localhost:5000`, the local-dev shape
12236 // every `feira chart` iteration path lands under) × every DNS-
12237 // 1123 `:nome` shape the peer `validate_nome_accepts_canonical_
12238 // forms` positive-set sweep documents (single-word, hyphen-
12239 // joined, single-char, two-char, digit-start, retry-suffixed).
12240 // Every accept-set pair the peer validate gates let through must
12241 // survive the resolved-OCI-ref projection byte-equal.
12242 for registry in [
12243 "ghcr.io/pleme-io/charts",
12244 "ghcr.io/pleme-io",
12245 "registry.example.com",
12246 "localhost:5000",
12247 ] {
12248 for nome in [
12249 "checkout",
12250 "cart-v2",
12251 "a",
12252 "db",
12253 "3rd-party-shim",
12254 "payment-retry",
12255 "0",
12256 ] {
12257 let c = caixa_with_nome(nome);
12258 let expected = format!(
12259 "{scheme}{registry}/{chart}",
12260 scheme = crate::OCI_SCHEME_PREFIX,
12261 chart = crate::lareira_chart_name(nome),
12262 );
12263 assert_eq!(
12264 c.oci_chart_ref(registry),
12265 expected,
12266 "Caixa::oci_chart_ref must compose \
12267 OCI_SCHEME_PREFIX ({scheme:?}) + registry ({registry:?}) + \
12268 lareira_chart_name(:nome ({nome:?})) verbatim — got {got:?}, \
12269 expected {expected:?}",
12270 scheme = crate::OCI_SCHEME_PREFIX,
12271 got = c.oci_chart_ref(registry),
12272 );
12273 }
12274 }
12275 }
12276
12277 #[test]
12278 fn oci_chart_ref_starts_with_lifted_scheme_prefix() {
12279 // Scheme-prefix-shape pin: every [`Caixa::oci_chart_ref`]
12280 // emission must begin with the canonical
12281 // [`crate::OCI_SCHEME_PREFIX`] byte-string on every input, guarding
12282 // a hypothetical future implementation that migrated the scheme
12283 // segment to an inline literal (`"oci://"`) that would silently
12284 // drift from any rebrand of the lifted constant. Peer to the
12285 // sibling [`publish_tag_starts_with_default_publish_tag_prefix`]
12286 // + [`lareira_chart_name_starts_with_lifted_prefix`] pins on the
12287 // co-resident resolved-publish-tag / resolved-chart-name
12288 // composers' prefix axes.
12289 for registry in [
12290 "ghcr.io/pleme-io/charts",
12291 "ghcr.io/pleme-io",
12292 "localhost:5000",
12293 ] {
12294 for nome in ["checkout", "cart", "a", "payment-retry", "0"] {
12295 let c = caixa_with_nome(nome);
12296 let ref_ = c.oci_chart_ref(registry);
12297 assert!(
12298 ref_.starts_with(crate::OCI_SCHEME_PREFIX),
12299 "Caixa::oci_chart_ref emission {ref_:?} must start \
12300 with the lifted crate::OCI_SCHEME_PREFIX ({scheme:?}) \
12301 — registry ({registry:?}), :nome ({nome:?})",
12302 scheme = crate::OCI_SCHEME_PREFIX,
12303 );
12304 }
12305 }
12306 }
12307
12308 #[test]
12309 fn oci_chart_ref_byte_matches_canonical_helper_composition() {
12310 // Byte-parity pin: [`Caixa::oci_chart_ref`] must render byte-
12311 // identically to the manual open-coded
12312 // `caixa_core::oci_chart_ref(registry, caixa.nome())` two-step
12313 // composition every prior substrate-side caller re-derived.
12314 // Guards the paired-site convergence just applied at caixa-
12315 // tatara's [`derive_chart_ref`] helper (which now routes through
12316 // this accessor): a future implementation of this method that
12317 // reordered the composition arguments, swapped the `<scheme>`
12318 // constant for a different one, migrated the `<chart>` segment
12319 // off the paired [`crate::lareira_chart_name`] composer, or
12320 // interposed a canonicalization pass on either input axis
12321 // surfaces here as a caixa-core build-time test failure rather
12322 // than as a downstream `helm install` / FluxCD OCI-source
12323 // reconcile / tatara `Process`-CR mismatch far from this
12324 // method's source. Sibling to the peer
12325 // [`lareira_chart_name_byte_matches_canonical_helper_composition`]
12326 // / [`publish_tag_byte_matches_manual_composition`] /
12327 // [`canonical_git_url_byte_matches_manual_composition`] byte-
12328 // parity pins that carry the same discipline on the co-resident
12329 // resolved-chart-name / resolved-publish-tag / resolved-git-URL
12330 // composers.
12331 for registry in [
12332 "ghcr.io/pleme-io/charts",
12333 "ghcr.io/pleme-io",
12334 "registry.example.com",
12335 "localhost:5000",
12336 ] {
12337 for nome in [
12338 "checkout",
12339 "cart-v2",
12340 "a",
12341 "db",
12342 "3rd-party-shim",
12343 "payment-retry",
12344 ] {
12345 let c = caixa_with_nome(nome);
12346 let manual = crate::oci_chart_ref(registry, c.nome());
12347 assert_eq!(
12348 c.oci_chart_ref(registry),
12349 manual,
12350 "Caixa::oci_chart_ref must byte-equal the manual \
12351 open-coded `caixa_core::oci_chart_ref(registry, \
12352 caixa.nome())` composition across every representative \
12353 (registry, :nome) pair — registry ({registry:?}), \
12354 :nome ({nome:?}), got {got:?}, expected {manual:?}",
12355 got = c.oci_chart_ref(registry),
12356 );
12357 }
12358 }
12359 }
12360
12361 // ── Caixa::descricao — outer top-level Option<&str> scalar accessor ──
12362
12363 #[test]
12364 fn descricao_returns_descricao_byte_string_verbatim_across_permutations() {
12365 // The canonical per-`Caixa` `:descricao` free-form-prose scalar
12366 // pin: [`Caixa::descricao`] must return the `:descricao` typed
12367 // byte-string verbatim as an `Option<&str>`, byte-equal to the
12368 // raw `self.descricao.as_deref()` access across every
12369 // representative value in the accept-set — `None` (the "omit
12370 // the slot to defer to the per-renderer `caixa.nome`-derived
12371 // fallback" arm every existing fixture without a `:descricao`
12372 // line carries), `Some("")` (a past-the-guard sentinel that
12373 // pins the accessor doesn't perform a silent `Some("") → None`
12374 // collapse on the empty arm — validate rejects `Some("")`
12375 // through `DescricaoEmpty` but the accessor must ship the raw
12376 // slot verbatim so a validate-time gate regression surfaces at
12377 // the caixa-helm / caixa-feira emit boundary rather than being
12378 // silently absorbed into the per-renderer `caixa.nome`-derived
12379 // fallback), `Some("Checkout flow.")` (the canonical one-line
12380 // prose descriptor the peer
12381 // `validate_descricao_accepts_canonical_value` positive sweep
12382 // exercises), `Some("Canonical Rust→wasm32-wasip2 caixa
12383 // Servico.")` (the multi-byte Unicode continuation-byte shape
12384 // the `hello-rio` fixture carries), `Some("→ — · ✓")` (a
12385 // multi-glyph Unicode shape the peer
12386 // `is_chart_description_shape` predicate accepts), and five
12387 // past-the-guard sentinels for the `DescricaoInvalid` refusal
12388 // cases (`Some(" Checkout flow.")` leading-whitespace,
12389 // `Some("Checkout flow. ")` trailing-whitespace,
12390 // `Some("Checkout\nflow.")` embedded-LF,
12391 // `Some("Checkout\tflow.")` embedded-TAB, and
12392 // `Some("Checkout\x00flow.")` embedded-NUL — the sentinels pin
12393 // the accessor doesn't silently absorb the refusal cases into
12394 // a fallback).
12395 //
12396 // Third outer top-level [`Caixa`] `Option<&str>`-return scalar
12397 // accessor pin on the substrate primitive — sibling of the peer
12398 // [`Caixa::licenca`] (6d5bc28) and [`Caixa::repositorio`]
12399 // (cc7332d) pins that opened the "outer [`Caixa`]
12400 // `Option<&str>` scalar" projection pin pattern this pin folds
12401 // on. Sibling in shape to the peer per-`:placement`
12402 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
12403 // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
12404 // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
12405 // axes, extended onto the outer top-level [`Caixa`] universal-
12406 // axis surface. Pins against a future silent detour that
12407 // returned an owned `Option<String>` (which would type-check
12408 // but silently allocate on every accessor call, breaking the
12409 // zero-cost projection every peer sibling accessor carries), a
12410 // `Some("") → None` collapse (which would silently absorb the
12411 // `DescricaoEmpty` refusal case at the accessor boundary and
12412 // the caixa-helm `Chart.yaml` `description:` fold would
12413 // silently render a `caixa.nome`-derived fallback on a
12414 // struct-literal `Caixa { descricao: Some(""), .. }`), or a
12415 // `None → Some(<default>)` collapse (which would silently
12416 // reify the per-renderer `caixa.nome`-derived fallback at the
12417 // accessor boundary and every downstream consumer keying off
12418 // the `Option::is_none()` discriminator would lose the "author
12419 // omitted the slot" signal).
12420 for descricao in [
12421 None,
12422 Some(""),
12423 Some("Checkout flow."),
12424 Some("Canonical Rust→wasm32-wasip2 caixa Servico."),
12425 Some("→ — · ✓"),
12426 Some(" Checkout flow."),
12427 Some("Checkout flow. "),
12428 Some("Checkout\nflow."),
12429 Some("Checkout\tflow."),
12430 Some("Checkout\x00flow."),
12431 ] {
12432 let c = caixa_with_descricao(descricao);
12433 assert_eq!(
12434 c.descricao(),
12435 descricao,
12436 "Caixa::descricao must return :descricao verbatim (got \
12437 {:?}, expected {descricao:?})",
12438 c.descricao(),
12439 );
12440 assert_eq!(
12441 c.descricao(),
12442 c.descricao.as_deref(),
12443 "Caixa::descricao must byte-equal the raw \
12444 `self.descricao.as_deref()` field access across every \
12445 value in the Option<&str> accept-set",
12446 );
12447 }
12448 }
12449
12450 #[test]
12451 fn validate_descricao_empty_arm_routes_through_accessor() {
12452 // Composition pin: [`Caixa::validate_descricao`]'s empty-arm
12453 // gate must key off [`Caixa::descricao`], not the raw
12454 // `self.descricao.as_deref()` field access. Structurally: a
12455 // `Caixa { descricao: Some(""), .. }` must surface the
12456 // `DescricaoEmpty` refusal exactly, and a
12457 // `Caixa { descricao: Some("Checkout flow."), .. }` (the
12458 // canonical one-line-prose form) must pass validate. The pair
12459 // jointly pins the accessor + validate-gate composition: any
12460 // future silent detour that had the accessor return `None` on
12461 // the empty arm (a `.filter(|s| !s.is_empty())` collapse) would
12462 // silently absorb the `DescricaoEmpty` refusal at the accessor
12463 // boundary and the validate gate would accept a struct-literal
12464 // `Caixa { descricao: Some(""), .. }` — the composition pin
12465 // catches that at caixa-core build time.
12466 //
12467 // Peer of the [`Caixa::licenca`] (6d5bc28)
12468 // `validate_licenca_empty_arm_routes_through_accessor` and
12469 // [`Caixa::repositorio`] (cc7332d)
12470 // `validate_repositorio_empty_arm_routes_through_accessor`
12471 // composition pins on the sibling outer top-level [`Caixa`]
12472 // `Option<&str>` universal-axis surface — same "the validate /
12473 // shape-gate predicate must route through the substrate-
12474 // primitive typed dispatch" discipline extended onto the third
12475 // outer top-level [`Caixa`] universal-axis `Option<&str>`-
12476 // composition surface.
12477 let c = caixa_with_descricao(Some(""));
12478 assert!(
12479 matches!(c.validate_descricao(), Err(ManifestError::DescricaoEmpty),),
12480 "validate_descricao must reject descricao == Some(\"\") \
12481 with DescricaoEmpty — the accessor and the validate gate \
12482 must route through the same substrate-primitive typed \
12483 dispatch on the :descricao empty arm",
12484 );
12485 let c = caixa_with_descricao(Some("Checkout flow."));
12486 assert!(
12487 c.validate_descricao().is_ok(),
12488 "validate_descricao must accept descricao == \
12489 Some(\"Checkout flow.\") (the canonical one-line-prose \
12490 chart-description shape)",
12491 );
12492 }
12493
12494 #[test]
12495 fn descricao_projects_option_str_by_borrow() {
12496 // The by-borrow pin: [`Caixa::descricao`] returns
12497 // `Option<&str>` by borrow — the `&str` borrows the underlying
12498 // `String` storage of the `Option<String>` slot and the
12499 // accessor must not allocate a fresh `String` on every call.
12500 // Peer of the [`Caixa::licenca`] (6d5bc28) and
12501 // [`Caixa::repositorio`] (cc7332d) by-borrow pins on the peer
12502 // outer top-level [`Caixa`] `Option<&str>`-return axes, and of
12503 // the per-`:placement`
12504 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
12505 // borrow pin on the peer per-M3-mesh-slot `Option<&str>`-
12506 // return axis, extended onto the third outer top-level
12507 // [`Caixa`] universal-axis `Option<&str>` shape — the
12508 // accessor's returned `&str` must borrow from `&self` (the
12509 // returned reference's lifetime is tied to `&self`), and
12510 // calling the accessor twice on the same [`Caixa`] must yield
12511 // the same `Option<&str>` verbatim (idempotent, no side
12512 // effects on `&self`).
12513 //
12514 // Pins against a future silent detour that returned an owned
12515 // `Option<String>` (which would type-check but silently
12516 // allocate on every call, breaking the zero-cost projection
12517 // every peer sibling accessor carries), or a one-arm-only
12518 // accessor that returned a saturating value on some sentinel
12519 // input (breaking the pass-through invariant the sibling
12520 // required-scalar accessors carry).
12521 for descricao in [
12522 None,
12523 Some(""),
12524 Some("Checkout flow."),
12525 Some("Canonical Rust→wasm32-wasip2 caixa Servico."),
12526 ] {
12527 let c = caixa_with_descricao(descricao);
12528 let first = c.descricao();
12529 let second = c.descricao();
12530 assert_eq!(
12531 first, second,
12532 "Caixa::descricao must be idempotent — two successive \
12533 calls on the same &self must return the same \
12534 Option<&str>",
12535 );
12536 assert_eq!(
12537 first, descricao,
12538 "Caixa::descricao must return :descricao verbatim by \
12539 borrow — got {first:?}, expected {descricao:?}",
12540 );
12541 }
12542 }
12543
12544 // ── validate_edicao — universal-axis language-edition shape ──
12545
12546 fn caixa_with_edicao(edicao: Option<&str>) -> Caixa {
12547 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12548 c.edicao = edicao.map(String::from);
12549 c
12550 }
12551
12552 #[test]
12553 fn validate_edicao_accepts_none() {
12554 // The omit-the-slot identity: `:edicao` is optional. The
12555 // gate is a no-op when the author didn't declare a value —
12556 // every caixa without an `:edicao` line trivially passes,
12557 // and the substrate-side build pipeline falls back to the
12558 // documented default edition. Mirrors the peer
12559 // `validate_licenca_accepts_none` posture on the sibling
12560 // `Option<String>` Caixa slot.
12561 let c = caixa_with_edicao(None);
12562 c.validate_edicao().unwrap();
12563 }
12564
12565 #[test]
12566 fn validate_edicao_accepts_canonical_value() {
12567 // Positive control: the canonical `"2026"` edition every
12568 // existing renderer-side fixture (`caixa-helm`, `caixa-flux`,
12569 // `caixa-mesh`) carries by construction passes the gate.
12570 // Future-introduced sibling editions (`"2027"`, `"2030"`,
12571 // `"2049"`) that match the same 4-digit ASCII decimal year
12572 // shape must also trivially pass — the structural shape
12573 // predicate accepts every well-formed year regardless of
12574 // whether the substrate yet understands the specific value
12575 // (a future known-edition allowlist tightens that).
12576 for ed in ["2026", "2027", "2030", "2049"] {
12577 let c = caixa_with_edicao(Some(ed));
12578 c.validate_edicao()
12579 .unwrap_or_else(|err| panic!("canonical {ed:?} must pass: {err:?}"));
12580 }
12581 }
12582
12583 #[test]
12584 fn validate_edicao_rejects_empty_some() {
12585 // Canonical paste-from-blank-doc footgun. Without this gate
12586 // the empty `Some("")` silently lands as `(:edicao "")` in
12587 // the rendered caixa.lisp and a future renderer-side
12588 // consumer's `Option::unwrap_or_else` (which only fires on
12589 // `None`) skips its fallback. Mirrors the peer
12590 // [`ManifestError::LicencaEmpty`] empty-arm on the sibling
12591 // `Option<String>` Caixa slot.
12592 let c = caixa_with_edicao(Some(""));
12593 let err = c.validate_edicao().unwrap_err();
12594 assert!(matches!(err, ManifestError::EdicaoEmpty), "got {err:?}",);
12595 }
12596
12597 #[test]
12598 fn validate_edicao_rejects_free_form_non_year() {
12599 // Free-form non-year footgun: the bare `"x"` / `"latest"` /
12600 // `"nightly"` shapes carry no operational meaning on the
12601 // substrate's build-time edition selector. Until this gate
12602 // landed the bare empty-arm check let every such value
12603 // through and broke far from the source caixa.lisp. Peer
12604 // with the shape-predicate cascade
12605 // `validate_repositorio_rejects_missing_colon_separator`
12606 // establishes past its own empty arm.
12607 for ed in ["x", "latest", "nightly", "stable"] {
12608 let c = caixa_with_edicao(Some(ed));
12609 let err = c.validate_edicao().unwrap_err();
12610 assert!(
12611 matches!(err, ManifestError::EdicaoInvalid { .. }),
12612 "expected EdicaoInvalid on {ed:?}, got {err:?}",
12613 );
12614 }
12615 }
12616
12617 #[test]
12618 fn validate_edicao_rejects_trailing_whitespace() {
12619 // Paste-from-doc whitespace footgun. A trailing space in
12620 // the `:edicao` value would silently break the substrate's
12621 // build-time edition match-table lookup at the rendered
12622 // artifact's edition-selector consumer. The shape predicate
12623 // refuses every whitespace byte by construction (any byte
12624 // outside `0-9` fails `is_ascii_digit`). Peer with
12625 // `validate_repositorio_rejects_whitespace`.
12626 let c = caixa_with_edicao(Some("2026 "));
12627 let err = c.validate_edicao().unwrap_err();
12628 let ManifestError::EdicaoInvalid { edicao, .. } = err else {
12629 panic!("expected EdicaoInvalid, got {err:?}");
12630 };
12631 assert_eq!(edicao, "2026 ");
12632 }
12633
12634 #[test]
12635 fn validate_edicao_rejects_leading_whitespace() {
12636 // Symmetric paste-from-doc whitespace footgun on the leading
12637 // boundary — the gate refuses every shape with a non-digit
12638 // byte by construction.
12639 let c = caixa_with_edicao(Some(" 2026"));
12640 let err = c.validate_edicao().unwrap_err();
12641 assert!(
12642 matches!(err, ManifestError::EdicaoInvalid { .. }),
12643 "got {err:?}",
12644 );
12645 }
12646
12647 #[test]
12648 fn validate_edicao_rejects_control_char() {
12649 // Paste-from-multiline-doc CRLF footgun — control characters
12650 // at the value boundary break the substrate's build-time
12651 // edition-selector parser. Peer with
12652 // `validate_repositorio_rejects_control_char`.
12653 let c = caixa_with_edicao(Some("2026\n"));
12654 let err = c.validate_edicao().unwrap_err();
12655 assert!(
12656 matches!(err, ManifestError::EdicaoInvalid { .. }),
12657 "got {err:?}",
12658 );
12659 }
12660
12661 #[test]
12662 fn validate_edicao_rejects_non_ascii_lookalike() {
12663 // Fullwidth-keyboard look-alike footgun — `"2026"` is
12664 // the U+FF12 U+FF10 U+FF12 U+FF16 sequence (CJK fullwidth
12665 // digits), 4 codepoints but 12 UTF-8 bytes; the substrate's
12666 // edition selector wants an ASCII year, and the gate
12667 // refuses every non-ASCII shape by construction (length in
12668 // bytes is 12 ≠ 4, *and* every byte falls outside
12669 // `is_ascii_digit`'s `0-9` range).
12670 let c = caixa_with_edicao(Some("2026"));
12671 let err = c.validate_edicao().unwrap_err();
12672 assert!(
12673 matches!(err, ManifestError::EdicaoInvalid { .. }),
12674 "got {err:?}",
12675 );
12676 }
12677
12678 #[test]
12679 fn validate_edicao_rejects_version_tag_prefix() {
12680 // Common version-tag idiom footgun — `"v2026"` / `"e2026"`
12681 // / `"r2026"` are familiar shapes from git-tag / Rust
12682 // edition / release-tag conventions that don't apply to
12683 // the year-shaped edition axis. The shape predicate refuses
12684 // every leading non-digit prefix.
12685 for ed in ["v2026", "e2026", "r2026"] {
12686 let c = caixa_with_edicao(Some(ed));
12687 let err = c.validate_edicao().unwrap_err();
12688 assert!(
12689 matches!(err, ManifestError::EdicaoInvalid { .. }),
12690 "expected EdicaoInvalid on {ed:?}, got {err:?}",
12691 );
12692 }
12693 }
12694
12695 #[test]
12696 fn validate_edicao_rejects_decimal_shape() {
12697 // Decimal-shaped pseudo-version footgun — `"2026.1"` /
12698 // `"2026.0"` are familiar shapes from semver / float
12699 // conventions that don't apply to the year-shaped edition
12700 // axis. The shape predicate refuses every non-digit byte
12701 // (`.` falls outside `is_ascii_digit`).
12702 for ed in ["2026.1", "2026.0", "2026.0.1"] {
12703 let c = caixa_with_edicao(Some(ed));
12704 let err = c.validate_edicao().unwrap_err();
12705 assert!(
12706 matches!(err, ManifestError::EdicaoInvalid { .. }),
12707 "expected EdicaoInvalid on {ed:?}, got {err:?}",
12708 );
12709 }
12710 }
12711
12712 #[test]
12713 fn validate_edicao_rejects_wrong_length_numeric() {
12714 // Wrong-length numeric footgun — `"26"` (truncated) /
12715 // `"202"` (truncated) / `"20260"` (extra digit) / `"00026"`
12716 // (zero-padded too wide) all parse as integers but don't
12717 // name a 4-digit year. The shape predicate refuses every
12718 // value whose length isn't exactly 4 bytes.
12719 for ed in ["26", "202", "20260", "00026", "9"] {
12720 let c = caixa_with_edicao(Some(ed));
12721 let err = c.validate_edicao().unwrap_err();
12722 assert!(
12723 matches!(err, ManifestError::EdicaoInvalid { .. }),
12724 "expected EdicaoInvalid on {ed:?}, got {err:?}",
12725 );
12726 }
12727 }
12728
12729 #[test]
12730 fn validate_edicao_empty_takes_precedence_over_shape() {
12731 // Empty-first cascade pin: the empty `Some("")` surfaces
12732 // the narrower `EdicaoEmpty` not the shape-predicate-
12733 // wrapped `EdicaoInvalid`, mirroring the peer
12734 // `validate_repositorio_empty_takes_precedence_over_shape`
12735 // (`RepositorioEmpty` → `RepositorioInvalid`),
12736 // `NomeEmpty` → `NomeInvalid`, `VersaoEmpty` →
12737 // `VersaoInvalid`, `FonteRepoEmpty` → `FonteRepoInvalid`
12738 // cascades. The shape predicate also refuses the empty
12739 // input (defensively — `s.len() != 4`), but the
12740 // manifest-layer empty arm runs first to surface the
12741 // narrower diagnostic verbatim.
12742 let c = caixa_with_edicao(Some(""));
12743 let err = c.validate_edicao().unwrap_err();
12744 assert!(matches!(err, ManifestError::EdicaoEmpty), "got {err:?}",);
12745 }
12746
12747 #[test]
12748 fn validate_edicao_template_passes() {
12749 // Round-trip pin: the bare `Caixa::template` shape (which
12750 // carries `:edicao "2026"` verbatim) passes the gate by
12751 // construction. A future template-shape change that
12752 // introduced `(:edicao "")` or a non-year value would
12753 // surface here as a regression. Mirrors the peer
12754 // `validate_licenca_template_passes` pin.
12755 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12756 c.validate_edicao().unwrap();
12757 }
12758
12759 #[test]
12760 fn validate_edicao_diagnostic_names_offending_slot() {
12761 // Diagnostic-shape pin (peer with
12762 // `validate_licenca_diagnostic_names_offending_slot`): the
12763 // error's Display surfaces the `:edicao` slot name verbatim,
12764 // so a `feira lint` run can render the diagnostic without
12765 // re-parsing and the author can grep their caixa.lisp for
12766 // the offending `:edicao` line.
12767 let c = caixa_with_edicao(Some(""));
12768 let rendered = c.validate_edicao().unwrap_err().to_string();
12769 assert!(
12770 rendered.contains(":edicao"),
12771 "diagnostic must name the offending slot: {rendered}",
12772 );
12773 }
12774
12775 #[test]
12776 fn validate_edicao_invalid_diagnostic_carries_offending_value() {
12777 // Diagnostic-shape pin on the shape-predicate arm (peer
12778 // with `validate_repositorio_diagnostic_carries_offending_value`):
12779 // the error's Display surfaces the offending value + slot
12780 // name verbatim, so a `feira lint` run can render the
12781 // diagnostic without re-parsing and the author can grep
12782 // their caixa.lisp for the offending `:edicao` value.
12783 let c = caixa_with_edicao(Some("v2026"));
12784 let rendered = c.validate_edicao().unwrap_err().to_string();
12785 assert!(
12786 rendered.contains(":edicao"),
12787 "diagnostic must name the offending slot: {rendered}",
12788 );
12789 assert!(
12790 rendered.contains("v2026"),
12791 "diagnostic must quote the offending value: {rendered}",
12792 );
12793 }
12794
12795 // ── Caixa::edicao — outer top-level Option<&str> scalar accessor ──
12796
12797 #[test]
12798 fn edicao_returns_edicao_byte_string_verbatim_across_permutations() {
12799 // The canonical per-`Caixa` `:edicao` language-edition scalar
12800 // pin: [`Caixa::edicao`] must return the `:edicao` typed
12801 // byte-string verbatim as an `Option<&str>`, byte-equal to the
12802 // raw `self.edicao.as_deref()` access across every representative
12803 // value in the accept-set — `None` (the "omit the slot to defer
12804 // to the substrate's default edition" arm every existing
12805 // [`caixa-resolver`] fixture without an `:edicao` line carries),
12806 // `Some("")` (a past-the-guard sentinel that pins the accessor
12807 // doesn't perform a silent `Some("") → None` collapse on the
12808 // empty arm — validate rejects `Some("")` through `EdicaoEmpty`
12809 // but the accessor must ship the raw slot verbatim so a
12810 // validate-time gate regression surfaces at any future edition-
12811 // aware consumer's boundary rather than being silently absorbed
12812 // into the substrate's default edition), `Some("2026")` (the
12813 // canonical 4-digit-ASCII-decimal-year shape every `feira init`
12814 // template scaffolds via [`Caixa::template`] and every
12815 // renderer-side fixture at `caixa-helm/src/lib.rs:978` /
12816 // `caixa-flux/src/lib.rs:2319` / `caixa-mesh/src/lib.rs:3208`
12817 // carries by construction), `Some("2018")` / `Some("2021")` /
12818 // `Some("2024")` (canonical 4-digit-ASCII-decimal-year shapes
12819 // peer with Cargo's `[package] edition` grammar every future-
12820 // introduced sibling to `"2026"` will follow), and eight
12821 // past-the-guard sentinels for the `EdicaoInvalid` refusal cases
12822 // (`Some("2026 ")` trailing-whitespace, `Some(" 2026")` leading-
12823 // whitespace, `Some("2026\n")` embedded-LF, `Some("2026")`
12824 // fullwidth-non-ASCII-lookalike, `Some("v2026")` version-tag-
12825 // prefix, `Some("2026.1")` decimal-shape, `Some("26")` wrong-
12826 // length-numeric, `Some("latest")` free-form-non-year — the
12827 // sentinels pin the accessor doesn't silently absorb the
12828 // refusal cases into a substrate-default-edition fallback).
12829 //
12830 // Fourth and final outer top-level [`Caixa`] `Option<&str>`-
12831 // return scalar accessor pin on the substrate primitive —
12832 // sibling of the peer [`Caixa::licenca`] (6d5bc28),
12833 // [`Caixa::repositorio`] (cc7332d), and [`Caixa::descricao`]
12834 // (3f16e2f) pins that opened the "outer [`Caixa`]
12835 // `Option<&str>` scalar" projection pin pattern this pin folds
12836 // on. Sibling in shape to the peer per-`:placement`
12837 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
12838 // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
12839 // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
12840 // axes, extended onto the outer top-level [`Caixa`] universal-
12841 // axis surface's last unlifted `Option<String>` slot. Pins
12842 // against a future silent detour that returned an owned
12843 // `Option<String>` (which would type-check but silently
12844 // allocate on every accessor call, breaking the zero-cost
12845 // projection every peer sibling accessor carries), a
12846 // `Some("") → None` collapse (which would silently absorb the
12847 // `EdicaoEmpty` refusal case at the accessor boundary and any
12848 // future edition-aware consumer would silently fall back to
12849 // the substrate's default edition on a struct-literal
12850 // `Caixa { edicao: Some(""), .. }`), or a
12851 // `None → Some("2026")` collapse (which would silently reify
12852 // the substrate's default edition at the accessor boundary
12853 // and every downstream consumer keying off the
12854 // `Option::is_none()` discriminator would lose the "author
12855 // omitted the slot" signal).
12856 for edicao in [
12857 None,
12858 Some(""),
12859 Some("2026"),
12860 Some("2018"),
12861 Some("2021"),
12862 Some("2024"),
12863 Some("2026 "),
12864 Some(" 2026"),
12865 Some("2026\n"),
12866 Some("2026"),
12867 Some("v2026"),
12868 Some("2026.1"),
12869 Some("26"),
12870 Some("latest"),
12871 ] {
12872 let c = caixa_with_edicao(edicao);
12873 assert_eq!(
12874 c.edicao(),
12875 edicao,
12876 "Caixa::edicao must return :edicao verbatim (got {:?}, \
12877 expected {edicao:?})",
12878 c.edicao(),
12879 );
12880 assert_eq!(
12881 c.edicao(),
12882 c.edicao.as_deref(),
12883 "Caixa::edicao must byte-equal the raw \
12884 `self.edicao.as_deref()` field access across every \
12885 value in the Option<&str> accept-set",
12886 );
12887 }
12888 }
12889
12890 #[test]
12891 fn validate_edicao_empty_arm_routes_through_accessor() {
12892 // Composition pin: [`Caixa::validate_edicao`]'s empty-arm gate
12893 // must key off [`Caixa::edicao`], not the raw
12894 // `self.edicao.as_deref()` field access. Structurally: a
12895 // `Caixa { edicao: Some(""), .. }` must surface the
12896 // `EdicaoEmpty` refusal exactly, and a
12897 // `Caixa { edicao: Some("2026"), .. }` (the canonical
12898 // 4-digit-ASCII-decimal-year form) must pass validate. The
12899 // pair jointly pins the accessor + validate-gate composition:
12900 // any future silent detour that had the accessor return `None`
12901 // on the empty arm (a `.filter(|s| !s.is_empty())` collapse)
12902 // would silently absorb the `EdicaoEmpty` refusal at the
12903 // accessor boundary and the validate gate would accept a
12904 // struct-literal `Caixa { edicao: Some(""), .. }` — the
12905 // composition pin catches that at caixa-core build time.
12906 //
12907 // Peer of the [`Caixa::licenca`] (6d5bc28)
12908 // `validate_licenca_empty_arm_routes_through_accessor`,
12909 // [`Caixa::repositorio`] (cc7332d)
12910 // `validate_repositorio_empty_arm_routes_through_accessor`,
12911 // and [`Caixa::descricao`] (3f16e2f)
12912 // `validate_descricao_empty_arm_routes_through_accessor`
12913 // composition pins on the sibling outer top-level [`Caixa`]
12914 // `Option<&str>` universal-axis surface — same "the validate /
12915 // shape-gate predicate must route through the substrate-
12916 // primitive typed dispatch" discipline extended onto the
12917 // fourth and final outer top-level [`Caixa`] universal-axis
12918 // `Option<&str>`-composition surface, closing the accessor-
12919 // composition family.
12920 let c = caixa_with_edicao(Some(""));
12921 assert!(
12922 matches!(c.validate_edicao(), Err(ManifestError::EdicaoEmpty)),
12923 "validate_edicao must reject edicao == Some(\"\") with \
12924 EdicaoEmpty — the accessor and the validate gate must \
12925 route through the same substrate-primitive typed dispatch \
12926 on the :edicao empty arm",
12927 );
12928 let c = caixa_with_edicao(Some("2026"));
12929 assert!(
12930 c.validate_edicao().is_ok(),
12931 "validate_edicao must accept edicao == Some(\"2026\") \
12932 (the canonical 4-digit-ASCII-decimal-year shape)",
12933 );
12934 }
12935
12936 #[test]
12937 fn edicao_projects_option_str_by_borrow() {
12938 // The by-borrow pin: [`Caixa::edicao`] returns
12939 // `Option<&str>` by borrow — the `&str` borrows the underlying
12940 // `String` storage of the `Option<String>` slot and the
12941 // accessor must not allocate a fresh `String` on every call.
12942 // Peer of the [`Caixa::licenca`] (6d5bc28),
12943 // [`Caixa::repositorio`] (cc7332d), and [`Caixa::descricao`]
12944 // (3f16e2f) by-borrow pins on the peer outer top-level
12945 // [`Caixa`] `Option<&str>`-return axes, and of the
12946 // per-`:placement`
12947 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
12948 // borrow pin on the peer per-M3-mesh-slot `Option<&str>`-
12949 // return axis, extended onto the fourth and final outer top-
12950 // level [`Caixa`] universal-axis `Option<&str>` shape — the
12951 // accessor's returned `&str` must borrow from `&self` (the
12952 // returned reference's lifetime is tied to `&self`), and
12953 // calling the accessor twice on the same [`Caixa`] must yield
12954 // the same `Option<&str>` verbatim (idempotent, no side
12955 // effects on `&self`).
12956 //
12957 // Pins against a future silent detour that returned an owned
12958 // `Option<String>` (which would type-check but silently
12959 // allocate on every call, breaking the zero-cost projection
12960 // every peer sibling accessor carries), or a one-arm-only
12961 // accessor that returned a saturating value on some sentinel
12962 // input (breaking the pass-through invariant the sibling
12963 // required-scalar accessors carry).
12964 for edicao in [None, Some(""), Some("2026"), Some("2018")] {
12965 let c = caixa_with_edicao(edicao);
12966 let first = c.edicao();
12967 let second = c.edicao();
12968 assert_eq!(
12969 first, second,
12970 "Caixa::edicao must be idempotent — two successive \
12971 calls on the same &self must return the same \
12972 Option<&str>",
12973 );
12974 assert_eq!(
12975 first, edicao,
12976 "Caixa::edicao must return :edicao verbatim by \
12977 borrow — got {first:?}, expected {edicao:?}",
12978 );
12979 }
12980 }
12981
12982 #[test]
12983 fn nome_returns_nome_byte_string_verbatim_across_permutations() {
12984 // The canonical per-`Caixa` `:nome` universal-axis DNS-1123-
12985 // label caixa-identity scalar pin: [`Caixa::nome`] must return
12986 // the `:nome` typed `String` verbatim as `&str`, byte-equal to
12987 // the raw field access across every representative value in
12988 // the accept-set — the canonical `"demo"` template baseline
12989 // (the same `feira init`-scaffolded default the sibling
12990 // `validate_nome_accepts_canonical_template` positive-control
12991 // gate pins), plus every sibling per-typed-slot atom accessor's
12992 // canonical positive-arm byte-string (`"catalog"` per
12993 // [`crate::aplicacao::Membro::nome`], `"cart"` per the peer
12994 // per-`:contratos` `:de`, `"hello-rio"` per the canonical
12995 // `caixa-helm`/`caixa-flux` cross-crate integration-test
12996 // fixture, `"checkout"` per the M3 mesh-slot Aplicacao
12997 // canonical example), plus every past-the-guard sentinel for
12998 // the `NomeEmpty` / `NomeInvalid` / `NomeChartNameBudgetExceeded`
12999 // refusal cases (`""`, `"Bad_Name"`, `"a"` × 56 — 56 bytes fits
13000 // the bare DNS-1123 63-byte cap but overflows the joint
13001 // `lareira-<nome>` chart-name budget the sibling
13002 // [`Caixa::validate_nome_chart_name_budget`] gate closes on).
13003 //
13004 // The past-the-guard sentinels pin the accessor doesn't
13005 // silently absorb the refusal cases into a template-derived
13006 // fallback (a future `.nome().is_empty().then(|| "demo")`
13007 // collapse would silently absorb the `NomeEmpty` refusal at
13008 // the accessor boundary and the validate gate would accept a
13009 // struct-literal `Caixa { nome: "".into(), .. }` — the pin
13010 // catches that at caixa-core build time).
13011 //
13012 // First outer top-level [`Caixa`] `&str`-return required-
13013 // scalar accessor pin — opens the "outer [`Caixa`] `&str`
13014 // required-scalar" projection pattern the sibling per-`Caixa`
13015 // `:versao` future lift folds on. Sibling in shape to the peer
13016 // per-`:membros` [`crate::aplicacao::Membro::nome`] (4a32abf)
13017 // required-`String`-carry accessor pin on the sibling per-
13018 // sub-struct required-axis, extended onto the outer top-level
13019 // [`Caixa`] universal-axis required-`String`-carry axis.
13020 for nome in [
13021 "demo",
13022 "catalog",
13023 "cart",
13024 "hello-rio",
13025 "checkout",
13026 "",
13027 "Bad_Name",
13028 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
13029 ] {
13030 let c = caixa_with_nome(nome);
13031 assert_eq!(
13032 c.nome(),
13033 nome,
13034 "Caixa::nome must return :nome verbatim (got {}, \
13035 expected {nome})",
13036 c.nome(),
13037 );
13038 assert_eq!(
13039 c.nome(),
13040 c.nome.as_str(),
13041 "Caixa::nome must byte-equal the raw .nome field \
13042 access across every value in the String accept-set",
13043 );
13044 }
13045 }
13046
13047 #[test]
13048 fn validate_nome_empty_arm_routes_through_accessor() {
13049 // Composition pin: [`Caixa::validate_nome`]'s empty-arm must
13050 // key off [`Caixa::nome`], not the raw `.nome` field access.
13051 // Structurally: a `Caixa { nome: "".into(), .. }` must surface
13052 // the `NomeEmpty` refusal exactly, and the canonical `"demo"`
13053 // template baseline (the peer positive-arm the sibling
13054 // `validate_nome_accepts_canonical_template` gate carves out)
13055 // must pass validate. The pair jointly pins the accessor +
13056 // validate-gate composition: any future silent detour that
13057 // had the accessor return a fresh `"demo"` on the empty arm
13058 // (a `.nome().is_empty().then(|| "demo")` fallback collapse)
13059 // would silently absorb the `NomeEmpty` refusal at the
13060 // accessor boundary and the validate gate would accept a
13061 // struct-literal `Caixa { nome: "".into(), .. }` — the
13062 // composition pin catches that at caixa-core build time.
13063 //
13064 // Peer of the sibling per-`Caixa`
13065 // `validate_licenca_empty_arm_routes_through_accessor` (6d5bc28)
13066 // / `validate_repositorio_empty_arm_routes_through_accessor`
13067 // (cc7332d) / `validate_descricao_empty_arm_routes_through_accessor`
13068 // (3f16e2f) / `validate_edicao_empty_arm_routes_through_accessor`
13069 // (2641cbd) composition pins on the sibling outer top-level
13070 // [`Caixa`] `Option<&str>` axes — same "the validate /
13071 // shape-gate predicate must route through the substrate-
13072 // primitive typed dispatch" discipline extended onto the peer
13073 // outer top-level [`Caixa`] required-`&str` composition axis.
13074 let c = caixa_with_nome("");
13075 assert!(
13076 matches!(c.validate_nome(), Err(ManifestError::NomeEmpty)),
13077 "validate_nome must reject nome == \"\" with NomeEmpty — \
13078 the accessor and the validate gate must route through the \
13079 same substrate-primitive typed dispatch on the :nome \
13080 empty-arm",
13081 );
13082 let c = caixa_with_nome("demo");
13083 assert!(
13084 c.validate_nome().is_ok(),
13085 "validate_nome must accept nome == \"demo\" (the canonical \
13086 DNS-1123-label template baseline)",
13087 );
13088 }
13089
13090 #[test]
13091 fn nome_projects_str_by_borrow() {
13092 // The by-borrow pin: [`Caixa::nome`] returns `&str` by borrow
13093 // — the `&str` borrows the underlying `String` storage of the
13094 // required `nome` slot and the accessor must not allocate a
13095 // fresh `String` on every call. Peer of the [`Caixa::licenca`]
13096 // (6d5bc28) / [`Caixa::repositorio`] (cc7332d) /
13097 // [`Caixa::descricao`] (3f16e2f) / [`Caixa::edicao`] (2641cbd)
13098 // by-borrow pins on the peer outer top-level [`Caixa`]
13099 // `Option<&str>`-return axes, extended onto the first outer
13100 // top-level [`Caixa`] required-`&str`-return axis — the
13101 // accessor's returned `&str` must borrow from `&self` (the
13102 // returned reference's lifetime is tied to `&self`), and
13103 // calling the accessor twice on the same [`Caixa`] must yield
13104 // the same `&str` verbatim (idempotent, no side effects on
13105 // `&self`).
13106 //
13107 // Pins against a future silent detour that returned an owned
13108 // `String` (which would type-check but silently allocate on
13109 // every call, breaking the zero-cost projection every peer
13110 // sibling accessor carries), an accidental
13111 // `.nome.to_lowercase()` detour that returned a fresh
13112 // allocation through an already-DNS-1123-lowercase-only
13113 // string (breaking a future `const fn` regression), or a
13114 // one-arm-only accessor that returned a canonicalized value
13115 // on some sentinel input (breaking the pass-through invariant
13116 // the sibling required-scalar accessors carry).
13117 for nome in ["demo", "catalog", "hello-rio", "checkout"] {
13118 let c = caixa_with_nome(nome);
13119 let first = c.nome();
13120 let second = c.nome();
13121 assert_eq!(
13122 first, second,
13123 "Caixa::nome must be idempotent — two successive calls \
13124 on the same &self must return the same &str",
13125 );
13126 assert_eq!(
13127 first, nome,
13128 "Caixa::nome must return :nome verbatim by borrow — \
13129 got {first}, expected {nome}",
13130 );
13131 }
13132 }
13133
13134 #[test]
13135 fn versao_returns_versao_byte_string_verbatim_across_permutations() {
13136 // The canonical per-`Caixa` `:versao` universal-axis SemVer-2
13137 // pinned-version scalar pin: [`Caixa::versao`] must return the
13138 // `:versao` typed `String` verbatim as `&str`, byte-equal to the
13139 // raw `.versao` field access across every representative value
13140 // in the accept-set — the canonical `"0.1.0"` template baseline
13141 // (the same `feira init`-scaffolded default the sibling
13142 // `validate_versao_accepts_canonical_template` positive-control
13143 // gate pins), plus every canonical SemVer-2 shape the sibling
13144 // `validate_versao_accepts_canonical_forms` positive-arm sweep
13145 // covers (`"0.0.0"`, `"1.0.0"`, `"0.2.0-rc.1"`,
13146 // `"1.0.0-alpha.0"`, `"1.0.0+build.42"`, `"1.0.0-rc.1+build.42"`,
13147 // `"10.20.30"`), plus every past-the-guard sentinel for the
13148 // `VersaoEmpty` / `VersaoInvalid` refusal cases (`""` the empty
13149 // arm, `"v0.1.0"` the git-tag-shape-leak footgun, `"0.1"` the
13150 // missing-patch footgun, `"^0.1"` the requirement-shape-leak
13151 // footgun, `"0.1.0.0"` the four-part-Java-convention footgun,
13152 // `"latest"` the docker-tag-shape footgun — the sentinels pin
13153 // the accessor doesn't silently absorb the refusal cases into a
13154 // template-derived fallback like `"0.1.0"`).
13155 //
13156 // The past-the-guard sentinels pin the accessor doesn't silently
13157 // absorb the refusal cases into a template-derived fallback (a
13158 // future `.versao().is_empty().then(|| "0.1.0")` collapse would
13159 // silently absorb the `VersaoEmpty` refusal at the accessor
13160 // boundary and the validate gate would accept a struct-literal
13161 // `Caixa { versao: "".into(), .. }` — the pin catches that at
13162 // caixa-core build time).
13163 //
13164 // Second outer top-level [`Caixa`] `&str`-return required-scalar
13165 // accessor pin — folds on the "outer [`Caixa`] `&str` required-
13166 // scalar" projection pattern the sibling per-`Caixa`
13167 // [`Caixa::nome`] (e6b7d97) opened. Sibling in shape to the peer
13168 // per-`:membros` [`crate::aplicacao::Membro::versao_requirement`]
13169 // (4127bb6) / per-`:children`
13170 // [`crate::supervisor::ChildSpec::versao_requirement`] (2c053c8)
13171 // / per-`:upgrade-from`
13172 // [`crate::UpgradeFromEntry::prior_versao`] (75d27a8) per-sub-
13173 // struct `:versao`-shaped `&str`-return accessor pins on the
13174 // sibling per-typed-slot version-carrier axes, extended onto the
13175 // second outer top-level [`Caixa`] universal-axis required-
13176 // `String`-carry axis so the two universal-axis identity-
13177 // carrying scalars every `defcaixa` form supplies (`:nome` +
13178 // `:versao`) share the same "one typed dispatch per axis" pin
13179 // discipline.
13180 for versao in [
13181 "0.1.0",
13182 "0.0.0",
13183 "1.0.0",
13184 "0.2.0-rc.1",
13185 "1.0.0-alpha.0",
13186 "1.0.0+build.42",
13187 "1.0.0-rc.1+build.42",
13188 "10.20.30",
13189 "",
13190 "v0.1.0",
13191 "0.1",
13192 "^0.1",
13193 "0.1.0.0",
13194 "latest",
13195 ] {
13196 let c = caixa_with_versao(versao);
13197 assert_eq!(
13198 c.versao(),
13199 versao,
13200 "Caixa::versao must return :versao verbatim (got {}, \
13201 expected {versao})",
13202 c.versao(),
13203 );
13204 assert_eq!(
13205 c.versao(),
13206 c.versao.as_str(),
13207 "Caixa::versao must byte-equal the raw .versao field \
13208 access across every value in the String accept-set",
13209 );
13210 }
13211 }
13212
13213 #[test]
13214 fn validate_versao_empty_arm_routes_through_accessor() {
13215 // Composition pin: [`Caixa::validate_versao`]'s empty-arm gate
13216 // must key off [`Caixa::versao`], not the raw `.versao` field
13217 // access. Structurally: a `Caixa { versao: "".into(), .. }` must
13218 // surface the `VersaoEmpty` refusal exactly, and the canonical
13219 // `"0.1.0"` template baseline (the peer positive-arm the sibling
13220 // `validate_versao_accepts_canonical_template` gate carves out)
13221 // must pass validate. The pair jointly pins the accessor +
13222 // validate-gate composition: any future silent detour that had
13223 // the accessor return a fresh `"0.1.0"` on the empty arm
13224 // (a `.versao().is_empty().then(|| "0.1.0")` fallback collapse)
13225 // would silently absorb the `VersaoEmpty` refusal at the
13226 // accessor boundary and the validate gate would accept a
13227 // struct-literal `Caixa { versao: "".into(), .. }` — the
13228 // composition pin catches that at caixa-core build time.
13229 //
13230 // Peer of the sibling per-`Caixa`
13231 // `validate_nome_empty_arm_routes_through_accessor` (e6b7d97)
13232 // composition pin on the sibling outer top-level [`Caixa`]
13233 // required-`&str` universal-axis surface — same "the validate /
13234 // shape-gate predicate must route through the substrate-
13235 // primitive typed dispatch" discipline extended onto the peer
13236 // outer top-level [`Caixa`] required-`&str` universal-axis
13237 // pinned-version composition axis, closing the second
13238 // coordinate of the "one canonical typed dispatch per per-Caixa
13239 // required-`&str` universal-axis" discipline.
13240 let c = caixa_with_versao("");
13241 assert!(
13242 matches!(c.validate_versao(), Err(ManifestError::VersaoEmpty)),
13243 "validate_versao must reject versao == \"\" with VersaoEmpty — \
13244 the accessor and the validate gate must route through the \
13245 same substrate-primitive typed dispatch on the :versao \
13246 empty-arm",
13247 );
13248 let c = caixa_with_versao("0.1.0");
13249 assert!(
13250 c.validate_versao().is_ok(),
13251 "validate_versao must accept versao == \"0.1.0\" (the \
13252 canonical SemVer-2 template baseline)",
13253 );
13254 }
13255
13256 #[test]
13257 fn versao_projects_str_by_borrow() {
13258 // The by-borrow pin: [`Caixa::versao`] returns `&str` by borrow
13259 // — the `&str` borrows the underlying `String` storage of the
13260 // required `versao` slot and the accessor must not allocate a
13261 // fresh `String` on every call. Peer of the [`Caixa::nome`]
13262 // (e6b7d97) by-borrow pin on the sibling outer top-level
13263 // [`Caixa`] required-`&str`-return axis, extended onto the
13264 // second outer top-level [`Caixa`] required-`&str`-return
13265 // universal-axis pinned-version surface — the accessor's
13266 // returned `&str` must borrow from `&self` (the returned
13267 // reference's lifetime is tied to `&self`), and calling the
13268 // accessor twice on the same [`Caixa`] must yield the same
13269 // `&str` verbatim (idempotent, no side effects on `&self`).
13270 //
13271 // Pins against a future silent detour that returned an owned
13272 // `String` (which would type-check but silently allocate on
13273 // every call, breaking the zero-cost projection every peer
13274 // sibling accessor carries), an accidental
13275 // `semver::Version::parse(&self.versao).unwrap().to_string()`
13276 // detour that returned a canonicalized fresh allocation through
13277 // an already-canonical byte-string (breaking a future `const fn`
13278 // regression and silently absorbing the `VersaoInvalid` refusal
13279 // at the accessor boundary), or a one-arm-only accessor that
13280 // returned a canonicalized value on some sentinel input
13281 // (breaking the pass-through invariant the sibling required-
13282 // scalar accessors carry).
13283 for versao in ["0.1.0", "1.0.0", "0.2.0-rc.1", "1.0.0+build.42"] {
13284 let c = caixa_with_versao(versao);
13285 let first = c.versao();
13286 let second = c.versao();
13287 assert_eq!(
13288 first, second,
13289 "Caixa::versao must be idempotent — two successive \
13290 calls on the same &self must return the same &str",
13291 );
13292 assert_eq!(
13293 first, versao,
13294 "Caixa::versao must return :versao verbatim by borrow \
13295 — got {first}, expected {versao}",
13296 );
13297 }
13298 }
13299
13300 fn caixa_with_kind(kind: CaixaKind) -> Caixa {
13301 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
13302 c.kind = kind;
13303 c
13304 }
13305
13306 #[test]
13307 fn kind_returns_kind_variant_verbatim_across_permutations() {
13308 // The canonical per-`Caixa` `:kind` universal-axis closed-set-
13309 // enum discriminant pin: [`Caixa::kind`] must return the `:kind`
13310 // typed [`CaixaKind`] variant verbatim by `Copy`, byte-equal to
13311 // the raw `.kind` field access across every variant in the
13312 // closed accept-set (`Biblioteca` — the library kind that
13313 // exports lisp forms; `Binario` — the nix-built executable kind
13314 // under `exe/`; `Servico` — the wasm-component daemon kind
13315 // under `servicos/`; `Supervisor` — the OTP-shaped hierarchical
13316 // reconciliation kind; `Aplicacao` — the M3 typed-mesh
13317 // composition kind).
13318 //
13319 // Pins against a future silent detour that re-derived the kind
13320 // from a peer axis (an accidental fallback to
13321 // `if !servicos.is_empty() { Servico } else if
13322 // !membros.is_empty() { Aplicacao } else { Biblioteca }`
13323 // collapse that read the code-surface / mesh-slot columns into
13324 // the kind discriminator), a variant remap the operator
13325 // authors on one consumer without the other, or a stale-derive
13326 // detour that substituted [`CaixaKind::Biblioteca`] as the
13327 // default when the field held any other variant (which would
13328 // silently collapse the distinction between "author explicitly
13329 // declared `:kind Servico`" and "author declared any other
13330 // kind" every downstream renderer-dispatch site depends on).
13331 //
13332 // First outer top-level [`Caixa`] `Copy`-return required-enum-
13333 // discriminant accessor pin — opens the "outer [`Caixa`]
13334 // `Copy`-return required-discriminant" projection pattern.
13335 // Sibling in shape to the peer per-`:supervisor`
13336 // [`crate::supervisor::SupervisorSpec::estrategia`] (eafb619),
13337 // per-`:placement` [`crate::aplicacao::Placement::estrategia`]
13338 // (921fe1b), and per-`:children`
13339 // [`crate::supervisor::ChildSpec::restart`] (dfb4a81)
13340 // `Copy`-return closed-set-enum discriminant accessor pins on
13341 // the sibling nested-spec typed-slot discriminator axes,
13342 // extended here to the outer top-level [`Caixa`] universal-
13343 // axis surface.
13344 for kind in [
13345 CaixaKind::Biblioteca,
13346 CaixaKind::Binario,
13347 CaixaKind::Servico,
13348 CaixaKind::Supervisor,
13349 CaixaKind::Aplicacao,
13350 ] {
13351 let c = caixa_with_kind(kind);
13352 assert_eq!(
13353 c.kind(),
13354 kind,
13355 "Caixa::kind must return :kind verbatim (got {:?}, \
13356 expected {kind:?})",
13357 c.kind(),
13358 );
13359 assert_eq!(
13360 c.kind(),
13361 c.kind,
13362 "Caixa::kind accessor and .kind field access must \
13363 byte-equal — the accessor is the substrate-primitive \
13364 typed dispatch every downstream kind-gate consumer \
13365 must route through",
13366 );
13367 }
13368 }
13369
13370 #[test]
13371 fn require_kind_reads_through_lifted_kind_accessor() {
13372 // Two-consumer coherence pin: the [`crate::render::require_kind`]
13373 // entry-gate predicate (the canonical two-line
13374 // `require_kind(caixa, Servico)?` prelude every per-Servico /
13375 // per-Aplicacao renderer runs at its entry-point) and the
13376 // sibling [`crate::render::KindMismatch`] error carrier's
13377 // `actual:` field (which names the offending caixa's variant
13378 // in the diagnostic) must both key off the lifted accessor, so
13379 // any future rebrand on the typed slot's reader shape lands at
13380 // exactly one place. Pins the two-site coherence by exercising
13381 // every off-diagonal `(actual, expected)` pair across the
13382 // closed accept-set — the `KindMismatch { actual, expected }`
13383 // surfaced on the mismatch arm must byte-equal the pair the
13384 // accessor returns for each side.
13385 //
13386 // Peer of the sibling per-`:placement`
13387 // `validate_placement_reads_through_lifted_estrategia_accessor`
13388 // (921fe1b) two-arm consumer-coherence pin on the M3 mesh-slot
13389 // `Copy`-return discriminant axis — same "the entry-gate
13390 // predicate and the error carrier's `actual:` field must route
13391 // through the substrate-primitive typed dispatch" discipline
13392 // extended onto the outer top-level [`Caixa`] universal-axis
13393 // discriminant surface.
13394 for expected in [
13395 CaixaKind::Biblioteca,
13396 CaixaKind::Binario,
13397 CaixaKind::Servico,
13398 CaixaKind::Supervisor,
13399 CaixaKind::Aplicacao,
13400 ] {
13401 for actual in [
13402 CaixaKind::Biblioteca,
13403 CaixaKind::Binario,
13404 CaixaKind::Servico,
13405 CaixaKind::Supervisor,
13406 CaixaKind::Aplicacao,
13407 ] {
13408 let c = caixa_with_kind(actual);
13409 let result = crate::render::require_kind(&c, expected);
13410 if expected == actual {
13411 assert!(
13412 result.is_ok(),
13413 "require_kind must accept when actual == expected \
13414 (actual={actual:?}, expected={expected:?})",
13415 );
13416 } else {
13417 let err = result.expect_err("require_kind must reject when actual != expected");
13418 assert_eq!(
13419 err.actual,
13420 c.kind(),
13421 "KindMismatch.actual must byte-equal Caixa::kind() \
13422 — the error carrier's `actual:` field reads \
13423 through the lifted accessor",
13424 );
13425 assert_eq!(
13426 err.expected, expected,
13427 "KindMismatch.expected must byte-equal the \
13428 expected variant passed to require_kind",
13429 );
13430 }
13431 }
13432 }
13433 }
13434
13435 #[test]
13436 fn aplicacao_view_kind_gate_routes_through_accessor() {
13437 // Composition pin: [`Caixa::aplicacao_view`]'s kind-gate arm
13438 // must key off [`Caixa::kind`], not the raw `.kind` field
13439 // access. Structurally: a `Caixa { kind: X, .. }` for any
13440 // non-`Aplicacao` variant must fold to `None` on the
13441 // `aplicacao_view` composer (the "kind mismatch → no typed
13442 // view" contract every downstream Aplicacao consumer keys off
13443 // via `?`), and a `Caixa { kind: Aplicacao, .. }` must fold to
13444 // `Some(_)`. The pair jointly pins the accessor + view-gate
13445 // composition: any future silent detour that had the accessor
13446 // return a fresh [`CaixaKind::Aplicacao`] on some sentinel
13447 // input would silently absorb the kind-mismatch case at the
13448 // accessor boundary and every per-Aplicacao renderer would
13449 // silently render a non-Aplicacao caixa's mesh slots — the
13450 // composition pin catches that at caixa-core build time.
13451 //
13452 // Peer of the sibling per-`Caixa`
13453 // `validate_nome_empty_arm_routes_through_accessor` (e6b7d97) /
13454 // `validate_versao_empty_arm_routes_through_accessor` (20c0539)
13455 // composition pins on the sibling outer top-level [`Caixa`]
13456 // required-`&str` universal-axis surfaces — same "the
13457 // composer / validate gate must route through the substrate-
13458 // primitive typed dispatch" discipline extended onto the
13459 // outer top-level [`Caixa`] `Copy`-return required-
13460 // discriminant composition axis.
13461 for kind in [
13462 CaixaKind::Biblioteca,
13463 CaixaKind::Binario,
13464 CaixaKind::Servico,
13465 CaixaKind::Supervisor,
13466 ] {
13467 let c = caixa_with_kind(kind);
13468 assert!(
13469 c.aplicacao_view().is_none(),
13470 "aplicacao_view must return None on non-Aplicacao \
13471 kind {kind:?} — the composer's kind-gate must route \
13472 through Caixa::kind()",
13473 );
13474 }
13475 let c = caixa_with_kind(CaixaKind::Aplicacao);
13476 assert!(
13477 c.aplicacao_view().is_some(),
13478 "aplicacao_view must return Some on kind Aplicacao — \
13479 the composer's kind-gate must accept the matching arm \
13480 through Caixa::kind()",
13481 );
13482 }
13483
13484 #[test]
13485 fn supervisor_view_kind_gate_routes_through_accessor() {
13486 // Composition pin (mirror of the sibling
13487 // `aplicacao_view_kind_gate_routes_through_accessor` on the
13488 // second `_view` composer): [`Caixa::supervisor_view`]'s kind-
13489 // gate arm must key off [`Caixa::kind`], not the raw `.kind`
13490 // field access. A `Caixa { kind: X, .. }` for any non-
13491 // `Supervisor` variant must fold to `None` on the
13492 // `supervisor_view` composer, and a `Caixa { kind:
13493 // Supervisor, .. }` must fold to `Some(_)`. Same peer
13494 // composition pin discipline on the second `_view` composer
13495 // axis.
13496 for kind in [
13497 CaixaKind::Biblioteca,
13498 CaixaKind::Binario,
13499 CaixaKind::Servico,
13500 CaixaKind::Aplicacao,
13501 ] {
13502 let c = caixa_with_kind(kind);
13503 assert!(
13504 c.supervisor_view().is_none(),
13505 "supervisor_view must return None on non-Supervisor \
13506 kind {kind:?} — the composer's kind-gate must route \
13507 through Caixa::kind()",
13508 );
13509 }
13510 let mut c = caixa_with_kind(CaixaKind::Supervisor);
13511 // A Supervisor caixa needs a strategy + at least one child to
13512 // fold to a Some(_) that also validates; the composer itself
13513 // requires only the kind arm, so bare kind flip is enough to
13514 // pin the `Some(_)` return, but we populate the minimum
13515 // supervisor shape so a future strengthening of the composer
13516 // to reject an empty spec doesn't false-positive this pin.
13517 c.estrategia = Some(crate::supervisor::RestartStrategy::OneForOne);
13518 c.children = vec![crate::supervisor::ChildSpec {
13519 caixa: "child".into(),
13520 versao: "^0.1".into(),
13521 restart: crate::supervisor::RestartPolicy::Permanent,
13522 }];
13523 assert!(
13524 c.supervisor_view().is_some(),
13525 "supervisor_view must return Some on kind Supervisor — \
13526 the composer's kind-gate must accept the matching arm \
13527 through Caixa::kind()",
13528 );
13529 }
13530
13531 #[test]
13532 fn kind_projects_by_copy() {
13533 // The by-`Copy` pin: [`Caixa::kind`] returns a fresh
13534 // [`CaixaKind`] by `Copy` — the accessor must not borrow from
13535 // `&self` (the returned value is owned, `Copy`-projected from
13536 // the underlying [`CaixaKind`] storage; two calls on the same
13537 // [`Caixa`] must yield byte-equal values). Peer of the peer
13538 // per-`:placement` `Placement::estrategia` / per-`:supervisor`
13539 // `SupervisorSpec::estrategia` / per-`:children`
13540 // `ChildSpec::restart` `Copy`-return discriminant accessor
13541 // pins on the sibling nested-spec typed-slot discriminator
13542 // axes, extended onto the first outer top-level [`Caixa`]
13543 // required-`Copy`-return axis — pins against a future silent
13544 // detour that returned `&CaixaKind` (which would type-check
13545 // but silently constrain every consumer's callsite to a
13546 // borrow-shaped dispatch, breaking the zero-cost `Copy`
13547 // projection every peer sibling accessor carries).
13548 for kind in [
13549 CaixaKind::Biblioteca,
13550 CaixaKind::Binario,
13551 CaixaKind::Servico,
13552 CaixaKind::Supervisor,
13553 CaixaKind::Aplicacao,
13554 ] {
13555 let c = caixa_with_kind(kind);
13556 let first: CaixaKind = c.kind();
13557 let second: CaixaKind = c.kind();
13558 assert_eq!(
13559 first, second,
13560 "Caixa::kind must be idempotent — two successive \
13561 calls on the same &self must return the same \
13562 CaixaKind variant",
13563 );
13564 assert_eq!(
13565 first, kind,
13566 "Caixa::kind must return :kind verbatim by Copy — \
13567 got {first:?}, expected {kind:?}",
13568 );
13569 }
13570 }
13571
13572 // ── Caixa::autores — outer top-level &[T] slice accessor ──────────
13573
13574 #[test]
13575 fn autores_returns_autores_slice_verbatim_across_permutations() {
13576 // The canonical per-`Caixa` `:autores` universal-axis maintainer-
13577 // name-list slice pin: [`Caixa::autores`] must return the
13578 // `:autores` typed [`Vec<String>`] list verbatim as a
13579 // `&[String]`, byte-equal to the raw `self.autores.as_slice()`
13580 // access across every representative value in the accept-set —
13581 // `[]` (the "no maintainers declared" arm every existing
13582 // fixture without an `:autores` line carries), `[""]` (a past-
13583 // the-guard sentinel that pins the accessor doesn't perform a
13584 // silent `[""] → []` collapse on the empty-entry arm — validate
13585 // rejects `[""]` through `AutorEmpty` but the accessor must
13586 // ship the raw slot verbatim so a validate-time gate regression
13587 // surfaces at the caixa-helm emit boundary rather than being
13588 // silently absorbed into a maintainer-drop), `["pleme-io"]` (the
13589 // canonical single-maintainer form every `feira init` template
13590 // scaffolds), `["alice", "bob"]` (a canonical multi-maintainer
13591 // form), `["alice <alice@example.com>", "bob <bob@example.com>"]`
13592 // (the canonical RFC-5322 `<name> <email>` form the
13593 // `is_chart_maintainer_name_shape` predicate accepts), and
13594 // `["pleme-io", "pleme-io"]` (a past-the-guard duplicate
13595 // sentinel — validate rejects through `AutorDuplicate` but the
13596 // accessor must ship the raw slot verbatim).
13597 //
13598 // First outer top-level [`Caixa`] `&[T]`-return slice accessor
13599 // pin on the substrate primitive — opens the "outer [`Caixa`]
13600 // `&[T]` slice" projection pattern the sibling per-`Caixa`
13601 // `:etiquetas` / `:deps` / `:deps-dev` / `:exe` / `:bibliotecas`
13602 // / `:servicos` / `:upgrade-from` / `:children` future lifts
13603 // fold on. Sibling in shape to the peer per-`:supervisor`
13604 // [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
13605 // per-`:placement` [`crate::aplicacao::Placement::clusters`]
13606 // (a6e18d7), per-`:membros`
13607 // [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
13608 // per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
13609 // (0dcc926), and per-`:upgrade-from :instructions`
13610 // [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
13611 // `&[T]`-return slice accessor pins on the sibling per-M2 /
13612 // per-M3 typed-slot list axes, extended onto the outer top-
13613 // level [`Caixa`] universal-axis surface. Pins against a future
13614 // silent detour that returned an owned `Vec<String>` (which
13615 // would type-check but silently clone on every accessor call,
13616 // breaking the zero-cost projection every peer sibling slice
13617 // accessor carries), a `[""] → []` collapse (which would
13618 // silently absorb the `AutorEmpty` refusal case at the accessor
13619 // boundary), or a `["a", "a"] → ["a"]` dedup collapse (which
13620 // would silently absorb the `AutorDuplicate` refusal case at
13621 // the accessor boundary and the caixa-helm `maintainers:` fold
13622 // would silently render a dedupped list on a struct-literal
13623 // `Caixa { autores: vec!["a".into(), "a".into()], .. }`).
13624 for autores in [
13625 vec![],
13626 vec![""],
13627 vec!["pleme-io"],
13628 vec!["alice", "bob"],
13629 vec!["alice <alice@example.com>", "bob <bob@example.com>"],
13630 vec!["pleme-io", "pleme-io"],
13631 ] {
13632 let c = caixa_with_autores(autores.clone());
13633 let expected: Vec<String> = autores.iter().map(|s| (*s).to_string()).collect();
13634 assert_eq!(
13635 c.autores(),
13636 expected.as_slice(),
13637 "Caixa::autores must return :autores verbatim (got {:?}, \
13638 expected {expected:?})",
13639 c.autores(),
13640 );
13641 assert_eq!(
13642 c.autores(),
13643 c.autores.as_slice(),
13644 "Caixa::autores must byte-equal the raw \
13645 `self.autores.as_slice()` field access across every \
13646 value in the Vec<String> accept-set",
13647 );
13648 }
13649 }
13650
13651 #[test]
13652 fn validate_autores_empty_entry_arm_routes_through_accessor() {
13653 // Composition pin: [`Caixa::validate_autores`]'s per-entry
13654 // empty-arm gate must key off [`Caixa::autores`], not the raw
13655 // `&self.autores` field-borrow walk. Structurally: a
13656 // `Caixa { autores: vec!["".into()], .. }` must surface the
13657 // `AutorEmpty` refusal exactly, and a
13658 // `Caixa { autores: vec!["pleme-io".into()], .. }` (the
13659 // canonical single-maintainer form) must pass validate. The
13660 // pair jointly pins the accessor + validate-gate composition:
13661 // any future silent detour that had the accessor return an
13662 // empty slice on the `[""]` arm (a
13663 // `.iter().filter(|s| !s.is_empty()).collect()` collapse)
13664 // would silently absorb the `AutorEmpty` refusal at the
13665 // accessor boundary and the validate gate would accept a
13666 // struct-literal `Caixa { autores: vec!["".into()], .. }` —
13667 // the composition pin catches that at caixa-core build time.
13668 //
13669 // Peer of the per-`Caixa` [`Caixa::validate_licenca`] (6d5bc28)
13670 // accessor-composition pin
13671 // (`validate_licenca_empty_arm_routes_through_accessor`) on the
13672 // sibling `Option<&str>`-composition axis and the
13673 // per-`:politicas :circuit-breaker`
13674 // [`crate::aplicacao::CircuitBreaker::max_failures`] (3a74062)
13675 // accessor-composition pin
13676 // (`validate_politicas_max_failures_zero_floor_arm_routes_through_accessor`)
13677 // on the sibling required-`u32`-composition axis — same "the
13678 // validate / shape-gate predicate must route through the
13679 // substrate-primitive typed dispatch" discipline extended onto
13680 // the outer top-level [`Caixa`] universal-axis `&[T]`-
13681 // composition surface.
13682 let c = caixa_with_autores(vec![""]);
13683 assert!(
13684 matches!(c.validate_autores(), Err(ManifestError::AutorEmpty)),
13685 "validate_autores must reject autores == vec![\"\"] with \
13686 AutorEmpty — the accessor and the validate gate must \
13687 route through the same substrate-primitive typed dispatch \
13688 on the :autores per-entry empty arm",
13689 );
13690 let c = caixa_with_autores(vec!["pleme-io"]);
13691 assert!(
13692 c.validate_autores().is_ok(),
13693 "validate_autores must accept autores == vec![\"pleme-io\"] \
13694 (the canonical single-maintainer shape every `feira init` \
13695 template scaffolds)",
13696 );
13697 }
13698
13699 #[test]
13700 fn autores_projects_slice_by_borrow() {
13701 // The by-borrow pin: [`Caixa::autores`] returns `&[String]` by
13702 // borrow — the returned slice borrows the underlying
13703 // `Vec<String>` storage of the `:autores` slot and the
13704 // accessor must not clone the backing `Vec` on every call.
13705 // Peer of the per-`:membros`
13706 // [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36) /
13707 // per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
13708 // (0dcc926) / per-`:placement`
13709 // [`crate::aplicacao::Placement::clusters`] (a6e18d7) /
13710 // per-`:supervisor` [`crate::supervisor::SupervisorSpec::children`]
13711 // (bc92bce) by-borrow pins on the sibling per-M2 / per-M3
13712 // typed-slot `&[T]`-return axes, extended onto the outer top-
13713 // level [`Caixa`] universal-axis `&[String]` shape — the
13714 // accessor's returned slice must borrow from `&self` (the
13715 // returned reference's lifetime is tied to `&self`), and
13716 // calling the accessor twice on the same [`Caixa`] must yield
13717 // slices that are pointer-equal (the underlying byte-buffer is
13718 // the storage `Vec`'s allocation, not a fresh copy) as well as
13719 // value-equal (idempotent, no side effects on `&self`).
13720 //
13721 // Pins against a future silent detour that returned an owned
13722 // `Vec<String>` (which would type-check but silently clone on
13723 // every call, breaking the zero-cost projection every peer
13724 // sibling slice accessor carries), a `&Vec<String>` return
13725 // (which would leak the backing `Vec`'s grow/push/reserve
13726 // surface no downstream consumer reaches for), or a one-arm-
13727 // only accessor that returned a saturating value on some
13728 // sentinel input (breaking the pass-through invariant the
13729 // sibling slice accessors carry).
13730 for autores in [
13731 vec![],
13732 vec!["pleme-io"],
13733 vec!["alice", "bob"],
13734 vec!["pleme-io", "pleme-io"],
13735 ] {
13736 let c = caixa_with_autores(autores.clone());
13737 let expected: Vec<String> = autores.iter().map(|s| (*s).to_string()).collect();
13738 let first = c.autores();
13739 let second = c.autores();
13740 assert_eq!(
13741 first, second,
13742 "Caixa::autores must be idempotent — two successive \
13743 calls on the same &self must return the same \
13744 &[String]",
13745 );
13746 assert_eq!(
13747 first.as_ptr(),
13748 second.as_ptr(),
13749 "Caixa::autores must borrow the underlying Vec<String> \
13750 storage — two successive calls must return slices \
13751 with the same backing pointer (a fresh Vec<String> \
13752 clone would change the pointer on every call)",
13753 );
13754 assert_eq!(
13755 first,
13756 expected.as_slice(),
13757 "Caixa::autores must return :autores verbatim by \
13758 borrow — got {first:?}, expected {expected:?}",
13759 );
13760 }
13761 }
13762
13763 // ── Caixa::etiquetas — outer top-level &[T] slice accessor ────────
13764
13765 #[test]
13766 fn etiquetas_returns_etiquetas_slice_verbatim_across_permutations() {
13767 // The canonical per-`Caixa` `:etiquetas` universal-axis
13768 // registry-search-tag-list slice pin: [`Caixa::etiquetas`] must
13769 // return the `:etiquetas` typed [`Vec<String>`] list verbatim
13770 // as a `&[String]`, byte-equal to the raw
13771 // `self.etiquetas.as_slice()` access across every representative
13772 // value in the accept-set — `[]` (the "no tags declared" arm
13773 // every existing fixture without an `:etiquetas` line carries),
13774 // `[""]` (a past-the-guard sentinel that pins the accessor
13775 // doesn't perform a silent `[""] → []` collapse on the empty-
13776 // entry arm — validate rejects `[""]` through `EtiquetaEmpty`
13777 // but the accessor must ship the raw slot verbatim so a
13778 // validate-time gate regression surfaces at the caixa-helm emit
13779 // boundary rather than being silently absorbed into a keyword-
13780 // drop), `["demo"]` (the canonical single-tag form every
13781 // `feira init` template scaffolds), `["example", "aplicacao",
13782 // "mesh", "ecommerce", "demo"]` (the canonical multi-tag form
13783 // the checkout-aplicacao fixture emits), and `["demo", "demo"]`
13784 // (a past-the-guard duplicate sentinel — validate rejects
13785 // through `EtiquetaDuplicate` but the accessor must ship the
13786 // raw slot verbatim so the caixa-helm `BTreeSet::collect` dedup
13787 // at chart-render time isn't silently promoted into the
13788 // accessor boundary and struct-literal
13789 // `Caixa { etiquetas: vec!["demo".into(), "demo".into()], .. }`
13790 // fixtures continue to expose the duplicate at the accessor).
13791 //
13792 // Second outer top-level [`Caixa`] `&[T]`-return slice accessor
13793 // pin on the substrate primitive — folds on the "outer
13794 // [`Caixa`] `&[T]` slice" projection pattern
13795 // `autores_returns_autores_slice_verbatim_across_permutations`
13796 // (b5d813f) opened, sibling in shape and idiom. Pins against a
13797 // future silent detour that returned an owned `Vec<String>`
13798 // (which would type-check but silently clone on every accessor
13799 // call, breaking the zero-cost projection every peer sibling
13800 // slice accessor carries), a `[""] → []` collapse (which would
13801 // silently absorb the `EtiquetaEmpty` refusal case at the
13802 // accessor boundary), or a `["a", "a"] → ["a"]` dedup collapse
13803 // (which would silently absorb the `EtiquetaDuplicate` refusal
13804 // case at the accessor boundary — the caixa-helm chart-render
13805 // `BTreeSet::collect` dedup is downstream of the accessor and
13806 // must not be silently promoted into it).
13807 for etiquetas in [
13808 vec![],
13809 vec![""],
13810 vec!["demo"],
13811 vec!["example", "aplicacao", "mesh", "ecommerce", "demo"],
13812 vec!["demo", "demo"],
13813 ] {
13814 let c = caixa_with_etiquetas(etiquetas.clone());
13815 let expected: Vec<String> = etiquetas.iter().map(|s| (*s).to_string()).collect();
13816 assert_eq!(
13817 c.etiquetas(),
13818 expected.as_slice(),
13819 "Caixa::etiquetas must return :etiquetas verbatim (got \
13820 {:?}, expected {expected:?})",
13821 c.etiquetas(),
13822 );
13823 assert_eq!(
13824 c.etiquetas(),
13825 c.etiquetas.as_slice(),
13826 "Caixa::etiquetas must byte-equal the raw \
13827 `self.etiquetas.as_slice()` field access across every \
13828 value in the Vec<String> accept-set",
13829 );
13830 }
13831 }
13832
13833 #[test]
13834 fn validate_etiquetas_empty_entry_arm_routes_through_accessor() {
13835 // Composition pin: [`Caixa::validate_etiquetas`]'s per-entry
13836 // empty-arm gate must key off [`Caixa::etiquetas`], not the raw
13837 // `&self.etiquetas` field-borrow walk. Structurally: a
13838 // `Caixa { etiquetas: vec!["".into()], .. }` must surface the
13839 // `EtiquetaEmpty` refusal exactly, and a
13840 // `Caixa { etiquetas: vec!["demo".into()], .. }` (the canonical
13841 // single-tag form) must pass validate. The pair jointly pins
13842 // the accessor + validate-gate composition: any future silent
13843 // detour that had the accessor return an empty slice on the
13844 // `[""]` arm (a
13845 // `.iter().filter(|s| !s.is_empty()).collect()` collapse) would
13846 // silently absorb the `EtiquetaEmpty` refusal at the accessor
13847 // boundary and the validate gate would accept a struct-literal
13848 // `Caixa { etiquetas: vec!["".into()], .. }` — the composition
13849 // pin catches that at caixa-core build time.
13850 //
13851 // Peer of the per-`Caixa` `validate_autores_empty_arm_routes_
13852 // through_accessor` (b5d813f) accessor-composition pin on the
13853 // sibling `&[T]`-composition axis — same "the validate / shape-
13854 // gate predicate must route through the substrate-primitive
13855 // typed dispatch" discipline extended onto the sibling outer
13856 // top-level [`Caixa`] `&[T]`-composition surface.
13857 let c = caixa_with_etiquetas(vec![""]);
13858 assert!(
13859 matches!(c.validate_etiquetas(), Err(ManifestError::EtiquetaEmpty)),
13860 "validate_etiquetas must reject etiquetas == vec![\"\"] \
13861 with EtiquetaEmpty — the accessor and the validate gate \
13862 must route through the same substrate-primitive typed \
13863 dispatch on the :etiquetas per-entry empty arm",
13864 );
13865 let c = caixa_with_etiquetas(vec!["demo"]);
13866 assert!(
13867 c.validate_etiquetas().is_ok(),
13868 "validate_etiquetas must accept etiquetas == vec![\"demo\"] \
13869 (the canonical single-tag shape every `feira init` \
13870 template scaffolds)",
13871 );
13872 }
13873
13874 #[test]
13875 fn etiquetas_projects_slice_by_borrow() {
13876 // The by-borrow pin: [`Caixa::etiquetas`] returns `&[String]`
13877 // by borrow — the returned slice borrows the underlying
13878 // `Vec<String>` storage of the `:etiquetas` slot and the
13879 // accessor must not clone the backing `Vec` on every call.
13880 // Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
13881 // (b5d813f) by-borrow pin on the sibling outer top-level
13882 // [`Caixa`] `&[String]`-return axis — the accessor's returned
13883 // slice must borrow from `&self` (the returned reference's
13884 // lifetime is tied to `&self`), and calling the accessor twice
13885 // on the same [`Caixa`] must yield slices that are pointer-
13886 // equal (the underlying byte-buffer is the storage `Vec`'s
13887 // allocation, not a fresh copy) as well as value-equal
13888 // (idempotent, no side effects on `&self`).
13889 //
13890 // Pins against a future silent detour that returned an owned
13891 // `Vec<String>` (which would type-check but silently clone on
13892 // every call, breaking the zero-cost projection every peer
13893 // sibling slice accessor carries), a `&Vec<String>` return
13894 // (which would leak the backing `Vec`'s grow/push/reserve
13895 // surface no downstream consumer reaches for), or a one-arm-
13896 // only accessor that returned a saturating value on some
13897 // sentinel input (breaking the pass-through invariant the
13898 // sibling slice accessors carry).
13899 for etiquetas in [
13900 vec![],
13901 vec!["demo"],
13902 vec!["example", "aplicacao", "mesh"],
13903 vec!["demo", "demo"],
13904 ] {
13905 let c = caixa_with_etiquetas(etiquetas.clone());
13906 let expected: Vec<String> = etiquetas.iter().map(|s| (*s).to_string()).collect();
13907 let first = c.etiquetas();
13908 let second = c.etiquetas();
13909 assert_eq!(
13910 first, second,
13911 "Caixa::etiquetas must be idempotent — two successive \
13912 calls on the same &self must return the same \
13913 &[String]",
13914 );
13915 assert_eq!(
13916 first.as_ptr(),
13917 second.as_ptr(),
13918 "Caixa::etiquetas must borrow the underlying \
13919 Vec<String> storage — two successive calls must \
13920 return slices with the same backing pointer (a fresh \
13921 Vec<String> clone would change the pointer on every \
13922 call)",
13923 );
13924 assert_eq!(
13925 first,
13926 expected.as_slice(),
13927 "Caixa::etiquetas must return :etiquetas verbatim by \
13928 borrow — got {first:?}, expected {expected:?}",
13929 );
13930 }
13931 }
13932
13933 // ── Caixa::bibliotecas — outer top-level &[T] slice accessor ──────
13934
13935 #[test]
13936 fn bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations() {
13937 // The canonical per-`Caixa` `:bibliotecas` universal-axis
13938 // library-source-path-list slice pin: [`Caixa::bibliotecas`]
13939 // must return the `:bibliotecas` typed [`Vec<String>`] list
13940 // verbatim as a `&[String]`, byte-equal to the raw
13941 // `self.bibliotecas.as_slice()` access across every
13942 // representative value in the accept-set — `[]` (the "no
13943 // libraries declared" arm every `:kind` other than `Biblioteca`
13944 // + every `Biblioteca` relying on the canonical
13945 // `lib/<nome>.lisp` implicit-default path carries; the
13946 // layout's [`crate::LayoutInvariants`] `MissingLib` arm-gate
13947 // fires exactly on this empty-slot + `Biblioteca`-kind
13948 // combination), `[""]` (a past-the-guard sentinel that pins
13949 // the accessor doesn't perform a silent `[""] → []` collapse
13950 // on the empty-entry arm — validate rejects `[""]` through
13951 // `CodePathEmpty { slot: ":bibliotecas" }` but the accessor
13952 // must ship the raw slot verbatim so a validate-time gate
13953 // regression surfaces at the `feira build` phase-1 parse
13954 // boundary rather than being silently absorbed into a
13955 // library-drop), `["lib/demo.lisp"]` (the canonical single-
13956 // entry form `Caixa::template` scaffolds and every `feira init`
13957 // template emits), `["lib/demo.lisp", "lib/helpers.lisp"]`
13958 // (the canonical multi-library form the
13959 // `validate_code_paths_accepts_explicit_relative_paths_on_
13960 // every_slot` fixture emits), and `["lib/foo.lisp",
13961 // "lib/foo.lisp"]` (a past-the-guard duplicate sentinel —
13962 // validate rejects through `CodePathDuplicate { slot:
13963 // ":bibliotecas" }` per the per-slot set-not-multiset gate,
13964 // but the accessor must ship the raw slot verbatim so the
13965 // `feira build` `for entry in caixa.bibliotecas()` parse walk
13966 // sees the duplicate at the accessor boundary and struct-
13967 // literal `Caixa { bibliotecas: vec!["lib/foo.lisp".into(),
13968 // "lib/foo.lisp".into()], .. }` fixtures continue to expose
13969 // the duplicate at the accessor).
13970 //
13971 // Third outer top-level [`Caixa`] `&[T]`-return slice accessor
13972 // pin on the substrate primitive — folds on the "outer
13973 // [`Caixa`] `&[T]` slice" projection pattern
13974 // `autores_returns_autores_slice_verbatim_across_permutations`
13975 // (b5d813f) opened and
13976 // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
13977 // (78c7d3c) folded on, sibling in shape and idiom. Pins
13978 // against a future silent detour that returned an owned
13979 // `Vec<String>` (which would type-check but silently clone on
13980 // every accessor call, breaking the zero-cost projection
13981 // every peer sibling slice accessor carries), a `[""] → []`
13982 // collapse (which would silently absorb the `CodePathEmpty`
13983 // refusal case at the accessor boundary), or a `["lib/foo.lisp",
13984 // "lib/foo.lisp"] → ["lib/foo.lisp"]` dedup collapse (which
13985 // would silently absorb the `CodePathDuplicate` refusal case
13986 // at the accessor boundary — the per-slot set-not-multiset
13987 // gate is downstream of the accessor and must not be silently
13988 // promoted into it).
13989 for bibliotecas in [
13990 vec![],
13991 vec![""],
13992 vec!["lib/demo.lisp"],
13993 vec!["lib/demo.lisp", "lib/helpers.lisp"],
13994 vec!["lib/foo.lisp", "lib/foo.lisp"],
13995 ] {
13996 let c = caixa_with_code_paths(bibliotecas.clone(), vec![], vec![]);
13997 let expected: Vec<String> = bibliotecas.iter().map(|s| (*s).to_string()).collect();
13998 assert_eq!(
13999 c.bibliotecas(),
14000 expected.as_slice(),
14001 "Caixa::bibliotecas must return :bibliotecas verbatim \
14002 (got {:?}, expected {expected:?})",
14003 c.bibliotecas(),
14004 );
14005 assert_eq!(
14006 c.bibliotecas(),
14007 c.bibliotecas.as_slice(),
14008 "Caixa::bibliotecas must byte-equal the raw \
14009 `self.bibliotecas.as_slice()` field access across \
14010 every value in the Vec<String> accept-set",
14011 );
14012 }
14013 }
14014
14015 #[test]
14016 fn validate_code_paths_bibliotecas_empty_arm_routes_through_accessor() {
14017 // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
14018 // empty-arm gate on the `:bibliotecas` slot must key off
14019 // [`Caixa::bibliotecas`], not a divergent raw
14020 // `&self.bibliotecas` field-borrow walk. Structurally: a
14021 // `Caixa { bibliotecas: vec!["".into()], .. }` must surface
14022 // the `CodePathEmpty { slot: ":bibliotecas" }` refusal
14023 // exactly, and a `Caixa { bibliotecas: vec!["lib/demo.lisp".
14024 // into()], .. }` (the canonical single-library form
14025 // `Caixa::template` scaffolds) must pass validate. The pair
14026 // jointly pins the accessor + validate-gate composition: any
14027 // future silent detour that had the accessor return an empty
14028 // slice on the `[""]` arm (a `.iter().filter(|s|
14029 // !s.is_empty()).collect()` collapse) would silently absorb
14030 // the `CodePathEmpty` refusal at the accessor boundary and
14031 // the validate gate would accept a struct-literal
14032 // `Caixa { bibliotecas: vec!["".into()], .. }` — the
14033 // composition pin catches that at caixa-core build time.
14034 //
14035 // Peer of the per-`Caixa` `validate_autores_empty_arm_routes_
14036 // through_accessor` (b5d813f) and
14037 // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
14038 // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
14039 // composition axes — same "the validate / shape-gate
14040 // predicate must route through the substrate-primitive typed
14041 // dispatch" discipline extended onto the sibling outer top-
14042 // level [`Caixa`] `&[T]`-composition surface. Nominally the
14043 // in-tree `validate_code_paths` production body still keys
14044 // off the internal `[(":bibliotecas", &self.bibliotecas,
14045 // CodePathFileType::LispSource), (":exe", &self.exe, ..),
14046 // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
14047 // (the tuple's homogeneous slice-typed shape blocks a per-
14048 // element accessor swap in isolation — a future companion
14049 // lift for `:exe` and `:servicos` on the same outer-`Caixa`
14050 // `&[T]` slice-accessor axis closes that tuple onto the
14051 // triple of typed dispatches as a unit); the composition pin
14052 // catches any future accessor-side silent filter drop against
14053 // that eventual tuple-closure regardless of whether the
14054 // `:bibliotecas` slot is threaded through the accessor or the
14055 // raw field access at the tuple's construction site.
14056 let c = caixa_with_code_paths(vec![""], vec![], vec![]);
14057 assert!(
14058 matches!(
14059 c.validate_code_paths(),
14060 Err(ManifestError::CodePathEmpty {
14061 slot: ":bibliotecas"
14062 })
14063 ),
14064 "validate_code_paths must reject bibliotecas == vec![\"\"] \
14065 with CodePathEmpty {{ slot: \":bibliotecas\" }} — the \
14066 accessor and the validate gate must route through the \
14067 same substrate-primitive typed dispatch on the \
14068 :bibliotecas per-entry empty arm",
14069 );
14070 let c = caixa_with_code_paths(vec!["lib/demo.lisp"], vec![], vec![]);
14071 assert!(
14072 c.validate_code_paths().is_ok(),
14073 "validate_code_paths must accept bibliotecas == \
14074 vec![\"lib/demo.lisp\"] (the canonical single-library \
14075 shape every `feira init` template scaffolds)",
14076 );
14077 }
14078
14079 #[test]
14080 fn bibliotecas_projects_slice_by_borrow() {
14081 // The by-borrow pin: [`Caixa::bibliotecas`] returns
14082 // `&[String]` by borrow — the returned slice borrows the
14083 // underlying `Vec<String>` storage of the `:bibliotecas` slot
14084 // and the accessor must not clone the backing `Vec` on every
14085 // call. Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
14086 // (b5d813f) and `etiquetas_projects_slice_by_borrow` (78c7d3c)
14087 // by-borrow pins on the sibling outer top-level [`Caixa`]
14088 // `&[String]`-return axes — the accessor's returned slice
14089 // must borrow from `&self` (the returned reference's lifetime
14090 // is tied to `&self`), and calling the accessor twice on the
14091 // same [`Caixa`] must yield slices that are pointer-equal
14092 // (the underlying byte-buffer is the storage `Vec`'s
14093 // allocation, not a fresh copy) as well as value-equal
14094 // (idempotent, no side effects on `&self`).
14095 //
14096 // Pins against a future silent detour that returned an owned
14097 // `Vec<String>` (which would type-check but silently clone on
14098 // every call, breaking the zero-cost projection every peer
14099 // sibling slice accessor carries), a `&Vec<String>` return
14100 // (which would leak the backing `Vec`'s grow/push/reserve
14101 // surface no downstream consumer reaches for), or a one-arm-
14102 // only accessor that returned a saturating value on some
14103 // sentinel input (breaking the pass-through invariant the
14104 // sibling slice accessors carry).
14105 for bibliotecas in [
14106 vec![],
14107 vec!["lib/demo.lisp"],
14108 vec!["lib/demo.lisp", "lib/helpers.lisp"],
14109 vec!["lib/foo.lisp", "lib/foo.lisp"],
14110 ] {
14111 let c = caixa_with_code_paths(bibliotecas.clone(), vec![], vec![]);
14112 let expected: Vec<String> = bibliotecas.iter().map(|s| (*s).to_string()).collect();
14113 let first = c.bibliotecas();
14114 let second = c.bibliotecas();
14115 assert_eq!(
14116 first, second,
14117 "Caixa::bibliotecas must be idempotent — two \
14118 successive calls on the same &self must return the \
14119 same &[String]",
14120 );
14121 assert_eq!(
14122 first.as_ptr(),
14123 second.as_ptr(),
14124 "Caixa::bibliotecas must borrow the underlying \
14125 Vec<String> storage — two successive calls must \
14126 return slices with the same backing pointer (a \
14127 fresh Vec<String> clone would change the pointer on \
14128 every call)",
14129 );
14130 assert_eq!(
14131 first,
14132 expected.as_slice(),
14133 "Caixa::bibliotecas must return :bibliotecas verbatim \
14134 by borrow — got {first:?}, expected {expected:?}",
14135 );
14136 }
14137 }
14138
14139 // ── Caixa::exe — outer top-level &[T] slice accessor ──────────────
14140
14141 #[test]
14142 fn exe_returns_exe_slice_verbatim_across_permutations() {
14143 // The canonical per-`Caixa` `:exe` universal-axis
14144 // nix-built-executable-entry-path-list slice pin: [`Caixa::exe`]
14145 // must return the `:exe` typed [`Vec<String>`] list verbatim as
14146 // a `&[String]`, byte-equal to the raw `self.exe.as_slice()`
14147 // access across every representative value in the accept-set —
14148 // `[]` (the "no executable declared" arm every `:kind` other
14149 // than `Binario` carries; the layout's [`crate::LayoutInvariants`]
14150 // `BinarioWithoutExe` arm-gate fires exactly on this empty-slot
14151 // + `Binario`-kind combination), `[""]` (a past-the-guard
14152 // sentinel that pins the accessor doesn't perform a silent
14153 // `[""] → []` collapse on the empty-entry arm — validate rejects
14154 // `[""]` through `CodePathEmpty { slot: ":exe" }` but the
14155 // accessor must ship the raw slot verbatim so a validate-time
14156 // gate regression surfaces at the layout / `feira nix` boundary
14157 // rather than being silently absorbed into an executable-drop),
14158 // `["exe/cli"]` (the canonical single-entry Binario form every
14159 // in-tree `caixa_with_code_paths` positive control uses),
14160 // `["exe/cli", "exe/serve"]` (the canonical multi-executable
14161 // form the `validate_code_paths_accepts_explicit_relative_paths_
14162 // on_every_slot` fixture emits), and `["exe/cli", "exe/cli"]`
14163 // (a past-the-guard duplicate sentinel — validate rejects
14164 // through `CodePathDuplicate { slot: ":exe" }` per the per-slot
14165 // set-not-multiset gate, but the accessor must ship the raw
14166 // slot verbatim so struct-literal `Caixa { exe: vec!["exe/cli".
14167 // into(), "exe/cli".into()], .. }` fixtures continue to expose
14168 // the duplicate at the accessor).
14169 //
14170 // Fourth outer top-level [`Caixa`] `&[T]`-return slice accessor
14171 // pin on the substrate primitive — folds on the "outer
14172 // [`Caixa`] `&[T]` slice" projection pattern
14173 // `autores_returns_autores_slice_verbatim_across_permutations`
14174 // (b5d813f) opened,
14175 // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
14176 // (78c7d3c) folded on, and
14177 // `bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
14178 // (8a36c23) closed the universal-axis text-tag family of.
14179 // Opens the outer-`Caixa` foreign-code-slot `&[T]` sub-family
14180 // the sibling `:servicos` future lift closes onto. Pins against
14181 // a future silent detour that returned an owned `Vec<String>`
14182 // (which would type-check but silently clone on every accessor
14183 // call, breaking the zero-cost projection every peer sibling
14184 // slice accessor carries), a `[""] → []` collapse (which would
14185 // silently absorb the `CodePathEmpty` refusal case at the
14186 // accessor boundary), or an `["exe/cli", "exe/cli"] →
14187 // ["exe/cli"]` dedup collapse (which would silently absorb the
14188 // `CodePathDuplicate` refusal case at the accessor boundary —
14189 // the per-slot set-not-multiset gate is downstream of the
14190 // accessor and must not be silently promoted into it).
14191 for exe in [
14192 vec![],
14193 vec![""],
14194 vec!["exe/cli"],
14195 vec!["exe/cli", "exe/serve"],
14196 vec!["exe/cli", "exe/cli"],
14197 ] {
14198 let c = caixa_with_code_paths(vec![], exe.clone(), vec![]);
14199 let expected: Vec<String> = exe.iter().map(|s| (*s).to_string()).collect();
14200 assert_eq!(
14201 c.exe(),
14202 expected.as_slice(),
14203 "Caixa::exe must return :exe verbatim (got {:?}, \
14204 expected {expected:?})",
14205 c.exe(),
14206 );
14207 assert_eq!(
14208 c.exe(),
14209 c.exe.as_slice(),
14210 "Caixa::exe must byte-equal the raw \
14211 `self.exe.as_slice()` field access across every value \
14212 in the Vec<String> accept-set",
14213 );
14214 }
14215 }
14216
14217 #[test]
14218 fn validate_code_paths_exe_empty_arm_routes_through_accessor() {
14219 // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
14220 // empty-arm gate on the `:exe` slot must key off
14221 // [`Caixa::exe`], not a divergent raw `&self.exe` field-borrow
14222 // walk. Structurally: a `Caixa { exe: vec!["".into()], .. }`
14223 // must surface the `CodePathEmpty { slot: ":exe" }` refusal
14224 // exactly, and a `Caixa { exe: vec!["exe/cli".into()], .. }`
14225 // (the canonical single-executable form every in-tree
14226 // `caixa_with_code_paths` positive control uses) must pass
14227 // validate. The pair jointly pins the accessor + validate-gate
14228 // composition: any future silent detour that had the accessor
14229 // return an empty slice on the `[""]` arm (a
14230 // `.iter().filter(|s| !s.is_empty()).collect()` collapse) would
14231 // silently absorb the `CodePathEmpty` refusal at the accessor
14232 // boundary and the validate gate would accept a struct-literal
14233 // `Caixa { exe: vec!["".into()], .. }` — the composition pin
14234 // catches that at caixa-core build time.
14235 //
14236 // Peer of the per-`Caixa`
14237 // `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
14238 // (8a36c23), `validate_autores_empty_arm_routes_through_accessor`
14239 // (b5d813f), and
14240 // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
14241 // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
14242 // composition axes — same "the validate / shape-gate predicate
14243 // must route through the substrate-primitive typed dispatch"
14244 // discipline extended onto the sibling outer top-level [`Caixa`]
14245 // `&[T]`-composition surface. Nominally the in-tree
14246 // `validate_code_paths` production body still keys off the
14247 // internal `[(":bibliotecas", &self.bibliotecas,
14248 // CodePathFileType::LispSource), (":exe", &self.exe, ..),
14249 // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
14250 // (the tuple's homogeneous slice-typed shape blocks a per-
14251 // element accessor swap in isolation — a future companion lift
14252 // for `:servicos` on the same outer-`Caixa` `&[T]` slice-
14253 // accessor axis closes that tuple onto the triple of typed
14254 // dispatches as a unit); the composition pin catches any future
14255 // accessor-side silent filter drop against that eventual tuple-
14256 // closure regardless of whether the `:exe` slot is threaded
14257 // through the accessor or the raw field access at the tuple's
14258 // construction site.
14259 let c = caixa_with_code_paths(vec![], vec![""], vec![]);
14260 assert!(
14261 matches!(
14262 c.validate_code_paths(),
14263 Err(ManifestError::CodePathEmpty { slot: ":exe" })
14264 ),
14265 "validate_code_paths must reject exe == vec![\"\"] \
14266 with CodePathEmpty {{ slot: \":exe\" }} — the \
14267 accessor and the validate gate must route through the \
14268 same substrate-primitive typed dispatch on the \
14269 :exe per-entry empty arm",
14270 );
14271 let c = caixa_with_code_paths(vec![], vec!["exe/cli"], vec![]);
14272 assert!(
14273 c.validate_code_paths().is_ok(),
14274 "validate_code_paths must accept exe == vec![\"exe/cli\"] \
14275 (the canonical single-executable shape every in-tree \
14276 `caixa_with_code_paths` positive control uses)",
14277 );
14278 }
14279
14280 #[test]
14281 fn exe_projects_slice_by_borrow() {
14282 // The by-borrow pin: [`Caixa::exe`] returns `&[String]` by
14283 // borrow — the returned slice borrows the underlying
14284 // `Vec<String>` storage of the `:exe` slot and the accessor
14285 // must not clone the backing `Vec` on every call. Peer of the
14286 // per-`Caixa` `autores_projects_slice_by_borrow` (b5d813f),
14287 // `etiquetas_projects_slice_by_borrow` (78c7d3c), and
14288 // `bibliotecas_projects_slice_by_borrow` (8a36c23) by-borrow
14289 // pins on the sibling outer top-level [`Caixa`] `&[String]`-
14290 // return axes — the accessor's returned slice must borrow from
14291 // `&self` (the returned reference's lifetime is tied to
14292 // `&self`), and calling the accessor twice on the same
14293 // [`Caixa`] must yield slices that are pointer-equal (the
14294 // underlying byte-buffer is the storage `Vec`'s allocation,
14295 // not a fresh copy) as well as value-equal (idempotent, no
14296 // side effects on `&self`).
14297 //
14298 // Pins against a future silent detour that returned an owned
14299 // `Vec<String>` (which would type-check but silently clone on
14300 // every call, breaking the zero-cost projection every peer
14301 // sibling slice accessor carries), a `&Vec<String>` return
14302 // (which would leak the backing `Vec`'s grow/push/reserve
14303 // surface no downstream consumer reaches for), or a one-arm-
14304 // only accessor that returned a saturating value on some
14305 // sentinel input (breaking the pass-through invariant the
14306 // sibling slice accessors carry).
14307 for exe in [
14308 vec![],
14309 vec!["exe/cli"],
14310 vec!["exe/cli", "exe/serve"],
14311 vec!["exe/cli", "exe/cli"],
14312 ] {
14313 let c = caixa_with_code_paths(vec![], exe.clone(), vec![]);
14314 let expected: Vec<String> = exe.iter().map(|s| (*s).to_string()).collect();
14315 let first = c.exe();
14316 let second = c.exe();
14317 assert_eq!(
14318 first, second,
14319 "Caixa::exe must be idempotent — two successive calls \
14320 on the same &self must return the same &[String]",
14321 );
14322 assert_eq!(
14323 first.as_ptr(),
14324 second.as_ptr(),
14325 "Caixa::exe must borrow the underlying Vec<String> \
14326 storage — two successive calls must return slices \
14327 with the same backing pointer (a fresh Vec<String> \
14328 clone would change the pointer on every call)",
14329 );
14330 assert_eq!(
14331 first,
14332 expected.as_slice(),
14333 "Caixa::exe must return :exe verbatim by borrow — \
14334 got {first:?}, expected {expected:?}",
14335 );
14336 }
14337 }
14338
14339 // ── Caixa::servicos — outer top-level &[T] slice accessor ─────────
14340
14341 #[test]
14342 fn servicos_returns_servicos_slice_verbatim_across_permutations() {
14343 // The canonical per-`Caixa` `:servicos` universal-axis
14344 // ComputeUnit-CR-YAML-entry-path-list slice pin:
14345 // [`Caixa::servicos`] must return the `:servicos` typed
14346 // [`Vec<String>`] list verbatim as a `&[String]`, byte-equal to
14347 // the raw `self.servicos.as_slice()` access across every
14348 // representative value in the accept-set — `[]` (the "no
14349 // ComputeUnit-CR declared" arm every `:kind` other than
14350 // `Servico` carries; the layout's [`crate::LayoutInvariants`]
14351 // `ServicoWithoutServicos` arm-gate fires exactly on this
14352 // empty-slot + `Servico`-kind combination), `[""]` (a past-the-
14353 // guard sentinel that pins the accessor doesn't perform a
14354 // silent `[""] → []` collapse on the empty-entry arm — validate
14355 // rejects `[""]` through `CodePathEmpty { slot: ":servicos" }`
14356 // but the accessor must ship the raw slot verbatim so a
14357 // validate-time gate regression surfaces at the layout /
14358 // per-Servico renderer boundary rather than being silently
14359 // absorbed into a component-drop),
14360 // `["servicos/demo.computeunit.yaml"]` (the canonical
14361 // singleton V0-shape every in-tree `caixa_with_code_paths`
14362 // positive control uses; the same shape
14363 // [`crate::require_single_servico`] admits),
14364 // `["servicos/a.computeunit.yaml", "servicos/b.computeunit.
14365 // yaml"]` (a past-the-guard `len != 1` sentinel — the V0
14366 // singularity gate rejects through `ServicoCountMismatch
14367 // { count: 2 }` but the accessor must ship the raw slot
14368 // verbatim so struct-literal `Caixa { servicos: vec![...,
14369 // ...], .. }` fixtures continue to expose the count at the
14370 // accessor), and `["servicos/a.computeunit.yaml",
14371 // "servicos/a.computeunit.yaml"]` (a past-the-guard duplicate
14372 // sentinel — validate rejects through
14373 // `CodePathDuplicate { slot: ":servicos" }` per the per-slot
14374 // set-not-multiset gate, but the accessor must ship the raw
14375 // slot verbatim so struct-literal fixtures continue to expose
14376 // the duplicate at the accessor).
14377 //
14378 // Fifth and final outer top-level [`Caixa`] `&[T]`-return
14379 // slice accessor pin on the substrate primitive — folds on the
14380 // "outer [`Caixa`] `&[T]` slice" projection pattern
14381 // `autores_returns_autores_slice_verbatim_across_permutations`
14382 // (b5d813f) opened,
14383 // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
14384 // (78c7d3c) folded on,
14385 // `bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
14386 // (8a36c23) closed the universal-axis text-tag family of, and
14387 // `exe_returns_exe_slice_verbatim_across_permutations`
14388 // (65d9527) opened the foreign-code-slot sub-family of. Closes
14389 // the outer-`Caixa` foreign-code-slot `&[T]` sub-family — the
14390 // trio of code-surface list slots (`:bibliotecas` + `:exe` +
14391 // `:servicos`) now each carries a substrate-canonical slice
14392 // accessor. Pins against a future silent detour that returned
14393 // an owned `Vec<String>` (which would type-check but silently
14394 // clone on every accessor call, breaking the zero-cost
14395 // projection every peer sibling slice accessor carries), a
14396 // `[""] → []` collapse (which would silently absorb the
14397 // `CodePathEmpty` refusal case at the accessor boundary), an
14398 // `[a, a] → [a]` dedup collapse (which would silently absorb
14399 // the `CodePathDuplicate` refusal case at the accessor
14400 // boundary — the per-slot set-not-multiset gate is downstream
14401 // of the accessor and must not be silently promoted into it),
14402 // or a `[a, b] → [a]` singleton collapse (which would silently
14403 // absorb the V0 `ServicoCountMismatch` refusal case at the
14404 // accessor boundary — the V0 singularity gate is downstream of
14405 // the accessor and must not be silently promoted into it).
14406 for servicos in [
14407 vec![],
14408 vec![""],
14409 vec!["servicos/demo.computeunit.yaml"],
14410 vec!["servicos/a.computeunit.yaml", "servicos/b.computeunit.yaml"],
14411 vec!["servicos/a.computeunit.yaml", "servicos/a.computeunit.yaml"],
14412 ] {
14413 let c = caixa_with_code_paths(vec![], vec![], servicos.clone());
14414 let expected: Vec<String> = servicos.iter().map(|s| (*s).to_string()).collect();
14415 assert_eq!(
14416 c.servicos(),
14417 expected.as_slice(),
14418 "Caixa::servicos must return :servicos verbatim (got \
14419 {:?}, expected {expected:?})",
14420 c.servicos(),
14421 );
14422 assert_eq!(
14423 c.servicos(),
14424 c.servicos.as_slice(),
14425 "Caixa::servicos must byte-equal the raw \
14426 `self.servicos.as_slice()` field access across every \
14427 value in the Vec<String> accept-set",
14428 );
14429 }
14430 }
14431
14432 #[test]
14433 fn validate_code_paths_servicos_empty_arm_routes_through_accessor() {
14434 // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
14435 // empty-arm gate on the `:servicos` slot must key off
14436 // [`Caixa::servicos`], not a divergent raw `&self.servicos`
14437 // field-borrow walk. Structurally: a `Caixa { servicos:
14438 // vec!["".into()], .. }` must surface the `CodePathEmpty
14439 // { slot: ":servicos" }` refusal exactly, and a `Caixa
14440 // { servicos: vec!["servicos/demo.computeunit.yaml".into()],
14441 // .. }` (the canonical singleton V0-shape every in-tree
14442 // `caixa_with_code_paths` positive control uses) must pass
14443 // validate. The pair jointly pins the accessor + validate-gate
14444 // composition: any future silent detour that had the accessor
14445 // return an empty slice on the `[""]` arm (a `.iter().filter
14446 // (|s| !s.is_empty()).collect()` collapse) would silently
14447 // absorb the `CodePathEmpty` refusal at the accessor boundary
14448 // and the validate gate would accept a struct-literal
14449 // `Caixa { servicos: vec!["".into()], .. }` — the composition
14450 // pin catches that at caixa-core build time.
14451 //
14452 // Peer of the per-`Caixa`
14453 // `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
14454 // (8a36c23), `validate_code_paths_exe_empty_arm_routes_through_accessor`
14455 // (65d9527), `validate_autores_empty_arm_routes_through_accessor`
14456 // (b5d813f), and
14457 // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
14458 // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
14459 // composition axes — same "the validate / shape-gate predicate
14460 // must route through the substrate-primitive typed dispatch"
14461 // discipline extended onto the sibling outer top-level
14462 // [`Caixa`] `&[T]`-composition surface, closing the trio of
14463 // code-surface accessor-composition pins on the same axis.
14464 // Nominally the in-tree `validate_code_paths` production body
14465 // still keys off the internal
14466 // `[(":bibliotecas", &self.bibliotecas,
14467 // CodePathFileType::LispSource), (":exe", &self.exe, ..),
14468 // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
14469 // (the tuple's homogeneous `&Vec<String>`-typed shape blocks a
14470 // per-element accessor swap in isolation — a future companion
14471 // lift promotes the tuple's element type to `&[String]` and
14472 // threads the triple of typed dispatches through as a unit);
14473 // the composition pin catches any future accessor-side silent
14474 // filter drop against that eventual tuple-closure regardless
14475 // of whether the `:servicos` slot is threaded through the
14476 // accessor or the raw field access at the tuple's construction
14477 // site.
14478 let c = caixa_with_code_paths(vec![], vec![], vec![""]);
14479 assert!(
14480 matches!(
14481 c.validate_code_paths(),
14482 Err(ManifestError::CodePathEmpty { slot: ":servicos" })
14483 ),
14484 "validate_code_paths must reject servicos == vec![\"\"] \
14485 with CodePathEmpty {{ slot: \":servicos\" }} — the \
14486 accessor and the validate gate must route through the \
14487 same substrate-primitive typed dispatch on the \
14488 :servicos per-entry empty arm",
14489 );
14490 let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/demo.computeunit.yaml"]);
14491 assert!(
14492 c.validate_code_paths().is_ok(),
14493 "validate_code_paths must accept servicos == \
14494 vec![\"servicos/demo.computeunit.yaml\"] (the canonical \
14495 singleton V0-shape every in-tree `caixa_with_code_paths` \
14496 positive control uses)",
14497 );
14498 }
14499
14500 #[test]
14501 fn servicos_projects_slice_by_borrow() {
14502 // The by-borrow pin: [`Caixa::servicos`] returns `&[String]` by
14503 // borrow — the returned slice borrows the underlying
14504 // `Vec<String>` storage of the `:servicos` slot and the
14505 // accessor must not clone the backing `Vec` on every call.
14506 // Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
14507 // (b5d813f), `etiquetas_projects_slice_by_borrow` (78c7d3c),
14508 // `bibliotecas_projects_slice_by_borrow` (8a36c23), and
14509 // `exe_projects_slice_by_borrow` (65d9527) by-borrow pins on
14510 // the sibling outer top-level [`Caixa`] `&[String]`-return
14511 // axes — the accessor's returned slice must borrow from
14512 // `&self` (the returned reference's lifetime is tied to
14513 // `&self`), and calling the accessor twice on the same
14514 // [`Caixa`] must yield slices that are pointer-equal (the
14515 // underlying byte-buffer is the storage `Vec`'s allocation,
14516 // not a fresh copy) as well as value-equal (idempotent, no
14517 // side effects on `&self`).
14518 //
14519 // Pins against a future silent detour that returned an owned
14520 // `Vec<String>` (which would type-check but silently clone on
14521 // every call, breaking the zero-cost projection every peer
14522 // sibling slice accessor carries), a `&Vec<String>` return
14523 // (which would leak the backing `Vec`'s grow/push/reserve
14524 // surface no downstream consumer reaches for), or a one-arm-
14525 // only accessor that returned a saturating value on some
14526 // sentinel input (breaking the pass-through invariant the
14527 // sibling slice accessors carry).
14528 for servicos in [
14529 vec![],
14530 vec!["servicos/demo.computeunit.yaml"],
14531 vec!["servicos/a.computeunit.yaml", "servicos/b.computeunit.yaml"],
14532 vec!["servicos/a.computeunit.yaml", "servicos/a.computeunit.yaml"],
14533 ] {
14534 let c = caixa_with_code_paths(vec![], vec![], servicos.clone());
14535 let expected: Vec<String> = servicos.iter().map(|s| (*s).to_string()).collect();
14536 let first = c.servicos();
14537 let second = c.servicos();
14538 assert_eq!(
14539 first, second,
14540 "Caixa::servicos must be idempotent — two successive \
14541 calls on the same &self must return the same &[String]",
14542 );
14543 assert_eq!(
14544 first.as_ptr(),
14545 second.as_ptr(),
14546 "Caixa::servicos must borrow the underlying \
14547 Vec<String> storage — two successive calls must \
14548 return slices with the same backing pointer (a fresh \
14549 Vec<String> clone would change the pointer on every \
14550 call)",
14551 );
14552 assert_eq!(
14553 first,
14554 expected.as_slice(),
14555 "Caixa::servicos must return :servicos verbatim by \
14556 borrow — got {first:?}, expected {expected:?}",
14557 );
14558 }
14559 }
14560
14561 // ── Caixa::deps — outer top-level &[Dep] slice accessor ───────────
14562
14563 fn caixa_with_deps(deps: Vec<Dep>) -> Caixa {
14564 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
14565 c.deps = deps;
14566 c
14567 }
14568
14569 #[test]
14570 fn deps_returns_deps_slice_verbatim_across_permutations() {
14571 // The canonical per-`Caixa` `:deps` universal-axis runtime-
14572 // dependency-declaration-list slice pin: [`Caixa::deps`] must
14573 // return the `:deps` typed [`Vec<Dep>`] list verbatim as a
14574 // `&[Dep]`, element-equal to the raw `self.deps.as_slice()`
14575 // access across every representative value in the accept-set —
14576 // `[]` (the "no runtime deps declared" arm every existing
14577 // fixture without a `:deps` line carries; the
14578 // [`Caixa::template`] scaffold emits `:deps ()`), a canonical
14579 // single-entry list (the shape most consumer caixas carry), a
14580 // canonical two-entry list (the multi-dep runtime closure), and
14581 // two past-the-guard sentinels — a `[""]`-`:nome` entry
14582 // ([`Self::validate_deps`] rejects through `NomeEmpty` /
14583 // `NomeInvalid` but the accessor must ship the raw slot
14584 // verbatim) and a `[a, a]` duplicate (validate rejects through
14585 // `DuplicateNome { list: ":deps" }` but the accessor must ship
14586 // the raw slot verbatim so struct-literal fixtures continue to
14587 // expose the duplicate at the accessor).
14588 //
14589 // First outer top-level [`Caixa`] `&[Dep]`-return slice accessor
14590 // pin on the substrate primitive — opens the outer-`Caixa`
14591 // dependency-slot `&[Dep]` sub-family the sibling `:deps-dev`
14592 // future lift closes on. Peer of the closed outer-`Caixa`
14593 // foreign-code-slot `&[String]` sub-family
14594 // (`bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
14595 // 8a36c23, `exe_returns_exe_slice_verbatim_across_permutations`
14596 // 65d9527, `servicos_returns_servicos_slice_verbatim_across_permutations`
14597 // 611f78b) and the outer-`Caixa` universal-axis text-tag family
14598 // (`autores_returns_autores_slice_verbatim_across_permutations`
14599 // b5d813f, `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
14600 // 78c7d3c) — extends the "outer [`Caixa`] `&[T]` slice"
14601 // projection pattern onto a novel element-type axis (`Dep`
14602 // composite vs the prior sibling family's `String` scalar).
14603 // Pins against a future silent detour that returned an owned
14604 // `Vec<Dep>` (which would type-check but silently clone on every
14605 // accessor call, breaking the zero-cost projection every peer
14606 // sibling slice accessor carries), a `[""] → []` collapse (which
14607 // would silently absorb the `NomeEmpty` refusal case at the
14608 // accessor boundary), or a `[a, a] → [a]` dedup collapse (which
14609 // would silently absorb the `DuplicateNome` refusal case at the
14610 // accessor boundary).
14611 for deps in [
14612 vec![],
14613 vec![Dep::simple("", "^0.1")],
14614 vec![Dep::simple("caixa-teia", "^0.1")],
14615 vec![
14616 Dep::simple("caixa-teia", "^0.1"),
14617 Dep::simple("caixa-core", "^0.1"),
14618 ],
14619 vec![
14620 Dep::simple("caixa-teia", "^0.1"),
14621 Dep::simple("caixa-teia", "^0.2"),
14622 ],
14623 ] {
14624 let c = caixa_with_deps(deps.clone());
14625 assert_eq!(
14626 c.deps(),
14627 deps.as_slice(),
14628 "Caixa::deps must return :deps verbatim (got {:?}, \
14629 expected {deps:?})",
14630 c.deps(),
14631 );
14632 assert_eq!(
14633 c.deps(),
14634 c.deps.as_slice(),
14635 "Caixa::deps must element-equal the raw \
14636 `self.deps.as_slice()` field access across every \
14637 value in the Vec<Dep> accept-set",
14638 );
14639 }
14640 }
14641
14642 #[test]
14643 fn validate_deps_duplicate_arm_routes_through_accessor() {
14644 // Composition pin: [`Caixa::validate_deps`]'s within-`:deps`
14645 // duplicate-`:nome` gate must key off [`Caixa::deps`], not the
14646 // raw `&self.deps` field-borrow walk. Structurally: a `Caixa
14647 // { deps: vec![Dep::simple("d", "^0.1"), Dep::simple("d",
14648 // "^0.2")], .. }` must surface the `DuplicateNome { list:
14649 // ":deps" }` refusal exactly, and a `Caixa { deps: vec![
14650 // Dep::simple("d", "^0.1")], .. }` (the canonical single-entry
14651 // form) must pass validate. The pair jointly pins the accessor +
14652 // validate-gate composition: any future silent detour that had
14653 // the accessor return a dedupped slice on the `[a, a]` arm (a
14654 // `.iter().unique_by(|d| d.nome.as_str()).collect()` collapse)
14655 // would silently absorb the `DuplicateNome` refusal at the
14656 // accessor boundary and the validate gate would accept a
14657 // struct-literal `Caixa` carrying the drift — the composition
14658 // pin catches that at caixa-core build time.
14659 //
14660 // Peer of the per-`Caixa`
14661 // `validate_autores_empty_entry_arm_routes_through_accessor`
14662 // (b5d813f), `validate_etiquetas_empty_entry_arm_routes_through_accessor`
14663 // (78c7d3c), `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
14664 // (8a36c23), `validate_code_paths_exe_empty_arm_routes_through_accessor`
14665 // (65d9527), and `validate_code_paths_servicos_empty_arm_routes_through_accessor`
14666 // (611f78b) accessor-composition pins on the sibling `&[T]`-
14667 // composition axes — same "the validate gate must route through
14668 // the substrate-primitive typed dispatch" discipline extended
14669 // onto the sibling outer top-level [`Caixa`] `&[Dep]`-
14670 // composition surface, opening the outer-`Caixa` dependency-slot
14671 // arm of the composition-pin family.
14672 let c = caixa_with_deps(vec![Dep::simple("d", "^0.1"), Dep::simple("d", "^0.2")]);
14673 let err = c.validate_deps().unwrap_err();
14674 assert!(
14675 matches!(
14676 err,
14677 DepError::DuplicateNome { ref nome, list } if nome == "d"
14678 && list == crate::render::DEP_AUTHOR_KEY_DEPS
14679 ),
14680 "validate_deps must reject deps == \
14681 vec![Dep(\"d\",\"^0.1\"), Dep(\"d\",\"^0.2\")] with \
14682 DuplicateNome {{ nome: \"d\", list: \":deps\" }} — the \
14683 accessor and the validate gate must route through the \
14684 same substrate-primitive typed dispatch on the :deps \
14685 within-list duplicate arm (got {err:?})",
14686 );
14687 let c = caixa_with_deps(vec![Dep::simple("d", "^0.1")]);
14688 assert!(
14689 c.validate_deps().is_ok(),
14690 "validate_deps must accept deps == vec![Dep(\"d\",\"^0.1\")] \
14691 (the canonical single-entry form)",
14692 );
14693 }
14694
14695 #[test]
14696 fn deps_projects_slice_by_borrow() {
14697 // The by-borrow pin: [`Caixa::deps`] returns `&[Dep]` by borrow
14698 // — the returned slice borrows the underlying `Vec<Dep>` storage
14699 // of the `:deps` slot and the accessor must not clone the
14700 // backing `Vec` on every call. Peer of the per-`Caixa`
14701 // `autores_projects_slice_by_borrow` (b5d813f),
14702 // `etiquetas_projects_slice_by_borrow` (78c7d3c),
14703 // `bibliotecas_projects_slice_by_borrow` (8a36c23),
14704 // `exe_projects_slice_by_borrow` (65d9527), and
14705 // `servicos_projects_slice_by_borrow` (611f78b) by-borrow pins
14706 // on the sibling outer top-level [`Caixa`] `&[String]`-return
14707 // axes — the accessor's returned slice must borrow from `&self`
14708 // (the returned reference's lifetime is tied to `&self`), and
14709 // calling the accessor twice on the same [`Caixa`] must yield
14710 // slices that are pointer-equal (the underlying byte-buffer is
14711 // the storage `Vec`'s allocation, not a fresh copy) as well as
14712 // value-equal (idempotent, no side effects on `&self`).
14713 //
14714 // Pins against a future silent detour that returned an owned
14715 // `Vec<Dep>` (which would type-check but silently clone on
14716 // every call), a `&Vec<Dep>` return (which would leak the
14717 // backing `Vec`'s grow/push/reserve surface no downstream
14718 // consumer reaches for), or a one-arm-only accessor that
14719 // returned a saturating value on some sentinel input.
14720 for deps in [
14721 vec![],
14722 vec![Dep::simple("caixa-teia", "^0.1")],
14723 vec![
14724 Dep::simple("caixa-teia", "^0.1"),
14725 Dep::simple("caixa-core", "^0.1"),
14726 ],
14727 ] {
14728 let c = caixa_with_deps(deps.clone());
14729 let first = c.deps();
14730 let second = c.deps();
14731 assert_eq!(
14732 first, second,
14733 "Caixa::deps must be idempotent — two successive calls \
14734 on the same &self must return the same &[Dep]",
14735 );
14736 assert_eq!(
14737 first.as_ptr(),
14738 second.as_ptr(),
14739 "Caixa::deps must borrow the underlying Vec<Dep> \
14740 storage — two successive calls must return slices \
14741 with the same backing pointer (a fresh Vec<Dep> clone \
14742 would change the pointer on every call)",
14743 );
14744 assert_eq!(
14745 first,
14746 deps.as_slice(),
14747 "Caixa::deps must return :deps verbatim by borrow — \
14748 got {first:?}, expected {deps:?}",
14749 );
14750 }
14751 }
14752
14753 // ── Caixa::deps_dev — outer top-level &[Dep] slice accessor ──────
14754
14755 fn caixa_with_deps_dev(deps_dev: Vec<Dep>) -> Caixa {
14756 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
14757 c.deps_dev = deps_dev;
14758 c
14759 }
14760
14761 #[test]
14762 fn deps_dev_returns_deps_dev_slice_verbatim_across_permutations() {
14763 // The canonical per-`Caixa` `:deps-dev` universal-axis dev-only-
14764 // dependency-declaration-list slice pin: [`Caixa::deps_dev`]
14765 // must return the `:deps-dev` typed [`Vec<Dep>`] list verbatim as
14766 // a `&[Dep]`, element-equal to the raw `self.deps_dev.as_slice()`
14767 // access across every representative value in the accept-set —
14768 // `[]` (the "no dev deps declared" arm every existing fixture
14769 // without a `:deps-dev` line carries; the [`Caixa::template`]
14770 // scaffold emits `:deps-dev ()`), a canonical single-entry list
14771 // (the shape most consumer caixas carry — a `tatara-check` dev
14772 // pin), a canonical two-entry list (the multi-dev-dep closure),
14773 // and two past-the-guard sentinels — a `[""]`-`:nome` entry
14774 // ([`Self::validate_deps`] rejects through `NomeEmpty` /
14775 // `NomeInvalid` but the accessor must ship the raw slot
14776 // verbatim) and a `[a, a]` duplicate (validate rejects through
14777 // `DuplicateNome { list: ":deps-dev" }` but the accessor must
14778 // ship the raw slot verbatim so struct-literal fixtures continue
14779 // to expose the duplicate at the accessor).
14780 //
14781 // Second outer top-level [`Caixa`] `&[Dep]`-return slice-accessor
14782 // pin on the substrate primitive — closes the outer-`Caixa`
14783 // dependency-slot `&[Dep]` sub-family the sibling
14784 // `deps_returns_deps_slice_verbatim_across_permutations`
14785 // (ad34b4e) opened on. Folds the "outer [`Caixa`] `&[Dep]`
14786 // slice" projection pattern onto the sibling dev-dep axis —
14787 // pins against a future silent detour that returned an owned
14788 // `Vec<Dep>` (which would type-check but silently clone on every
14789 // accessor call, breaking the zero-cost projection every peer
14790 // sibling slice accessor carries), a `[""] → []` collapse (which
14791 // would silently absorb the `NomeEmpty` refusal case at the
14792 // accessor boundary), or a `[a, a] → [a]` dedup collapse (which
14793 // would silently absorb the `DuplicateNome` refusal case at the
14794 // accessor boundary).
14795 for deps_dev in [
14796 vec![],
14797 vec![Dep::simple("", "^0.1")],
14798 vec![Dep::simple("tatara-check", "^0.1")],
14799 vec![
14800 Dep::simple("tatara-check", "^0.1"),
14801 Dep::simple("caixa-lint", "^0.1"),
14802 ],
14803 vec![
14804 Dep::simple("tatara-check", "^0.1"),
14805 Dep::simple("tatara-check", "^0.2"),
14806 ],
14807 ] {
14808 let c = caixa_with_deps_dev(deps_dev.clone());
14809 assert_eq!(
14810 c.deps_dev(),
14811 deps_dev.as_slice(),
14812 "Caixa::deps_dev must return :deps-dev verbatim (got \
14813 {:?}, expected {deps_dev:?})",
14814 c.deps_dev(),
14815 );
14816 assert_eq!(
14817 c.deps_dev(),
14818 c.deps_dev.as_slice(),
14819 "Caixa::deps_dev must element-equal the raw \
14820 `self.deps_dev.as_slice()` field access across every \
14821 value in the Vec<Dep> accept-set",
14822 );
14823 }
14824 }
14825
14826 #[test]
14827 fn validate_deps_duplicate_deps_dev_arm_routes_through_accessor() {
14828 // Composition pin: [`Caixa::validate_deps`]'s within-`:deps-dev`
14829 // duplicate-`:nome` gate must key off [`Caixa::deps_dev`], not
14830 // the raw `&self.deps_dev` field-borrow walk. Structurally: a
14831 // `Caixa { deps_dev: vec![Dep::simple("d", "^0.1"),
14832 // Dep::simple("d", "^0.2")], .. }` must surface the
14833 // `DuplicateNome { list: ":deps-dev" }` refusal exactly, and a
14834 // `Caixa { deps_dev: vec![Dep::simple("d", "^0.1")], .. }` (the
14835 // canonical single-entry form) must pass validate. The pair
14836 // jointly pins the accessor + validate-gate composition: any
14837 // future silent detour that had the accessor return a dedupped
14838 // slice on the `[a, a]` arm (a
14839 // `.iter().unique_by(|d| d.nome.as_str()).collect()` collapse)
14840 // would silently absorb the `DuplicateNome` refusal at the
14841 // accessor boundary and the validate gate would accept a
14842 // struct-literal `Caixa` carrying the drift — the composition
14843 // pin catches that at caixa-core build time.
14844 //
14845 // Peer of `validate_deps_duplicate_arm_routes_through_accessor`
14846 // (ad34b4e) on the sibling `:deps` axis — same "the validate
14847 // gate must route through the substrate-primitive typed
14848 // dispatch" discipline folded onto the sibling `:deps-dev`
14849 // axis, closing the two-list dep-graph composition-pin family.
14850 // The `:deps-dev` diagnostic must carry the
14851 // `DEP_AUTHOR_KEY_DEPS_DEV` list-tag (not
14852 // `DEP_AUTHOR_KEY_DEPS`) so the emitted error names the
14853 // offending list unambiguously.
14854 let c = caixa_with_deps_dev(vec![Dep::simple("d", "^0.1"), Dep::simple("d", "^0.2")]);
14855 let err = c.validate_deps().unwrap_err();
14856 assert!(
14857 matches!(
14858 err,
14859 DepError::DuplicateNome { ref nome, list } if nome == "d"
14860 && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
14861 ),
14862 "validate_deps must reject deps_dev == \
14863 vec![Dep(\"d\",\"^0.1\"), Dep(\"d\",\"^0.2\")] with \
14864 DuplicateNome {{ nome: \"d\", list: \":deps-dev\" }} — the \
14865 accessor and the validate gate must route through the \
14866 same substrate-primitive typed dispatch on the :deps-dev \
14867 within-list duplicate arm (got {err:?})",
14868 );
14869 let c = caixa_with_deps_dev(vec![Dep::simple("d", "^0.1")]);
14870 assert!(
14871 c.validate_deps().is_ok(),
14872 "validate_deps must accept deps_dev == \
14873 vec![Dep(\"d\",\"^0.1\")] (the canonical single-entry form)",
14874 );
14875 }
14876
14877 #[test]
14878 fn deps_dev_projects_slice_by_borrow() {
14879 // The by-borrow pin: [`Caixa::deps_dev`] returns `&[Dep]` by
14880 // borrow — the returned slice borrows the underlying `Vec<Dep>`
14881 // storage of the `:deps-dev` slot and the accessor must not
14882 // clone the backing `Vec` on every call. Peer of
14883 // `deps_projects_slice_by_borrow` (ad34b4e) on the sibling
14884 // `:deps` axis, and of the per-`Caixa`
14885 // `autores_projects_slice_by_borrow` (b5d813f),
14886 // `etiquetas_projects_slice_by_borrow` (78c7d3c),
14887 // `bibliotecas_projects_slice_by_borrow` (8a36c23),
14888 // `exe_projects_slice_by_borrow` (65d9527), and
14889 // `servicos_projects_slice_by_borrow` (611f78b) by-borrow pins
14890 // on the sibling outer top-level [`Caixa`] `&[String]`-return
14891 // axes — the accessor's returned slice must borrow from `&self`
14892 // (the returned reference's lifetime is tied to `&self`), and
14893 // calling the accessor twice on the same [`Caixa`] must yield
14894 // slices that are pointer-equal (the underlying byte-buffer is
14895 // the storage `Vec`'s allocation, not a fresh copy) as well as
14896 // value-equal (idempotent, no side effects on `&self`).
14897 //
14898 // Pins against a future silent detour that returned an owned
14899 // `Vec<Dep>` (which would type-check but silently clone on
14900 // every call), a `&Vec<Dep>` return (which would leak the
14901 // backing `Vec`'s grow/push/reserve surface no downstream
14902 // consumer reaches for), or a one-arm-only accessor that
14903 // returned a saturating value on some sentinel input.
14904 for deps_dev in [
14905 vec![],
14906 vec![Dep::simple("tatara-check", "^0.1")],
14907 vec![
14908 Dep::simple("tatara-check", "^0.1"),
14909 Dep::simple("caixa-lint", "^0.1"),
14910 ],
14911 ] {
14912 let c = caixa_with_deps_dev(deps_dev.clone());
14913 let first = c.deps_dev();
14914 let second = c.deps_dev();
14915 assert_eq!(
14916 first, second,
14917 "Caixa::deps_dev must be idempotent — two successive \
14918 calls on the same &self must return the same &[Dep]",
14919 );
14920 assert_eq!(
14921 first.as_ptr(),
14922 second.as_ptr(),
14923 "Caixa::deps_dev must borrow the underlying Vec<Dep> \
14924 storage — two successive calls must return slices \
14925 with the same backing pointer (a fresh Vec<Dep> clone \
14926 would change the pointer on every call)",
14927 );
14928 assert_eq!(
14929 first,
14930 deps_dev.as_slice(),
14931 "Caixa::deps_dev must return :deps-dev verbatim by \
14932 borrow — got {first:?}, expected {deps_dev:?}",
14933 );
14934 }
14935 }
14936
14937 // ── Caixa::limits — outer top-level Option<&LimitsSpec> composite-reference accessor ──
14938
14939 fn caixa_with_limits(limits: Option<crate::LimitsSpec>) -> Caixa {
14940 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
14941 c.limits = limits;
14942 c
14943 }
14944
14945 #[test]
14946 fn limits_returns_limits_option_ref_verbatim_across_permutations() {
14947 // The canonical per-`Caixa` `:limits` M2 typed-slot outer-
14948 // composite optional-composite-reference-shape pin:
14949 // [`Caixa::limits`] must return the `:limits` typed
14950 // `Option<LimitsSpec>` verbatim as an `Option<&LimitsSpec>`
14951 // reference over the same backing storage the raw
14952 // `self.limits.as_ref()` field access borrows from, byte-equal
14953 // across every representative fixture in the accept-set — the
14954 // author-omitted `None` shape (the "engine-default applies"
14955 // partition every downstream Servico M2 overlay emitter treats
14956 // as "emit nothing"), the empty-composite `Some(LimitsSpec {
14957 // .. default })` shape ([`LimitsSpec::is_empty`] holds — every
14958 // per-axis cap is `None`, so the peer M2 overlay emitter's
14959 // `.is_empty()`-gated projection still emits nothing but the
14960 // outer presence-bit is `Some`, so [`Caixa::declared_servico_slots`]
14961 // still pushes the `M2_AUTHOR_KEY_LIMITS` label), a single-axis
14962 // fixture (only `:memory` set — the canonical shape most
14963 // memory-heavy Servicos carry), and a fully-populated composite
14964 // (every per-axis cap set — the canonical shape a
14965 // sandboxed-by-default Servico carries).
14966 //
14967 // Pins against a future silent detour that returned a fresh-
14968 // cloned [`LimitsSpec`] copy (which would type-check via the
14969 // `Clone` impl but silently break every downstream caller that
14970 // relied on the reference sharing the composite's backing
14971 // identity), a reference to an operator-resolved overlay (the
14972 // future per-cluster `:limits-overrides` slot — its resolution
14973 // must land at exactly this accessor body, not silently divert
14974 // the raw slot away from a second consumer), a
14975 // `None` → `Some(LimitsSpec::default)` cluster-default
14976 // projection (which would collapse the load-bearing
14977 // "author-omitted `:limits` ⇒ engine-default applies" partition
14978 // the peer [`crate::render::servico_m2_overlay`] emitter and
14979 // the peer [`Caixa::declared_servico_slots`] enumerator both
14980 // read), or an axis-shuffled projection (a future detour that
14981 // swapped `memory` and `fuel` through the accessor would
14982 // silently split the paired [`crate::StandardLayout::verify`]
14983 // per-`:limits` shape gate's traversal input from the peer
14984 // `servico_m2_overlay` emitter's projection input).
14985 //
14986 // First outer top-level [`Caixa`] `Option<&Composite>`-return
14987 // composite-reference accessor pin on the substrate primitive
14988 // — opens the outer-`Caixa` `Option<&Composite>` composite-
14989 // reference projection pattern the sibling `:behavior`
14990 // [`crate::BehaviorSpec`] / `:politicas`
14991 // [`crate::aplicacao::MeshPolicy`] / `:placement`
14992 // [`crate::aplicacao::Placement`] / `:entrada`
14993 // [`crate::aplicacao::Entrada`] future outer-composite lifts
14994 // fold on. Peer of the closed M3 outer-composite family the
14995 // sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
14996 // [`crate::AplicacaoSpec::placement`] (9abb8f0) /
14997 // [`crate::AplicacaoSpec::entrada`] (d32111c) composite-
14998 // reference accessor pins already carry on the outer
14999 // [`crate::AplicacaoSpec`] altitude — extends the outer-
15000 // accessor byte-equal-projection discipline onto the outer
15001 // top-level [`Caixa`] M2 Servico-runtime slot altitude.
15002 use crate::LimitsSpec;
15003 use std::time::Duration;
15004 let fixtures: Vec<Option<LimitsSpec>> = vec![
15005 None,
15006 Some(LimitsSpec::default()),
15007 Some(LimitsSpec {
15008 memory: Some(64 * 1024 * 1024),
15009 ..Default::default()
15010 }),
15011 Some(LimitsSpec {
15012 memory: Some(64 * 1024 * 1024),
15013 fuel: Some(1_000_000),
15014 wall_clock: Some(Duration::from_secs(30)),
15015 cpu: Some(500),
15016 }),
15017 ];
15018 for limits in fixtures {
15019 let c = caixa_with_limits(limits.clone());
15020 assert_eq!(
15021 c.limits(),
15022 limits.as_ref(),
15023 "Caixa::limits must return :limits verbatim (got {:?}, \
15024 expected {:?})",
15025 c.limits(),
15026 limits.as_ref(),
15027 );
15028 match (c.limits(), c.limits.as_ref()) {
15029 (Some(a), Some(b)) => assert!(
15030 std::ptr::eq(a, b),
15031 "Caixa::limits accessor and self.limits.as_ref() \
15032 field access must borrow the same backing storage \
15033 — the accessor is the substrate-primitive typed \
15034 dispatch every downstream Servico-M2-overlay \
15035 composite consumer must route through, and a \
15036 reference-identity split would silently break \
15037 every consumer that relied on the borrow sharing \
15038 the composite's storage",
15039 ),
15040 (None, None) => {}
15041 _ => panic!(
15042 "Caixa::limits presence bit must byte-equal \
15043 self.limits.is_some() — a presence-bit drift would \
15044 silently split the paired StandardLayout::verify \
15045 per-`:limits` shape gate's traversal head from \
15046 the peer render::servico_m2_overlay M2 overlay \
15047 emitter's traversal head from the peer \
15048 Caixa::declared_servico_slots M2 declared-slot \
15049 enumerator's presence probe",
15050 ),
15051 }
15052 assert_eq!(
15053 c.limits().is_some(),
15054 c.limits.is_some(),
15055 "Caixa::limits().is_some() must byte-equal \
15056 self.limits.is_some() — a presence-bit drift would \
15057 silently split every downstream Option<&LimitsSpec> \
15058 consumer's partition on the engine-default arm",
15059 );
15060 }
15061 }
15062
15063 #[test]
15064 fn declared_servico_slots_limits_arm_routes_through_accessor() {
15065 // Composition pin: [`Caixa::declared_servico_slots`]'s
15066 // `:limits` presence-probe arm must key off [`Caixa::limits`],
15067 // not the raw `self.limits.is_some()` field-probe. Structurally:
15068 // a `Caixa { limits: Some(LimitsSpec::default()), .. }` must
15069 // still push `M2_AUTHOR_KEY_LIMITS` onto the declared-slot list
15070 // (the presence bit is `Some`, so the M2 kind-coherence gate
15071 // must surface the slot as "declared" even when every per-axis
15072 // cap is unset), and a `Caixa { limits: None, .. }` must NOT
15073 // push the label (the "author omitted the slot entirely"
15074 // partition). The pair jointly pins the accessor + declared-
15075 // slot enumerator composition: any future silent detour that
15076 // had the accessor collapse `Some(LimitsSpec::default())` to
15077 // `None` (a `.filter(|l| !l.is_empty())` projection) would
15078 // silently absorb the "declared but empty" arm at the
15079 // accessor boundary and the [`crate::LayoutError::ServicoSlotsOnNonServico`]
15080 // kind-coherence gate would silently accept a
15081 // struct-literal `Caixa` carrying the drift.
15082 //
15083 // Peer of the sibling per-`Caixa`
15084 // `validate_deps_duplicate_arm_routes_through_accessor` (ad34b4e)
15085 // and `validate_deps_duplicate_deps_dev_arm_routes_through_accessor`
15086 // (f7fd81e) accessor-composition pins on the sibling `:deps` /
15087 // `:deps-dev` outer-`&[Dep]`-composition axes — same "the
15088 // enumerator gate must route through the substrate-primitive
15089 // typed dispatch" discipline extended onto the outer top-level
15090 // [`Caixa`] `Option<&LimitsSpec>`-composition surface, opening
15091 // the outer-`Caixa` M2 Servico-runtime-slot arm of the
15092 // composition-pin family.
15093 use crate::LimitsSpec;
15094 let c = caixa_with_limits(Some(LimitsSpec::default()));
15095 let slots = c.declared_servico_slots();
15096 assert!(
15097 slots.contains(&crate::render::M2_AUTHOR_KEY_LIMITS),
15098 "declared_servico_slots must push M2_AUTHOR_KEY_LIMITS \
15099 when `:limits` is Some (even for LimitsSpec::default()) \
15100 — the accessor and the enumerator gate must route through \
15101 the same substrate-primitive typed dispatch on the outer \
15102 :limits presence bit (got slots={slots:?})",
15103 );
15104 let c = caixa_with_limits(None);
15105 let slots = c.declared_servico_slots();
15106 assert!(
15107 !slots.contains(&crate::render::M2_AUTHOR_KEY_LIMITS),
15108 "declared_servico_slots must NOT push M2_AUTHOR_KEY_LIMITS \
15109 when `:limits` is None — the author-omitted arm must \
15110 route through the accessor's None-return unchanged (got \
15111 slots={slots:?})",
15112 );
15113 }
15114
15115 #[test]
15116 fn servico_m2_overlay_limits_arm_routes_through_accessor() {
15117 // Composition pin: [`crate::render::servico_m2_overlay`]'s
15118 // per-`:limits` M2 overlay emit arm must key off
15119 // [`Caixa::limits`], not the raw `&caixa.limits` field-borrow.
15120 // Structurally: a `Caixa { limits: Some(LimitsSpec { memory:
15121 // Some(64 MiB), .. default }), .. }` must surface the
15122 // `M2_KEY_LIMITS` key with the per-axis
15123 // `memory: "64MiB"` sub-mapping in the overlay, a `Caixa {
15124 // limits: Some(LimitsSpec::default()), .. }` must omit the
15125 // key entirely (the `.is_empty()`-gated inner arm elides an
15126 // empty composite even when the outer presence bit is `Some`),
15127 // and a `Caixa { limits: None, .. }` must also omit the key
15128 // (the "author omitted the slot entirely" partition). The
15129 // three-fixture family jointly pins the accessor + M2 overlay
15130 // emitter composition: any future silent detour that had the
15131 // accessor return a fresh-cloned copy on the `Some` arm (a
15132 // `LimitsSpec::clone()` projection) would silently break the
15133 // reference-identity pin the peer per-axis
15134 // `serde_yaml::to_value(limits)` projection reads from.
15135 use crate::LimitsSpec;
15136 use crate::render::{M2_KEY_LIMITS, servico_m2_overlay};
15137 let c = caixa_with_limits(Some(LimitsSpec {
15138 memory: Some(64 * 1024 * 1024),
15139 ..Default::default()
15140 }));
15141 let overlay = servico_m2_overlay(&c).unwrap();
15142 assert!(
15143 overlay.contains_key(M2_KEY_LIMITS),
15144 "servico_m2_overlay must surface M2_KEY_LIMITS when \
15145 `:limits` carries a non-empty composite — the accessor \
15146 and the M2 overlay emitter must route through the same \
15147 substrate-primitive typed dispatch on the outer :limits \
15148 composite (got overlay={overlay:?})",
15149 );
15150 let c = caixa_with_limits(Some(LimitsSpec::default()));
15151 let overlay = servico_m2_overlay(&c).unwrap();
15152 assert!(
15153 !overlay.contains_key(M2_KEY_LIMITS),
15154 "servico_m2_overlay must omit M2_KEY_LIMITS when \
15155 `:limits` is Some(LimitsSpec::default()) — the empty \
15156 composite's `.is_empty()`-gated inner arm must elide \
15157 the key regardless of the outer presence bit (got \
15158 overlay={overlay:?})",
15159 );
15160 let c = caixa_with_limits(None);
15161 let overlay = servico_m2_overlay(&c).unwrap();
15162 assert!(
15163 !overlay.contains_key(M2_KEY_LIMITS),
15164 "servico_m2_overlay must omit M2_KEY_LIMITS when \
15165 `:limits` is None — the author-omitted arm must route \
15166 through the accessor's None-return unchanged (got \
15167 overlay={overlay:?})",
15168 );
15169 }
15170
15171 #[test]
15172 fn limits_projects_option_ref_by_borrow() {
15173 // The by-borrow pin: [`Caixa::limits`] returns
15174 // `Option<&LimitsSpec>` by borrow — the returned reference
15175 // borrows the underlying `Option<LimitsSpec>` storage of the
15176 // `:limits` slot and the accessor must not clone the backing
15177 // composite on every call. Peer of the sibling
15178 // `deps_projects_slice_by_borrow` (ad34b4e) /
15179 // `deps_dev_projects_slice_by_borrow` (f7fd81e) by-borrow pins
15180 // on the outer top-level [`Caixa`] `&[Dep]`-return axes —
15181 // extended here to the outer [`Caixa`] `Option<&Composite>`-
15182 // return axis: the accessor's returned reference must borrow
15183 // from `&self` (the returned reference's lifetime is tied to
15184 // `&self`), and calling the accessor twice on the same
15185 // [`Caixa`] must yield references that are pointer-equal (the
15186 // underlying byte-buffer is the storage `LimitsSpec`'s
15187 // allocation, not a fresh copy) as well as value-equal
15188 // (idempotent, no side effects on `&self`).
15189 //
15190 // Pins against a future silent detour that returned an owned
15191 // `LimitsSpec` (which would type-check via the `Clone` impl
15192 // but silently clone on every call), a `&LimitsSpec` panic-
15193 // return on the `None` arm (which would collapse the load-
15194 // bearing `Option` presence-bit into a runtime panic), or a
15195 // one-arm-only accessor that returned a saturating composite
15196 // on some sentinel input.
15197 use crate::LimitsSpec;
15198 use std::time::Duration;
15199 for limits in [
15200 Some(LimitsSpec::default()),
15201 Some(LimitsSpec {
15202 memory: Some(64 * 1024 * 1024),
15203 fuel: Some(1_000_000),
15204 wall_clock: Some(Duration::from_secs(30)),
15205 cpu: Some(500),
15206 }),
15207 ] {
15208 let c = caixa_with_limits(limits.clone());
15209 let first = c.limits().unwrap();
15210 let second = c.limits().unwrap();
15211 assert_eq!(
15212 first, second,
15213 "Caixa::limits must be idempotent — two successive \
15214 calls on the same &self must return the same \
15215 &LimitsSpec",
15216 );
15217 assert!(
15218 std::ptr::eq(first, second),
15219 "Caixa::limits must borrow the underlying \
15220 Option<LimitsSpec> storage — two successive calls \
15221 must return references with the same backing pointer \
15222 (a fresh LimitsSpec clone would change the pointer \
15223 on every call)",
15224 );
15225 assert_eq!(
15226 Some(first),
15227 limits.as_ref(),
15228 "Caixa::limits must return :limits verbatim by borrow \
15229 — got {first:?}, expected {:?}",
15230 limits.as_ref(),
15231 );
15232 }
15233 let c = caixa_with_limits(None);
15234 assert!(
15235 c.limits().is_none(),
15236 "Caixa::limits must return None when :limits is absent — \
15237 the author-omitted arm must project through the \
15238 accessor's Option::None unchanged",
15239 );
15240 }
15241
15242 // ── Caixa::behavior — outer top-level Option<&BehaviorSpec> composite-reference accessor ──
15243
15244 fn caixa_with_behavior(behavior: Option<crate::BehaviorSpec>) -> Caixa {
15245 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15246 c.behavior = behavior;
15247 c
15248 }
15249
15250 #[test]
15251 fn behavior_returns_behavior_option_ref_verbatim_across_permutations() {
15252 // The canonical per-`Caixa` `:behavior` M2 typed-slot outer-
15253 // composite optional-composite-reference-shape pin:
15254 // [`Caixa::behavior`] must return the `:behavior` typed
15255 // `Option<BehaviorSpec>` verbatim as an `Option<&BehaviorSpec>`
15256 // reference over the same backing storage the raw
15257 // `self.behavior.as_ref()` field access borrows from, byte-equal
15258 // across every representative fixture in the accept-set — the
15259 // author-omitted `None` shape (the "runtime-default applies"
15260 // partition every downstream Servico M2 overlay emitter treats
15261 // as "emit nothing"), the empty-composite `Some(BehaviorSpec {
15262 // .. default })` shape ([`BehaviorSpec::is_empty`] holds —
15263 // every per-callback path is `None`, so the peer M2 overlay
15264 // emitter's `.is_empty()`-gated projection still emits nothing
15265 // but the outer presence-bit is `Some`, so
15266 // [`Caixa::declared_servico_slots`] still pushes the
15267 // `M2_AUTHOR_KEY_BEHAVIOR` label), a single-callback fixture
15268 // (only `:on-state-change` set — the canonical shape a caixa
15269 // that only wires the hot-upgrade migration path carries), and
15270 // a fully-populated composite (every per-callback path set —
15271 // the canonical shape a fully-instrumented gen_server-shaped
15272 // Servico carries).
15273 //
15274 // Peer of the sibling
15275 // `limits_returns_limits_option_ref_verbatim_across_permutations`
15276 // (b2bd9d7) opening fixture-family + reference-identity +
15277 // presence-bit tetrad pin on the outer top-level [`Caixa`]
15278 // `Option<&Composite>`-return sub-family — extended here to the
15279 // second axis of that sub-family so both of the currently-lifted
15280 // M2 Servico-runtime `Option<&Composite>` slots (`:limits` /
15281 // `:behavior`) carry the same "byte-equal, borrow-shared,
15282 // presence-bit-preserved" outer-accessor discipline.
15283 //
15284 // Pins against a future silent detour that returned a fresh-
15285 // cloned [`crate::BehaviorSpec`] copy (which would type-check
15286 // via the `Clone` impl but silently break every downstream
15287 // caller that relied on the reference sharing the composite's
15288 // backing identity), a reference to an operator-resolved
15289 // overlay (a future per-cluster `:behavior-overrides` slot —
15290 // its resolution must land at exactly this accessor body, not
15291 // silently divert the raw slot away from a second consumer), a
15292 // `None` → `Some(BehaviorSpec::default)` cluster-default
15293 // projection (which would collapse the load-bearing
15294 // "author-omitted `:behavior` ⇒ runtime-default applies"
15295 // partition the peer [`crate::render::servico_m2_overlay`]
15296 // emitter, the peer [`Caixa::declared_servico_slots`]
15297 // enumerator, and the cross-slot
15298 // [`crate::upgrade::validate_upgrade_from_against_behavior`]
15299 // gate all read), or a callback-shuffled projection (a future
15300 // detour that swapped `on_init` and `on_terminate` through the
15301 // accessor would silently split the paired
15302 // [`crate::StandardLayout::verify`] per-`:behavior` shape gate's
15303 // traversal input from the peer `servico_m2_overlay` emitter's
15304 // projection input from the cross-slot `:state-change`
15305 // composition gate's traversal input).
15306 use crate::BehaviorSpec;
15307 use std::path::PathBuf;
15308 let fixtures: Vec<Option<BehaviorSpec>> = vec![
15309 None,
15310 Some(BehaviorSpec::default()),
15311 Some(BehaviorSpec {
15312 on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
15313 ..Default::default()
15314 }),
15315 Some(BehaviorSpec {
15316 on_init: Some(PathBuf::from("lib/init.lisp")),
15317 on_call: Some(PathBuf::from("lib/handlers.lisp")),
15318 on_cast: Some(PathBuf::from("lib/handlers.lisp")),
15319 on_info: Some(PathBuf::from("lib/handlers.lisp")),
15320 on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
15321 on_terminate: Some(PathBuf::from("lib/cleanup.lisp")),
15322 }),
15323 ];
15324 for behavior in fixtures {
15325 let c = caixa_with_behavior(behavior.clone());
15326 assert_eq!(
15327 c.behavior(),
15328 behavior.as_ref(),
15329 "Caixa::behavior must return :behavior verbatim (got \
15330 {:?}, expected {:?})",
15331 c.behavior(),
15332 behavior.as_ref(),
15333 );
15334 match (c.behavior(), c.behavior.as_ref()) {
15335 (Some(a), Some(b)) => assert!(
15336 std::ptr::eq(a, b),
15337 "Caixa::behavior accessor and self.behavior.as_ref() \
15338 field access must borrow the same backing storage \
15339 — the accessor is the substrate-primitive typed \
15340 dispatch every downstream Servico-M2-overlay \
15341 composite consumer must route through, and a \
15342 reference-identity split would silently break \
15343 every consumer that relied on the borrow sharing \
15344 the composite's storage",
15345 ),
15346 (None, None) => {}
15347 _ => panic!(
15348 "Caixa::behavior presence bit must byte-equal \
15349 self.behavior.is_some() — a presence-bit drift \
15350 would silently split the paired \
15351 StandardLayout::verify per-`:behavior` shape \
15352 gate's traversal head from the peer \
15353 render::servico_m2_overlay M2 overlay emitter's \
15354 traversal head from the cross-slot \
15355 validate_upgrade_from_against_behavior \
15356 composition gate's traversal head from the peer \
15357 Caixa::declared_servico_slots M2 declared-slot \
15358 enumerator's presence probe",
15359 ),
15360 }
15361 assert_eq!(
15362 c.behavior().is_some(),
15363 c.behavior.is_some(),
15364 "Caixa::behavior().is_some() must byte-equal \
15365 self.behavior.is_some() — a presence-bit drift would \
15366 silently split every downstream Option<&BehaviorSpec> \
15367 consumer's partition on the runtime-default arm",
15368 );
15369 }
15370 }
15371
15372 #[test]
15373 fn declared_servico_slots_behavior_arm_routes_through_accessor() {
15374 // Composition pin: [`Caixa::declared_servico_slots`]'s
15375 // `:behavior` presence-probe arm must key off
15376 // [`Caixa::behavior`], not the raw `self.behavior.is_some()`
15377 // field-probe. Structurally: a `Caixa { behavior:
15378 // Some(BehaviorSpec::default()), .. }` must still push
15379 // `M2_AUTHOR_KEY_BEHAVIOR` onto the declared-slot list (the
15380 // presence bit is `Some`, so the M2 kind-coherence gate must
15381 // surface the slot as "declared" even when every per-callback
15382 // path is unset), and a `Caixa { behavior: None, .. }` must
15383 // NOT push the label (the "author omitted the slot entirely"
15384 // partition). The pair jointly pins the accessor + declared-
15385 // slot enumerator composition: any future silent detour that
15386 // had the accessor collapse `Some(BehaviorSpec::default())`
15387 // to `None` (a `.filter(|b| !b.is_empty())` projection) would
15388 // silently absorb the "declared but empty" arm at the
15389 // accessor boundary and the
15390 // [`crate::LayoutError::ServicoSlotsOnNonServico`]
15391 // kind-coherence gate would silently accept a struct-literal
15392 // `Caixa` carrying the drift.
15393 //
15394 // Peer of the sibling
15395 // `declared_servico_slots_limits_arm_routes_through_accessor`
15396 // (b2bd9d7) composition pin on the sibling `:limits` outer-
15397 // `Option<&LimitsSpec>` arm of the same
15398 // [`Caixa::declared_servico_slots`] M2 declared-slot
15399 // enumerator's traversal — same "the enumerator gate must
15400 // route through the substrate-primitive typed dispatch"
15401 // discipline extended onto the outer top-level [`Caixa`]
15402 // `Option<&BehaviorSpec>`-composition surface.
15403 use crate::BehaviorSpec;
15404 let c = caixa_with_behavior(Some(BehaviorSpec::default()));
15405 let slots = c.declared_servico_slots();
15406 assert!(
15407 slots.contains(&crate::render::M2_AUTHOR_KEY_BEHAVIOR),
15408 "declared_servico_slots must push M2_AUTHOR_KEY_BEHAVIOR \
15409 when `:behavior` is Some (even for BehaviorSpec::default()) \
15410 — the accessor and the enumerator gate must route through \
15411 the same substrate-primitive typed dispatch on the outer \
15412 :behavior presence bit (got slots={slots:?})",
15413 );
15414 let c = caixa_with_behavior(None);
15415 let slots = c.declared_servico_slots();
15416 assert!(
15417 !slots.contains(&crate::render::M2_AUTHOR_KEY_BEHAVIOR),
15418 "declared_servico_slots must NOT push M2_AUTHOR_KEY_BEHAVIOR \
15419 when `:behavior` is None — the author-omitted arm must \
15420 route through the accessor's None-return unchanged (got \
15421 slots={slots:?})",
15422 );
15423 }
15424
15425 #[test]
15426 fn servico_m2_overlay_behavior_arm_routes_through_accessor() {
15427 // Composition pin: [`crate::render::servico_m2_overlay`]'s
15428 // per-`:behavior` M2 overlay emit arm must key off
15429 // [`Caixa::behavior`], not the raw `&caixa.behavior`
15430 // field-borrow. Structurally: a `Caixa { behavior:
15431 // Some(BehaviorSpec { on_state_change: Some(...), .. default
15432 // }), .. }` must surface the `M2_KEY_BEHAVIOR` key with the
15433 // per-callback `onStateChange` sub-mapping in the overlay, a
15434 // `Caixa { behavior: Some(BehaviorSpec::default()), .. }`
15435 // must omit the key entirely (the `.is_empty()`-gated inner
15436 // arm elides an empty composite even when the outer presence
15437 // bit is `Some`), and a `Caixa { behavior: None, .. }` must
15438 // also omit the key (the "author omitted the slot entirely"
15439 // partition). The three-fixture family jointly pins the
15440 // accessor + M2 overlay emitter composition: any future
15441 // silent detour that had the accessor return a fresh-cloned
15442 // copy on the `Some` arm (a `BehaviorSpec::clone()`
15443 // projection) would silently break the reference-identity
15444 // pin the peer per-callback `serde_yaml::to_value(behavior)`
15445 // projection reads from.
15446 //
15447 // Peer of the sibling
15448 // `servico_m2_overlay_limits_arm_routes_through_accessor`
15449 // (b2bd9d7) composition pin on the sibling `:limits` outer-
15450 // `Option<&LimitsSpec>` arm of the same
15451 // [`crate::render::servico_m2_overlay`] M2 overlay emitter's
15452 // traversal — same "the emitter must route through the
15453 // substrate-primitive typed dispatch on the outer composite"
15454 // discipline extended onto the outer top-level [`Caixa`]
15455 // `Option<&BehaviorSpec>`-composition surface.
15456 use crate::BehaviorSpec;
15457 use crate::render::{M2_KEY_BEHAVIOR, servico_m2_overlay};
15458 use std::path::PathBuf;
15459 let c = caixa_with_behavior(Some(BehaviorSpec {
15460 on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
15461 ..Default::default()
15462 }));
15463 let overlay = servico_m2_overlay(&c).unwrap();
15464 assert!(
15465 overlay.contains_key(M2_KEY_BEHAVIOR),
15466 "servico_m2_overlay must surface M2_KEY_BEHAVIOR when \
15467 `:behavior` carries a non-empty composite — the accessor \
15468 and the M2 overlay emitter must route through the same \
15469 substrate-primitive typed dispatch on the outer :behavior \
15470 composite (got overlay={overlay:?})",
15471 );
15472 let c = caixa_with_behavior(Some(BehaviorSpec::default()));
15473 let overlay = servico_m2_overlay(&c).unwrap();
15474 assert!(
15475 !overlay.contains_key(M2_KEY_BEHAVIOR),
15476 "servico_m2_overlay must omit M2_KEY_BEHAVIOR when \
15477 `:behavior` is Some(BehaviorSpec::default()) — the empty \
15478 composite's `.is_empty()`-gated inner arm must elide the \
15479 key regardless of the outer presence bit (got \
15480 overlay={overlay:?})",
15481 );
15482 let c = caixa_with_behavior(None);
15483 let overlay = servico_m2_overlay(&c).unwrap();
15484 assert!(
15485 !overlay.contains_key(M2_KEY_BEHAVIOR),
15486 "servico_m2_overlay must omit M2_KEY_BEHAVIOR when \
15487 `:behavior` is None — the author-omitted arm must route \
15488 through the accessor's None-return unchanged (got \
15489 overlay={overlay:?})",
15490 );
15491 }
15492
15493 #[test]
15494 fn behavior_projects_option_ref_by_borrow() {
15495 // The by-borrow pin: [`Caixa::behavior`] returns
15496 // `Option<&BehaviorSpec>` by borrow — the returned reference
15497 // borrows the underlying `Option<BehaviorSpec>` storage of the
15498 // `:behavior` slot and the accessor must not clone the backing
15499 // composite on every call. Peer of the sibling
15500 // `limits_projects_option_ref_by_borrow` (b2bd9d7) by-borrow
15501 // pin on the outer top-level [`Caixa`] `Option<&Composite>`-
15502 // return sub-family — extended here to the second axis of the
15503 // same sub-family: the accessor's returned reference must
15504 // borrow from `&self` (the returned reference's lifetime is
15505 // tied to `&self`), and calling the accessor twice on the same
15506 // [`Caixa`] must yield references that are pointer-equal (the
15507 // underlying byte-buffer is the storage `BehaviorSpec`'s
15508 // allocation, not a fresh copy) as well as value-equal
15509 // (idempotent, no side effects on `&self`).
15510 //
15511 // Pins against a future silent detour that returned an owned
15512 // `BehaviorSpec` (which would type-check via the `Clone` impl
15513 // but silently clone on every call), a `&BehaviorSpec` panic-
15514 // return on the `None` arm (which would collapse the load-
15515 // bearing `Option` presence-bit into a runtime panic), or a
15516 // one-arm-only accessor that returned a saturating composite
15517 // on some sentinel input.
15518 use crate::BehaviorSpec;
15519 use std::path::PathBuf;
15520 for behavior in [
15521 Some(BehaviorSpec::default()),
15522 Some(BehaviorSpec {
15523 on_init: Some(PathBuf::from("lib/init.lisp")),
15524 on_call: Some(PathBuf::from("lib/handlers.lisp")),
15525 on_cast: Some(PathBuf::from("lib/handlers.lisp")),
15526 on_info: Some(PathBuf::from("lib/handlers.lisp")),
15527 on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
15528 on_terminate: Some(PathBuf::from("lib/cleanup.lisp")),
15529 }),
15530 ] {
15531 let c = caixa_with_behavior(behavior.clone());
15532 let first = c.behavior().unwrap();
15533 let second = c.behavior().unwrap();
15534 assert_eq!(
15535 first, second,
15536 "Caixa::behavior must be idempotent — two successive \
15537 calls on the same &self must return the same \
15538 &BehaviorSpec",
15539 );
15540 assert!(
15541 std::ptr::eq(first, second),
15542 "Caixa::behavior must borrow the underlying \
15543 Option<BehaviorSpec> storage — two successive calls \
15544 must return references with the same backing pointer \
15545 (a fresh BehaviorSpec clone would change the pointer \
15546 on every call)",
15547 );
15548 assert_eq!(
15549 Some(first),
15550 behavior.as_ref(),
15551 "Caixa::behavior must return :behavior verbatim by \
15552 borrow — got {first:?}, expected {:?}",
15553 behavior.as_ref(),
15554 );
15555 }
15556 let c = caixa_with_behavior(None);
15557 assert!(
15558 c.behavior().is_none(),
15559 "Caixa::behavior must return None when :behavior is absent \
15560 — the author-omitted arm must project through the \
15561 accessor's Option::None unchanged",
15562 );
15563 }
15564
15565 // ── Caixa::politicas — outer top-level Option<&MeshPolicy> composite-reference accessor ──
15566
15567 fn caixa_aplicacao_with_politicas(politicas: Option<crate::aplicacao::MeshPolicy>) -> Caixa {
15568 use crate::aplicacao::{Membro, WitContract};
15569 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15570 c.kind = CaixaKind::Aplicacao;
15571 c.membros = vec![Membro {
15572 caixa: "a".into(),
15573 versao: "^0.1".into(),
15574 }];
15575 c.contratos = vec![WitContract {
15576 de: "a".into(),
15577 para: "a".into(),
15578 wit: "wasi:http/proxy".into(),
15579 endpoint: Some("/x".into()),
15580 subject: None,
15581 slot: None,
15582 }];
15583 c.politicas = politicas;
15584 c
15585 }
15586
15587 #[test]
15588 fn politicas_returns_politicas_option_ref_verbatim_across_permutations() {
15589 // The canonical per-`Caixa` `:politicas` M3 mesh-slot outer-
15590 // composite optional-composite-reference-shape pin:
15591 // [`Caixa::politicas`] must return the `:politicas` typed
15592 // `Option<MeshPolicy>` verbatim as an `Option<&MeshPolicy>`
15593 // reference over the same backing storage the raw
15594 // `self.politicas.as_ref()` field access borrows from,
15595 // byte-equal across every representative fixture in the
15596 // accept-set — the author-omitted `None` shape (the "cluster-
15597 // default applies" partition every downstream mesh-artifact
15598 // emitter treats as "emit no `:politicas` overlay"), the
15599 // empty-composite `Some(MeshPolicy { .. default })` shape
15600 // ([`crate::aplicacao::MeshPolicy::is_empty`] holds — every
15601 // per-axis mesh-policy scalar is `None`, so the peer inner
15602 // [`crate::AplicacaoSpec::politicas`] `.is_empty()`-gated
15603 // caixa-mesh overlay elides every per-axis emit but the outer
15604 // presence-bit is `Some`, so [`Caixa::declared_mesh_slots`]
15605 // still pushes the `M3_AUTHOR_KEY_POLITICAS` label), a
15606 // single-axis fixture (only `:timeout` set — the canonical
15607 // shape a latency-sensitive Aplicacao carries), and a
15608 // fully-populated composite (every per-axis mesh-policy
15609 // scalar set — the canonical shape a fully-governed
15610 // Aplicacao carries).
15611 //
15612 // Pins against a future silent detour that returned a fresh-
15613 // cloned [`crate::aplicacao::MeshPolicy`] copy (which would
15614 // type-check via the `Clone` impl but silently break every
15615 // downstream caller that relied on the reference sharing the
15616 // composite's backing identity), a reference to an operator-
15617 // resolved overlay (the future per-cluster
15618 // `:politicas-overrides` slot — its resolution must land at
15619 // exactly this accessor body, not silently divert the raw
15620 // slot away from the peer [`Caixa::declared_mesh_slots`]
15621 // enumerator's presence probe), a
15622 // `None` → `Some(MeshPolicy::default)` cluster-default
15623 // projection (which would collapse the load-bearing
15624 // "author-omitted `:politicas` ⇒ cluster-default applies"
15625 // partition the peer [`Caixa::declared_mesh_slots`]
15626 // enumerator and the peer [`Caixa::aplicacao_view`]
15627 // Aplicacao-composition seed both read), or an axis-shuffled
15628 // projection (a future detour that swapped `timeout` and
15629 // `retries` through the accessor would silently split the
15630 // paired [`Caixa::aplicacao_view`] seed's fold input from the
15631 // sibling M3 mesh-artifact emitter's projection input).
15632 //
15633 // Third outer top-level [`Caixa`] `Option<&Composite>`-return
15634 // composite-reference accessor pin on the substrate primitive
15635 // — peer of the sibling
15636 // `limits_returns_limits_option_ref_verbatim_across_permutations`
15637 // (b2bd9d7) and
15638 // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
15639 // (35d8b52) opening tetrad pins on the outer top-level
15640 // [`Caixa`] `Option<&Composite>`-return sub-family — extended
15641 // here to the first of the three M3 mesh-slot axes so the
15642 // opening third of the outer `Option<&Composite>` sub-family
15643 // carries the same "byte-equal, borrow-shared, presence-bit-
15644 // preserved" outer-accessor discipline.
15645 use crate::aplicacao::{CircuitBreaker, MeshPolicy, RateLimit};
15646 use std::time::Duration;
15647 let fixtures: Vec<Option<MeshPolicy>> = vec![
15648 None,
15649 Some(MeshPolicy::default()),
15650 Some(MeshPolicy {
15651 timeout: Some(Duration::from_secs(30)),
15652 ..Default::default()
15653 }),
15654 Some(MeshPolicy {
15655 timeout: Some(Duration::from_secs(30)),
15656 retries: Some(3),
15657 circuit_breaker: Some(CircuitBreaker {
15658 max_failures: 5,
15659 window: Duration::from_secs(60),
15660 }),
15661 mtls_required: Some(true),
15662 rate_limit: Some(RateLimit {
15663 rate: 100,
15664 window: Duration::from_secs(1),
15665 }),
15666 }),
15667 ];
15668 for politicas in fixtures {
15669 let c = caixa_aplicacao_with_politicas(politicas.clone());
15670 assert_eq!(
15671 c.politicas(),
15672 politicas.as_ref(),
15673 "Caixa::politicas must return :politicas verbatim (got \
15674 {:?}, expected {:?})",
15675 c.politicas(),
15676 politicas.as_ref(),
15677 );
15678 match (c.politicas(), c.politicas.as_ref()) {
15679 (Some(a), Some(b)) => assert!(
15680 std::ptr::eq(a, b),
15681 "Caixa::politicas accessor and self.politicas.as_ref() \
15682 field access must borrow the same backing storage \
15683 — the accessor is the substrate-primitive typed \
15684 dispatch every downstream Aplicacao-mesh-overlay \
15685 composite consumer must route through, and a \
15686 reference-identity split would silently break \
15687 every consumer that relied on the borrow sharing \
15688 the composite's storage",
15689 ),
15690 (None, None) => {}
15691 _ => panic!(
15692 "Caixa::politicas presence bit must byte-equal \
15693 self.politicas.is_some() — a presence-bit drift \
15694 would silently split the paired \
15695 Caixa::aplicacao_view Aplicacao-composition seed's \
15696 traversal head from the peer \
15697 Caixa::declared_mesh_slots M3 declared-slot \
15698 enumerator's presence probe",
15699 ),
15700 }
15701 assert_eq!(
15702 c.politicas().is_some(),
15703 c.politicas.is_some(),
15704 "Caixa::politicas().is_some() must byte-equal \
15705 self.politicas.is_some() — a presence-bit drift would \
15706 silently split every downstream Option<&MeshPolicy> \
15707 consumer's partition on the cluster-default arm",
15708 );
15709 }
15710 }
15711
15712 #[test]
15713 fn declared_mesh_slots_politicas_arm_routes_through_accessor() {
15714 // Composition pin: [`Caixa::declared_mesh_slots`]'s
15715 // `:politicas` presence-probe arm must key off
15716 // [`Caixa::politicas`], not the raw `self.politicas.is_some()`
15717 // field-probe. Structurally: a `Caixa { politicas:
15718 // Some(MeshPolicy::default()), .. }` must still push
15719 // `M3_AUTHOR_KEY_POLITICAS` onto the declared-slot list (the
15720 // presence bit is `Some`, so the M3 kind-coherence gate must
15721 // surface the slot as "declared" even when every per-axis
15722 // scalar is unset), and a `Caixa { politicas: None, .. }` must
15723 // NOT push the label (the "author omitted the slot entirely"
15724 // partition). The pair jointly pins the accessor + declared-
15725 // slot enumerator composition: any future silent detour that
15726 // had the accessor collapse `Some(MeshPolicy::default())` to
15727 // `None` (a `.filter(|p| !p.is_empty())` projection) would
15728 // silently absorb the "declared but empty" arm at the
15729 // accessor boundary and the
15730 // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
15731 // coherence gate would silently accept a struct-literal
15732 // `Caixa` carrying the drift.
15733 //
15734 // Peer of the sibling
15735 // `declared_servico_slots_limits_arm_routes_through_accessor`
15736 // (b2bd9d7) and
15737 // `declared_servico_slots_behavior_arm_routes_through_accessor`
15738 // (35d8b52) composition pins on the sibling `:limits` /
15739 // `:behavior` outer-`Option<&Composite>` arms of the peer
15740 // [`Caixa::declared_servico_slots`] M2 declared-slot
15741 // enumerator's traversal — same "the enumerator gate must
15742 // route through the substrate-primitive typed dispatch"
15743 // discipline extended onto the outer top-level [`Caixa`] M3
15744 // mesh-slot family so the [`Caixa::declared_mesh_slots`]
15745 // enumerator carries the same routing invariant as its M2
15746 // sibling.
15747 use crate::aplicacao::MeshPolicy;
15748 let c = caixa_aplicacao_with_politicas(Some(MeshPolicy::default()));
15749 let slots = c.declared_mesh_slots();
15750 assert!(
15751 slots.contains(&crate::render::M3_AUTHOR_KEY_POLITICAS),
15752 "declared_mesh_slots must push M3_AUTHOR_KEY_POLITICAS \
15753 when `:politicas` is Some (even for MeshPolicy::default()) \
15754 — the accessor and the enumerator gate must route through \
15755 the same substrate-primitive typed dispatch on the outer \
15756 :politicas presence bit (got slots={slots:?})",
15757 );
15758 let c = caixa_aplicacao_with_politicas(None);
15759 let slots = c.declared_mesh_slots();
15760 assert!(
15761 !slots.contains(&crate::render::M3_AUTHOR_KEY_POLITICAS),
15762 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_POLITICAS \
15763 when `:politicas` is None — the author-omitted arm must \
15764 route through the accessor's None-return unchanged (got \
15765 slots={slots:?})",
15766 );
15767 }
15768
15769 #[test]
15770 fn aplicacao_view_politicas_arm_folds_through_accessor() {
15771 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:politicas`
15772 // Aplicacao-composition seed must fold through
15773 // [`Caixa::politicas`], not the raw
15774 // `self.politicas.clone().unwrap_or_default()` field-borrow.
15775 // Structurally: a `Caixa { politicas: Some(MeshPolicy {
15776 // timeout: Some(30s), .. default }), kind: Aplicacao, .. }`
15777 // must surface a projected [`crate::AplicacaoSpec`] whose
15778 // `politicas().timeout()` field byte-equals the outer
15779 // composite's `timeout` scalar (the fold must project the
15780 // authored composite verbatim), a `Caixa { politicas:
15781 // Some(MeshPolicy::default()), kind: Aplicacao, .. }` must
15782 // surface an [`crate::AplicacaoSpec`] whose `politicas()`
15783 // byte-equals [`crate::aplicacao::MeshPolicy::default`] (the
15784 // fold's empty-composite arm collapses to the same default the
15785 // author-omitted arm does), and a `Caixa { politicas: None,
15786 // kind: Aplicacao, .. }` must surface an
15787 // [`crate::AplicacaoSpec`] whose `politicas()` byte-equals
15788 // [`crate::aplicacao::MeshPolicy::default`] (the "author
15789 // omitted the slot entirely" arm folds through the
15790 // `unwrap_or_default` onto the cluster-default). The triad
15791 // jointly pins the accessor + Aplicacao-composition seed
15792 // composition: any future silent detour that had the accessor
15793 // divert the raw slot away from the seed's fold (an operator-
15794 // resolved overlay's default-fold arm silently differing from
15795 // the raw slot's default-fold arm) would silently split the
15796 // build-time mesh-artifact emission gate from the caixa-mesh
15797 // renderer's Aplicacao-view input at the composition boundary.
15798 use crate::aplicacao::MeshPolicy;
15799 use std::time::Duration;
15800 let c = caixa_aplicacao_with_politicas(Some(MeshPolicy {
15801 timeout: Some(Duration::from_secs(30)),
15802 ..Default::default()
15803 }));
15804 let view = c.aplicacao_view().unwrap();
15805 assert_eq!(
15806 view.politicas().timeout(),
15807 Some(Duration::from_secs(30)),
15808 "Caixa::aplicacao_view must fold the authored :politicas \
15809 :timeout scalar through the accessor verbatim onto the \
15810 projected AplicacaoSpec — a future silent detour at the \
15811 seed's fold arm would surface here as a projected-scalar \
15812 drift (got {:?})",
15813 view.politicas().timeout(),
15814 );
15815 let c = caixa_aplicacao_with_politicas(Some(MeshPolicy::default()));
15816 let view = c.aplicacao_view().unwrap();
15817 assert_eq!(
15818 view.politicas(),
15819 &MeshPolicy::default(),
15820 "Caixa::aplicacao_view must fold Some(MeshPolicy::default()) \
15821 through the accessor onto MeshPolicy::default — the empty- \
15822 composite arm collapses to the same default the author- \
15823 omitted arm does (got {:?})",
15824 view.politicas(),
15825 );
15826 let c = caixa_aplicacao_with_politicas(None);
15827 let view = c.aplicacao_view().unwrap();
15828 assert_eq!(
15829 view.politicas(),
15830 &MeshPolicy::default(),
15831 "Caixa::aplicacao_view must fold None through the accessor's \
15832 unwrap_or_default onto MeshPolicy::default — the author- \
15833 omitted arm must route through the accessor's None-return \
15834 unchanged (got {:?})",
15835 view.politicas(),
15836 );
15837 }
15838
15839 #[test]
15840 fn politicas_projects_option_ref_by_borrow() {
15841 // The by-borrow pin: [`Caixa::politicas`] returns
15842 // `Option<&MeshPolicy>` by borrow — the returned reference
15843 // borrows the underlying `Option<MeshPolicy>` storage of the
15844 // `:politicas` slot and the accessor must not clone the
15845 // backing composite on every call. Peer of the sibling
15846 // `limits_projects_option_ref_by_borrow` (b2bd9d7) and
15847 // `behavior_projects_option_ref_by_borrow` (35d8b52) by-borrow
15848 // pins on the outer top-level [`Caixa`]
15849 // `Option<&Composite>`-return sub-family — extended here to
15850 // the third axis of the same sub-family: the accessor's
15851 // returned reference must borrow from `&self` (the returned
15852 // reference's lifetime is tied to `&self`), and calling the
15853 // accessor twice on the same [`Caixa`] must yield references
15854 // that are pointer-equal (the underlying byte-buffer is the
15855 // storage `MeshPolicy`'s allocation, not a fresh copy) as
15856 // well as value-equal (idempotent, no side effects on
15857 // `&self`).
15858 //
15859 // Pins against a future silent detour that returned an owned
15860 // `MeshPolicy` (which would type-check via the `Clone` impl
15861 // but silently clone on every call), a `&MeshPolicy` panic-
15862 // return on the `None` arm (which would collapse the load-
15863 // bearing `Option` presence-bit into a runtime panic), or a
15864 // one-arm-only accessor that returned a saturating composite
15865 // on some sentinel input.
15866 use crate::aplicacao::{CircuitBreaker, MeshPolicy, RateLimit};
15867 use std::time::Duration;
15868 for politicas in [
15869 Some(MeshPolicy::default()),
15870 Some(MeshPolicy {
15871 timeout: Some(Duration::from_secs(30)),
15872 retries: Some(3),
15873 circuit_breaker: Some(CircuitBreaker {
15874 max_failures: 5,
15875 window: Duration::from_secs(60),
15876 }),
15877 mtls_required: Some(true),
15878 rate_limit: Some(RateLimit {
15879 rate: 100,
15880 window: Duration::from_secs(1),
15881 }),
15882 }),
15883 ] {
15884 let c = caixa_aplicacao_with_politicas(politicas.clone());
15885 let first = c.politicas().unwrap();
15886 let second = c.politicas().unwrap();
15887 assert_eq!(
15888 first, second,
15889 "Caixa::politicas must be idempotent — two successive \
15890 calls on the same &self must return the same \
15891 &MeshPolicy",
15892 );
15893 assert!(
15894 std::ptr::eq(first, second),
15895 "Caixa::politicas must borrow the underlying \
15896 Option<MeshPolicy> storage — two successive calls \
15897 must return references with the same backing pointer \
15898 (a fresh MeshPolicy clone would change the pointer on \
15899 every call)",
15900 );
15901 assert_eq!(
15902 Some(first),
15903 politicas.as_ref(),
15904 "Caixa::politicas must return :politicas verbatim by \
15905 borrow — got {first:?}, expected {:?}",
15906 politicas.as_ref(),
15907 );
15908 }
15909 let c = caixa_aplicacao_with_politicas(None);
15910 assert!(
15911 c.politicas().is_none(),
15912 "Caixa::politicas must return None when :politicas is \
15913 absent — the author-omitted arm must project through the \
15914 accessor's Option::None unchanged",
15915 );
15916 }
15917
15918 // ── Caixa::placement — outer top-level Option<&Placement> composite-reference accessor ──
15919
15920 fn caixa_aplicacao_with_placement(placement: Option<crate::aplicacao::Placement>) -> Caixa {
15921 use crate::aplicacao::{Membro, WitContract};
15922 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15923 c.kind = CaixaKind::Aplicacao;
15924 c.membros = vec![Membro {
15925 caixa: "a".into(),
15926 versao: "^0.1".into(),
15927 }];
15928 c.contratos = vec![WitContract {
15929 de: "a".into(),
15930 para: "a".into(),
15931 wit: "wasi:http/proxy".into(),
15932 endpoint: Some("/x".into()),
15933 subject: None,
15934 slot: None,
15935 }];
15936 c.placement = placement;
15937 c
15938 }
15939
15940 #[test]
15941 fn placement_returns_placement_option_ref_verbatim_across_permutations() {
15942 // The canonical per-`Caixa` `:placement` M3 mesh-slot outer-
15943 // composite optional-composite-reference-shape pin:
15944 // [`Caixa::placement`] must return the `:placement` typed
15945 // `Option<Placement>` verbatim as an `Option<&Placement>`
15946 // reference over the same backing storage the raw
15947 // `self.placement.as_ref()` field access borrows from,
15948 // byte-equal across every representative fixture in the
15949 // accept-set — the author-omitted `None` shape (the
15950 // "cluster-default applies" partition every downstream mesh-
15951 // artifact emitter treats as "emit no `:placement` overlay"),
15952 // the empty-composite `Some(Placement { .. default })` shape
15953 // (`estrategia: SingleNode`, empty clusters, no shard-key /
15954 // affinity — the outer presence-bit is `Some` so
15955 // [`Caixa::declared_mesh_slots`] still pushes the
15956 // `M3_AUTHOR_KEY_PLACEMENT` label), a single-axis
15957 // `Replicated`-on-two-clusters fixture (the canonical shape a
15958 // stateless HTTP Aplicacao carries), and a fully-populated
15959 // `Sharded`-with-shard-key-and-affinity fixture (the canonical
15960 // shape a stateful Akka-style cluster-sharding Aplicacao
15961 // carries).
15962 //
15963 // Pins against a future silent detour that returned a fresh-
15964 // cloned [`crate::aplicacao::Placement`] copy (which would
15965 // type-check via the `Clone` impl but silently break every
15966 // downstream caller that relied on the reference sharing the
15967 // composite's backing identity), a reference to an operator-
15968 // resolved overlay (the future per-cluster
15969 // `:placement-overrides` slot — its resolution must land at
15970 // exactly this accessor body, not silently divert the raw
15971 // slot away from the peer [`Caixa::declared_mesh_slots`]
15972 // enumerator's presence probe), a `None` →
15973 // `Some(Placement::default)` cluster-default projection (which
15974 // would collapse the load-bearing "author-omitted `:placement`
15975 // ⇒ cluster-default applies" partition the peer
15976 // [`Caixa::declared_mesh_slots`] enumerator and the peer
15977 // [`Caixa::aplicacao_view`] Aplicacao-composition seed both
15978 // read), or an axis-shuffled projection (a future detour that
15979 // swapped `clusters` and `affinity` through the accessor would
15980 // silently split the paired [`Caixa::aplicacao_view`] seed's
15981 // fold input from the sibling M3 mesh-artifact emitter's
15982 // projection input).
15983 //
15984 // Fourth outer top-level [`Caixa`] `Option<&Composite>`-return
15985 // composite-reference accessor pin on the substrate primitive
15986 // — peer of the sibling
15987 // `limits_returns_limits_option_ref_verbatim_across_permutations`
15988 // (b2bd9d7),
15989 // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
15990 // (35d8b52), and
15991 // `politicas_returns_politicas_option_ref_verbatim_across_permutations`
15992 // (5d23d29) opening triad pins on the outer top-level
15993 // [`Caixa`] `Option<&Composite>`-return sub-family — extended
15994 // here to the second of the three M3 mesh-slot axes so the
15995 // opening four-fifths of the outer `Option<&Composite>` sub-
15996 // family carries the same "byte-equal, borrow-shared,
15997 // presence-bit-preserved" outer-accessor discipline.
15998 use crate::aplicacao::{Placement, PlacementStrategy};
15999 let fixtures: Vec<Option<Placement>> = vec![
16000 None,
16001 Some(Placement::default()),
16002 Some(Placement {
16003 estrategia: PlacementStrategy::Replicated,
16004 clusters: vec!["rio".into(), "sao-paulo".into()],
16005 affinity: None,
16006 shard_key: None,
16007 }),
16008 Some(Placement {
16009 estrategia: PlacementStrategy::Sharded,
16010 clusters: vec!["rio".into(), "sao-paulo".into(), "brasilia".into()],
16011 affinity: Some("data-locality".into()),
16012 shard_key: Some("$tenantId".into()),
16013 }),
16014 ];
16015 for placement in fixtures {
16016 let c = caixa_aplicacao_with_placement(placement.clone());
16017 assert_eq!(
16018 c.placement(),
16019 placement.as_ref(),
16020 "Caixa::placement must return :placement verbatim (got \
16021 {:?}, expected {:?})",
16022 c.placement(),
16023 placement.as_ref(),
16024 );
16025 match (c.placement(), c.placement.as_ref()) {
16026 (Some(a), Some(b)) => assert!(
16027 std::ptr::eq(a, b),
16028 "Caixa::placement accessor and self.placement.as_ref() \
16029 field access must borrow the same backing storage \
16030 — the accessor is the substrate-primitive typed \
16031 dispatch every downstream Aplicacao-distribution- \
16032 overlay composite consumer must route through, and \
16033 a reference-identity split would silently break \
16034 every consumer that relied on the borrow sharing \
16035 the composite's storage",
16036 ),
16037 (None, None) => {}
16038 _ => panic!(
16039 "Caixa::placement presence bit must byte-equal \
16040 self.placement.is_some() — a presence-bit drift \
16041 would silently split the paired \
16042 Caixa::aplicacao_view Aplicacao-composition seed's \
16043 traversal head from the peer \
16044 Caixa::declared_mesh_slots M3 declared-slot \
16045 enumerator's presence probe",
16046 ),
16047 }
16048 assert_eq!(
16049 c.placement().is_some(),
16050 c.placement.is_some(),
16051 "Caixa::placement().is_some() must byte-equal \
16052 self.placement.is_some() — a presence-bit drift would \
16053 silently split every downstream Option<&Placement> \
16054 consumer's partition on the cluster-default arm",
16055 );
16056 }
16057 }
16058
16059 #[test]
16060 fn declared_mesh_slots_placement_arm_routes_through_accessor() {
16061 // Composition pin: [`Caixa::declared_mesh_slots`]'s
16062 // `:placement` presence-probe arm must key off
16063 // [`Caixa::placement`], not the raw `self.placement.is_some()`
16064 // field-probe. Structurally: a `Caixa { placement:
16065 // Some(Placement::default()), .. }` must still push
16066 // `M3_AUTHOR_KEY_PLACEMENT` onto the declared-slot list (the
16067 // presence bit is `Some`, so the M3 kind-coherence gate must
16068 // surface the slot as "declared" even when every per-axis
16069 // scalar defers to the cluster-default arm), and a `Caixa {
16070 // placement: None, .. }` must NOT push the label (the "author
16071 // omitted the slot entirely" partition). The pair jointly pins
16072 // the accessor + declared-slot enumerator composition: any
16073 // future silent detour that had the accessor collapse
16074 // `Some(Placement::default())` to `None` (a `.filter(|p|
16075 // p.clusters().is_empty().not())` projection) would silently
16076 // absorb the "declared but empty" arm at the accessor boundary
16077 // and the [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
16078 // kind-coherence gate would silently accept a struct-literal
16079 // `Caixa` carrying the drift.
16080 //
16081 // Peer of the sibling
16082 // `declared_servico_slots_limits_arm_routes_through_accessor`
16083 // (b2bd9d7),
16084 // `declared_servico_slots_behavior_arm_routes_through_accessor`
16085 // (35d8b52), and
16086 // `declared_mesh_slots_politicas_arm_routes_through_accessor`
16087 // (5d23d29) composition pins on the sibling `:limits` /
16088 // `:behavior` / `:politicas` outer-`Option<&Composite>` arms
16089 // — same "the enumerator gate must route through the
16090 // substrate-primitive typed dispatch" discipline extended onto
16091 // the second of the three M3 mesh-slot axes so the
16092 // [`Caixa::declared_mesh_slots`] enumerator carries the same
16093 // routing invariant on the `:placement` arm as the peer
16094 // `:politicas` arm.
16095 use crate::aplicacao::Placement;
16096 let c = caixa_aplicacao_with_placement(Some(Placement::default()));
16097 let slots = c.declared_mesh_slots();
16098 assert!(
16099 slots.contains(&crate::render::M3_AUTHOR_KEY_PLACEMENT),
16100 "declared_mesh_slots must push M3_AUTHOR_KEY_PLACEMENT \
16101 when `:placement` is Some (even for Placement::default()) \
16102 — the accessor and the enumerator gate must route through \
16103 the same substrate-primitive typed dispatch on the outer \
16104 :placement presence bit (got slots={slots:?})",
16105 );
16106 let c = caixa_aplicacao_with_placement(None);
16107 let slots = c.declared_mesh_slots();
16108 assert!(
16109 !slots.contains(&crate::render::M3_AUTHOR_KEY_PLACEMENT),
16110 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_PLACEMENT \
16111 when `:placement` is None — the author-omitted arm must \
16112 route through the accessor's None-return unchanged (got \
16113 slots={slots:?})",
16114 );
16115 }
16116
16117 #[test]
16118 fn aplicacao_view_placement_arm_folds_through_accessor() {
16119 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:placement`
16120 // Aplicacao-composition seed must fold through
16121 // [`Caixa::placement`], not the raw
16122 // `self.placement.clone().unwrap_or_default()` field-borrow.
16123 // Structurally: a `Caixa { placement: Some(Placement {
16124 // estrategia: Replicated, clusters: ["rio"], .. default }),
16125 // kind: Aplicacao, .. }` must surface a projected
16126 // [`crate::AplicacaoSpec`] whose `placement().estrategia()` +
16127 // `placement().clusters()` byte-equal the outer composite's
16128 // authored values (the fold must project the authored
16129 // composite verbatim), a `Caixa { placement:
16130 // Some(Placement::default()), kind: Aplicacao, .. }` must
16131 // surface an [`crate::AplicacaoSpec`] whose `placement()`
16132 // byte-equals [`crate::aplicacao::Placement::default`] (the
16133 // fold's empty-composite arm collapses to the same default
16134 // the author-omitted arm does), and a `Caixa { placement:
16135 // None, kind: Aplicacao, .. }` must surface an
16136 // [`crate::AplicacaoSpec`] whose `placement()` byte-equals
16137 // [`crate::aplicacao::Placement::default`] (the "author
16138 // omitted the slot entirely" arm folds through the
16139 // `unwrap_or_default` onto the cluster-default). The triad
16140 // jointly pins the accessor + Aplicacao-composition seed
16141 // composition: any future silent detour that had the accessor
16142 // divert the raw slot away from the seed's fold (an operator-
16143 // resolved overlay's default-fold arm silently differing from
16144 // the raw slot's default-fold arm) would silently split the
16145 // build-time distribution-artifact emission gate from the
16146 // caixa-mesh renderer's Aplicacao-view input at the
16147 // composition boundary.
16148 use crate::aplicacao::{Placement, PlacementStrategy};
16149 let c = caixa_aplicacao_with_placement(Some(Placement {
16150 estrategia: PlacementStrategy::Replicated,
16151 clusters: vec!["rio".into()],
16152 affinity: None,
16153 shard_key: None,
16154 }));
16155 let view = c.aplicacao_view().unwrap();
16156 assert_eq!(
16157 view.placement().estrategia(),
16158 PlacementStrategy::Replicated,
16159 "Caixa::aplicacao_view must fold the authored :placement \
16160 :estrategia scalar through the accessor verbatim onto the \
16161 projected AplicacaoSpec — a future silent detour at the \
16162 seed's fold arm would surface here as a projected-scalar \
16163 drift (got {:?})",
16164 view.placement().estrategia(),
16165 );
16166 assert_eq!(
16167 view.placement().clusters(),
16168 &["rio"],
16169 "Caixa::aplicacao_view must fold the authored :placement \
16170 :clusters list through the accessor verbatim onto the \
16171 projected AplicacaoSpec — a future silent detour at the \
16172 seed's fold arm would surface here as a projected-list \
16173 drift (got {:?})",
16174 view.placement().clusters(),
16175 );
16176 let c = caixa_aplicacao_with_placement(Some(Placement::default()));
16177 let view = c.aplicacao_view().unwrap();
16178 assert_eq!(
16179 view.placement(),
16180 &Placement::default(),
16181 "Caixa::aplicacao_view must fold Some(Placement::default()) \
16182 through the accessor onto Placement::default — the empty- \
16183 composite arm collapses to the same default the author- \
16184 omitted arm does (got {:?})",
16185 view.placement(),
16186 );
16187 let c = caixa_aplicacao_with_placement(None);
16188 let view = c.aplicacao_view().unwrap();
16189 assert_eq!(
16190 view.placement(),
16191 &Placement::default(),
16192 "Caixa::aplicacao_view must fold None through the accessor's \
16193 unwrap_or_default onto Placement::default — the author- \
16194 omitted arm must route through the accessor's None-return \
16195 unchanged (got {:?})",
16196 view.placement(),
16197 );
16198 }
16199
16200 #[test]
16201 fn placement_projects_option_ref_by_borrow() {
16202 // The by-borrow pin: [`Caixa::placement`] returns
16203 // `Option<&Placement>` by borrow — the returned reference
16204 // borrows the underlying `Option<Placement>` storage of the
16205 // `:placement` slot and the accessor must not clone the
16206 // backing composite on every call. Peer of the sibling
16207 // `limits_projects_option_ref_by_borrow` (b2bd9d7),
16208 // `behavior_projects_option_ref_by_borrow` (35d8b52), and
16209 // `politicas_projects_option_ref_by_borrow` (5d23d29) by-borrow
16210 // pins on the outer top-level [`Caixa`]
16211 // `Option<&Composite>`-return sub-family — extended here to
16212 // the fourth axis of the same sub-family: the accessor's
16213 // returned reference must borrow from `&self` (the returned
16214 // reference's lifetime is tied to `&self`), and calling the
16215 // accessor twice on the same [`Caixa`] must yield references
16216 // that are pointer-equal (the underlying byte-buffer is the
16217 // storage `Placement`'s allocation, not a fresh copy) as well
16218 // as value-equal (idempotent, no side effects on `&self`).
16219 //
16220 // Pins against a future silent detour that returned an owned
16221 // `Placement` (which would type-check via the `Clone` impl
16222 // but silently clone on every call), a `&Placement` panic-
16223 // return on the `None` arm (which would collapse the load-
16224 // bearing `Option` presence-bit into a runtime panic), or a
16225 // one-arm-only accessor that returned a saturating composite
16226 // on some sentinel input.
16227 use crate::aplicacao::{Placement, PlacementStrategy};
16228 for placement in [
16229 Some(Placement::default()),
16230 Some(Placement {
16231 estrategia: PlacementStrategy::Sharded,
16232 clusters: vec!["rio".into(), "sao-paulo".into()],
16233 affinity: Some("data-locality".into()),
16234 shard_key: Some("$tenantId".into()),
16235 }),
16236 ] {
16237 let c = caixa_aplicacao_with_placement(placement.clone());
16238 let first = c.placement().unwrap();
16239 let second = c.placement().unwrap();
16240 assert_eq!(
16241 first, second,
16242 "Caixa::placement must be idempotent — two successive \
16243 calls on the same &self must return the same \
16244 &Placement",
16245 );
16246 assert!(
16247 std::ptr::eq(first, second),
16248 "Caixa::placement must borrow the underlying \
16249 Option<Placement> storage — two successive calls \
16250 must return references with the same backing pointer \
16251 (a fresh Placement clone would change the pointer on \
16252 every call)",
16253 );
16254 assert_eq!(
16255 Some(first),
16256 placement.as_ref(),
16257 "Caixa::placement must return :placement verbatim by \
16258 borrow — got {first:?}, expected {:?}",
16259 placement.as_ref(),
16260 );
16261 }
16262 let c = caixa_aplicacao_with_placement(None);
16263 assert!(
16264 c.placement().is_none(),
16265 "Caixa::placement must return None when :placement is \
16266 absent — the author-omitted arm must project through the \
16267 accessor's Option::None unchanged",
16268 );
16269 }
16270
16271 // ── Caixa::entrada — outer top-level Option<&Entrada> composite-reference accessor ──
16272
16273 fn caixa_aplicacao_with_entrada(entrada: Option<crate::aplicacao::Entrada>) -> Caixa {
16274 use crate::aplicacao::{Membro, WitContract};
16275 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16276 c.kind = CaixaKind::Aplicacao;
16277 c.membros = vec![Membro {
16278 caixa: "a".into(),
16279 versao: "^0.1".into(),
16280 }];
16281 c.contratos = vec![WitContract {
16282 de: "a".into(),
16283 para: "a".into(),
16284 wit: "wasi:http/proxy".into(),
16285 endpoint: Some("/x".into()),
16286 subject: None,
16287 slot: None,
16288 }];
16289 c.entrada = entrada;
16290 c
16291 }
16292
16293 #[test]
16294 fn entrada_returns_entrada_option_ref_verbatim_across_permutations() {
16295 // The canonical per-`Caixa` `:entrada` M3 mesh-slot outer-
16296 // composite optional-composite-reference-shape pin:
16297 // [`Caixa::entrada`] must return the `:entrada` typed
16298 // `Option<Entrada>` verbatim as an `Option<&Entrada>`
16299 // reference over the same backing storage the raw
16300 // `self.entrada.as_ref()` field access borrows from,
16301 // byte-equal across every representative fixture in the
16302 // accept-set — the author-omitted `None` shape (the
16303 // "cluster-internal Aplicacao" partition every downstream
16304 // Gateway-API emitter treats as "emit no listener + no
16305 // HTTPRoute"), a bare-`host`/`para` minimum-composite fixture
16306 // (empty `paths` — the resolved-paths fallback the peer
16307 // [`crate::aplicacao::Entrada::resolved_paths`] cascade folds
16308 // onto the substrate catch-all), and a fully-populated
16309 // multi-path-with-non-default-port fixture (the canonical
16310 // shape a public HTTP Aplicacao carries).
16311 //
16312 // Pins against a future silent detour that returned a fresh-
16313 // cloned [`crate::aplicacao::Entrada`] copy (which would
16314 // type-check via the `Clone` impl but silently break every
16315 // downstream caller that relied on the reference sharing the
16316 // composite's backing identity), a reference to an operator-
16317 // resolved overlay (the future per-cluster
16318 // `:entrada-overrides` slot — its resolution must land at
16319 // exactly this accessor body, not silently divert the raw
16320 // slot away from the peer [`Caixa::declared_mesh_slots`]
16321 // enumerator's presence probe), or an axis-shuffled projection
16322 // (a future detour that swapped `host` and `para` through the
16323 // accessor would silently split the paired
16324 // [`Caixa::aplicacao_view`] seed's forward input from the
16325 // sibling M3 gateway-artifact emitter's projection input).
16326 //
16327 // Fifth and final outer top-level [`Caixa`]
16328 // `Option<&Composite>`-return composite-reference accessor pin
16329 // on the substrate primitive — peer of the sibling
16330 // `limits_returns_limits_option_ref_verbatim_across_permutations`
16331 // (b2bd9d7),
16332 // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
16333 // (35d8b52),
16334 // `politicas_returns_politicas_option_ref_verbatim_across_permutations`
16335 // (5d23d29), and
16336 // `placement_returns_placement_option_ref_verbatim_across_permutations`
16337 // (4fb8074) opening tetrad pins on the outer top-level
16338 // [`Caixa`] `Option<&Composite>`-return sub-family — extended
16339 // here to the third and final M3 mesh-slot axis so the closed
16340 // outer `Option<&Composite>` sub-family carries the same
16341 // "byte-equal, borrow-shared, presence-bit-preserved" outer-
16342 // accessor discipline across all five arms.
16343 use crate::aplicacao::Entrada;
16344 let fixtures: Vec<Option<Entrada>> = vec![
16345 None,
16346 Some(Entrada {
16347 host: "checkout.quero.cloud".into(),
16348 para: "gateway".into(),
16349 paths: Vec::new(),
16350 port: crate::DEFAULT_SERVICO_PORT,
16351 }),
16352 Some(Entrada {
16353 host: "api.pleme.io".into(),
16354 para: "public-api".into(),
16355 paths: vec!["/v1".into(), "/v2".into()],
16356 port: 8080,
16357 }),
16358 ];
16359 for entrada in fixtures {
16360 let c = caixa_aplicacao_with_entrada(entrada.clone());
16361 assert_eq!(
16362 c.entrada(),
16363 entrada.as_ref(),
16364 "Caixa::entrada must return :entrada verbatim (got \
16365 {:?}, expected {:?})",
16366 c.entrada(),
16367 entrada.as_ref(),
16368 );
16369 match (c.entrada(), c.entrada.as_ref()) {
16370 (Some(a), Some(b)) => assert!(
16371 std::ptr::eq(a, b),
16372 "Caixa::entrada accessor and self.entrada.as_ref() \
16373 field access must borrow the same backing storage \
16374 — the accessor is the substrate-primitive typed \
16375 dispatch every downstream Aplicacao-external- \
16376 gateway composite consumer must route through, and \
16377 a reference-identity split would silently break \
16378 every consumer that relied on the borrow sharing \
16379 the composite's storage",
16380 ),
16381 (None, None) => {}
16382 _ => panic!(
16383 "Caixa::entrada presence bit must byte-equal \
16384 self.entrada.is_some() — a presence-bit drift \
16385 would silently split the paired \
16386 Caixa::aplicacao_view Aplicacao-composition seed's \
16387 traversal head from the peer \
16388 Caixa::declared_mesh_slots M3 declared-slot \
16389 enumerator's presence probe",
16390 ),
16391 }
16392 assert_eq!(
16393 c.entrada().is_some(),
16394 c.entrada.is_some(),
16395 "Caixa::entrada().is_some() must byte-equal \
16396 self.entrada.is_some() — a presence-bit drift would \
16397 silently split every downstream Option<&Entrada> \
16398 consumer's partition on the cluster-internal arm",
16399 );
16400 }
16401 }
16402
16403 #[test]
16404 fn declared_mesh_slots_entrada_arm_routes_through_accessor() {
16405 // Composition pin: [`Caixa::declared_mesh_slots`]'s `:entrada`
16406 // presence-probe arm must key off [`Caixa::entrada`], not the
16407 // raw `self.entrada.is_some()` field-probe. Structurally: a
16408 // `Caixa { entrada: Some(Entrada { host: "...", para: "...",
16409 // paths: [], port: DEFAULT_SERVICO_PORT }), .. }` must push
16410 // `M3_AUTHOR_KEY_ENTRADA` onto the declared-slot list (the
16411 // presence bit is `Some`, so the M3 kind-coherence gate must
16412 // surface the slot as "declared" even when every per-axis
16413 // scalar defers to the substrate catch-all / default port),
16414 // and a `Caixa { entrada: None, .. }` must NOT push the label
16415 // (the "author omitted the slot entirely" partition). The pair
16416 // jointly pins the accessor + declared-slot enumerator
16417 // composition: any future silent detour that had the accessor
16418 // collapse `Some(Entrada { paths: [], .. })` to `None` (a
16419 // `.filter(|e| !e.paths.is_empty())` projection) would silently
16420 // absorb the "declared but empty-paths" arm at the accessor
16421 // boundary and the
16422 // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
16423 // coherence gate would silently accept a struct-literal
16424 // `Caixa` carrying the drift.
16425 //
16426 // Peer of the sibling
16427 // `declared_servico_slots_limits_arm_routes_through_accessor`
16428 // (b2bd9d7),
16429 // `declared_servico_slots_behavior_arm_routes_through_accessor`
16430 // (35d8b52),
16431 // `declared_mesh_slots_politicas_arm_routes_through_accessor`
16432 // (5d23d29), and
16433 // `declared_mesh_slots_placement_arm_routes_through_accessor`
16434 // (4fb8074) composition pins on the sibling `:limits` /
16435 // `:behavior` / `:politicas` / `:placement` outer-
16436 // `Option<&Composite>` arms — same "the enumerator gate must
16437 // route through the substrate-primitive typed dispatch"
16438 // discipline extended onto the third and final M3 mesh-slot
16439 // axis so the [`Caixa::declared_mesh_slots`] enumerator now
16440 // carries the routing invariant on every M3 mesh-slot arm.
16441 use crate::aplicacao::Entrada;
16442 let c = caixa_aplicacao_with_entrada(Some(Entrada {
16443 host: "checkout.quero.cloud".into(),
16444 para: "gateway".into(),
16445 paths: Vec::new(),
16446 port: crate::DEFAULT_SERVICO_PORT,
16447 }));
16448 let slots = c.declared_mesh_slots();
16449 assert!(
16450 slots.contains(&crate::render::M3_AUTHOR_KEY_ENTRADA),
16451 "declared_mesh_slots must push M3_AUTHOR_KEY_ENTRADA when \
16452 `:entrada` is Some (even for empty-paths / default-port) \
16453 — the accessor and the enumerator gate must route through \
16454 the same substrate-primitive typed dispatch on the outer \
16455 :entrada presence bit (got slots={slots:?})",
16456 );
16457 let c = caixa_aplicacao_with_entrada(None);
16458 let slots = c.declared_mesh_slots();
16459 assert!(
16460 !slots.contains(&crate::render::M3_AUTHOR_KEY_ENTRADA),
16461 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_ENTRADA \
16462 when `:entrada` is None — the author-omitted arm must \
16463 route through the accessor's None-return unchanged (got \
16464 slots={slots:?})",
16465 );
16466 }
16467
16468 #[test]
16469 fn aplicacao_view_entrada_arm_folds_through_accessor() {
16470 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:entrada`
16471 // Aplicacao-composition seed must fold through
16472 // [`Caixa::entrada`], not the raw `self.entrada.clone()` field-
16473 // borrow. Structurally: a `Caixa { entrada: Some(Entrada {
16474 // host: "api.pleme.io", para: "public-api", paths: ["/v1"],
16475 // port: 8080 }), kind: Aplicacao, .. }` must surface a projected
16476 // [`crate::AplicacaoSpec`] whose `entrada().unwrap()` byte-
16477 // equals the outer composite's authored value (the fold must
16478 // project the authored composite verbatim), and a `Caixa {
16479 // entrada: None, kind: Aplicacao, .. }` must surface an
16480 // [`crate::AplicacaoSpec`] whose `entrada()` is `None` (the
16481 // "author omitted the slot entirely" arm folds through the
16482 // accessor's `Option::cloned` onto the same `None` presence
16483 // bit — unlike the peer `:politicas` / `:placement` arms
16484 // `:entrada` has no cluster-default fold, the omitted arm
16485 // stays omitted). The pair jointly pins the accessor +
16486 // Aplicacao-composition seed composition: any future silent
16487 // detour that had the accessor divert the raw slot away from
16488 // the seed's fold (an operator-resolved overlay's forward arm
16489 // silently differing from the raw slot's forward arm) would
16490 // silently split the build-time gateway-artifact emission gate
16491 // from the caixa-mesh renderer's Aplicacao-view input at the
16492 // composition boundary.
16493 use crate::aplicacao::Entrada;
16494 let authored = Entrada {
16495 host: "api.pleme.io".into(),
16496 para: "public-api".into(),
16497 paths: vec!["/v1".into()],
16498 port: 8080,
16499 };
16500 let c = caixa_aplicacao_with_entrada(Some(authored.clone()));
16501 let view = c.aplicacao_view().unwrap();
16502 assert_eq!(
16503 view.entrada(),
16504 Some(&authored),
16505 "Caixa::aplicacao_view must fold the authored :entrada \
16506 composite through the accessor verbatim onto the \
16507 projected AplicacaoSpec — a future silent detour at the \
16508 seed's fold arm would surface here as a projected- \
16509 composite drift (got {:?})",
16510 view.entrada(),
16511 );
16512 let c = caixa_aplicacao_with_entrada(None);
16513 let view = c.aplicacao_view().unwrap();
16514 assert!(
16515 view.entrada().is_none(),
16516 "Caixa::aplicacao_view must fold None through the \
16517 accessor's Option::cloned onto None — the author- \
16518 omitted arm must route through the accessor's None-return \
16519 unchanged (got {:?})",
16520 view.entrada(),
16521 );
16522 }
16523
16524 #[test]
16525 fn entrada_projects_option_ref_by_borrow() {
16526 // The by-borrow pin: [`Caixa::entrada`] returns
16527 // `Option<&Entrada>` by borrow — the returned reference
16528 // borrows the underlying `Option<Entrada>` storage of the
16529 // `:entrada` slot and the accessor must not clone the backing
16530 // composite on every call. Peer of the sibling
16531 // `limits_projects_option_ref_by_borrow` (b2bd9d7),
16532 // `behavior_projects_option_ref_by_borrow` (35d8b52),
16533 // `politicas_projects_option_ref_by_borrow` (5d23d29), and
16534 // `placement_projects_option_ref_by_borrow` (4fb8074) by-
16535 // borrow pins on the outer top-level [`Caixa`]
16536 // `Option<&Composite>`-return sub-family — extended here to
16537 // the fifth and final axis of the same sub-family, closing
16538 // the discipline: the accessor's returned reference must
16539 // borrow from `&self` (the returned reference's lifetime is
16540 // tied to `&self`), and calling the accessor twice on the
16541 // same [`Caixa`] must yield references that are pointer-equal
16542 // (the underlying byte-buffer is the storage `Entrada`'s
16543 // allocation, not a fresh copy) as well as value-equal
16544 // (idempotent, no side effects on `&self`).
16545 //
16546 // Pins against a future silent detour that returned an owned
16547 // `Entrada` (which would type-check via the `Clone` impl but
16548 // silently clone on every call), a `&Entrada` panic-return on
16549 // the `None` arm (which would collapse the load-bearing
16550 // `Option` presence-bit into a runtime panic), or a one-arm-
16551 // only accessor that returned a saturating composite on some
16552 // sentinel input.
16553 use crate::aplicacao::Entrada;
16554 for entrada in [
16555 Some(Entrada {
16556 host: "checkout.quero.cloud".into(),
16557 para: "gateway".into(),
16558 paths: Vec::new(),
16559 port: crate::DEFAULT_SERVICO_PORT,
16560 }),
16561 Some(Entrada {
16562 host: "api.pleme.io".into(),
16563 para: "public-api".into(),
16564 paths: vec!["/v1".into(), "/v2".into()],
16565 port: 8080,
16566 }),
16567 ] {
16568 let c = caixa_aplicacao_with_entrada(entrada.clone());
16569 let first = c.entrada().unwrap();
16570 let second = c.entrada().unwrap();
16571 assert_eq!(
16572 first, second,
16573 "Caixa::entrada must be idempotent — two successive \
16574 calls on the same &self must return the same &Entrada",
16575 );
16576 assert!(
16577 std::ptr::eq(first, second),
16578 "Caixa::entrada must borrow the underlying \
16579 Option<Entrada> storage — two successive calls must \
16580 return references with the same backing pointer (a \
16581 fresh Entrada clone would change the pointer on every \
16582 call)",
16583 );
16584 assert_eq!(
16585 Some(first),
16586 entrada.as_ref(),
16587 "Caixa::entrada must return :entrada verbatim by \
16588 borrow — got {first:?}, expected {:?}",
16589 entrada.as_ref(),
16590 );
16591 }
16592 let c = caixa_aplicacao_with_entrada(None);
16593 assert!(
16594 c.entrada().is_none(),
16595 "Caixa::entrada must return None when :entrada is absent \
16596 — the author-omitted arm must project through the \
16597 accessor's Option::None unchanged",
16598 );
16599 }
16600
16601 // ── Caixa::estrategia — outer top-level Option<RestartStrategy> flat-spread supervisor-tree accessor ──
16602
16603 fn caixa_with_estrategia(estrategia: Option<crate::supervisor::RestartStrategy>) -> Caixa {
16604 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16605 c.estrategia = estrategia;
16606 c
16607 }
16608
16609 #[test]
16610 fn estrategia_returns_estrategia_option_verbatim_across_permutations() {
16611 // The canonical per-`Caixa` `:estrategia` M2 supervisor-tree-slot
16612 // flat-spread `Option<RestartStrategy>`-return `Copy`-composite-
16613 // enum-arm scalar shape pin: [`Caixa::estrategia`] must return
16614 // the `:estrategia` typed `Option<crate::supervisor::RestartStrategy>`
16615 // verbatim as an `Option<RestartStrategy>` `Copy`-projected value
16616 // over the same discriminant the raw `self.estrategia` field
16617 // access carries, byte-equal across every representative fixture
16618 // in the accept-set — the author-omitted `None` shape (the
16619 // "defer to [`RestartStrategy::default`] through the
16620 // [`Self::supervisor_view`] `unwrap_or_default()` fold" partition
16621 // every non-`Supervisor`-kind `defcaixa` carries by
16622 // `#[serde(default)]`), and each of the four closed-set variants
16623 // [`RestartStrategy::OneForOne`] / [`RestartStrategy::OneForAll`]
16624 // / [`RestartStrategy::RestForOne`] /
16625 // [`RestartStrategy::SimpleOneForOne`] the author-declared arm
16626 // partitions on.
16627 //
16628 // Pins against a future silent detour that re-derived the
16629 // strategy from a peer axis (an accidental fallback to
16630 // `if children.is_empty() { SimpleOneForOne } else { OneForOne }`
16631 // collapse that read the outer `:children` list-length axis into
16632 // the strategy discriminator at the accessor boundary), a
16633 // stale-derive detour that substituted [`RestartStrategy::default`]
16634 // when the outer `Option` held `None` (which would silently
16635 // collapse the load-bearing "author explicitly declared
16636 // `:estrategia OneForOne`" vs "author omitted the slot and
16637 // inherited the default" partition the [`Self::declared_supervisor_slots`]
16638 // presence-probe reads — the enumerator gate would still push
16639 // `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA` on the omitted arm, silently
16640 // splitting the paired [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
16641 // kind-coherence gate's traversal head from the
16642 // [`Self::supervisor_view`] `unwrap_or_default()` fold's
16643 // composition head), a reference to an operator-resolved overlay
16644 // (the future per-cluster `:estrategia-overrides` slot — its
16645 // resolution must land at exactly this accessor body, not
16646 // silently divert the raw slot away from a second consumer), or
16647 // an axis-remap projection (a future detour that mapped
16648 // `OneForAll` through the accessor onto `OneForOne` would
16649 // silently split every downstream sibling-restart-strategy
16650 // consumer's per-arm fan-out).
16651 //
16652 // First outer top-level [`Caixa`] `Option<Copy>`-return
16653 // supervisor-tree-slot flat-spread accessor pin on the substrate
16654 // primitive — opens the outer-`Caixa` `Option<Copy>` flat-spread
16655 // projection pattern the sibling per-`Caixa` `:max-restarts` /
16656 // `:restart-window` future outer-scalar pins fold on. Peer of
16657 // the inner-altitude
16658 // `supervisor_spec_estrategia_returns_estrategia_verbatim_across_permutations`
16659 // (eafb619) pin on the post-composition [`SupervisorSpec`]
16660 // altitude — same "the substrate-primitive accessor must byte-
16661 // equal the raw field access verbatim across every author-
16662 // declared value" discipline extended onto the pre-composition
16663 // outer author-surface [`Caixa`] altitude. Peer of the closed
16664 // outer-`Caixa` `Option<&Composite>` composite-reference family
16665 // the sibling `limits` / `behavior` / `politicas` / `placement` /
16666 // `entrada`
16667 // `..._returns_..._option_ref_verbatim_across_permutations` pins
16668 // already carry on the outer `Option<&Composite>` altitude.
16669 use crate::supervisor::RestartStrategy;
16670 let fixtures: Vec<Option<RestartStrategy>> = vec![
16671 None,
16672 Some(RestartStrategy::OneForOne),
16673 Some(RestartStrategy::OneForAll),
16674 Some(RestartStrategy::RestForOne),
16675 Some(RestartStrategy::SimpleOneForOne),
16676 ];
16677 for estrategia in fixtures {
16678 let c = caixa_with_estrategia(estrategia);
16679 assert_eq!(
16680 c.estrategia(),
16681 estrategia,
16682 "Caixa::estrategia must return :estrategia verbatim (got \
16683 {:?}, expected {:?})",
16684 c.estrategia(),
16685 estrategia,
16686 );
16687 assert_eq!(
16688 c.estrategia(),
16689 c.estrategia,
16690 "Caixa::estrategia accessor and self.estrategia field \
16691 access must byte-equal — the accessor is the substrate-\
16692 primitive typed dispatch every downstream supervisor-\
16693 tree flat-spread consumer must route through, and a \
16694 discriminant split would silently break every consumer \
16695 that relied on the accessor sharing the field's own \
16696 Option<Copy> shape",
16697 );
16698 assert_eq!(
16699 c.estrategia().is_some(),
16700 c.estrategia.is_some(),
16701 "Caixa::estrategia().is_some() must byte-equal \
16702 self.estrategia.is_some() — a presence-bit drift would \
16703 silently split the paired Caixa::declared_supervisor_slots \
16704 presence-probe arm from the Caixa::supervisor_view \
16705 unwrap_or_default() fold's composition input",
16706 );
16707 }
16708 }
16709
16710 #[test]
16711 fn declared_supervisor_slots_estrategia_arm_routes_through_accessor() {
16712 // Composition pin: [`Caixa::declared_supervisor_slots`]'s
16713 // `:estrategia` presence-probe arm must key off
16714 // [`Caixa::estrategia`], not the raw `self.estrategia.is_some()`
16715 // field-probe. Structurally: every `Caixa { estrategia:
16716 // Some(RestartStrategy::_), .. }` variant must push
16717 // `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA` onto the declared-slot list
16718 // (the presence bit is `Some` for every closed-set variant, so
16719 // the M2 supervisor-tree kind-coherence gate must surface the
16720 // slot as "declared" regardless of which variant the author
16721 // picked), and a `Caixa { estrategia: None, .. }` must NOT push
16722 // the label (the "author omitted the slot entirely, deferring
16723 // to [`RestartStrategy::default`] through the supervisor_view
16724 // fold" partition). The pair jointly pins the accessor +
16725 // declared-slot enumerator composition: any future silent detour
16726 // that had the accessor collapse `Some(RestartStrategy::default())`
16727 // to `None` (a `.filter(|e| *e != RestartStrategy::default())`
16728 // projection) would silently absorb the "declared but default-
16729 // valued" arm at the accessor boundary and the
16730 // [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
16731 // coherence gate would silently accept a struct-literal `Caixa`
16732 // carrying the drift.
16733 //
16734 // Peer of the sibling per-`Caixa`
16735 // `declared_servico_slots_limits_arm_routes_through_accessor`
16736 // (b2bd9d7) accessor-composition pin on the sibling outer-`Caixa`
16737 // `Option<&LimitsSpec>` composition axis — same "the enumerator
16738 // gate must route through the substrate-primitive typed
16739 // dispatch" discipline extended onto the flat-spread M2
16740 // supervisor-tree `Option<RestartStrategy>`-composition surface,
16741 // opening the outer-`Caixa` supervisor-tree-slot arm of the
16742 // composition-pin family.
16743 use crate::supervisor::RestartStrategy;
16744 for estrategia in [
16745 RestartStrategy::OneForOne,
16746 RestartStrategy::OneForAll,
16747 RestartStrategy::RestForOne,
16748 RestartStrategy::SimpleOneForOne,
16749 ] {
16750 let c = caixa_with_estrategia(Some(estrategia));
16751 let slots = c.declared_supervisor_slots();
16752 assert!(
16753 slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA),
16754 "declared_supervisor_slots must push \
16755 SUPERVISOR_AUTHOR_KEY_ESTRATEGIA when `:estrategia` is \
16756 Some({estrategia:?}) — the accessor and the enumerator \
16757 gate must route through the same substrate-primitive \
16758 typed dispatch on the outer :estrategia presence bit \
16759 (got slots={slots:?})",
16760 );
16761 }
16762 let c = caixa_with_estrategia(None);
16763 let slots = c.declared_supervisor_slots();
16764 assert!(
16765 !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA),
16766 "declared_supervisor_slots must NOT push \
16767 SUPERVISOR_AUTHOR_KEY_ESTRATEGIA when `:estrategia` is None \
16768 — the author-omitted arm must route through the accessor's \
16769 None-return unchanged (got slots={slots:?})",
16770 );
16771 }
16772
16773 #[test]
16774 fn supervisor_view_estrategia_arm_routes_through_accessor() {
16775 // Composition pin: [`Caixa::supervisor_view`]'s per-`:estrategia`
16776 // [`SupervisorSpec`] construction arm must key off
16777 // [`Caixa::estrategia`]'s `unwrap_or_default()` fold, not the raw
16778 // `self.estrategia.unwrap_or_default()` field-fold. Structurally:
16779 // for every `:kind Supervisor` `Caixa` carrying an author-
16780 // declared `Some(RestartStrategy::_)` variant, the composed
16781 // [`SupervisorSpec`]'s `.estrategia` field must byte-equal the
16782 // outer accessor's declared variant unchanged; and for a
16783 // `:kind Supervisor` `Caixa` carrying `None`, the composed
16784 // [`SupervisorSpec`]'s `.estrategia` field must byte-equal
16785 // [`RestartStrategy::default`] (the [`RestartStrategy::OneForOne`]
16786 // arm the flat-spread `unwrap_or_default()` fold projects to on
16787 // the author-omitted arm — this is the *composition* between the
16788 // outer `Option<RestartStrategy>` accessor's presence-bit
16789 // surface and the inner post-composition non-`Option`
16790 // [`SupervisorSpec::estrategia`] altitude). The pair jointly
16791 // pins the accessor + supervisor_view composition: any future
16792 // silent detour that had the accessor promote `None` to
16793 // `Some(RestartStrategy::default())` (a `.or_else(|| Some(RestartStrategy::default()))`
16794 // projection) would silently collapse the two arms into one at
16795 // the accessor boundary and the [`Self::declared_supervisor_slots`]
16796 // presence probe would silently drift from the composition site.
16797 //
16798 // Peer of the sibling M2 supervisor-slot post-composition
16799 // `validate_reads_through_lifted_estrategia_accessor` (eafb619)
16800 // pin on the [`SupervisorSpec::validate`] altitude — this pin
16801 // extends that inner-altitude accessor-routing discipline onto
16802 // the pre-composition outer author-surface [`Caixa`] altitude,
16803 // pinning the composition edge between the flat-spread outer
16804 // `Option<RestartStrategy>` and the composed [`SupervisorSpec`]
16805 // `RestartStrategy` axes.
16806 use crate::CaixaKind;
16807 use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
16808 for estrategia in [
16809 RestartStrategy::OneForOne,
16810 RestartStrategy::OneForAll,
16811 RestartStrategy::RestForOne,
16812 RestartStrategy::SimpleOneForOne,
16813 ] {
16814 let mut c = caixa_with_estrategia(Some(estrategia));
16815 c.kind = CaixaKind::Supervisor;
16816 // Route the `SimpleOneForOne ↔ non-SimpleOneForOne` fixture-
16817 // shape partition through the [`gen_platform::IsVariant`]
16818 // derive-generated
16819 // [`RestartStrategy::is_simple_one_for_one`] predicate rather
16820 // than the raw `matches!(estrategia, RestartStrategy::
16821 // SimpleOneForOne)` open-coded pattern-match — same closed-
16822 // set-typed-enum arm-discriminator dispatch discipline the
16823 // sibling [`crate::upgrade::UpgradeInstruction::is_restart`]
16824 // convergence (915a934) extended onto its two paired positive
16825 // / negated `matches!` sites and the peer
16826 // [`crate::aplicacao::PlacementStrategy`] `IsVariant`-derived
16827 // predicate convergence (766ec63) extended onto the M3 mesh-
16828 // slot per-`:placement` distribution-strategy discriminator
16829 // axis. See the sibling `supervisor::tests::
16830 // round_trip_all_strategies` and
16831 // `supervisor::tests::supervisor_spec_estrategia_returns_estrategia_verbatim_across_permutations`
16832 // fixtures — the three sites (all test-only,
16833 // acknowledged in 915a934's Prior-commits footnote as the
16834 // outstanding follow-up) now consult one typed dispatch on
16835 // the substrate primitive.
16836 c.children = if estrategia.is_simple_one_for_one() {
16837 Vec::new()
16838 } else {
16839 vec![ChildSpec {
16840 caixa: "worker".into(),
16841 versao: "^0.1".into(),
16842 restart: RestartPolicy::Permanent,
16843 }]
16844 };
16845 let view = c.supervisor_view().expect(
16846 "supervisor_view must materialize a SupervisorSpec for a \
16847 :kind Supervisor Caixa carrying a Some(:estrategia) slot",
16848 );
16849 assert_eq!(
16850 view.estrategia(),
16851 c.estrategia().unwrap(),
16852 "supervisor_view must carry the outer Caixa::estrategia() \
16853 declared variant onto the composed SupervisorSpec.estrategia \
16854 field verbatim on the Some arm (got {:?}, expected {:?})",
16855 view.estrategia(),
16856 c.estrategia().unwrap(),
16857 );
16858 }
16859 // The author-omitted arm: outer `None` → composed
16860 // `RestartStrategy::default()` through the flat-spread
16861 // `unwrap_or_default()` fold.
16862 let mut c = caixa_with_estrategia(None);
16863 c.kind = CaixaKind::Supervisor;
16864 // Populate children so the sibling supervisor slots are coherent
16865 // for the [`Self::supervisor_view`] projection; the `:estrategia`
16866 // arm still defers to [`RestartStrategy::default`] on the
16867 // author-omitted arm even when the sibling slots carry values.
16868 c.children = vec![ChildSpec {
16869 caixa: "worker".into(),
16870 versao: "^0.1".into(),
16871 restart: RestartPolicy::Permanent,
16872 }];
16873 let view = c.supervisor_view().expect(
16874 "supervisor_view must materialize a SupervisorSpec for a \
16875 :kind Supervisor Caixa carrying a None `:estrategia` slot",
16876 );
16877 assert_eq!(
16878 view.estrategia(),
16879 RestartStrategy::default(),
16880 "supervisor_view must project the outer Caixa::estrategia() \
16881 None arm onto RestartStrategy::default() through the flat-\
16882 spread unwrap_or_default() fold (got {:?}, expected {:?})",
16883 view.estrategia(),
16884 RestartStrategy::default(),
16885 );
16886 assert!(
16887 c.estrategia().is_none(),
16888 "Caixa::estrategia() must remain None on the author-omitted \
16889 arm — the supervisor_view fold must not mutate the outer \
16890 flat-spread presence bit",
16891 );
16892 }
16893
16894 #[test]
16895 fn estrategia_projects_option_by_copy() {
16896 // The by-`Copy` pin: [`Caixa::estrategia`] returns
16897 // `Option<RestartStrategy>` by value (`RestartStrategy: Copy`) —
16898 // the accessor does not borrow `&self` past the call (no
16899 // lifetime on the return type), and calling the accessor twice
16900 // on the same [`Caixa`] must yield discriminant-equal values
16901 // (idempotent, no side effects on `&self`). Peer of the sibling
16902 // outer-`Caixa` `Option<&Composite>` by-borrow
16903 // `limits_projects_option_ref_by_borrow` (b2bd9d7) /
16904 // `behavior_projects_option_ref_by_borrow` (35d8b52) /
16905 // `politicas_projects_option_ref_by_borrow` (5d23d29) /
16906 // `placement_projects_option_ref_by_borrow` (4fb8074) /
16907 // `entrada_projects_option_ref_by_borrow` (e4128e4) by-borrow
16908 // pins on the outer-`Caixa` `Option<&Composite>`-return axes —
16909 // extended here to the outer-`Caixa` `Option<Copy>`-return
16910 // flat-spread axis. The `Copy` discipline replaces the pointer-
16911 // equality claim the by-borrow siblings pin (a fresh `Copy` of a
16912 // `Copy` discriminant is definitionally the same discriminant, so
16913 // the axis reduces to discriminant equality).
16914 //
16915 // Pins against a future silent detour that returned a fresh
16916 // `Option<&RestartStrategy>` (which would type-check but silently
16917 // introduce a borrow of `&self` past the call, collapsing the
16918 // load-bearing "no lifetime on the return type" `Copy` projection
16919 // the flat-spread axis's `Option<Copy>` shape carries), a stale-
16920 // read side effect that flipped the outer discriminant on
16921 // successive calls, or an axis-remap projection that returned a
16922 // different variant than the field storage.
16923 use crate::supervisor::RestartStrategy;
16924 for estrategia in [
16925 Some(RestartStrategy::OneForOne),
16926 Some(RestartStrategy::OneForAll),
16927 Some(RestartStrategy::RestForOne),
16928 Some(RestartStrategy::SimpleOneForOne),
16929 ] {
16930 let c = caixa_with_estrategia(estrategia);
16931 let first = c.estrategia();
16932 let second = c.estrategia();
16933 assert_eq!(
16934 first, second,
16935 "Caixa::estrategia must be idempotent — two successive \
16936 calls on the same &self must return the same \
16937 Option<RestartStrategy>",
16938 );
16939 assert_eq!(
16940 first, estrategia,
16941 "Caixa::estrategia must return :estrategia verbatim by \
16942 Copy — got {first:?}, expected {estrategia:?}",
16943 );
16944 }
16945 let c = caixa_with_estrategia(None);
16946 assert!(
16947 c.estrategia().is_none(),
16948 "Caixa::estrategia must return None when :estrategia is \
16949 absent — the author-omitted arm must project through the \
16950 accessor's Option::None unchanged",
16951 );
16952 }
16953
16954 // ── Caixa::max_restarts / Caixa::restart_window —
16955 // outer top-level M2 supervisor-tree-slot flat-spread accessors
16956 // (Option<u32> / Option<&str>) folding on the ed04d3c
16957 // Caixa::estrategia Option<Copy> sub-family ─────────────────────
16958
16959 fn caixa_with_max_restarts(max_restarts: Option<u32>) -> Caixa {
16960 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16961 c.max_restarts = max_restarts;
16962 c
16963 }
16964
16965 fn caixa_supervisor_with_max_restarts_and_window(
16966 max_restarts: Option<u32>,
16967 restart_window: Option<&str>,
16968 ) -> Caixa {
16969 use crate::CaixaKind;
16970 use crate::supervisor::{ChildSpec, RestartPolicy};
16971 let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
16972 c.kind = CaixaKind::Supervisor;
16973 c.max_restarts = max_restarts;
16974 c.restart_window = restart_window.map(str::to_string);
16975 c.children = vec![ChildSpec {
16976 caixa: "worker".into(),
16977 versao: "^0.1".into(),
16978 restart: RestartPolicy::Permanent,
16979 }];
16980 c
16981 }
16982
16983 #[test]
16984 fn max_restarts_returns_max_restarts_option_verbatim_across_permutations() {
16985 // Value-shape pin: [`Caixa::max_restarts`] returns the
16986 // `:max-restarts` typed `Option<u32>` verbatim, `Copy`-projected
16987 // from the typed slot's own storage, byte-equal across the
16988 // author-omitted `None` arm (the "defer to the
16989 // [`Self::supervisor_view`] `unwrap_or(5)` OTP-canonical
16990 // `{intensity, 5, 60}` default" partition every
16991 // non-`Supervisor`-kind caixa carries by `#[serde(default)]`)
16992 // and each of the representative fixtures in the accept-set —
16993 // `0` (the zero-floor arm the peer
16994 // [`crate::supervisor::SupervisorSpec::validate`]
16995 // [`crate::SupervisorError::ZeroMaxRestarts`] gate refuses on
16996 // the post-composition altitude — the accessor must ship the
16997 // raw slot verbatim so struct-literal fixtures continue to
16998 // expose the zero at the accessor boundary), the OTP-canonical
16999 // `5` default (`{intensity, 5, 60}` worker-supervisor from
17000 // Learn You Some Erlang), `1000` (the
17001 // [`SUPERVISOR_MAX_RESTARTS_MAX`] cap the peer post-composition
17002 // upper-bound gate accepts on the boundary), `u32::MAX` (a
17003 // past-the-cap sentinel that the substrate-primitive accessor
17004 // must still ship verbatim). Second outer top-level
17005 // [`Caixa`] `Option<Copy>`-return supervisor-tree flat-spread
17006 // pin — folds on the sibling
17007 // `estrategia_returns_estrategia_option_verbatim_across_permutations`
17008 // (ed04d3c) pin's `Option<Copy>` shape, extending the sub-family
17009 // onto the sibling `Option<u32>` restart-budget-count arm.
17010 let fixtures: Vec<Option<u32>> = vec![None, Some(0), Some(5), Some(1000), Some(u32::MAX)];
17011 for max_restarts in fixtures {
17012 let c = caixa_with_max_restarts(max_restarts);
17013 assert_eq!(
17014 c.max_restarts(),
17015 max_restarts,
17016 "Caixa::max_restarts must return :max-restarts verbatim \
17017 (got {:?}, expected {max_restarts:?})",
17018 c.max_restarts(),
17019 );
17020 assert_eq!(
17021 c.max_restarts(),
17022 c.max_restarts,
17023 "Caixa::max_restarts accessor and self.max_restarts \
17024 field access must byte-equal — a presence-bit or count \
17025 drift would silently split the paired \
17026 Caixa::declared_supervisor_slots presence-probe arm \
17027 from the Caixa::supervisor_view unwrap_or(5) fold's \
17028 composition input",
17029 );
17030 }
17031 }
17032
17033 #[test]
17034 fn max_restarts_projects_option_by_copy() {
17035 // The by-`Copy` pin: [`Caixa::max_restarts`] returns
17036 // `Option<u32>` by value (`u32: Copy`) — the accessor does not
17037 // borrow `&self` past the call (no lifetime on the return type),
17038 // and calling the accessor twice on the same [`Caixa`] must
17039 // yield equal values (idempotent, no side effects). Peer of the
17040 // sibling `estrategia_projects_option_by_copy` (ed04d3c) pin on
17041 // the outer-`Caixa` `Option<Copy>`-return flat-spread axis.
17042 for max_restarts in [Some(0u32), Some(5), Some(1000), Some(u32::MAX), None] {
17043 let c = caixa_with_max_restarts(max_restarts);
17044 let first = c.max_restarts();
17045 let second = c.max_restarts();
17046 assert_eq!(
17047 first, second,
17048 "Caixa::max_restarts must be idempotent — two successive \
17049 calls on the same &self must return the same Option<u32>",
17050 );
17051 assert_eq!(
17052 first, max_restarts,
17053 "Caixa::max_restarts must return :max-restarts verbatim \
17054 by Copy — got {first:?}, expected {max_restarts:?}",
17055 );
17056 }
17057 }
17058
17059 #[test]
17060 fn declared_supervisor_slots_max_restarts_arm_routes_through_accessor() {
17061 // Composition pin: [`Caixa::declared_supervisor_slots`]'s
17062 // `:max-restarts` presence-probe arm must key off
17063 // [`Caixa::max_restarts`], not the raw
17064 // `self.max_restarts.is_some()` field-probe. Structurally: every
17065 // `Caixa { max_restarts: Some(_), .. }` variant must push
17066 // `SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS` onto the declared-slot
17067 // list (the presence bit is `Some` for every representative
17068 // count, so the M2 kind-coherence gate must surface the slot as
17069 // "declared"), and a `Caixa { max_restarts: None, .. }` must
17070 // NOT push the label. Peer of the sibling
17071 // `declared_supervisor_slots_estrategia_arm_routes_through_accessor`
17072 // (ed04d3c) composition pin — same routing-through-accessor
17073 // discipline extended onto the sibling flat-spread `Option<u32>`
17074 // arm.
17075 for max_restarts in [0u32, 5, 1000, u32::MAX] {
17076 let c = caixa_with_max_restarts(Some(max_restarts));
17077 let slots = c.declared_supervisor_slots();
17078 assert!(
17079 slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS),
17080 "declared_supervisor_slots must push \
17081 SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS when `:max-restarts` \
17082 is Some({max_restarts}) — the accessor and the \
17083 enumerator gate must route through the same \
17084 substrate-primitive typed dispatch on the outer \
17085 :max-restarts presence bit (got slots={slots:?})",
17086 );
17087 }
17088 let c = caixa_with_max_restarts(None);
17089 let slots = c.declared_supervisor_slots();
17090 assert!(
17091 !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS),
17092 "declared_supervisor_slots must NOT push \
17093 SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS when `:max-restarts` is \
17094 None — the author-omitted arm must route through the \
17095 accessor's None-return unchanged (got slots={slots:?})",
17096 );
17097 }
17098
17099 #[test]
17100 fn supervisor_view_max_restarts_arm_routes_through_accessor() {
17101 // Composition pin: [`Caixa::supervisor_view`]'s per-`:max-restarts`
17102 // [`SupervisorSpec`] construction arm must key off
17103 // [`Caixa::max_restarts`]'s `unwrap_or(5)` fold, not the raw
17104 // `self.max_restarts.unwrap_or(5)` field-fold. Structurally: for
17105 // every `:kind Supervisor` `Caixa` carrying an author-declared
17106 // `Some(n)`, the composed [`SupervisorSpec`]'s `.max_restarts()`
17107 // must byte-equal `n`; and for a `:kind Supervisor` `Caixa`
17108 // carrying `None`, the composed [`SupervisorSpec`]'s
17109 // `.max_restarts()` must byte-equal the OTP-canonical `5`. Peer
17110 // of the sibling
17111 // `supervisor_view_estrategia_arm_routes_through_accessor`
17112 // (ed04d3c) composition pin.
17113 for max_restarts in [1u32, 5, 1000] {
17114 let c = caixa_supervisor_with_max_restarts_and_window(Some(max_restarts), None);
17115 let view = c.supervisor_view().expect(
17116 "supervisor_view must materialize a SupervisorSpec for a \
17117 :kind Supervisor Caixa carrying a Some(:max-restarts)",
17118 );
17119 assert_eq!(
17120 view.max_restarts(),
17121 max_restarts,
17122 "supervisor_view must carry the outer \
17123 Caixa::max_restarts() Some arm onto the composed \
17124 SupervisorSpec.max_restarts field verbatim (got {}, \
17125 expected {max_restarts})",
17126 view.max_restarts(),
17127 );
17128 }
17129 let c = caixa_supervisor_with_max_restarts_and_window(None, None);
17130 let view = c.supervisor_view().expect(
17131 "supervisor_view must materialize a SupervisorSpec for a \
17132 :kind Supervisor Caixa carrying a None :max-restarts",
17133 );
17134 assert_eq!(
17135 view.max_restarts(),
17136 5,
17137 "supervisor_view must project the outer \
17138 Caixa::max_restarts() None arm onto the OTP-canonical \
17139 {{intensity, 5, 60}} default (5) through the flat-spread \
17140 unwrap_or(5) fold (got {})",
17141 view.max_restarts(),
17142 );
17143 assert!(
17144 c.max_restarts().is_none(),
17145 "Caixa::max_restarts() must remain None on the author-\
17146 omitted arm — the supervisor_view fold must not mutate \
17147 the outer flat-spread presence bit",
17148 );
17149 }
17150
17151 #[test]
17152 fn supervisor_view_estrategia_fallback_routes_through_lifted_default() {
17153 // Composition pin: [`Caixa::supervisor_view`]'s author-omitted
17154 // `:estrategia` arm must degrade onto the substrate-canonical
17155 // [`crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT`] typed
17156 // `pub const` — the Erlang/OTP-canonical `one_for_one` strategy
17157 // half of Learn You Some Erlang's `{one_for_one, intensity, 5, 60}`
17158 // worker-supervisor default — rather than the transitively-
17159 // derived [`crate::supervisor::RestartStrategy::default`] route
17160 // the prior `.unwrap_or_default()` fold reached for. Prior to the
17161 // lift the composition site carried `.unwrap_or_default()` with
17162 // no compile-time link back to the shared OTP-canonical strategy
17163 // default that the paired [`crate::supervisor::Default for
17164 // RestartStrategy`] impl and the [`crate::supervisor::Default for
17165 // SupervisorSpec`] impl's struct-literal `estrategia` field both
17166 // (now) route through the same lifted constant — so a future
17167 // rebrand of the OTP-canonical strategy default (an OTP
17168 // `rest_for_one` widening once the substrate discovers startup-
17169 // order-coupled child cohorts as the more common worker-
17170 // supervisor shape, a per-cluster overlay the operator pins
17171 // through the MESH-COMPOSITION §III.2 supervision-canary
17172 // `:estrategia-overrides` roadmap slot) would have had to migrate
17173 // the paired `MaxIntensity` + `Period` halves through the lifted
17174 // constants and the `one_for_one` half through a
17175 // `RestartStrategy::default()` route in lockstep or a
17176 // `:kind Supervisor` caixa carrying an author-omitted
17177 // `:estrategia` slot would silently resolve to a `SupervisorSpec`
17178 // whose `estrategia` disagreed with the paired
17179 // `SupervisorSpec::default()` view. Byte-parity against the
17180 // lifted constant closes the split. Peer of the sibling
17181 // [`supervisor_view_max_restarts_fallback_routes_through_lifted_default`]
17182 // composition pin on the paired `MaxIntensity` half + the
17183 // [`crate::supervisor::restart_strategy_default_routes_through_lifted_default`]
17184 // + [`crate::supervisor::supervisor_spec_default_estrategia_routes_through_lifted_default`]
17185 // pins on the sibling entry points onto the shared substrate
17186 // constant.
17187 use crate::CaixaKind;
17188 use crate::supervisor::{ChildSpec, RestartPolicy};
17189 let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
17190 c.kind = CaixaKind::Supervisor;
17191 c.estrategia = None;
17192 c.children = vec![ChildSpec {
17193 caixa: "worker".into(),
17194 versao: "^0.1".into(),
17195 restart: RestartPolicy::Permanent,
17196 }];
17197 let view = c.supervisor_view().expect(
17198 "supervisor_view must materialize a SupervisorSpec for a \
17199 :kind Supervisor Caixa carrying a None :estrategia",
17200 );
17201 assert_eq!(
17202 view.estrategia(),
17203 crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT,
17204 "supervisor_view must degrade the outer \
17205 Caixa::estrategia() None arm onto the lifted \
17206 SUPERVISOR_ESTRATEGIA_DEFAULT typed pub const (got {:?}, \
17207 expected {:?})",
17208 view.estrategia(),
17209 crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT,
17210 );
17211 }
17212
17213 #[test]
17214 fn supervisor_view_max_restarts_fallback_routes_through_lifted_default() {
17215 // Composition pin: [`Caixa::supervisor_view`]'s author-omitted
17216 // `:max-restarts` arm must degrade onto the substrate-canonical
17217 // [`crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT`] typed
17218 // `pub const` — the Erlang/OTP-canonical `{intensity, 5, 60}`
17219 // `MaxIntensity` default — rather than a raw `5` literal. Prior
17220 // to the lift the composition site carried an inline
17221 // `.unwrap_or(5)` with no compile-time link back to the shared
17222 // OTP-canonical default that the serde-side
17223 // `#[serde(default = "default_max_restarts")]` wire-format arm
17224 // and the [`Default for crate::supervisor::SupervisorSpec`]
17225 // struct-literal default arm both key off — so a future rebrand
17226 // of the OTP-canonical default (Elixir's `Supervisor` `3`
17227 // default, a per-cluster overlay the operator pins through the
17228 // MESH-COMPOSITION §III.2 supervision-canary
17229 // `:supervisor :max-restarts-overrides` roadmap slot) would
17230 // have had to be threaded through both the serde-side helper
17231 // and this view-construction arm in lockstep or a `:kind
17232 // Supervisor` caixa carrying `:max-restarts ()` would silently
17233 // resolve to a `SupervisorSpec` whose `max_restarts` disagreed
17234 // with the same fixture's serde-side `SupervisorSpec` view (an
17235 // author-omitted slot round-tripping through
17236 // `SupervisorSpec::default()` to the lifted constant, then
17237 // splitting to a stale literal past `supervisor_view`).
17238 // Byte-parity against the lifted constant closes the split.
17239 // Peer of the sibling
17240 // [`crate::supervisor::default_max_restarts_helper_routes_through_lifted_default`]
17241 // + [`crate::supervisor::supervisor_spec_default_max_restarts_routes_through_lifted_default`]
17242 // composition pins that close the same routing on the two
17243 // sibling entry points onto the shared substrate constant.
17244 let c = caixa_supervisor_with_max_restarts_and_window(None, None);
17245 let view = c.supervisor_view().expect(
17246 "supervisor_view must materialize a SupervisorSpec for a \
17247 :kind Supervisor Caixa carrying a None :max-restarts",
17248 );
17249 assert_eq!(
17250 view.max_restarts(),
17251 crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT,
17252 "supervisor_view must degrade the outer \
17253 Caixa::max_restarts() None arm onto the lifted \
17254 SUPERVISOR_MAX_RESTARTS_DEFAULT typed pub const (got {}, \
17255 expected {})",
17256 view.max_restarts(),
17257 crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT,
17258 );
17259 }
17260
17261 #[test]
17262 fn restart_window_returns_restart_window_option_verbatim_across_permutations() {
17263 // Value-shape pin: [`Caixa::restart_window`] returns the
17264 // `:restart-window` typed `Option<String>` verbatim as an
17265 // `Option<&str>`, borrowed from the typed slot's own storage,
17266 // byte-equal across the author-omitted `None` arm and each of
17267 // the representative fixtures in the accept-set — the canonical
17268 // `"60s"` from `{intensity, 5, 60}`, the sibling
17269 // canonical-magnitude forms (`"5m"` / `"1h"` / `"500ms"` / `"30"`
17270 // / `"0s"`) the shared codec's positive-set sweep pin covers,
17271 // plus a past-the-guard sentinel (`"1.5s"` — the fractional-
17272 // seconds drift the sibling [`Self::validate_restart_window`]
17273 // gate refuses; the accessor must ship the raw slot verbatim
17274 // so struct-literal fixtures continue to expose the drift at
17275 // the accessor boundary). Third outer top-level [`Caixa`]
17276 // supervisor-tree flat-spread pin — extends the sub-family onto
17277 // the sibling `Option<&str>` raw-duration-string arm.
17278 for window in [
17279 None,
17280 Some("60s"),
17281 Some("5m"),
17282 Some("1h"),
17283 Some("500ms"),
17284 Some("1.5s"),
17285 Some(""),
17286 ] {
17287 let c = caixa_with_restart_window(window);
17288 assert_eq!(
17289 c.restart_window(),
17290 window,
17291 "Caixa::restart_window must return :restart-window \
17292 verbatim as Option<&str> (got {:?}, expected {window:?})",
17293 c.restart_window(),
17294 );
17295 assert_eq!(
17296 c.restart_window(),
17297 c.restart_window.as_deref(),
17298 "Caixa::restart_window accessor and \
17299 self.restart_window.as_deref() field access must \
17300 byte-equal — a byte-level drift would silently split \
17301 the paired Caixa::declared_supervisor_slots \
17302 presence-probe arm from the \
17303 Caixa::validate_restart_window shared-codec gate and \
17304 the Caixa::supervisor_view soft-swallowing fold",
17305 );
17306 }
17307 }
17308
17309 #[test]
17310 fn restart_window_projects_slice_by_borrow() {
17311 // The by-borrow pin: [`Caixa::restart_window`] returns
17312 // `Option<&str>` by borrow — the returned string slice borrows
17313 // the underlying `Option<String>` storage of the `:restart-window`
17314 // slot and the accessor must not clone on every call. Peer of
17315 // the sibling outer top-level [`Caixa`] `Option<&str>`-return
17316 // by-borrow pins on the universal-axis scalar family
17317 // (`licenca_projects_option_ref_by_borrow` /
17318 // `descricao_projects_option_ref_by_borrow` and siblings) —
17319 // extended onto the M2 supervisor-tree flat-spread
17320 // `Option<&str>` raw-duration-string axis.
17321 for window in [None, Some("60s"), Some("5m"), Some("")] {
17322 let c = caixa_with_restart_window(window);
17323 let first = c.restart_window();
17324 let second = c.restart_window();
17325 assert_eq!(
17326 first, second,
17327 "Caixa::restart_window must be idempotent — two \
17328 successive calls on the same &self must return the \
17329 same Option<&str>",
17330 );
17331 if let (Some(a), Some(b)) = (first, second) {
17332 assert_eq!(
17333 a.as_ptr(),
17334 b.as_ptr(),
17335 "Caixa::restart_window must borrow the underlying \
17336 String storage — two successive Some-arm calls must \
17337 return slices with the same backing pointer (a fresh \
17338 String clone would change the pointer on every call)",
17339 );
17340 }
17341 assert_eq!(
17342 first, window,
17343 "Caixa::restart_window must return :restart-window \
17344 verbatim by borrow — got {first:?}, expected {window:?}",
17345 );
17346 }
17347 }
17348
17349 #[test]
17350 fn declared_supervisor_slots_restart_window_arm_routes_through_accessor() {
17351 // Composition pin: [`Caixa::declared_supervisor_slots`]'s
17352 // `:restart-window` presence-probe arm must key off
17353 // [`Caixa::restart_window`], not the raw
17354 // `self.restart_window.is_some()` field-probe. Structurally:
17355 // every `Caixa { restart_window: Some(_), .. }` must push
17356 // `SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW` onto the declared-slot
17357 // list, and a `Caixa { restart_window: None, .. }` must NOT
17358 // push the label. Peer of the sibling
17359 // `declared_supervisor_slots_max_restarts_arm_routes_through_accessor`
17360 // routing pin.
17361 for window in ["60s", "5m", "1h", "500ms", "1.5s", ""] {
17362 let c = caixa_with_restart_window(Some(window));
17363 let slots = c.declared_supervisor_slots();
17364 assert!(
17365 slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW),
17366 "declared_supervisor_slots must push \
17367 SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW when \
17368 `:restart-window` is Some({window:?}) — the accessor \
17369 and the enumerator gate must route through the same \
17370 substrate-primitive typed dispatch on the outer \
17371 :restart-window presence bit (got slots={slots:?})",
17372 );
17373 }
17374 let c = caixa_with_restart_window(None);
17375 let slots = c.declared_supervisor_slots();
17376 assert!(
17377 !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW),
17378 "declared_supervisor_slots must NOT push \
17379 SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW when `:restart-window` \
17380 is None — the author-omitted arm must route through the \
17381 accessor's None-return unchanged (got slots={slots:?})",
17382 );
17383 }
17384
17385 #[test]
17386 fn validate_restart_window_arm_routes_through_accessor() {
17387 // Composition pin: [`Caixa::validate_restart_window`]'s
17388 // shared-codec fold arm must key off [`Caixa::restart_window`],
17389 // not the raw `self.restart_window.as_deref()` field-projection.
17390 // Structurally: (1) `None` → `Ok(())` (the "omit the slot to
17391 // express no reset" canonical shape); (2) a canonical `Some`
17392 // arm (`"60s"`) → `Ok(())`; (3) a codec-rejected `Some` arm
17393 // (`"1.5s"`) → `Err(RestartWindowMalformed { restart_window,
17394 // .. })` carrying the offending raw string verbatim. The three
17395 // arms jointly pin that the validator's raw-string binding is
17396 // the accessor's return, not a peer projection — any future
17397 // silent detour that had the accessor collapse `Some("")` to
17398 // `None` would silently absorb the empty-after-trim refusal
17399 // case at the accessor boundary.
17400 caixa_with_restart_window(None)
17401 .validate_restart_window()
17402 .expect("None :restart-window must validate through the accessor");
17403 caixa_with_restart_window(Some("60s"))
17404 .validate_restart_window()
17405 .expect("canonical :restart-window \"60s\" must validate through the accessor");
17406 let err = caixa_with_restart_window(Some("1.5s"))
17407 .validate_restart_window()
17408 .expect_err("fractional-seconds :restart-window must fail through the accessor");
17409 assert!(
17410 matches!(
17411 err,
17412 ManifestError::RestartWindowMalformed { ref restart_window, .. }
17413 if restart_window == "1.5s"
17414 ),
17415 "validator must carry the offending raw string verbatim \
17416 from the accessor's borrowed &str (got {err:?})",
17417 );
17418 }
17419
17420 #[test]
17421 fn supervisor_view_restart_window_arm_routes_through_accessor() {
17422 // Composition pin: [`Caixa::supervisor_view`]'s
17423 // per-`:restart-window` [`SupervisorSpec`] construction arm
17424 // must key off [`Caixa::restart_window`]'s soft-swallowing
17425 // `.and_then(|s| duration_codec::parse(s).ok())` fold, not the
17426 // raw `self.restart_window.as_deref().and_then(…)` field-fold.
17427 // Structurally: (1) `None` → `SupervisorSpec.restart_window ==
17428 // None` (the "never reset" sentinel); (2) canonical `Some("60s")`
17429 // → `SupervisorSpec.restart_window == Some(Duration::from_secs(60))`
17430 // (the shared codec's canonical parse); (3) codec-rejected
17431 // `Some("1.5s")` → `SupervisorSpec.restart_window == None`
17432 // (the soft-swallow preserving the view's best-effort shape).
17433 let c = caixa_supervisor_with_max_restarts_and_window(None, None);
17434 let view = c.supervisor_view().expect("Supervisor kind has a view");
17435 assert_eq!(
17436 view.restart_window(),
17437 None,
17438 "supervisor_view must project outer None :restart-window \
17439 onto None on the composed SupervisorSpec (never-reset \
17440 sentinel) through the accessor's None-return unchanged",
17441 );
17442
17443 let c = caixa_supervisor_with_max_restarts_and_window(None, Some("60s"));
17444 let view = c.supervisor_view().expect("Supervisor kind has a view");
17445 assert_eq!(
17446 view.restart_window(),
17447 Some(std::time::Duration::from_secs(60)),
17448 "supervisor_view must fold outer Some(\"60s\") through the \
17449 shared duration_codec into Duration::from_secs(60) on the \
17450 composed SupervisorSpec (accessor's Some(&str) → codec \
17451 parse → Some(Duration))",
17452 );
17453
17454 let c = caixa_supervisor_with_max_restarts_and_window(None, Some("1.5s"));
17455 let view = c.supervisor_view().expect("Supervisor kind has a view");
17456 assert_eq!(
17457 view.restart_window(),
17458 None,
17459 "supervisor_view must soft-swallow the shared-codec parse \
17460 failure to None (the view's best-effort shape the sibling \
17461 manifest-level validate_restart_window surfaces as \
17462 RestartWindowMalformed); the accessor's raw-string return \
17463 is the single input every downstream consumer keys off",
17464 );
17465 }
17466
17467 // ── Caixa::upgrade_from — outer top-level &[UpgradeFromEntry] composite-slice accessor ──
17468
17469 fn caixa_with_upgrade_from(upgrade_from: Vec<crate::upgrade::UpgradeFromEntry>) -> Caixa {
17470 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
17471 c.upgrade_from = upgrade_from;
17472 c
17473 }
17474
17475 #[test]
17476 fn upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations() {
17477 // The canonical per-`Caixa` `:upgrade-from` M2 typed-slot
17478 // outer-composite `&[UpgradeFromEntry]`-return slice-shape
17479 // pin: [`Caixa::upgrade_from`] must return the `:upgrade-from`
17480 // typed `Vec<UpgradeFromEntry>` verbatim as a
17481 // `&[UpgradeFromEntry]` slice-view over the same backing
17482 // buffer the raw `self.upgrade_from.as_slice()` field access
17483 // borrows from, element-equal across every representative
17484 // fixture in the accept-set — `[]` (the "no hot-upgrade path
17485 // declared" arm every `defcaixa` without an `:upgrade-from`
17486 // block carries; `#[serde(default)]` folds an omitted slot
17487 // onto `Vec::new()`), a canonical single-entry `Restart`
17488 // fixture (the shape most Servicos carry — a single prior
17489 // version with the fallback strategy), a canonical multi-
17490 // entry list carrying every typed instruction variant
17491 // (`LoadModule` / `StateChange` / `SoftPurge` / `Purge` /
17492 // `Restart`), and a past-the-guard sentinel — a duplicate-
17493 // `:from` `[(0.1.0, Restart), (0.1.0, Restart)]` entry pair
17494 // ([`crate::upgrade::validate_upgrade_from`] rejects through
17495 // `DuplicateFrom { from: "0.1.0" }` but the accessor must
17496 // ship the raw slot verbatim so struct-literal fixtures
17497 // continue to expose the duplicate at the accessor boundary).
17498 //
17499 // Pins against a future silent detour that returned an owned
17500 // `Vec<UpgradeFromEntry>` (which would type-check but silently
17501 // clone on every accessor call, breaking the zero-cost
17502 // projection every peer sibling slice accessor carries), a
17503 // `[dup, dup] → [dup]` dedup collapse (which would silently
17504 // absorb the `DuplicateFrom` refusal case at the accessor
17505 // boundary and the [`crate::StandardLayout::verify`] cross-
17506 // entry gate would silently accept a struct-literal `Caixa`
17507 // carrying the drift), a reference to an operator-resolved
17508 // overlay (the future per-cluster `:upgrade-overrides` slot
17509 // — its resolution must land at exactly this accessor body,
17510 // not silently divert the raw slot away from a second
17511 // consumer), or an axis-shuffled projection (a future detour
17512 // that reordered entries through the accessor would silently
17513 // split the paired [`crate::StandardLayout::verify`] per-
17514 // `:upgrade-from` shape gate's traversal input from the peer
17515 // [`crate::render::servico_m2_overlay`] emitter's projection
17516 // input, since the operator's hot-upgrade dispatch matches
17517 // per-`:from` and axis reordering would silently split the
17518 // per-entry script-path existence probe's iteration order
17519 // from the M2 overlay emitter's serialized-entry order).
17520 //
17521 // First outer top-level [`Caixa`] `&[Composite]`-return
17522 // slice accessor pin on the substrate primitive for M2 / M3
17523 // typed-slot vec-carry axes — opens the outer-`Caixa`
17524 // `&[Composite]` composite-slice projection pattern the
17525 // sibling `:children` [`crate::supervisor::ChildSpec`] /
17526 // `:membros` [`crate::aplicacao::Membro`] / `:contratos`
17527 // [`crate::aplicacao::WitContract`] future outer-composite-
17528 // slice pins fold on. Peer of the closed outer-`Caixa`
17529 // scalar `Option<&Composite>` composite-reference family the
17530 // sibling `limits` / `behavior` / `politicas` / `placement`
17531 // / `entrada` `..._returns_..._option_ref_verbatim_across_
17532 // permutations` pins closed (b2bd9d7 → e4128e4) — extends
17533 // the "byte-equal, borrow-shared" outer-accessor discipline
17534 // onto the outer-`Caixa` `&[Composite]` vec-carry altitude.
17535 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
17536 let fixtures: Vec<Vec<UpgradeFromEntry>> = vec![
17537 vec![],
17538 vec![UpgradeFromEntry {
17539 from: "0.0.1".into(),
17540 instructions: vec![UpgradeInstruction::Restart],
17541 }],
17542 vec![
17543 UpgradeFromEntry {
17544 from: "0.0.1".into(),
17545 instructions: vec![
17546 UpgradeInstruction::LoadModule {
17547 module: "demo".into(),
17548 },
17549 UpgradeInstruction::SoftPurge {
17550 module: "demo".into(),
17551 },
17552 ],
17553 },
17554 UpgradeFromEntry {
17555 from: "0.0.2".into(),
17556 instructions: vec![
17557 UpgradeInstruction::StateChange {
17558 script: "servicos/upgrade.lisp".into(),
17559 },
17560 UpgradeInstruction::Purge {
17561 module: "demo".into(),
17562 },
17563 UpgradeInstruction::Restart,
17564 ],
17565 },
17566 ],
17567 vec![
17568 UpgradeFromEntry {
17569 from: "0.1.0".into(),
17570 instructions: vec![UpgradeInstruction::Restart],
17571 },
17572 UpgradeFromEntry {
17573 from: "0.1.0".into(),
17574 instructions: vec![UpgradeInstruction::Restart],
17575 },
17576 ],
17577 ];
17578 for upgrade_from in fixtures {
17579 let c = caixa_with_upgrade_from(upgrade_from.clone());
17580 assert_eq!(
17581 c.upgrade_from(),
17582 upgrade_from.as_slice(),
17583 "Caixa::upgrade_from must return :upgrade-from \
17584 verbatim (got {:?}, expected {upgrade_from:?})",
17585 c.upgrade_from(),
17586 );
17587 assert_eq!(
17588 c.upgrade_from(),
17589 c.upgrade_from.as_slice(),
17590 "Caixa::upgrade_from must element-equal the raw \
17591 `self.upgrade_from.as_slice()` field access across \
17592 every value in the Vec<UpgradeFromEntry> accept-set",
17593 );
17594 assert_eq!(
17595 c.upgrade_from().is_empty(),
17596 c.upgrade_from.is_empty(),
17597 "Caixa::upgrade_from().is_empty() must byte-equal \
17598 self.upgrade_from.is_empty() — a presence-bit drift \
17599 would silently split the paired \
17600 Caixa::declared_servico_slots M2 declared-slot \
17601 enumerator's presence probe from the peer \
17602 crate::render::servico_m2_overlay M2 overlay \
17603 emitter's presence gate",
17604 );
17605 }
17606 }
17607
17608 #[test]
17609 fn declared_servico_slots_upgrade_from_arm_routes_through_accessor() {
17610 // Composition pin: [`Caixa::declared_servico_slots`]'s
17611 // `:upgrade-from` presence-probe arm must key off
17612 // [`Caixa::upgrade_from`], not the raw
17613 // `self.upgrade_from.is_empty()` field-probe. Structurally: a
17614 // `Caixa { upgrade_from: vec![UpgradeFromEntry { from: "0.0.1",
17615 // instructions: vec![Restart] }], .. }` must push
17616 // `M2_AUTHOR_KEY_UPGRADE_FROM` onto the declared-slot list
17617 // (the presence bit is non-empty, so the M2 kind-coherence
17618 // gate must surface the slot as "declared"), and a `Caixa {
17619 // upgrade_from: vec![], .. }` must NOT push the label (the
17620 // "author omitted the slot entirely" arm — the empty-slice
17621 // partition the serde-default folds onto). The pair jointly
17622 // pins the accessor + declared-slot enumerator composition:
17623 // any future silent detour that had the accessor collapse
17624 // `[Restart]` to `[]` (a `.filter(|e| !e.instructions.
17625 // is_empty())` projection) would silently absorb the
17626 // "declared but degenerate" arm at the accessor boundary and
17627 // the [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-
17628 // coherence gate would silently accept a struct-literal
17629 // `Caixa` carrying the drift.
17630 //
17631 // Peer of the sibling
17632 // `declared_servico_slots_limits_arm_routes_through_accessor`
17633 // (b2bd9d7) and
17634 // `declared_servico_slots_behavior_arm_routes_through_accessor`
17635 // (35d8b52) composition pins on the sibling `:limits` /
17636 // `:behavior` outer-`Option<&Composite>` arms — same "the
17637 // enumerator gate must route through the substrate-primitive
17638 // typed dispatch" discipline extended onto the third M2
17639 // Servico-runtime slot axis, closing the enumerator's routing
17640 // invariant on every M2 arm.
17641 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
17642 let c = caixa_with_upgrade_from(vec![UpgradeFromEntry {
17643 from: "0.0.1".into(),
17644 instructions: vec![UpgradeInstruction::Restart],
17645 }]);
17646 let slots = c.declared_servico_slots();
17647 assert!(
17648 slots.contains(&crate::render::M2_AUTHOR_KEY_UPGRADE_FROM),
17649 "declared_servico_slots must push \
17650 M2_AUTHOR_KEY_UPGRADE_FROM when `:upgrade-from` is \
17651 non-empty — the accessor and the enumerator gate must \
17652 route through the same substrate-primitive typed \
17653 dispatch on the outer :upgrade-from presence bit (got \
17654 slots={slots:?})",
17655 );
17656 let c = caixa_with_upgrade_from(vec![]);
17657 let slots = c.declared_servico_slots();
17658 assert!(
17659 !slots.contains(&crate::render::M2_AUTHOR_KEY_UPGRADE_FROM),
17660 "declared_servico_slots must NOT push \
17661 M2_AUTHOR_KEY_UPGRADE_FROM when `:upgrade-from` is \
17662 empty — the author-omitted arm must route through the \
17663 accessor's empty-slice return unchanged (got \
17664 slots={slots:?})",
17665 );
17666 }
17667
17668 #[test]
17669 fn servico_m2_overlay_upgrade_from_arm_routes_through_accessor() {
17670 // Composition pin: [`crate::render::servico_m2_overlay`]'s
17671 // per-`:upgrade-from` M2 overlay emit arm must key off
17672 // [`Caixa::upgrade_from`], not the raw
17673 // `!caixa.upgrade_from.is_empty()` presence gate + the
17674 // `serde_yaml::to_value(&caixa.upgrade_from)` projection.
17675 // Structurally: a `Caixa { upgrade_from: vec![UpgradeFromEntry
17676 // { from: "0.0.1", instructions: vec![Restart] }], .. }` must
17677 // surface the `M2_KEY_UPGRADE_FROM` key with a per-entry
17678 // sequence in the overlay (the emitter fans onto the serde
17679 // slice-serialization), and a `Caixa { upgrade_from: vec![],
17680 // .. }` must omit the key entirely (the empty-slice
17681 // partition — the `!.is_empty()` outer gate elides the key
17682 // when the author omitted the slot). The pair jointly pins
17683 // the accessor + M2 overlay emitter composition: any future
17684 // silent detour that had the accessor return a fresh-cloned
17685 // `Vec<UpgradeFromEntry>` copy would silently break the
17686 // reference-identity pin the peer per-entry
17687 // `serde_yaml::to_value(caixa.upgrade_from())` projection
17688 // reads from — the projection would clone once per accessor
17689 // call instead of borrowing the storage buffer verbatim.
17690 //
17691 // Peer of the sibling
17692 // `servico_m2_overlay_limits_arm_routes_through_accessor`
17693 // (b2bd9d7) and
17694 // `servico_m2_overlay_behavior_arm_routes_through_accessor`
17695 // (35d8b52) composition pins on the sibling `:limits` /
17696 // `:behavior` outer-`Option<&Composite>` arms — same "the
17697 // M2 overlay emitter must route through the substrate-
17698 // primitive typed dispatch" discipline extended onto the
17699 // third M2 Servico-runtime slot axis, closing the overlay
17700 // emitter's routing invariant on every M2 arm.
17701 use crate::render::{M2_KEY_UPGRADE_FROM, servico_m2_overlay};
17702 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
17703 let c = caixa_with_upgrade_from(vec![UpgradeFromEntry {
17704 from: "0.0.1".into(),
17705 instructions: vec![UpgradeInstruction::Restart],
17706 }]);
17707 let overlay = servico_m2_overlay(&c).unwrap();
17708 assert!(
17709 overlay.contains_key(M2_KEY_UPGRADE_FROM),
17710 "servico_m2_overlay must surface M2_KEY_UPGRADE_FROM when \
17711 `:upgrade-from` is non-empty — the accessor and the M2 \
17712 overlay emitter must route through the same substrate- \
17713 primitive typed dispatch on the outer :upgrade-from \
17714 slice (got overlay={overlay:?})",
17715 );
17716 let c = caixa_with_upgrade_from(vec![]);
17717 let overlay = servico_m2_overlay(&c).unwrap();
17718 assert!(
17719 !overlay.contains_key(M2_KEY_UPGRADE_FROM),
17720 "servico_m2_overlay must omit M2_KEY_UPGRADE_FROM when \
17721 `:upgrade-from` is empty — the empty-slice partition \
17722 must route through the accessor's empty-slice return \
17723 unchanged (got overlay={overlay:?})",
17724 );
17725 }
17726
17727 #[test]
17728 fn upgrade_from_projects_slice_by_borrow() {
17729 // The by-borrow pin: [`Caixa::upgrade_from`] returns
17730 // `&[UpgradeFromEntry]` by borrow — the returned slice
17731 // borrows the underlying `Vec<UpgradeFromEntry>` storage of
17732 // the `:upgrade-from` slot and the accessor must not clone
17733 // the backing `Vec` on every call. Peer of the sibling
17734 // outer top-level [`Caixa`] `&[T]`-return by-borrow pins
17735 // (`autores_projects_slice_by_borrow` b5d813f,
17736 // `etiquetas_projects_slice_by_borrow` 78c7d3c,
17737 // `bibliotecas_projects_slice_by_borrow` 8a36c23,
17738 // `exe_projects_slice_by_borrow` 65d9527,
17739 // `servicos_projects_slice_by_borrow` 611f78b,
17740 // `deps_projects_slice_by_borrow` ad34b4e,
17741 // `deps_dev_projects_slice_by_borrow` f7fd81e) on the
17742 // sibling outer top-level [`Caixa`] scalar-element `&[T]`
17743 // axes — extended here to the first outer-`Caixa`
17744 // composite-element `&[Composite]` axis: the accessor's
17745 // returned slice must borrow from `&self` (the returned
17746 // reference's lifetime is tied to `&self`), and calling the
17747 // accessor twice on the same [`Caixa`] must yield slices
17748 // that are pointer-equal (the underlying byte-buffer is the
17749 // storage `Vec`'s allocation, not a fresh copy) as well as
17750 // value-equal (idempotent, no side effects on `&self`).
17751 //
17752 // Pins against a future silent detour that returned an owned
17753 // `Vec<UpgradeFromEntry>` (which would type-check but
17754 // silently clone on every call), a `&Vec<UpgradeFromEntry>`
17755 // return (which would leak the backing `Vec`'s
17756 // grow/push/reserve surface no downstream consumer reaches
17757 // for), or a one-arm-only accessor that returned a
17758 // saturating value on some sentinel input.
17759 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
17760 for upgrade_from in [
17761 vec![],
17762 vec![UpgradeFromEntry {
17763 from: "0.0.1".into(),
17764 instructions: vec![UpgradeInstruction::Restart],
17765 }],
17766 vec![
17767 UpgradeFromEntry {
17768 from: "0.0.1".into(),
17769 instructions: vec![UpgradeInstruction::Restart],
17770 },
17771 UpgradeFromEntry {
17772 from: "0.0.2".into(),
17773 instructions: vec![UpgradeInstruction::SoftPurge {
17774 module: "demo".into(),
17775 }],
17776 },
17777 ],
17778 ] {
17779 let c = caixa_with_upgrade_from(upgrade_from.clone());
17780 let first = c.upgrade_from();
17781 let second = c.upgrade_from();
17782 assert_eq!(
17783 first, second,
17784 "Caixa::upgrade_from must be idempotent — two \
17785 successive calls on the same &self must return the \
17786 same &[UpgradeFromEntry]",
17787 );
17788 assert_eq!(
17789 first.as_ptr(),
17790 second.as_ptr(),
17791 "Caixa::upgrade_from must borrow the underlying \
17792 Vec<UpgradeFromEntry> storage — two successive calls \
17793 must return slices with the same backing pointer (a \
17794 fresh Vec<UpgradeFromEntry> clone would change the \
17795 pointer on every call)",
17796 );
17797 assert_eq!(
17798 first,
17799 upgrade_from.as_slice(),
17800 "Caixa::upgrade_from must return :upgrade-from \
17801 verbatim by borrow — got {first:?}, expected \
17802 {upgrade_from:?}",
17803 );
17804 }
17805 }
17806
17807 // ── Caixa::children — outer top-level &[ChildSpec] composite-slice accessor ──
17808
17809 fn caixa_with_children(children: Vec<crate::supervisor::ChildSpec>) -> Caixa {
17810 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
17811 c.children = children;
17812 c
17813 }
17814
17815 #[test]
17816 fn children_returns_children_slice_verbatim_across_permutations() {
17817 // The canonical per-`Caixa` `:children` M2 supervisor-tree-slot
17818 // outer-composite `&[ChildSpec]`-return slice-shape pin:
17819 // [`Caixa::children`] must return the `:children` typed
17820 // `Vec<ChildSpec>` verbatim as a `&[ChildSpec]` slice-view over
17821 // the same backing buffer the raw `self.children.as_slice()`
17822 // field access borrows from, element-equal across every
17823 // representative fixture in the accept-set — `[]` (the "no
17824 // static children declared" arm every non-`Supervisor`-kind
17825 // `defcaixa` carries by `#[serde(default)]` and every
17826 // `SimpleOneForOne` supervisor carries by cross-slot refusal),
17827 // a canonical single-child `Permanent` fixture (the shape
17828 // most `OneForOne` supervisors carry — a single long-running
17829 // worker child), a canonical multi-child list carrying every
17830 // typed restart-policy variant (`Permanent` / `Transient` /
17831 // `Temporary`), and a past-the-guard sentinel — a duplicate
17832 // `:caixa` `[("w", ...), ("w", ...)]` entry pair
17833 // ([`crate::SupervisorSpec::validate`] rejects through
17834 // `DuplicateChildNome { nome: "w" }` but the accessor must
17835 // ship the raw slot verbatim so struct-literal fixtures
17836 // continue to expose the duplicate at the accessor boundary).
17837 //
17838 // Pins against a future silent detour that returned an owned
17839 // `Vec<ChildSpec>` (which would type-check but silently clone
17840 // on every accessor call, breaking the zero-cost projection
17841 // every peer sibling slice accessor carries), a `[dup, dup] →
17842 // [dup]` dedup collapse (which would silently absorb the
17843 // `DuplicateChildNome` refusal case at the accessor boundary
17844 // and the [`crate::StandardLayout::verify`] cross-child gate
17845 // would silently accept a struct-literal `Caixa` carrying the
17846 // drift), a reference to an operator-resolved overlay (the
17847 // future per-cluster `:children-overrides` slot — its
17848 // resolution must land at exactly this accessor body, not
17849 // silently divert the raw slot away from a second consumer),
17850 // or an axis-shuffled projection (a future detour that
17851 // reordered children through the accessor would silently
17852 // split the paired [`crate::StandardLayout::verify`] per-
17853 // supervisor gate's traversal input from the peer
17854 // [`Self::supervisor_view`] fold-in path's clone-order input,
17855 // since the OTP `RestForOne` restart strategy dispatches on
17856 // declared child order and axis reordering would silently
17857 // split the operator's per-cluster restart-fan-out order
17858 // from the caixa.lisp source-order).
17859 //
17860 // Second outer top-level [`Caixa`] `&[Composite]`-return slice
17861 // accessor pin on the substrate primitive for M2 / M3 typed-
17862 // slot vec-carry axes — folds on the outer-`Caixa`
17863 // `&[Composite]` composite-slice sub-family the sibling
17864 // `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
17865 // (2a1f907) pin opened, peer at the outer altitude of the
17866 // closed inner-`SupervisorSpec` `SupervisorSpec::children`
17867 // (bc92bce) accessor on the same OTP-supervisor static-child-
17868 // list axis.
17869 use crate::supervisor::{ChildSpec, RestartPolicy};
17870 let fixtures: Vec<Vec<ChildSpec>> = vec![
17871 vec![],
17872 vec![ChildSpec {
17873 caixa: "worker".into(),
17874 versao: "^0.1".into(),
17875 restart: RestartPolicy::Permanent,
17876 }],
17877 vec![
17878 ChildSpec {
17879 caixa: "worker-a".into(),
17880 versao: "^0.1".into(),
17881 restart: RestartPolicy::Permanent,
17882 },
17883 ChildSpec {
17884 caixa: "worker-b".into(),
17885 versao: "^0.1".into(),
17886 restart: RestartPolicy::Transient,
17887 },
17888 ChildSpec {
17889 caixa: "worker-c".into(),
17890 versao: "^0.1".into(),
17891 restart: RestartPolicy::Temporary,
17892 },
17893 ],
17894 vec![
17895 ChildSpec {
17896 caixa: "w".into(),
17897 versao: "^0.1".into(),
17898 restart: RestartPolicy::Permanent,
17899 },
17900 ChildSpec {
17901 caixa: "w".into(),
17902 versao: "^0.1".into(),
17903 restart: RestartPolicy::Permanent,
17904 },
17905 ],
17906 ];
17907 for children in fixtures {
17908 let c = caixa_with_children(children.clone());
17909 assert_eq!(
17910 c.children(),
17911 children.as_slice(),
17912 "Caixa::children must return :children verbatim \
17913 (got {:?}, expected {children:?})",
17914 c.children(),
17915 );
17916 assert_eq!(
17917 c.children(),
17918 c.children.as_slice(),
17919 "Caixa::children must element-equal the raw \
17920 `self.children.as_slice()` field access across \
17921 every value in the Vec<ChildSpec> accept-set",
17922 );
17923 assert_eq!(
17924 c.children().is_empty(),
17925 c.children.is_empty(),
17926 "Caixa::children().is_empty() must byte-equal \
17927 self.children.is_empty() — a presence-bit drift \
17928 would silently split the paired \
17929 Caixa::declared_supervisor_slots supervisor-tree \
17930 declared-slot enumerator's presence probe from the \
17931 peer Caixa::supervisor_view typed-view composer's \
17932 fold-in path",
17933 );
17934 }
17935 }
17936
17937 #[test]
17938 fn declared_supervisor_slots_children_arm_routes_through_accessor() {
17939 // Composition pin: [`Caixa::declared_supervisor_slots`]'s
17940 // `:children` presence-probe arm must key off
17941 // [`Caixa::children`], not the raw
17942 // `!self.children.is_empty()` field-probe. Structurally: a
17943 // `Caixa { children: vec![ChildSpec { caixa: "w", versao:
17944 // "^0.1", restart: Permanent }], .. }` must push
17945 // `SUPERVISOR_AUTHOR_KEY_CHILDREN` onto the declared-slot list
17946 // (the presence bit is non-empty, so the supervisor-tree
17947 // kind-coherence gate must surface the slot as "declared"),
17948 // and a `Caixa { children: vec![], .. }` must NOT push the
17949 // label (the "author omitted the slot entirely" arm — the
17950 // empty-slice partition the serde-default folds onto). The
17951 // pair jointly pins the accessor + declared-slot enumerator
17952 // composition: any future silent detour that had the accessor
17953 // collapse `[Permanent]` to `[]` (a `.filter(|c| c.nome() !=
17954 // "__reserved__")` projection) would silently absorb the
17955 // "declared but degenerate" arm at the accessor boundary and
17956 // the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
17957 // kind-coherence gate would silently accept a struct-literal
17958 // `Caixa` carrying the drift.
17959 //
17960 // Peer of the sibling
17961 // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
17962 // (2a1f907) on the M2 `:upgrade-from` composite-slice arm —
17963 // same "the enumerator gate must route through the substrate-
17964 // primitive typed dispatch" discipline extended onto the
17965 // supervisor-tree `:children` composite-slice arm.
17966 use crate::supervisor::{ChildSpec, RestartPolicy};
17967 let c = caixa_with_children(vec![ChildSpec {
17968 caixa: "w".into(),
17969 versao: "^0.1".into(),
17970 restart: RestartPolicy::Permanent,
17971 }]);
17972 let slots = c.declared_supervisor_slots();
17973 assert!(
17974 slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN),
17975 "declared_supervisor_slots must push \
17976 SUPERVISOR_AUTHOR_KEY_CHILDREN when `:children` is \
17977 non-empty — the accessor and the enumerator gate must \
17978 route through the same substrate-primitive typed \
17979 dispatch on the outer :children presence bit (got \
17980 slots={slots:?})",
17981 );
17982 let c = caixa_with_children(vec![]);
17983 let slots = c.declared_supervisor_slots();
17984 assert!(
17985 !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN),
17986 "declared_supervisor_slots must NOT push \
17987 SUPERVISOR_AUTHOR_KEY_CHILDREN when `:children` is \
17988 empty — the author-omitted arm must route through the \
17989 accessor's empty-slice return unchanged (got \
17990 slots={slots:?})",
17991 );
17992 }
17993
17994 #[test]
17995 fn supervisor_view_children_arm_routes_through_accessor() {
17996 // Composition pin: [`Caixa::supervisor_view`]'s per-`:children`
17997 // fold-in arm must key off [`Caixa::children`], not the raw
17998 // `self.children.clone()` field-clone. Structurally: a `Caixa {
17999 // kind: Supervisor, estrategia: Some(OneForOne), children:
18000 // vec![ChildSpec { caixa: "w", .. }], .. }` must fold the
18001 // per-child list through the accessor into the typed
18002 // [`SupervisorSpec`] view's `children` field verbatim — every
18003 // entry the accessor surfaces must land in the view's
18004 // `children` slot in the same order. The pair jointly pins the
18005 // accessor + view-composer composition: any future silent
18006 // detour that had the accessor return a fresh-cloned
18007 // `Vec<ChildSpec>` copy would silently break the reference-
18008 // identity pin the peer `supervisor_view` fold-in path reads
18009 // from — the fold would clone once more per accessor call
18010 // instead of borrowing the storage buffer verbatim once.
18011 //
18012 // Peer of the sibling
18013 // `supervisor_view_kind_gate_routes_through_accessor` (35d8b52-
18014 // family) composition pin on the peer kind-gate arm — same
18015 // "the view composer must route through the substrate-
18016 // primitive typed dispatch" discipline extended onto the
18017 // per-`:children` fold-in arm, closing the supervisor-view
18018 // composer's routing invariant on the composite-slice input.
18019 use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
18020 let mut c = caixa_with_children(vec![
18021 ChildSpec {
18022 caixa: "worker-a".into(),
18023 versao: "^0.1".into(),
18024 restart: RestartPolicy::Permanent,
18025 },
18026 ChildSpec {
18027 caixa: "worker-b".into(),
18028 versao: "^0.1".into(),
18029 restart: RestartPolicy::Transient,
18030 },
18031 ]);
18032 c.kind = crate::CaixaKind::Supervisor;
18033 c.estrategia = Some(RestartStrategy::OneForOne);
18034 let view = c
18035 .supervisor_view()
18036 .expect("Supervisor kind must produce a supervisor_view");
18037 assert_eq!(
18038 view.children(),
18039 c.children(),
18040 "supervisor_view must fold Caixa::children verbatim into \
18041 SupervisorSpec::children — the accessor and the view \
18042 composer must route through the same substrate-primitive \
18043 typed dispatch on the outer :children slice (got view \
18044 children={:?}, expected {:?})",
18045 view.children(),
18046 c.children(),
18047 );
18048 }
18049
18050 #[test]
18051 fn children_projects_slice_by_borrow() {
18052 // The by-borrow pin: [`Caixa::children`] returns
18053 // `&[ChildSpec]` by borrow — the returned slice borrows the
18054 // underlying `Vec<ChildSpec>` storage of the `:children` slot
18055 // and the accessor must not clone the backing `Vec` on every
18056 // call. Peer of the sibling outer top-level [`Caixa`]
18057 // `&[T]`-return by-borrow pins (`autores_projects_slice_by_borrow`
18058 // b5d813f, `etiquetas_projects_slice_by_borrow` 78c7d3c,
18059 // `bibliotecas_projects_slice_by_borrow` 8a36c23,
18060 // `exe_projects_slice_by_borrow` 65d9527,
18061 // `servicos_projects_slice_by_borrow` 611f78b,
18062 // `deps_projects_slice_by_borrow` ad34b4e,
18063 // `deps_dev_projects_slice_by_borrow` f7fd81e,
18064 // `upgrade_from_projects_slice_by_borrow` 2a1f907) on the
18065 // sibling outer top-level [`Caixa`] scalar-element and
18066 // composite-element `&[T]` axes — folds on the outer-`Caixa`
18067 // composite-element `&[Composite]` axis: the accessor's
18068 // returned slice must borrow from `&self` (the returned
18069 // reference's lifetime is tied to `&self`), and calling the
18070 // accessor twice on the same [`Caixa`] must yield slices
18071 // that are pointer-equal (the underlying byte-buffer is the
18072 // storage `Vec`'s allocation, not a fresh copy) as well as
18073 // value-equal (idempotent, no side effects on `&self`).
18074 //
18075 // Pins against a future silent detour that returned an owned
18076 // `Vec<ChildSpec>` (which would type-check but silently clone
18077 // on every call), a `&Vec<ChildSpec>` return (which would leak
18078 // the backing `Vec`'s grow/push/reserve surface no downstream
18079 // consumer reaches for), or a one-arm-only accessor that
18080 // returned a saturating value on some sentinel input.
18081 use crate::supervisor::{ChildSpec, RestartPolicy};
18082 for children in [
18083 vec![],
18084 vec![ChildSpec {
18085 caixa: "w".into(),
18086 versao: "^0.1".into(),
18087 restart: RestartPolicy::Permanent,
18088 }],
18089 vec![
18090 ChildSpec {
18091 caixa: "worker-a".into(),
18092 versao: "^0.1".into(),
18093 restart: RestartPolicy::Permanent,
18094 },
18095 ChildSpec {
18096 caixa: "worker-b".into(),
18097 versao: "^0.1".into(),
18098 restart: RestartPolicy::Transient,
18099 },
18100 ],
18101 ] {
18102 let c = caixa_with_children(children.clone());
18103 let first = c.children();
18104 let second = c.children();
18105 assert_eq!(
18106 first, second,
18107 "Caixa::children must be idempotent — two successive \
18108 calls on the same &self must return the same \
18109 &[ChildSpec]",
18110 );
18111 assert_eq!(
18112 first.as_ptr(),
18113 second.as_ptr(),
18114 "Caixa::children must borrow the underlying \
18115 Vec<ChildSpec> storage — two successive calls must \
18116 return slices with the same backing pointer (a fresh \
18117 Vec<ChildSpec> clone would change the pointer on \
18118 every call)",
18119 );
18120 assert_eq!(
18121 first,
18122 children.as_slice(),
18123 "Caixa::children must return :children verbatim by \
18124 borrow — got {first:?}, expected {children:?}",
18125 );
18126 }
18127 }
18128
18129 // ── Caixa::membros — outer top-level &[Membro] composite-slice accessor ──
18130
18131 fn caixa_aplicacao_with_membros(membros: Vec<crate::aplicacao::Membro>) -> Caixa {
18132 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
18133 c.kind = CaixaKind::Aplicacao;
18134 c.membros = membros;
18135 c
18136 }
18137
18138 #[test]
18139 fn membros_returns_membros_slice_verbatim_across_permutations() {
18140 // The canonical per-`Caixa` `:membros` M3 mesh-slot outer-
18141 // composite `&[Membro]`-return slice-shape pin:
18142 // [`Caixa::membros`] must return the `:membros` typed
18143 // `Vec<Membro>` verbatim as a `&[Membro]` slice-view over the
18144 // same backing buffer the raw `self.membros.as_slice()` field
18145 // access borrows from, element-equal across every
18146 // representative fixture in the accept-set — `[]` (the "no
18147 // members declared" arm every non-`Aplicacao`-kind `defcaixa`
18148 // carries by `#[serde(default)]` and every partially-authored
18149 // Aplicacao carries before the
18150 // [`crate::AplicacaoError::MembrosEmpty`] gate fires), a
18151 // canonical single-member fixture (the shape a minimal
18152 // Aplicacao carries — one Servico wrapping one contained
18153 // computation), a canonical multi-member list carrying three
18154 // distinct entries (the canonical checkout-shape Aplicacao —
18155 // cart / pricing / auth — every canonical example carries), and
18156 // a past-the-guard sentinel — a duplicate `:caixa`
18157 // `[("cart", ...), ("cart", ...)]` entry pair
18158 // ([`crate::AplicacaoSpec::validate`] rejects through
18159 // `DuplicateMembro { nome: "cart" }` but the accessor must ship
18160 // the raw slot verbatim so struct-literal fixtures continue to
18161 // expose the duplicate at the accessor boundary).
18162 //
18163 // Pins against a future silent detour that returned an owned
18164 // `Vec<Membro>` (which would type-check but silently clone on
18165 // every accessor call, breaking the zero-cost projection every
18166 // peer sibling slice accessor carries), a `[dup, dup] → [dup]`
18167 // dedup collapse (which would silently absorb the
18168 // `DuplicateMembro` refusal case at the accessor boundary and
18169 // the [`crate::StandardLayout::verify`] cross-member gate would
18170 // silently accept a struct-literal `Caixa` carrying the drift),
18171 // a reference to an operator-resolved overlay (the future per-
18172 // cluster `:membros-overrides` slot — its resolution must land
18173 // at exactly this accessor body, not silently divert the raw
18174 // slot away from a second consumer), or an axis-shuffled
18175 // projection (a future detour that reordered members through
18176 // the accessor would silently split the paired
18177 // [`crate::StandardLayout::verify`] per-Aplicacao gate's
18178 // traversal input from the peer [`Self::aplicacao_view`] fold-
18179 // in path's clone-order input, since the canonical `:contratos`
18180 // `:de`/`:para` and `:entrada :para` cross-slot refusal probes
18181 // read the member set through the same slice).
18182 //
18183 // Third outer top-level [`Caixa`] `&[Composite]`-return slice
18184 // accessor pin on the substrate primitive for M2 / M3 typed-
18185 // slot vec-carry axes — opens the outer-`Caixa` M3 mesh-slot
18186 // arm of the `&[Composite]` composite-slice sub-family the
18187 // sibling M2 `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
18188 // (2a1f907) and
18189 // `children_returns_children_slice_verbatim_across_permutations`
18190 // (c17b51e) pins opened, peer at the outer altitude of the
18191 // closed inner-[`crate::AplicacaoSpec::membros`] (6c77e36)
18192 // accessor on the same MESH-COMPOSITION per-Aplicacao member-
18193 // list axis.
18194 use crate::aplicacao::Membro;
18195 let fixtures: Vec<Vec<Membro>> = vec![
18196 vec![],
18197 vec![Membro {
18198 caixa: "cart".into(),
18199 versao: "^0.1".into(),
18200 }],
18201 vec![
18202 Membro {
18203 caixa: "cart".into(),
18204 versao: "^0.1".into(),
18205 },
18206 Membro {
18207 caixa: "pricing".into(),
18208 versao: "^0.2".into(),
18209 },
18210 Membro {
18211 caixa: "auth".into(),
18212 versao: "^1.0".into(),
18213 },
18214 ],
18215 vec![
18216 Membro {
18217 caixa: "cart".into(),
18218 versao: "^0.1".into(),
18219 },
18220 Membro {
18221 caixa: "cart".into(),
18222 versao: "^0.1".into(),
18223 },
18224 ],
18225 ];
18226 for membros in fixtures {
18227 let c = caixa_aplicacao_with_membros(membros.clone());
18228 assert_eq!(
18229 c.membros(),
18230 membros.as_slice(),
18231 "Caixa::membros must return :membros verbatim \
18232 (got {:?}, expected {membros:?})",
18233 c.membros(),
18234 );
18235 assert_eq!(
18236 c.membros(),
18237 c.membros.as_slice(),
18238 "Caixa::membros must element-equal the raw \
18239 `self.membros.as_slice()` field access across every \
18240 value in the Vec<Membro> accept-set",
18241 );
18242 assert_eq!(
18243 c.membros().is_empty(),
18244 c.membros.is_empty(),
18245 "Caixa::membros().is_empty() must byte-equal \
18246 self.membros.is_empty() — a presence-bit drift would \
18247 silently split the paired Caixa::declared_mesh_slots \
18248 mesh declared-slot enumerator's presence probe from \
18249 the peer Caixa::aplicacao_view typed-view composer's \
18250 fold-in path",
18251 );
18252 }
18253 }
18254
18255 #[test]
18256 fn declared_mesh_slots_membros_arm_routes_through_accessor() {
18257 // Composition pin: [`Caixa::declared_mesh_slots`]'s `:membros`
18258 // presence-probe arm must key off [`Caixa::membros`], not the
18259 // raw `!self.membros.is_empty()` field-probe. Structurally: a
18260 // `Caixa { membros: vec![Membro { caixa: "cart", versao:
18261 // "^0.1" }], .. }` must push `M3_AUTHOR_KEY_MEMBROS` onto the
18262 // declared-slot list (the presence bit is non-empty, so the
18263 // mesh kind-coherence gate must surface the slot as
18264 // "declared"), and a `Caixa { membros: vec![], .. }` must NOT
18265 // push the label (the "author omitted the slot entirely" arm
18266 // — the empty-slice partition the serde-default folds onto).
18267 // The pair jointly pins the accessor + declared-slot
18268 // enumerator composition: any future silent detour that had
18269 // the accessor collapse `[Membro { .. }]` to `[]` (a
18270 // `.filter(|m| m.nome() != "__reserved__")` projection) would
18271 // silently absorb the "declared but degenerate" arm at the
18272 // accessor boundary and the
18273 // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
18274 // coherence gate would silently accept a struct-literal
18275 // `Caixa` carrying the drift.
18276 //
18277 // Peer of the sibling
18278 // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
18279 // (2a1f907) and
18280 // `declared_supervisor_slots_children_arm_routes_through_accessor`
18281 // (c17b51e) composition pins on the M2 `:upgrade-from` /
18282 // `:children` composite-slice arms — same "the enumerator gate
18283 // must route through the substrate-primitive typed dispatch"
18284 // discipline extended onto the M3 `:membros` composite-slice
18285 // arm, opening the M3 arm of the declared-slot enumerator's
18286 // routing invariant.
18287 use crate::aplicacao::Membro;
18288 let c = caixa_aplicacao_with_membros(vec![Membro {
18289 caixa: "cart".into(),
18290 versao: "^0.1".into(),
18291 }]);
18292 let slots = c.declared_mesh_slots();
18293 assert!(
18294 slots.contains(&crate::render::M3_AUTHOR_KEY_MEMBROS),
18295 "declared_mesh_slots must push M3_AUTHOR_KEY_MEMBROS when \
18296 `:membros` is non-empty — the accessor and the enumerator \
18297 gate must route through the same substrate-primitive \
18298 typed dispatch on the outer :membros presence bit (got \
18299 slots={slots:?})",
18300 );
18301 let c = caixa_aplicacao_with_membros(vec![]);
18302 let slots = c.declared_mesh_slots();
18303 assert!(
18304 !slots.contains(&crate::render::M3_AUTHOR_KEY_MEMBROS),
18305 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_MEMBROS \
18306 when `:membros` is empty — the author-omitted arm must \
18307 route through the accessor's empty-slice return unchanged \
18308 (got slots={slots:?})",
18309 );
18310 }
18311
18312 #[test]
18313 fn aplicacao_view_membros_arm_routes_through_accessor() {
18314 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:membros`
18315 // fold-in arm must key off [`Caixa::membros`], not the raw
18316 // `self.membros.clone()` field-clone. Structurally: a `Caixa {
18317 // kind: Aplicacao, membros: vec![Membro { caixa: "cart", .. },
18318 // Membro { caixa: "pricing", .. }], .. }` must fold the per-
18319 // member list through the accessor into the typed
18320 // [`crate::AplicacaoSpec`] view's `membros` slot verbatim —
18321 // every entry the accessor surfaces must land in the view's
18322 // `membros` slot in the same order. The pair jointly pins the
18323 // accessor + view-composer composition: any future silent
18324 // detour that had the accessor return a fresh-cloned
18325 // `Vec<Membro>` copy would silently break the reference-
18326 // identity pin the peer `aplicacao_view` fold-in path reads
18327 // from — the fold would clone once more per accessor call
18328 // instead of borrowing the storage buffer verbatim once.
18329 //
18330 // Peer of the sibling
18331 // `aplicacao_view_politicas_arm_folds_through_accessor`
18332 // (5d23d29) /
18333 // `aplicacao_view_placement_arm_folds_through_accessor`
18334 // (4fb8074) /
18335 // `aplicacao_view_entrada_arm_folds_through_accessor` (e4128e4)
18336 // composition pins on the M3 `:politicas` / `:placement` /
18337 // `:entrada` outer-`Option<&Composite>` arms — extended here to
18338 // the M3 `:membros` outer-`&[Composite]` composite-slice arm,
18339 // closing the aplicacao-view composer's routing invariant on
18340 // the composite-slice input.
18341 use crate::aplicacao::Membro;
18342 let c = caixa_aplicacao_with_membros(vec![
18343 Membro {
18344 caixa: "cart".into(),
18345 versao: "^0.1".into(),
18346 },
18347 Membro {
18348 caixa: "pricing".into(),
18349 versao: "^0.2".into(),
18350 },
18351 ]);
18352 let view = c
18353 .aplicacao_view()
18354 .expect("Aplicacao kind must produce an aplicacao_view");
18355 assert_eq!(
18356 view.membros(),
18357 c.membros(),
18358 "aplicacao_view must fold Caixa::membros verbatim into \
18359 AplicacaoSpec::membros — the accessor and the view \
18360 composer must route through the same substrate-primitive \
18361 typed dispatch on the outer :membros slice (got view \
18362 membros={:?}, expected {:?})",
18363 view.membros(),
18364 c.membros(),
18365 );
18366 }
18367
18368 #[test]
18369 fn membros_projects_slice_by_borrow() {
18370 // The by-borrow pin: [`Caixa::membros`] returns `&[Membro]` by
18371 // borrow — the returned slice borrows the underlying
18372 // `Vec<Membro>` storage of the `:membros` slot and the
18373 // accessor must not clone the backing `Vec` on every call.
18374 // Peer of the sibling outer top-level [`Caixa`] `&[T]`-return
18375 // by-borrow pins (`autores_projects_slice_by_borrow` b5d813f,
18376 // `etiquetas_projects_slice_by_borrow` 78c7d3c,
18377 // `bibliotecas_projects_slice_by_borrow` 8a36c23,
18378 // `exe_projects_slice_by_borrow` 65d9527,
18379 // `servicos_projects_slice_by_borrow` 611f78b,
18380 // `deps_projects_slice_by_borrow` ad34b4e,
18381 // `deps_dev_projects_slice_by_borrow` f7fd81e,
18382 // `upgrade_from_projects_slice_by_borrow` 2a1f907,
18383 // `children_projects_slice_by_borrow` c17b51e) on the sibling
18384 // outer top-level [`Caixa`] scalar-element and composite-
18385 // element `&[T]` axes — folds on the outer-`Caixa` M3 mesh-
18386 // slot composite-element `&[Composite]` axis: the accessor's
18387 // returned slice must borrow from `&self` (the returned
18388 // reference's lifetime is tied to `&self`), and calling the
18389 // accessor twice on the same [`Caixa`] must yield slices that
18390 // are pointer-equal (the underlying byte-buffer is the storage
18391 // `Vec`'s allocation, not a fresh copy) as well as value-equal
18392 // (idempotent, no side effects on `&self`).
18393 //
18394 // Pins against a future silent detour that returned an owned
18395 // `Vec<Membro>` (which would type-check but silently clone on
18396 // every call), a `&Vec<Membro>` return (which would leak the
18397 // backing `Vec`'s grow/push/reserve surface no downstream
18398 // consumer reaches for), or a one-arm-only accessor that
18399 // returned a saturating value on some sentinel input.
18400 use crate::aplicacao::Membro;
18401 for membros in [
18402 vec![],
18403 vec![Membro {
18404 caixa: "cart".into(),
18405 versao: "^0.1".into(),
18406 }],
18407 vec![
18408 Membro {
18409 caixa: "cart".into(),
18410 versao: "^0.1".into(),
18411 },
18412 Membro {
18413 caixa: "pricing".into(),
18414 versao: "^0.2".into(),
18415 },
18416 ],
18417 ] {
18418 let c = caixa_aplicacao_with_membros(membros.clone());
18419 let first = c.membros();
18420 let second = c.membros();
18421 assert_eq!(
18422 first, second,
18423 "Caixa::membros must be idempotent — two successive \
18424 calls on the same &self must return the same &[Membro]",
18425 );
18426 assert_eq!(
18427 first.as_ptr(),
18428 second.as_ptr(),
18429 "Caixa::membros must borrow the underlying Vec<Membro> \
18430 storage — two successive calls must return slices with \
18431 the same backing pointer (a fresh Vec<Membro> clone \
18432 would change the pointer on every call)",
18433 );
18434 assert_eq!(
18435 first,
18436 membros.as_slice(),
18437 "Caixa::membros must return :membros verbatim by borrow \
18438 — got {first:?}, expected {membros:?}",
18439 );
18440 }
18441 }
18442
18443 // ── Caixa::contratos — outer top-level &[WitContract] composite-slice accessor ──
18444
18445 fn caixa_aplicacao_with_contratos(contratos: Vec<crate::aplicacao::WitContract>) -> Caixa {
18446 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
18447 c.kind = CaixaKind::Aplicacao;
18448 c.contratos = contratos;
18449 c
18450 }
18451
18452 fn contrato_http_for_test(
18453 de: &str,
18454 para: &str,
18455 endpoint: &str,
18456 ) -> crate::aplicacao::WitContract {
18457 crate::aplicacao::WitContract {
18458 de: de.into(),
18459 para: para.into(),
18460 wit: "wasi:http/proxy".into(),
18461 endpoint: Some(endpoint.into()),
18462 subject: None,
18463 slot: None,
18464 }
18465 }
18466
18467 #[test]
18468 fn contratos_returns_contratos_slice_verbatim_across_permutations() {
18469 // The canonical per-`Caixa` `:contratos` M3 mesh-slot outer-
18470 // composite `&[WitContract]`-return slice-shape pin:
18471 // [`Caixa::contratos`] must return the `:contratos` typed
18472 // `Vec<WitContract>` verbatim as a `&[WitContract]` slice-view
18473 // over the same backing buffer the raw
18474 // `self.contratos.as_slice()` field access borrows from,
18475 // element-equal across every representative fixture in the
18476 // accept-set — `[]` (the "no contracts declared" arm every
18477 // non-`Aplicacao`-kind `defcaixa` carries by
18478 // `#[serde(default)]` and every leaf-Aplicacao with a single
18479 // member carries), a canonical single-edge fixture (the
18480 // minimal directed-graph shape: one HTTP-shape `(cart → catalog)`
18481 // edge), and a canonical multi-edge fixture with three distinct
18482 // edges (the checkout-shape Aplicacao's HTTP-fan pattern:
18483 // `(cart → catalog)`, `(cart → pricing)`, `(cart → auth)`).
18484 //
18485 // Pins against a future silent detour that returned an owned
18486 // `Vec<WitContract>` (which would type-check but silently clone
18487 // on every accessor call, breaking the zero-cost projection
18488 // every peer sibling slice accessor carries), an axis-shuffled
18489 // projection (a future detour that reordered edges through the
18490 // accessor would silently split the paired
18491 // [`crate::StandardLayout::verify`] per-Aplicacao gate's
18492 // traversal input from the peer [`Self::aplicacao_view`] fold-
18493 // in path's clone-order input, since every canonical
18494 // `caixa-mesh` renderer's per-`(:de, :para)` adjacency-list
18495 // seed dispatch reads the edge set through the same slice),
18496 // or a reference to an operator-resolved overlay (the future
18497 // per-cluster `:contratos-overrides` slot — its resolution
18498 // must land at exactly this accessor body, not silently divert
18499 // the raw slot away from a second consumer).
18500 //
18501 // Fourth outer top-level [`Caixa`] `&[Composite]`-return slice
18502 // accessor pin on the substrate primitive for M2 / M3 typed-
18503 // slot vec-carry axes — closes the outer-`Caixa`
18504 // `&[Composite]` composite-slice sub-family the sibling M2
18505 // `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
18506 // (2a1f907) and
18507 // `children_returns_children_slice_verbatim_across_permutations`
18508 // (c17b51e) pins opened and the M3
18509 // `membros_returns_membros_slice_verbatim_across_permutations`
18510 // (0f26987) pin folded on, closing the outer-`Caixa` M3 mesh-
18511 // slot arm of the composite-slice sub-family. Peer at the outer
18512 // altitude of the closed inner-
18513 // [`crate::AplicacaoSpec::contratos`] (0dcc926) accessor on the
18514 // same MESH-COMPOSITION per-Aplicacao contract-list axis.
18515 let fixtures: Vec<Vec<crate::aplicacao::WitContract>> = vec![
18516 vec![],
18517 vec![contrato_http_for_test("cart", "catalog", "/items")],
18518 vec![
18519 contrato_http_for_test("cart", "catalog", "/items"),
18520 contrato_http_for_test("cart", "pricing", "/price"),
18521 contrato_http_for_test("cart", "auth", "/whoami"),
18522 ],
18523 ];
18524 for contratos in fixtures {
18525 let c = caixa_aplicacao_with_contratos(contratos.clone());
18526 assert_eq!(
18527 c.contratos(),
18528 contratos.as_slice(),
18529 "Caixa::contratos must return :contratos verbatim \
18530 (got {:?}, expected {contratos:?})",
18531 c.contratos(),
18532 );
18533 assert_eq!(
18534 c.contratos(),
18535 c.contratos.as_slice(),
18536 "Caixa::contratos must element-equal the raw \
18537 `self.contratos.as_slice()` field access across every \
18538 value in the Vec<WitContract> accept-set",
18539 );
18540 assert_eq!(
18541 c.contratos().is_empty(),
18542 c.contratos.is_empty(),
18543 "Caixa::contratos().is_empty() must byte-equal \
18544 self.contratos.is_empty() — a presence-bit drift would \
18545 silently split the paired Caixa::declared_mesh_slots \
18546 mesh declared-slot enumerator's presence probe from \
18547 the peer Caixa::aplicacao_view typed-view composer's \
18548 fold-in path",
18549 );
18550 }
18551 }
18552
18553 #[test]
18554 fn declared_mesh_slots_contratos_arm_routes_through_accessor() {
18555 // Composition pin: [`Caixa::declared_mesh_slots`]'s `:contratos`
18556 // presence-probe arm must key off [`Caixa::contratos`], not the
18557 // raw `!self.contratos.is_empty()` field-probe. Structurally: a
18558 // `Caixa { contratos: vec![WitContract { .. }], .. }` must push
18559 // `M3_AUTHOR_KEY_CONTRATOS` onto the declared-slot list (the
18560 // presence bit is non-empty, so the mesh kind-coherence gate
18561 // must surface the slot as "declared"), and a `Caixa {
18562 // contratos: vec![], .. }` must NOT push the label (the "author
18563 // omitted the slot entirely" arm — the empty-slice partition
18564 // the serde-default folds onto). The pair jointly pins the
18565 // accessor + declared-slot enumerator composition: any future
18566 // silent detour that had the accessor collapse
18567 // `[WitContract { .. }]` to `[]` (a `.filter(|c| c.de() !=
18568 // "__reserved__")` projection) would silently absorb the
18569 // "declared but degenerate" arm at the accessor boundary and
18570 // the [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
18571 // coherence gate would silently accept a struct-literal
18572 // `Caixa` carrying the drift.
18573 //
18574 // Peer of the sibling
18575 // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
18576 // (2a1f907),
18577 // `declared_supervisor_slots_children_arm_routes_through_accessor`
18578 // (c17b51e), and
18579 // `declared_mesh_slots_membros_arm_routes_through_accessor`
18580 // (0f26987) composition pins on the M2 `:upgrade-from` /
18581 // `:children` / M3 `:membros` composite-slice arms — same "the
18582 // enumerator gate must route through the substrate-primitive
18583 // typed dispatch" discipline extended onto the M3 `:contratos`
18584 // composite-slice arm, closing the M3 mesh-slot arm of the
18585 // declared-slot enumerator's routing invariant on the
18586 // composite-slice inputs.
18587 let c = caixa_aplicacao_with_contratos(vec![contrato_http_for_test(
18588 "cart", "catalog", "/items",
18589 )]);
18590 let slots = c.declared_mesh_slots();
18591 assert!(
18592 slots.contains(&crate::render::M3_AUTHOR_KEY_CONTRATOS),
18593 "declared_mesh_slots must push M3_AUTHOR_KEY_CONTRATOS when \
18594 `:contratos` is non-empty — the accessor and the enumerator \
18595 gate must route through the same substrate-primitive \
18596 typed dispatch on the outer :contratos presence bit (got \
18597 slots={slots:?})",
18598 );
18599 let c = caixa_aplicacao_with_contratos(vec![]);
18600 let slots = c.declared_mesh_slots();
18601 assert!(
18602 !slots.contains(&crate::render::M3_AUTHOR_KEY_CONTRATOS),
18603 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_CONTRATOS \
18604 when `:contratos` is empty — the author-omitted arm must \
18605 route through the accessor's empty-slice return unchanged \
18606 (got slots={slots:?})",
18607 );
18608 }
18609
18610 #[test]
18611 fn aplicacao_view_contratos_arm_routes_through_accessor() {
18612 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:contratos`
18613 // fold-in arm must key off [`Caixa::contratos`], not the raw
18614 // `self.contratos.clone()` field-clone. Structurally: a `Caixa
18615 // { kind: Aplicacao, contratos: vec![WitContract { de: "cart",
18616 // .. }, WitContract { de: "pricing", .. }], .. }` must fold the
18617 // per-edge list through the accessor into the typed
18618 // [`crate::AplicacaoSpec`] view's `contratos` slot verbatim —
18619 // every entry the accessor surfaces must land in the view's
18620 // `contratos` slot in the same order. The pair jointly pins
18621 // the accessor + view-composer composition: a future silent
18622 // detour that had the accessor shuffle or drop an edge would
18623 // silently split the paired declared-slot enumerator's
18624 // presence bit from the typed-view composer's edge-list, a
18625 // two-consumer split at the enumerator and the view composer
18626 // far from the source `caixa.lisp`.
18627 //
18628 // Peer of the sibling
18629 // `aplicacao_view_membros_arm_routes_through_accessor`
18630 // (0f26987) composition pin on the M3 `:membros` outer-
18631 // `&[Composite]` composite-slice arm, closing the aplicacao-
18632 // view composer's routing invariant on the composite-slice
18633 // inputs at the outer altitude.
18634 let c = caixa_aplicacao_with_contratos(vec![
18635 contrato_http_for_test("cart", "catalog", "/items"),
18636 contrato_http_for_test("cart", "pricing", "/price"),
18637 ]);
18638 let view = c
18639 .aplicacao_view()
18640 .expect("Aplicacao kind must produce an aplicacao_view");
18641 assert_eq!(
18642 view.contratos(),
18643 c.contratos(),
18644 "aplicacao_view must fold Caixa::contratos verbatim into \
18645 AplicacaoSpec::contratos — the accessor and the view \
18646 composer must route through the same substrate-primitive \
18647 typed dispatch on the outer :contratos slice (got view \
18648 contratos={:?}, expected {:?})",
18649 view.contratos(),
18650 c.contratos(),
18651 );
18652 }
18653
18654 #[test]
18655 fn contratos_projects_slice_by_borrow() {
18656 // The by-borrow pin: [`Caixa::contratos`] returns `&[WitContract]`
18657 // by borrow — the returned slice borrows the underlying
18658 // `Vec<WitContract>` storage of the `:contratos` slot and the
18659 // accessor must not clone the backing `Vec` on every call.
18660 // Peer of the sibling outer top-level [`Caixa`] `&[T]`-return
18661 // by-borrow pins (`autores_projects_slice_by_borrow` b5d813f,
18662 // `etiquetas_projects_slice_by_borrow` 78c7d3c,
18663 // `bibliotecas_projects_slice_by_borrow` 8a36c23,
18664 // `exe_projects_slice_by_borrow` 65d9527,
18665 // `servicos_projects_slice_by_borrow` 611f78b,
18666 // `deps_projects_slice_by_borrow` ad34b4e,
18667 // `deps_dev_projects_slice_by_borrow` f7fd81e,
18668 // `upgrade_from_projects_slice_by_borrow` 2a1f907,
18669 // `children_projects_slice_by_borrow` c17b51e,
18670 // `membros_projects_slice_by_borrow` 0f26987) on the sibling
18671 // outer top-level [`Caixa`] scalar-element and composite-
18672 // element `&[T]` axes — closes the outer-`Caixa` M3 mesh-slot
18673 // composite-element `&[Composite]` axis on the by-borrow pin:
18674 // the accessor's returned slice must borrow from `&self` (the
18675 // returned reference's lifetime is tied to `&self`), and
18676 // calling the accessor twice on the same [`Caixa`] must yield
18677 // slices that are pointer-equal (the underlying byte-buffer is
18678 // the storage `Vec`'s allocation, not a fresh copy) as well as
18679 // value-equal (idempotent, no side effects on `&self`).
18680 //
18681 // Pins against a future silent detour that returned an owned
18682 // `Vec<WitContract>` (which would type-check but silently clone
18683 // on every call), a `&Vec<WitContract>` return (which would
18684 // leak the backing `Vec`'s grow/push/reserve surface no
18685 // downstream consumer reaches for), or a one-arm-only accessor
18686 // that returned a saturating value on some sentinel input.
18687 for contratos in [
18688 vec![],
18689 vec![contrato_http_for_test("cart", "catalog", "/items")],
18690 vec![
18691 contrato_http_for_test("cart", "catalog", "/items"),
18692 contrato_http_for_test("cart", "pricing", "/price"),
18693 ],
18694 ] {
18695 let c = caixa_aplicacao_with_contratos(contratos.clone());
18696 let first = c.contratos();
18697 let second = c.contratos();
18698 assert_eq!(
18699 first, second,
18700 "Caixa::contratos must be idempotent — two successive \
18701 calls on the same &self must return the same \
18702 &[WitContract]",
18703 );
18704 assert_eq!(
18705 first.as_ptr(),
18706 second.as_ptr(),
18707 "Caixa::contratos must borrow the underlying \
18708 Vec<WitContract> storage — two successive calls must \
18709 return slices with the same backing pointer (a fresh \
18710 Vec<WitContract> clone would change the pointer on \
18711 every call)",
18712 );
18713 assert_eq!(
18714 first,
18715 contratos.as_slice(),
18716 "Caixa::contratos must return :contratos verbatim by \
18717 borrow — got {first:?}, expected {contratos:?}",
18718 );
18719 }
18720 }
18721
18722 // ── drift-detection: Caixa top-level multi-word serde-derive-to-const identity ──
18723
18724 #[test]
18725 fn caixa_multi_word_serde_keys_match_lifted_top_level_key_consts() {
18726 // Load-bearing invariant: every multi-word top-level [`Caixa`]
18727 // serde-derived JSON key routes through a lifted `&'static str`
18728 // const. The Rust field names are `snake_case`
18729 // (`deps_dev` / `upgrade_from` / `max_restarts` /
18730 // `restart_window`); [`Caixa`]'s `#[serde(rename_all =
18731 // "camelCase")]` derive attribute maps each to the camelCase
18732 // byte-string the [`Caixa::to_lisp`] round-trip's
18733 // `serde_json::to_value(self)` step lands under before
18734 // `tatara_lisp::domain::json_to_sexp` re-projects the JSON keys
18735 // to the kebab-case `:deps-dev` / `:upgrade-from` /
18736 // `:max-restarts` / `:restart-window` author surface. Serialize
18737 // a fully-populated [`Caixa`] and pin that each canonical
18738 // byte-sequence appears verbatim in the JSON — a future
18739 // accidental `rename_all = "snake_case"` / `"kebab-case"` /
18740 // verbatim-field-name flip at the derive attribute (any of
18741 // which would silently break every [`Caixa::to_lisp`]
18742 // round-trip and the future M4 operator-side manifest ingest's
18743 // `Value::get(<key>)` navigation) surfaces here as a build-time
18744 // test failure at `manifest.rs`, not as an apply-time
18745 // `.get(<stale-canonical-const>)` returning `None` far from the
18746 // derive-attr drift's commit. Same discipline the sibling
18747 // `supervisor_spec_serde_keys_match_lifted_supervisor_key_consts`
18748 // (40cc4e5), `membro_serde_keys_match_lifted_membro_key_consts`
18749 // (ce80ca0), and `upgrade_from_entry_serde_keys_match_lifted_
18750 // m2_upgrade_from_key_consts` (36ffe65) pins established on the
18751 // sibling M2 supervision-tree, M3 [`Membro`] per-entry, and M2
18752 // [`UpgradeFromEntry`] per-entry axes — extended here to the
18753 // enclosing M0 [`Caixa`] top-level axis so the last of the four
18754 // multi-word top-level [`Caixa`] serde-derived JSON keys
18755 // (`depsDev`) joins the substrate's "one canonical byte-string
18756 // per typed serialized-key axis" discipline.
18757 use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
18758 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
18759 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
18760 c.deps_dev = vec![Dep::simple("tatara-check", "^0.1")];
18761 c.upgrade_from = vec![UpgradeFromEntry {
18762 from: "0.0.1".into(),
18763 instructions: vec![UpgradeInstruction::Restart],
18764 }];
18765 c.estrategia = Some(RestartStrategy::OneForOne);
18766 c.max_restarts = Some(3);
18767 c.restart_window = Some("60s".into());
18768 c.children = vec![ChildSpec {
18769 caixa: "child".into(),
18770 versao: "^0.1".into(),
18771 restart: RestartPolicy::Permanent,
18772 }];
18773 let json = serde_json::to_string(&c).unwrap();
18774 for key in [
18775 crate::render::CAIXA_KEY_DEPS_DEV,
18776 crate::render::M2_KEY_UPGRADE_FROM,
18777 crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
18778 crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
18779 ] {
18780 let quoted = format!("\"{key}\"");
18781 assert!(
18782 json.contains("ed),
18783 "serialized Caixa must carry the lifted top-level \
18784 multi-word byte-sequence {quoted} verbatim in the JSON \
18785 emission (got: {json})",
18786 );
18787 }
18788 }
18789
18790 #[test]
18791 fn caixa_top_level_multi_word_key_consts_are_pairwise_distinct() {
18792 // Cross-axis drift-detection pin: a future collapse of the four
18793 // canonical [`Caixa`] top-level multi-word byte-strings onto the
18794 // same value (e.g. an accidental copy-paste flip of
18795 // [`crate::render::CAIXA_KEY_DEPS_DEV`] to also read
18796 // `"upgradeFrom"`) would silently reroute every downstream
18797 // `Value::get(<key>)` probe on one axis onto the sibling axis's
18798 // top-level entry and pass every propagation-probe test that
18799 // expected only the stale axis's value. Peer of the sibling
18800 // four-way distinct pin on the `SUPERVISOR_KEY_*` tetrad
18801 // (40cc4e5) and the two-way pin on `MEMBRO_KEY_*` (ce80ca0).
18802 let all = [
18803 crate::render::CAIXA_KEY_DEPS_DEV,
18804 crate::render::M2_KEY_UPGRADE_FROM,
18805 crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
18806 crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
18807 ];
18808 for (i, a) in all.iter().enumerate() {
18809 for b in all.iter().skip(i + 1) {
18810 assert_ne!(
18811 a, b,
18812 "Caixa top-level multi-word key consts must be \
18813 pairwise-distinct canonical byte-sequences — got \
18814 `{a}` == `{b}`",
18815 );
18816 }
18817 }
18818 }
18819
18820 #[test]
18821 fn caixa_top_level_multi_word_key_consts_are_lower_camel_case_shape() {
18822 // Shape-pin: every [`Caixa`] top-level multi-word key const must
18823 // be a lowerCamelCase byte-sequence (no `snake_case`
18824 // underscores, no `kebab-case` hyphens, no leading colon, no
18825 // `PascalCase` leading capital, no whitespace / dots) — the
18826 // canonical shape the `#[serde(rename_all = "camelCase")]`
18827 // derive produces on [`Caixa`]. A future flip to a
18828 // non-camelCase attribute at the derive surfaces both here
18829 // (this test fails on the stale-constant shape) and at
18830 // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
18831 // (that test fails on the mismatch between const and derive).
18832 // Peer with `membro_key_consts_are_lower_camel_case_shape`
18833 // (ce80ca0) and `supervisor_key_consts_are_lower_camel_case_shape`
18834 // (40cc4e5) on the sibling per-entry / supervisor-tree axes.
18835 for key in [
18836 crate::render::CAIXA_KEY_DEPS_DEV,
18837 crate::render::M2_KEY_UPGRADE_FROM,
18838 crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
18839 crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
18840 ] {
18841 assert!(
18842 !key.is_empty(),
18843 "Caixa top-level multi-word key const must be non-empty \
18844 (got {key:?})"
18845 );
18846 let first = key.chars().next().unwrap();
18847 assert!(
18848 first.is_ascii_lowercase(),
18849 "Caixa top-level multi-word key const must lead with an \
18850 ASCII-lowercase byte (got {key:?}, leads with {first:?})",
18851 );
18852 assert!(
18853 key.chars().all(|c| c.is_ascii_alphanumeric()),
18854 "Caixa top-level multi-word key const must be \
18855 ASCII-alphanumeric only — no `_` / `-` / `:` / `.` / \
18856 whitespace (got {key:?})",
18857 );
18858 }
18859 }
18860
18861 #[test]
18862 fn caixa_key_deps_dev_pins_canonical_camel_case_byte_string() {
18863 // Scalar-value pin: the byte-string the
18864 // [`crate::render::CAIXA_KEY_DEPS_DEV`] const resolves to,
18865 // asserted verbatim. A future rebrand (`depsDev` → `devDeps`
18866 // matching Cargo's verbatim `dev-dependencies` axis, `depsDev`
18867 // → `depsTest` matching a hypothetical per-test-target
18868 // vocabulary flip) lands as an edit to exactly one const AND
18869 // one derive attribute — the sibling
18870 // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
18871 // pin already ties the const to the derive attribute, so a
18872 // rebrand that touches only one side of the pair fails at
18873 // caixa-core build time. Same "scalar-value pin per const"
18874 // discipline the sibling
18875 // `m2_top_level_author_key_consts_pin_canonical_kebab_case_labels`
18876 // (f49c8b0) and `contrato_key_consts_pin_canonical_camel_case_labels`
18877 // (ca463a4) pins carry on the peer M2 / M3 top-level slot axes.
18878 assert_eq!(crate::render::CAIXA_KEY_DEPS_DEV, "depsDev");
18879 }
18880
18881 #[test]
18882 fn caixa_key_deps_pins_canonical_byte_string() {
18883 // Scalar-value pin: the byte-string the
18884 // [`crate::render::CAIXA_KEY_DEPS`] const resolves to, asserted
18885 // verbatim. Peer of `caixa_key_deps_dev_pins_canonical_camel_case_byte_string`
18886 // on the two-list dep-graph serialized-key axis — the sibling
18887 // pin covers the multi-word `deps_dev → depsDev` camelCase
18888 // arm, this pin covers the single-word `deps → deps` no-op arm
18889 // (the [`crate::Caixa::deps`] field name carries no `_`, so the
18890 // `#[serde(rename_all = "camelCase")]` derive is a no-op on this
18891 // axis and the emitted JSON key equals the source-side field
18892 // name byte-for-byte). A future [`crate::Caixa::deps`] field
18893 // rename (`deps` → `dependencies` matching Cargo's verbatim
18894 // `[dependencies]` axis, `deps` → `runtime_deps` matching a
18895 // hypothetical per-runtime-target vocabulary flip) OR an added
18896 // `#[serde(rename = "…")]` explicit override lands as an edit
18897 // to exactly one const AND one derive-attr / field name — the
18898 // sibling `caixa_deps_serde_key_matches_lifted_caixa_key_deps`
18899 // pin ties the const to the emitted JSON key, so a rebrand
18900 // that touches only one side of the pair fails at caixa-core
18901 // build time.
18902 assert_eq!(crate::render::CAIXA_KEY_DEPS, "deps");
18903 }
18904
18905 #[test]
18906 fn caixa_deps_serde_key_matches_lifted_caixa_key_deps() {
18907 // Load-bearing invariant on the single-word `deps` top-level
18908 // axis: the byte-string [`crate::render::CAIXA_KEY_DEPS`] pins
18909 // must appear verbatim in the JSON [`Caixa::to_lisp`]'s
18910 // `serde_json::to_value(self)` step emits. Serialize a
18911 // populated [`Caixa`] whose `:deps` slot carries at least one
18912 // entry (the `#[serde(default)]` attribute on the field emits
18913 // an empty `[]` even without members, but a non-empty vec
18914 // additionally covers the codec's per-`Dep`-entry emission
18915 // path) and pin that `"deps"` appears verbatim in the JSON
18916 // emission — a future accidental `rename_all = "snake_case"` /
18917 // `"kebab-case"` flip at the derive attribute (or an added
18918 // `#[serde(rename = "…")]` explicit override on the field, or
18919 // a Rust field rename) would break every [`Caixa::to_lisp`]
18920 // round-trip and the future M4 operator-side manifest ingest's
18921 // `Value::get(CAIXA_KEY_DEPS)` navigation — surfaces here as a
18922 // build-time test failure at `manifest.rs`, not as an
18923 // apply-time `.get(<stale-canonical-const>)` returning `None`
18924 // far from the drift's commit. Peer of the sibling
18925 // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
18926 // multi-word pin on the same M0 [`Caixa`] top-level
18927 // serialized-key axis, extended here to the single-word arm
18928 // the multi-word test's `rename_all = "camelCase"` sweep can't
18929 // reach (single-word `deps → deps` is a no-op the multi-word
18930 // pin's `\"depsDev\"` / `\"upgradeFrom\"` / `\"maxRestarts\"` /
18931 // `\"restartWindow\"` byte-scan can never observe).
18932 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
18933 c.deps = vec![Dep::simple("caixa-core", "^0.1")];
18934 let json = serde_json::to_string(&c).unwrap();
18935 let quoted = format!("\"{}\"", crate::render::CAIXA_KEY_DEPS);
18936 assert!(
18937 json.contains("ed),
18938 "serialized Caixa must carry the lifted top-level `deps` \
18939 byte-sequence {quoted} verbatim in the JSON emission (got: \
18940 {json})",
18941 );
18942 }
18943
18944 #[test]
18945 fn caixa_dep_graph_two_list_key_consts_are_pairwise_distinct() {
18946 // Cross-axis drift-detection pin on the two-list dep-graph
18947 // renderer-side wire-key axis: a future collapse of the
18948 // canonical [`crate::render::CAIXA_KEY_DEPS`] /
18949 // [`crate::render::CAIXA_KEY_DEPS_DEV`] byte-strings onto the
18950 // same value (e.g. an accidental copy-paste flip of
18951 // `CAIXA_KEY_DEPS_DEV` to also read `"deps"`) would silently
18952 // reroute every downstream `Value::get(<key>)` probe on one
18953 // axis onto the sibling axis's dep-list and pass every
18954 // propagation-probe test that expected only the stale axis's
18955 // value — a dev-only dep would land in the runtime closure at
18956 // publish time, or a runtime dep would be excluded from the
18957 // published lacre. Peer of the sibling four-way distinct pin
18958 // on the top-level multi-word tetrad
18959 // (`caixa_top_level_multi_word_key_consts_are_pairwise_distinct`)
18960 // and the two-way pin on the sibling
18961 // [`DEP_AUTHOR_KEY_DEPS`] / [`DEP_AUTHOR_KEY_DEPS_DEV`]
18962 // author-facing arm (4da6fba's test), extended here to the
18963 // renderer-side wire-key arm of the same two-list dep-graph
18964 // axis so both halves of the "one canonical byte-string per
18965 // typed axis per (author, wire)" grid carry the same
18966 // distinct-ness discipline.
18967 assert_ne!(
18968 crate::render::CAIXA_KEY_DEPS,
18969 crate::render::CAIXA_KEY_DEPS_DEV,
18970 "CAIXA_KEY_DEPS and CAIXA_KEY_DEPS_DEV must be distinct \
18971 canonical byte-sequences on the two-list dep-graph \
18972 renderer-side wire-key axis"
18973 );
18974 }
18975
18976 // ── DepList / Caixa::push_dep pin ────────────────────────────────
18977 //
18978 // The compounding pin: the two-arm closed-set typed enum
18979 // [`crate::dep::DepList`] carries the runtime-closure `:deps`
18980 // (`Prod`) vs dev-only-closure `:deps-dev` (`Dev`) dispatch every
18981 // consumer of the top-level manifest's dep-mutation surface reads
18982 // through, and the typed dispatch [`Caixa::push_dep`] on the
18983 // substrate primitive folds the "select list → check within-list
18984 // dup → push" cascade onto one method call. Prior to this landing
18985 // the two axes lived across two `&'static str` constants
18986 // (`DEP_AUTHOR_KEY_DEPS`, `DEP_AUTHOR_KEY_DEPS_DEV`) with no closed-
18987 // set type carrying the pair; the `feira add` mutation site's
18988 // inline `if self.dev { &mut caixa.deps_dev } else { &mut
18989 // caixa.deps }` dispatch expressed no compile-time link back to
18990 // the substrate primitive, and a future third dep-list axis would
18991 // have silently split at every open-coded mutation site.
18992
18993 #[test]
18994 fn dep_list_as_str_routes_through_lifted_author_key_constants() {
18995 // Every arm returns the same `&'static str` the substrate's
18996 // canonical `DEP_AUTHOR_KEY_DEPS` / `DEP_AUTHOR_KEY_DEPS_DEV`
18997 // constants carry. A future rebrand on either constant reaches
18998 // the enum through one edit; a regression to inline literals
18999 // (e.g. `Prod => ":deps"`) would silently split the diagnostic
19000 // quotes from the wire-format constants every consumer routes
19001 // through and this pin flags it at build time.
19002 assert_eq!(
19003 crate::dep::DepList::Prod.as_str(),
19004 crate::render::DEP_AUTHOR_KEY_DEPS
19005 );
19006 assert_eq!(
19007 crate::dep::DepList::Dev.as_str(),
19008 crate::render::DEP_AUTHOR_KEY_DEPS_DEV
19009 );
19010 }
19011
19012 #[test]
19013 fn dep_list_display_routes_through_as_str() {
19014 // Same as-str-through-Display convergence discipline the
19015 // sibling closed-set typed enums carry — a `format!("{list}")`
19016 // call must land byte-for-byte on the accessor's return so a
19017 // future consumer that formats the enum for a diagnostic line
19018 // reaches the same wire-format constant the wire-format
19019 // producers do.
19020 assert_eq!(
19021 format!("{}", crate::dep::DepList::Prod),
19022 crate::dep::DepList::Prod.as_str()
19023 );
19024 assert_eq!(
19025 format!("{}", crate::dep::DepList::Dev),
19026 crate::dep::DepList::Dev.as_str()
19027 );
19028 }
19029
19030 #[test]
19031 fn dep_list_all_enumerates_every_variant_once() {
19032 // Exhaustive-iteration pin — every arm appears exactly once in
19033 // `ALL`, matching the closed set the compiler enforces on the
19034 // sibling `match self` arms. A future variant addition that
19035 // extends only one method's match without extending `ALL`
19036 // would silently drop the new arm from every consumer that
19037 // iterates the slice.
19038 let variants: &[crate::dep::DepList] = crate::dep::DepList::ALL;
19039 assert!(variants.contains(&crate::dep::DepList::Prod));
19040 assert!(variants.contains(&crate::dep::DepList::Dev));
19041 assert_eq!(variants.len(), 2);
19042 }
19043
19044 #[test]
19045 fn dep_list_from_wire_returns_prod_on_deps_wire_scalar() {
19046 // Reverse projection on the two-list dep-graph axis: the
19047 // author-surface wire tag the sibling `as_str` emitter walks
19048 // for `Prod` (`:deps` via `DEP_AUTHOR_KEY_DEPS`) parses back to
19049 // `Some(DepList::Prod)`. A regression that hand-rolled the
19050 // per-arm match without routing through the lifted
19051 // `DEP_AUTHOR_KEY_DEPS` const would silently disagree on any
19052 // future wire-tag rebrand and this pin flags it at build time.
19053 assert_eq!(
19054 crate::dep::DepList::from_wire(crate::render::DEP_AUTHOR_KEY_DEPS),
19055 Some(crate::dep::DepList::Prod)
19056 );
19057 }
19058
19059 #[test]
19060 fn dep_list_from_wire_returns_dev_on_deps_dev_wire_scalar() {
19061 // Peer of the `Prod`-arm pin on the dev-only axis: the
19062 // author-surface wire tag the sibling `as_str` emitter walks
19063 // for `Dev` (`:deps-dev` via `DEP_AUTHOR_KEY_DEPS_DEV`) parses
19064 // back to `Some(DepList::Dev)`. Same drift-detection posture
19065 // as the peer arm — the sibling method `match` arms are
19066 // compiler-checked exhaustive so a future variant addition
19067 // trips at build time.
19068 assert_eq!(
19069 crate::dep::DepList::from_wire(crate::render::DEP_AUTHOR_KEY_DEPS_DEV),
19070 Some(crate::dep::DepList::Dev)
19071 );
19072 }
19073
19074 #[test]
19075 fn dep_list_from_wire_returns_none_on_unknown_wire_scalar() {
19076 // Every input outside the closed-set arm-string set the
19077 // sibling `as_str` emitter walks lands on the terminal `None`
19078 // fallback — no silent-accept surface. Sweeps a set of
19079 // plausibly-adjacent scalars (unprefixed wire form, PascalCase
19080 // rebrand candidates, foreign wire tags, empty string) so a
19081 // future variant addition that widened one wire form without
19082 // extending the emitter's arm-set would trip the sibling
19083 // round-trip pin below rather than silently accepting the new
19084 // form here.
19085 for candidate in [
19086 "",
19087 "deps",
19088 "deps-dev",
19089 ":deps ",
19090 ":Deps",
19091 ":DEPS",
19092 ":build-dep",
19093 ":tool-dep",
19094 "prod",
19095 "dev",
19096 ] {
19097 assert_eq!(
19098 crate::dep::DepList::from_wire(candidate),
19099 None,
19100 "from_wire({candidate:?}) must return None; every input outside \
19101 the {{DEP_AUTHOR_KEY_DEPS, DEP_AUTHOR_KEY_DEPS_DEV}} accept-set \
19102 the sibling as_str emitter walks lands on the terminal fallback",
19103 );
19104 }
19105 }
19106
19107 #[test]
19108 fn dep_list_round_trips_through_as_str_and_from_wire() {
19109 // Load-bearing round-trip pin: every arm the `ALL` iteration
19110 // exposes survives the `as_str` → `from_wire` composition
19111 // byte-for-byte. Same discipline the sibling closed-set enums
19112 // carry — `CaixaKind` /
19113 // `RestartStrategy` / `RestartPolicy` /
19114 // `PlacementStrategy` — extended onto the two-list dep-graph
19115 // axis. A future variant addition that extends `ALL` +
19116 // `as_str` without extending `from_wire` (or vice versa)
19117 // trips at build time on this iteration because the compiler
19118 // enforces exhaustiveness on the sibling `match self` arms.
19119 for &list in crate::dep::DepList::ALL {
19120 assert_eq!(
19121 crate::dep::DepList::from_wire(list.as_str()),
19122 Some(list),
19123 "DepList::from_wire(as_str({list:?})) must round-trip to Some({list:?}) — \
19124 a silent split between the forward emitter and the reverse parser \
19125 would drift the two halves of the two-list dep-graph axis's typed dispatch",
19126 );
19127 }
19128 }
19129
19130 #[test]
19131 fn push_dep_routes_to_deps_slot_on_prod_arm() {
19132 // The `Prod` arm dispatches to the runtime-closure `:deps`
19133 // slot every downstream lacre-pipeline consumer resolves at
19134 // build time. A future arm that regressed to inline `&mut
19135 // self.deps_dev` on the `Prod` path would silently reroute
19136 // every runtime dep into the dev-only closure at publish time
19137 // — this pin refuses that regression.
19138 let src = Caixa::template("host");
19139 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
19140 let before_deps = caixa.deps().len();
19141 let before_deps_dev = caixa.deps_dev().len();
19142 let dep = Dep {
19143 nome: "caixa-teia".to_string(),
19144 versao: "^0.1".to_string(),
19145 fonte: None,
19146 opcional: false,
19147 caracteristicas: Vec::new(),
19148 };
19149 caixa
19150 .push_dep(crate::dep::DepList::Prod, dep)
19151 .expect("first push into :deps succeeds");
19152 assert_eq!(caixa.deps().len(), before_deps + 1);
19153 assert_eq!(caixa.deps_dev().len(), before_deps_dev);
19154 assert_eq!(caixa.deps().last().unwrap().nome(), "caixa-teia");
19155 }
19156
19157 #[test]
19158 fn push_dep_routes_to_deps_dev_slot_on_dev_arm() {
19159 // Peer of the sibling `Prod`-arm dispatch pin — the `Dev` arm
19160 // must dispatch to the dev-only-closure `:deps-dev` slot every
19161 // downstream test-facing artifact resolver reads. A future
19162 // regression that inverted the two arms would silently route
19163 // every dev-only dep into the runtime closure at publish time
19164 // and this pin catches it before the drift ships.
19165 let src = Caixa::template("host");
19166 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
19167 let dep = Dep {
19168 nome: "tatara-check".to_string(),
19169 versao: "*".to_string(),
19170 fonte: None,
19171 opcional: false,
19172 caracteristicas: Vec::new(),
19173 };
19174 caixa
19175 .push_dep(crate::dep::DepList::Dev, dep)
19176 .expect("first push into :deps-dev succeeds");
19177 assert!(caixa.deps().is_empty());
19178 assert_eq!(caixa.deps_dev().len(), 1);
19179 assert_eq!(caixa.deps_dev().last().unwrap().nome(), "tatara-check");
19180 }
19181
19182 #[test]
19183 fn push_dep_refuses_within_list_duplicate_nome_with_typed_error() {
19184 // Within-list dup check routes through the canonical
19185 // [`DepError::DuplicateNome`] carrier — the substrate's typed
19186 // diagnostic for the same axis [`Caixa::validate_deps`]'s
19187 // parse-time [`crate::render::insert_first_seen`] walk raises
19188 // on. Prior to the lift the mutation site's inline
19189 // `bail!("dep '{}' already declared", …)` string-diagnostic
19190 // path expressed no through-line back to the typed error;
19191 // routing every dep-list refusal through one carrier means an
19192 // author reading a `feira add` refusal and a `feira build`
19193 // refusal reaches for the same corrective surface without
19194 // switching diagnostic idioms.
19195 let src = Caixa::template("host");
19196 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
19197 let dep = Dep {
19198 nome: "caixa-teia".to_string(),
19199 versao: "^0.1".to_string(),
19200 fonte: None,
19201 opcional: false,
19202 caracteristicas: Vec::new(),
19203 };
19204 caixa
19205 .push_dep(crate::dep::DepList::Prod, dep.clone())
19206 .expect("first push succeeds");
19207 let dup = Dep {
19208 nome: "caixa-teia".to_string(),
19209 versao: "^0.2".to_string(),
19210 fonte: None,
19211 opcional: false,
19212 caracteristicas: Vec::new(),
19213 };
19214 let err = caixa
19215 .push_dep(crate::dep::DepList::Prod, dup)
19216 .expect_err("second push with same :nome refuses");
19217 assert_eq!(
19218 err,
19219 DepError::DuplicateNome {
19220 nome: "caixa-teia".to_string(),
19221 list: crate::render::DEP_AUTHOR_KEY_DEPS,
19222 }
19223 );
19224 // The refused mutation must not corrupt the target list —
19225 // exactly one entry lives past the refusal, matching the
19226 // canonical single-source-of-truth invariant `Caixa::deps()`
19227 // carries.
19228 assert_eq!(caixa.deps().len(), 1);
19229 }
19230
19231 #[test]
19232 fn push_dep_refuses_dup_on_dev_list_arm_names_deps_dev_key() {
19233 // Peer of the sibling `Prod`-arm dup-refusal pin — the `Dev`
19234 // arm's refusal must carry `DEP_AUTHOR_KEY_DEPS_DEV` in the
19235 // `list` payload so a future author reading the refusal grep's
19236 // for the correct `:deps-dev` block in their `caixa.lisp`,
19237 // not the sibling `:deps` block the runtime closure resolves.
19238 let src = Caixa::template("host");
19239 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
19240 let dep = Dep {
19241 nome: "tatara-check".to_string(),
19242 versao: "*".to_string(),
19243 fonte: None,
19244 opcional: false,
19245 caracteristicas: Vec::new(),
19246 };
19247 caixa
19248 .push_dep(crate::dep::DepList::Dev, dep.clone())
19249 .expect("first push succeeds");
19250 let err = caixa
19251 .push_dep(crate::dep::DepList::Dev, dep)
19252 .expect_err("second push with same :nome refuses");
19253 assert!(matches!(
19254 err,
19255 DepError::DuplicateNome {
19256 ref nome,
19257 list,
19258 } if nome == "tatara-check"
19259 && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
19260 ));
19261 }
19262
19263 #[test]
19264 fn push_dep_allows_same_nome_across_prod_and_dev_lists() {
19265 // The within-list dup check is scoped to the target arm — a
19266 // caixa may legitimately carry the same `:nome` under both
19267 // `:deps` and `:deps-dev` (though the substrate's peer
19268 // [`crate::Caixa::validate_deps`] walk still refuses the
19269 // shape at parse time; the mutation-site refusal is scoped to
19270 // the mutation-site's list to match the peer parse-time
19271 // per-list [`crate::render::insert_first_seen`] discipline).
19272 // The two arms hold independent seen-sets.
19273 let src = Caixa::template("host");
19274 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
19275 let dep_prod = Dep {
19276 nome: "shared".to_string(),
19277 versao: "^0.1".to_string(),
19278 fonte: None,
19279 opcional: false,
19280 caracteristicas: Vec::new(),
19281 };
19282 let dep_dev = Dep {
19283 nome: "shared".to_string(),
19284 versao: "*".to_string(),
19285 fonte: None,
19286 opcional: false,
19287 caracteristicas: Vec::new(),
19288 };
19289 caixa
19290 .push_dep(crate::dep::DepList::Prod, dep_prod)
19291 .expect("push into :deps succeeds");
19292 caixa
19293 .push_dep(crate::dep::DepList::Dev, dep_dev)
19294 .expect("push same :nome into :deps-dev succeeds");
19295 assert_eq!(caixa.deps().len(), 1);
19296 assert_eq!(caixa.deps_dev().len(), 1);
19297 }
19298
19299 #[test]
19300 fn deps_of_prod_returns_the_deps_slot_verbatim() {
19301 // The `Prod` arm of the typed-dispatch [`Caixa::deps_of`] read
19302 // accessor must project onto the runtime-closure `:deps` slot —
19303 // element-equal and length-equal to the sibling per-slot
19304 // [`Caixa::deps`] accessor's return over every per-caixa fixture.
19305 // A future arm that regressed to `self.deps_dev()` on the `Prod`
19306 // path would silently reroute every downstream typed-dispatch
19307 // walker (the [`Caixa::validate_deps`] per-list
19308 // [`crate::render::insert_first_seen`] dedup walk, any future
19309 // per-axis-parametrised consumer) into the sibling dev-only
19310 // closure and this pin refuses that regression.
19311 let src = Caixa::template("host");
19312 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
19313 assert_eq!(caixa.deps_of(crate::dep::DepList::Prod), caixa.deps());
19314 assert_eq!(caixa.deps_of(crate::dep::DepList::Prod).len(), 0);
19315 let dep = Dep {
19316 nome: "caixa-teia".to_string(),
19317 versao: "^0.1".to_string(),
19318 fonte: None,
19319 opcional: false,
19320 caracteristicas: Vec::new(),
19321 };
19322 caixa
19323 .push_dep(crate::dep::DepList::Prod, dep.clone())
19324 .expect("push into :deps succeeds");
19325 assert_eq!(caixa.deps_of(crate::dep::DepList::Prod), caixa.deps());
19326 assert_eq!(caixa.deps_of(crate::dep::DepList::Prod).len(), 1);
19327 assert_eq!(
19328 caixa.deps_of(crate::dep::DepList::Prod)[0].nome(),
19329 "caixa-teia"
19330 );
19331 }
19332
19333 #[test]
19334 fn deps_of_dev_returns_the_deps_dev_slot_verbatim() {
19335 // Peer of the sibling `Prod`-arm pin — the `Dev` arm of
19336 // [`Caixa::deps_of`] must project onto the dev-only-closure
19337 // `:deps-dev` slot, element-equal and length-equal to the
19338 // sibling per-slot [`Caixa::deps_dev`] accessor's return. A
19339 // future regression that inverted the two arms would silently
19340 // route every dev-list walker onto the runtime closure and this
19341 // pin catches it before the drift ships.
19342 let src = Caixa::template("host");
19343 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
19344 assert_eq!(caixa.deps_of(crate::dep::DepList::Dev), caixa.deps_dev());
19345 assert_eq!(caixa.deps_of(crate::dep::DepList::Dev).len(), 0);
19346 let dep = Dep {
19347 nome: "tatara-check".to_string(),
19348 versao: "*".to_string(),
19349 fonte: None,
19350 opcional: false,
19351 caracteristicas: Vec::new(),
19352 };
19353 caixa
19354 .push_dep(crate::dep::DepList::Dev, dep)
19355 .expect("push into :deps-dev succeeds");
19356 assert_eq!(caixa.deps_of(crate::dep::DepList::Dev), caixa.deps_dev());
19357 assert_eq!(caixa.deps_of(crate::dep::DepList::Dev).len(), 1);
19358 assert_eq!(
19359 caixa.deps_of(crate::dep::DepList::Dev)[0].nome(),
19360 "tatara-check"
19361 );
19362 }
19363
19364 #[test]
19365 fn deps_of_exhaustive_over_dep_list_all_covers_the_two_slots() {
19366 // Composition pin: iterating [`crate::dep::DepList::ALL`] through
19367 // [`Caixa::deps_of`] must land on the same two-slot partition the
19368 // per-slot [`Caixa::deps`] / [`Caixa::deps_dev`] accessors
19369 // expose — the canonical dispatch a future per-axis-parametrised
19370 // walker (a future `feira app graph` per-list dep summary, a
19371 // future M4 per-cluster dev-closure-audit overlay the CR
19372 // materializer resolves per-CR) reads through. Prior to the
19373 // lift the two-block iteration lived open-coded at every walker,
19374 // so a future third dep-list axis (`:deps-build`, per CAIXA-SDLC
19375 // §I) would have had to grow a third block at every consumer.
19376 // A regression that dropped the `Dev` arm from `ALL` would flip
19377 // the collected pairs to `[(":deps", &[])]` alone and this pin
19378 // refuses that shape.
19379 let src = Caixa::template("host");
19380 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
19381 let prod_dep = Dep {
19382 nome: "caixa-teia".to_string(),
19383 versao: "^0.1".to_string(),
19384 fonte: None,
19385 opcional: false,
19386 caracteristicas: Vec::new(),
19387 };
19388 let dev_dep = Dep {
19389 nome: "tatara-check".to_string(),
19390 versao: "*".to_string(),
19391 fonte: None,
19392 opcional: false,
19393 caracteristicas: Vec::new(),
19394 };
19395 caixa
19396 .push_dep(crate::dep::DepList::Prod, prod_dep)
19397 .expect("push into :deps succeeds");
19398 caixa
19399 .push_dep(crate::dep::DepList::Dev, dev_dep)
19400 .expect("push into :deps-dev succeeds");
19401 let collected: Vec<(&'static str, usize, &str)> = crate::dep::DepList::ALL
19402 .iter()
19403 .map(|&list| {
19404 let slice = caixa.deps_of(list);
19405 (list.as_str(), slice.len(), slice[0].nome())
19406 })
19407 .collect();
19408 assert_eq!(
19409 collected,
19410 vec![
19411 (crate::render::DEP_AUTHOR_KEY_DEPS, 1, "caixa-teia"),
19412 (crate::render::DEP_AUTHOR_KEY_DEPS_DEV, 1, "tatara-check"),
19413 ]
19414 );
19415 }
19416
19417 #[test]
19418 fn caixa_deps_of_is_const_fn() {
19419 // Fail-before-pass-after pin on [`Caixa::deps_of`]'s
19420 // `const`-eval-surface posture. The typed-dispatch read
19421 // accessor forwards through the sibling `pub const fn`
19422 // [`Caixa::deps`] / [`Caixa::deps_dev`] per-slot slice
19423 // accessors on the two [`crate::dep::DepList`] enum arms —
19424 // every operator in the body is already `const`-callable
19425 // (`DepList` is a plain `#[derive(Copy)]` closed-set
19426 // discriminator so the `match` arms are const-evaluable, and
19427 // each arm dispatches through the sibling `pub const fn`
19428 // slice accessor). Any future accidental downgrade to
19429 // non-`const` fails the `deps_of_via_const_fn` wrapper below
19430 // at caixa-core build time with E0015 (`cannot call non-const
19431 // method`), strictly stronger than a runtime `assert!` and
19432 // side-stepping the destructor-in-const restriction the
19433 // `Caixa` fixture's owning `String` / `Vec<Dep>` carriers
19434 // rule out on the direct-`const _: () = assert!(...)`
19435 // residence.
19436 //
19437 // Peer of the sibling outer-`Caixa` accessor family pins
19438 // ([`caixa_outer_string_slice_return_accessor_family_is_const_fn`]
19439 // on the `&[String]` universal-axis surface,
19440 // [`caixa_outer_composite_slice_return_accessor_family_is_const_fn`]
19441 // on the outer `&[T]` composite-slice surface,
19442 // [`caixa_outer_option_composite_reference_return_accessor_family_is_const_fn`]
19443 // on the outer `Option<&Composite>` surface) — this pin
19444 // extends the `const`-eval-surface discipline onto the outer-
19445 // `Caixa` typed-dispatch read surface on the [`DepList`]-keyed
19446 // dep-list axis, closing the outer-`Caixa` accessor family's
19447 // last unlifted `pub fn` on the read side.
19448 const fn deps_of_via_const_fn(c: &Caixa, list: crate::dep::DepList) -> &[Dep] {
19449 c.deps_of(list)
19450 }
19451 let src = Caixa::template("host");
19452 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
19453 // Empty-list arm: both `Prod` and `Dev` degenerate to the
19454 // empty slice with no silent `None` collapse — the
19455 // `#[serde(default)]` `Vec::new()` fold every `defcaixa` form
19456 // that omits the slot lands on.
19457 assert!(deps_of_via_const_fn(&caixa, crate::dep::DepList::Prod).is_empty());
19458 assert!(deps_of_via_const_fn(&caixa, crate::dep::DepList::Dev).is_empty());
19459 assert_eq!(
19460 deps_of_via_const_fn(&caixa, crate::dep::DepList::Prod),
19461 caixa.deps()
19462 );
19463 assert_eq!(
19464 deps_of_via_const_fn(&caixa, crate::dep::DepList::Dev),
19465 caixa.deps_dev()
19466 );
19467 // Populated arms: each list carries its own entry, and the
19468 // wrapper / direct dispatches agree byte-for-byte on the
19469 // slice-view under both non-empty arms.
19470 let prod_dep = Dep {
19471 nome: "caixa-teia".to_string(),
19472 versao: "^0.1".to_string(),
19473 fonte: None,
19474 opcional: false,
19475 caracteristicas: Vec::new(),
19476 };
19477 let dev_dep = Dep {
19478 nome: "tatara-check".to_string(),
19479 versao: "*".to_string(),
19480 fonte: None,
19481 opcional: false,
19482 caracteristicas: Vec::new(),
19483 };
19484 caixa
19485 .push_dep(crate::dep::DepList::Prod, prod_dep)
19486 .expect("push into :deps succeeds");
19487 caixa
19488 .push_dep(crate::dep::DepList::Dev, dev_dep)
19489 .expect("push into :deps-dev succeeds");
19490 assert_eq!(
19491 deps_of_via_const_fn(&caixa, crate::dep::DepList::Prod),
19492 caixa.deps()
19493 );
19494 assert_eq!(
19495 deps_of_via_const_fn(&caixa, crate::dep::DepList::Dev),
19496 caixa.deps_dev()
19497 );
19498 assert_eq!(
19499 deps_of_via_const_fn(&caixa, crate::dep::DepList::Prod)[0].nome(),
19500 "caixa-teia"
19501 );
19502 assert_eq!(
19503 deps_of_via_const_fn(&caixa, crate::dep::DepList::Dev)[0].nome(),
19504 "tatara-check"
19505 );
19506 }
19507
19508 #[test]
19509 fn validate_deps_iterates_through_dep_list_all_via_deps_of() {
19510 // Composition pin: the [`Caixa::validate_deps`] parse-time gate
19511 // must route its per-list [`crate::render::insert_first_seen`]
19512 // dedup walk through [`Caixa::deps_of`] + [`crate::dep::DepList::ALL`]
19513 // rather than the pre-lift open-coded two-block iteration over
19514 // `self.deps()` + `self.deps_dev()`. A regression that dropped
19515 // one arm (e.g. hand-inlining `self.deps()` alone) would silently
19516 // stop refusing within-list dups on the sibling arm; a
19517 // regression that flipped the arm-to-list-key mapping
19518 // (`Dev => DEP_AUTHOR_KEY_DEPS`) would silently mislabel the
19519 // diagnostic surface. Both drifts surface here through a paired
19520 // duplicate-name refusal per arm plus an offending-list-key
19521 // check on the emitted [`DepError::DuplicateNome`] carrier.
19522 for &list in crate::dep::DepList::ALL {
19523 let src = Caixa::template("host");
19524 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
19525 let dup = Dep {
19526 nome: "twin".to_string(),
19527 versao: "^0.1".to_string(),
19528 fonte: None,
19529 opcional: false,
19530 caracteristicas: Vec::new(),
19531 };
19532 match list {
19533 crate::dep::DepList::Prod => {
19534 caixa.deps.push(dup.clone());
19535 caixa.deps.push(dup);
19536 }
19537 crate::dep::DepList::Dev => {
19538 caixa.deps_dev.push(dup.clone());
19539 caixa.deps_dev.push(dup);
19540 }
19541 }
19542 let err = caixa
19543 .validate_deps()
19544 .expect_err("within-list duplicate :nome must refuse");
19545 assert_eq!(
19546 err,
19547 DepError::DuplicateNome {
19548 nome: "twin".to_string(),
19549 list: list.as_str(),
19550 },
19551 "validate_deps on {list} arm must emit \
19552 DepError::DuplicateNome carrying the arm's own \
19553 as_str() diagnostic — the arm-to-list-key mapping \
19554 flowed through DepList::ALL + Caixa::deps_of"
19555 );
19556 }
19557 }
19558
19559 #[test]
19560 fn caixa_licenca_default_pins_canonical_mit_byte() {
19561 // Bridge-arm pin: [`CAIXA_LICENCA_DEFAULT`] resolves to the
19562 // canonical SPDX-`"MIT"` byte today, the same license expression
19563 // every peer substrate-side consumer of the author-omitted
19564 // `:licenca` slot ([`caixa-helm`]'s `build_readme` fallback arm at
19565 // `caixa-helm/src/lib.rs`, the future M4
19566 // `mesh.pleme.io/v1alpha1/Aplicacao` CR materializer's per-CR
19567 // `Chart.yaml annotations["artifacthub.io/license"]` emitter this
19568 // crate's [`Caixa::validate_licenca`] docstring roadmap already
19569 // names as the second consumer) fills into its per-consumer
19570 // README/annotation emit site. Pin the literal here (peer with the
19571 // [`crate::version::DEFAULT_PUBLISH_TAG_PREFIX`] /
19572 // [`crate::version::DEFAULT_GIT_REMOTE`] /
19573 // [`crate::version::DEFAULT_PLEME_GIT_ORG`] canonical-literal pins
19574 // on the sibling lifted-constant surfaces) so a future
19575 // substrate-side license-fallback rebrand surfaces here as a
19576 // coordinated edit-point: the sibling caixa-helm
19577 // `build_readme_license_line_routes_through_lifted_caixa_licenca_default`
19578 // pinning test already pins the equality at the renderer-emit
19579 // axis; this pin closes the second coordinate of the pair by
19580 // anchoring the lifted constant's current byte to the canonical
19581 // CAIXA-SDLC §I license scaffold's documented shape.
19582 assert_eq!(CAIXA_LICENCA_DEFAULT, "MIT");
19583 }
19584
19585 // ── Caixa::validate_upgrade_from — compound per-Caixa entry gate on ──
19586 // ── the M2 `:upgrade-from` slot: folds the three top-level ──
19587 // ── `crate::upgrade` validators (per-entry + cross-entry ──
19588 // ── duplicate-`:from`, cross-slot `:from < :versao` precedence, ──
19589 // ── cross-slot `:state-change` ↔ `:on-state-change` composition) ──
19590 // ── onto one substrate primitive. Byte-for-byte equivalent to the ──
19591 // ── pre-fold three-block cascade at ──
19592 // ── `crate::layout::StandardLayout::verify` under the same ──
19593 // ── canonical dispatch order. ──
19594
19595 #[test]
19596 fn validate_upgrade_from_folds_per_entry_arm_matches_gate() {
19597 // Fail-before-pass-after per-arm equivalence pin on the
19598 // per-entry + cross-entry axis: a fixture whose `:upgrade-from`
19599 // carries a per-entry-invalid `:from` (git-tag shape `"v0.1.0"`,
19600 // which `semver::Version::parse` rejects) surfaces the same
19601 // [`crate::UpgradeError`] through the compound gate
19602 // [`Caixa::validate_upgrade_from`] and the standalone per-entry
19603 // gate [`crate::upgrade::validate_upgrade_from`] on the same
19604 // [`Caixa::upgrade_from`] slice. Pins the fold — a silent
19605 // regression that de-folded the per-entry arm would surface here
19606 // as a mismatch between the two dispatches. Sibling in shape to
19607 // the peer per-slot-≡-standalone equivalence pins the
19608 // [`crate::AplicacaoSpec::validate_contratos`] /
19609 // [`crate::MeshPolicy::validate`] /
19610 // [`crate::SupervisorSpec::validate_children`] compound gates
19611 // each carry on their axes.
19612 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
19613 c.upgrade_from = vec![crate::UpgradeFromEntry {
19614 from: "v0.1.0".into(),
19615 instructions: vec![crate::UpgradeInstruction::Restart],
19616 }];
19617 let via_method = c.validate_upgrade_from().unwrap_err();
19618 let via_standalone = crate::upgrade::validate_upgrade_from(c.upgrade_from()).unwrap_err();
19619 assert_eq!(
19620 via_method, via_standalone,
19621 "Caixa::validate_upgrade_from must surface the per-entry \
19622 axis's diagnostic byte-equal to the standalone \
19623 `crate::upgrade::validate_upgrade_from` on the same \
19624 upgrade_from() slice"
19625 );
19626 assert!(
19627 matches!(
19628 via_method,
19629 crate::UpgradeError::FromInvalid { ref from, .. } if from == "v0.1.0"
19630 ),
19631 "expected FromInvalid on the git-tag-shape `:from`, got {via_method:?}"
19632 );
19633 }
19634
19635 #[test]
19636 fn validate_upgrade_from_folds_versao_arm_matches_gate() {
19637 // Per-arm equivalence pin on the cross-slot `:from ↔ :versao`
19638 // precedence axis: a fixture with a well-formed `:from` (so the
19639 // per-entry arm passes) whose parsed semver is >= the caixa's
19640 // `:versao` under SemVer-2 precedence surfaces the same
19641 // [`crate::UpgradeError::FromNotBeforeVersao`] through both the
19642 // compound gate and the standalone
19643 // [`crate::upgrade::validate_upgrade_from_against_versao`] gate
19644 // keyed off the same `(upgrade_from, versao)` pair. Pins the
19645 // fold's second arm — reaching this arm through the compound
19646 // gate requires the per-entry arm to pass first, which itself
19647 // pins the per-arm cross-arm ordering.
19648 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
19649 c.versao = "0.1.0".into();
19650 c.upgrade_from = vec![crate::UpgradeFromEntry {
19651 from: "0.2.0".into(),
19652 instructions: vec![crate::UpgradeInstruction::Restart],
19653 }];
19654 let via_method = c.validate_upgrade_from().unwrap_err();
19655 let via_standalone =
19656 crate::upgrade::validate_upgrade_from_against_versao(c.upgrade_from(), c.versao())
19657 .unwrap_err();
19658 assert_eq!(
19659 via_method, via_standalone,
19660 "Caixa::validate_upgrade_from must surface the \
19661 `:from >= :versao` diagnostic byte-equal to the standalone \
19662 `crate::upgrade::validate_upgrade_from_against_versao` on \
19663 the same (upgrade_from, versao) pair"
19664 );
19665 assert!(
19666 matches!(
19667 via_method,
19668 crate::UpgradeError::FromNotBeforeVersao { ref from, ref versao }
19669 if from == "0.2.0" && versao == "0.1.0"
19670 ),
19671 "expected FromNotBeforeVersao carrying the offending pair, got {via_method:?}"
19672 );
19673 }
19674
19675 #[test]
19676 fn validate_upgrade_from_folds_behavior_arm_matches_gate() {
19677 // Per-arm equivalence pin on the cross-slot `:state-change ↔
19678 // :on-state-change` composition axis: a fixture with a
19679 // well-formed `:from` strictly less than `:versao` (so the
19680 // per-entry and versao arms both pass) whose `:instructions`
19681 // list carries a `(:state-change …)` instruction with no
19682 // `:behavior :on-state-change` callback declared surfaces the
19683 // same [`crate::UpgradeError::StateChangeWithoutOnStateChangeCallback`]
19684 // through both the compound gate and the standalone
19685 // [`crate::upgrade::validate_upgrade_from_against_behavior`]
19686 // gate keyed off the same `(upgrade_from, behavior)` pair.
19687 // Reaching this arm through the compound gate requires both
19688 // prior arms to pass first — the ordering pin below pins the
19689 // per-arm dispatch order explicitly.
19690 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
19691 c.versao = "0.2.0".into();
19692 c.behavior = None;
19693 c.upgrade_from = vec![crate::UpgradeFromEntry {
19694 from: "0.1.0".into(),
19695 instructions: vec![
19696 crate::UpgradeInstruction::LoadModule {
19697 module: "demo".into(),
19698 },
19699 crate::UpgradeInstruction::StateChange {
19700 script: std::path::PathBuf::from("lib/m.lisp"),
19701 },
19702 crate::UpgradeInstruction::SoftPurge {
19703 module: "demo-old".into(),
19704 },
19705 ],
19706 }];
19707 let via_method = c.validate_upgrade_from().unwrap_err();
19708 let via_standalone =
19709 crate::upgrade::validate_upgrade_from_against_behavior(c.upgrade_from(), c.behavior())
19710 .unwrap_err();
19711 assert_eq!(
19712 via_method, via_standalone,
19713 "Caixa::validate_upgrade_from must surface the \
19714 `:state-change` ↔ `:on-state-change` composition \
19715 diagnostic byte-equal to the standalone \
19716 `crate::upgrade::validate_upgrade_from_against_behavior` \
19717 on the same (upgrade_from, behavior) pair"
19718 );
19719 assert!(
19720 matches!(
19721 via_method,
19722 crate::UpgradeError::StateChangeWithoutOnStateChangeCallback {
19723 ref from,
19724 ref script,
19725 } if from == "0.1.0" && script == &std::path::PathBuf::from("lib/m.lisp")
19726 ),
19727 "expected StateChangeWithoutOnStateChangeCallback carrying \
19728 the offending (from, script) pair, got {via_method:?}"
19729 );
19730 }
19731
19732 #[test]
19733 fn validate_upgrade_from_per_entry_arm_fires_before_versao_arm() {
19734 // Cross-arm ordering pin between the first two arms of the
19735 // fold: a fixture carrying BOTH a per-entry-invalid `:from`
19736 // (`"v0.0.5"` — git-tag shape rejected by
19737 // [`crate::upgrade::validate_upgrade_from`]) AND a would-be
19738 // versao-precedence violation on a second entry (`"0.2.0" >=
19739 // :versao "0.1.0"`) surfaces the per-entry diagnostic first
19740 // through the compound gate. Sanity assertion: the second
19741 // entry alone under the same `:versao` trips the versao arm
19742 // on its own via the standalone
19743 // [`crate::upgrade::validate_upgrade_from_against_versao`], so
19744 // the per-entry-first surfacing is a real ordering property,
19745 // not a case where the versao arm silently accepts the
19746 // fixture. Pins the pre-fold layout wire-up's canonical
19747 // dispatch order (per-entry → versao → behavior) as a
19748 // property of the substrate primitive rather than a
19749 // convention of the layout call site.
19750 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
19751 c.versao = "0.1.0".into();
19752 c.upgrade_from = vec![
19753 crate::UpgradeFromEntry {
19754 from: "v0.0.5".into(),
19755 instructions: vec![crate::UpgradeInstruction::Restart],
19756 },
19757 crate::UpgradeFromEntry {
19758 from: "0.2.0".into(),
19759 instructions: vec![crate::UpgradeInstruction::Restart],
19760 },
19761 ];
19762 let err = c.validate_upgrade_from().unwrap_err();
19763 assert!(
19764 matches!(
19765 err,
19766 crate::UpgradeError::FromInvalid { ref from, .. } if from == "v0.0.5"
19767 ),
19768 "per-entry arm must fire before versao arm — expected \
19769 FromInvalid on `v0.0.5`, got {err:?}"
19770 );
19771 // Sanity: the versao-violating second entry alone under the
19772 // same `:versao` trips the versao arm on its own — proves the
19773 // per-entry-first surfacing above is a real ordering property.
19774 let sanity = crate::upgrade::validate_upgrade_from_against_versao(
19775 &[crate::UpgradeFromEntry {
19776 from: "0.2.0".into(),
19777 instructions: vec![crate::UpgradeInstruction::Restart],
19778 }],
19779 "0.1.0",
19780 )
19781 .unwrap_err();
19782 assert!(
19783 matches!(sanity, crate::UpgradeError::FromNotBeforeVersao { .. }),
19784 "sanity: the versao-violating fixture alone must trip the \
19785 versao arm — got {sanity:?}"
19786 );
19787 }
19788
19789 #[test]
19790 fn validate_upgrade_from_versao_arm_fires_before_behavior_arm() {
19791 // Cross-arm ordering pin between the second and third arms of
19792 // the fold: a fixture carrying BOTH a versao-precedence
19793 // violation (`:from "0.2.0" >= :versao "0.1.0"`) AND a
19794 // would-be missing-callback violation (a `(:state-change …)`
19795 // instruction with no `:behavior :on-state-change`) surfaces
19796 // the versao diagnostic first through the compound gate.
19797 // Sanity assertion: the missing-callback fixture alone (with
19798 // the versao-precedence violation removed by bumping
19799 // `:versao` past `:from`) trips the behavior arm on its own
19800 // via the standalone
19801 // [`crate::upgrade::validate_upgrade_from_against_behavior`],
19802 // so the versao-first surfacing is a real ordering property,
19803 // not a case where the behavior arm silently accepts the
19804 // fixture.
19805 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
19806 c.versao = "0.1.0".into();
19807 c.behavior = None;
19808 c.upgrade_from = vec![crate::UpgradeFromEntry {
19809 from: "0.2.0".into(),
19810 instructions: vec![
19811 crate::UpgradeInstruction::LoadModule {
19812 module: "demo".into(),
19813 },
19814 crate::UpgradeInstruction::StateChange {
19815 script: std::path::PathBuf::from("lib/m.lisp"),
19816 },
19817 ],
19818 }];
19819 let err = c.validate_upgrade_from().unwrap_err();
19820 assert!(
19821 matches!(
19822 err,
19823 crate::UpgradeError::FromNotBeforeVersao { ref from, .. } if from == "0.2.0"
19824 ),
19825 "versao arm must fire before behavior arm — expected \
19826 FromNotBeforeVersao on `0.2.0`, got {err:?}"
19827 );
19828 // Sanity: the same instructions under a `:versao` that
19829 // accepts the `:from` (so the versao arm passes) trips the
19830 // behavior arm — proves the versao-first surfacing above is a
19831 // real ordering property.
19832 let sanity = crate::upgrade::validate_upgrade_from_against_behavior(
19833 &[crate::UpgradeFromEntry {
19834 from: "0.2.0".into(),
19835 instructions: vec![
19836 crate::UpgradeInstruction::LoadModule {
19837 module: "demo".into(),
19838 },
19839 crate::UpgradeInstruction::StateChange {
19840 script: std::path::PathBuf::from("lib/m.lisp"),
19841 },
19842 ],
19843 }],
19844 None,
19845 )
19846 .unwrap_err();
19847 assert!(
19848 matches!(
19849 sanity,
19850 crate::UpgradeError::StateChangeWithoutOnStateChangeCallback { .. }
19851 ),
19852 "sanity: the missing-callback fixture alone must trip the \
19853 behavior arm — got {sanity:?}"
19854 );
19855 }
19856
19857 #[test]
19858 fn validate_upgrade_from_accepts_clean_fixture() {
19859 // Positive control: a well-formed `:upgrade-from` (single entry
19860 // with `:from` strictly less than `:versao`, no
19861 // `:state-change` instruction so the behavior arm is vacuous)
19862 // passes the compound gate cleanly. A future tightening of any
19863 // one arm's accepted set surfaces here as a test failure
19864 // first. Mirrors the peer `validate_versao_accepts_canonical_forms`
19865 // positive-control posture on the sibling per-Caixa gate.
19866 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
19867 c.versao = "0.2.0".into();
19868 c.upgrade_from = vec![crate::UpgradeFromEntry {
19869 from: "0.1.0".into(),
19870 instructions: vec![crate::UpgradeInstruction::Restart],
19871 }];
19872 c.validate_upgrade_from()
19873 .expect("clean fixture must pass the compound `:upgrade-from` gate");
19874 }
19875
19876 #[test]
19877 fn validate_upgrade_from_accepts_empty_upgrade_from() {
19878 // Positive control on the empty-list arm: a caixa without any
19879 // `:upgrade-from` block (the default `Vec::new()`
19880 // `#[serde(default)]` folds an omitted slot onto) passes the
19881 // compound gate cleanly regardless of `:versao` or `:behavior`
19882 // — each of the three standalone validators is vacuous on the
19883 // empty entry list. Pins the identity element of the fold on
19884 // the empty-slot side.
19885 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
19886 assert!(
19887 c.upgrade_from().is_empty(),
19888 "template caixa must carry an empty :upgrade-from — got {:?}",
19889 c.upgrade_from()
19890 );
19891 c.validate_upgrade_from()
19892 .expect("empty :upgrade-from must pass the compound gate cleanly");
19893 }
19894
19895 // ── Caixa::validate_limits — compound per-Caixa entry gate on ──
19896 // ── the M2 `:limits` slot: folds the ──
19897 // ── [`crate::LimitsSpec::validate`] four-axis cascade on the ──
19898 // ── present-slot arm and the `Option::None` identity element on ──
19899 // ── the absent-slot arm onto one substrate primitive. ──
19900 // ── Byte-for-byte equivalent to the pre-fold ──
19901 // ── `if let Some(l) = caixa.limits() { l.validate() }` ──
19902 // ── unwrap-and-dispatch pattern at ──
19903 // ── `crate::layout::StandardLayout::verify` (`layout.rs`). ──
19904
19905 #[test]
19906 fn validate_limits_folds_arm_matches_gate() {
19907 // Fail-before-pass-after per-arm equivalence pin on the
19908 // present-slot arm: a fixture whose `:limits` carries a
19909 // zero-floor-violating `:fuel` (`Some(0)`, which
19910 // [`crate::LimitsSpec::validate`] rejects through
19911 // [`crate::LimitsError::FuelZero`]) surfaces the same
19912 // [`crate::LimitsError`] byte-equal through both the compound
19913 // gate [`Caixa::validate_limits`] and the standalone
19914 // [`crate::LimitsSpec::validate`] gate on the same `LimitsSpec`
19915 // value. Pins the fold — a silent regression that de-folded
19916 // the present-slot arm would surface here as a mismatch
19917 // between the two dispatches. Sibling in shape to the peer
19918 // per-arm equivalence pins the
19919 // [`crate::AplicacaoSpec::validate_contratos`] /
19920 // [`crate::MeshPolicy::validate`] /
19921 // [`crate::SupervisorSpec::validate_children`] /
19922 // [`Caixa::validate_upgrade_from`] compound gates each carry
19923 // on their axes.
19924 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
19925 let l = crate::LimitsSpec {
19926 memory: None,
19927 fuel: Some(0),
19928 wall_clock: None,
19929 cpu: None,
19930 };
19931 c.limits = Some(l);
19932 let via_method = c.validate_limits().unwrap_err();
19933 let via_standalone = l.validate().unwrap_err();
19934 assert_eq!(
19935 via_method, via_standalone,
19936 "Caixa::validate_limits must surface the present-slot \
19937 arm's diagnostic byte-equal to the standalone \
19938 `LimitsSpec::validate` on the same `LimitsSpec` value"
19939 );
19940 assert!(
19941 matches!(via_method, crate::LimitsError::FuelZero),
19942 "expected FuelZero on the zero-floor-violating `:fuel`, \
19943 got {via_method:?}"
19944 );
19945 }
19946
19947 #[test]
19948 fn validate_limits_accepts_none() {
19949 // Positive control on the absent-slot arm (the fold's identity
19950 // element): a caixa without any `:limits` block (the
19951 // canonical "no bound declared — engine-default applies"
19952 // author shape [`crate::LimitsSpec::is_empty`]'s per-axis
19953 // `None` cascade reads, and the shape the [`Caixa::template`]
19954 // scaffold emits by construction) passes the compound gate
19955 // cleanly, regardless of any per-axis defect a subsequent
19956 // `Some(_)` binding would surface. Pins the identity element
19957 // of the fold on the absent-slot side, matching the peer
19958 // `validate_upgrade_from_accepts_empty_upgrade_from` positive-
19959 // control posture on the sibling M2 slot.
19960 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
19961 assert!(
19962 c.limits().is_none(),
19963 "template caixa must carry an absent :limits — got {:?}",
19964 c.limits()
19965 );
19966 c.validate_limits()
19967 .expect("absent :limits must pass the compound gate cleanly");
19968 }
19969
19970 #[test]
19971 fn validate_limits_accepts_clean_fixture() {
19972 // Positive control on the present-slot arm: a caixa whose
19973 // `:limits` is `Some(LimitsSpec::default())` (all four axes
19974 // `None` — every axis absent under the outer `Some(_)`
19975 // binding, so every present-slot arm on
19976 // [`crate::LimitsSpec::validate`] is vacuous) passes the
19977 // compound gate cleanly. A future tightening of any one axis
19978 // that surfaces a diagnostic on the all-`None` `LimitsSpec`
19979 // would land here as a test failure first. Pins the
19980 // present-slot arm's accept-shape on the canonical
19981 // "declared-but-empty" author fixture the
19982 // `limits_round_trip_via_json` peer already round-trips
19983 // (`caixa-core/src/manifest.rs:6971`).
19984 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
19985 c.limits = Some(crate::LimitsSpec::default());
19986 c.validate_limits()
19987 .expect("Some(LimitsSpec::default()) must pass the compound gate cleanly");
19988 }
19989
19990 // ── Caixa::validate_behavior — compound per-Caixa entry gate on ──
19991 // ── the M2 `:behavior` slot's pure value-shape surface: folds ──
19992 // ── the [`crate::BehaviorSpec::validate`] six-slot cascade on ──
19993 // ── the present-slot arm and the `Option::None` identity ──
19994 // ── element on the absent-slot arm onto one substrate primitive.──
19995 // ── Byte-for-byte equivalent to the pre-fold ──
19996 // ── `if let Some(b) = caixa.behavior() { b.validate() }` ──
19997 // ── unwrap-and-dispatch pattern at ──
19998 // ── `crate::layout::StandardLayout::verify` (`layout.rs`). The ──
19999 // ── on-disk callback-path existence walk stays open-coded at ──
20000 // ── the layout altitude because it needs the ──
20001 // ── [`crate::layout::LayoutInvariants::exists`] filesystem ──
20002 // ── oracle the pure typed-shape surface has no reference to — ──
20003 // ── mirror of the peer M2 `:upgrade-from` per-instruction ──
20004 // ── script-path existence probe that stayed at the layout ──
20005 // ── altitude after the [`Caixa::validate_upgrade_from`] lift ──
20006 // ── (d6801df) for the same reason. ──
20007
20008 #[test]
20009 fn validate_behavior_folds_arm_matches_gate() {
20010 // Fail-before-pass-after per-arm equivalence pin on the
20011 // present-slot arm: a fixture whose `:behavior` carries an
20012 // absolute-path `:on-init` (`"/etc/passwd"`, which
20013 // [`crate::BehaviorSpec::validate`] rejects through
20014 // [`crate::BehaviorError::AbsolutePath`]) surfaces the same
20015 // [`crate::BehaviorError`] byte-equal through both the
20016 // compound gate [`Caixa::validate_behavior`] and the standalone
20017 // [`crate::BehaviorSpec::validate`] gate on the same
20018 // `BehaviorSpec` value. Pins the fold — a silent regression
20019 // that de-folded the present-slot arm would surface here as a
20020 // mismatch between the two dispatches. Sibling in shape to the
20021 // peer per-arm equivalence pins the
20022 // [`Caixa::validate_limits`] (baa4688),
20023 // [`Caixa::validate_upgrade_from`] (d6801df),
20024 // [`crate::MeshPolicy::validate`],
20025 // [`crate::AplicacaoSpec::validate_contratos`], and
20026 // [`crate::SupervisorSpec::validate_children`] compound gates
20027 // each carry on their axes.
20028 use crate::BehaviorSpec;
20029 use std::path::PathBuf;
20030 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20031 let b = BehaviorSpec {
20032 on_init: Some(PathBuf::from("/etc/passwd")),
20033 ..Default::default()
20034 };
20035 c.behavior = Some(b.clone());
20036 let via_method = c.validate_behavior().unwrap_err();
20037 let via_standalone = b.validate().unwrap_err();
20038 assert_eq!(
20039 via_method, via_standalone,
20040 "Caixa::validate_behavior must surface the present-slot \
20041 arm's diagnostic byte-equal to the standalone \
20042 `BehaviorSpec::validate` on the same `BehaviorSpec` value"
20043 );
20044 assert!(
20045 matches!(via_method, crate::BehaviorError::AbsolutePath { .. }),
20046 "expected AbsolutePath on the absolute `:on-init` path, \
20047 got {via_method:?}"
20048 );
20049 }
20050
20051 #[test]
20052 fn validate_behavior_accepts_none() {
20053 // Positive control on the absent-slot arm (the fold's identity
20054 // element): a caixa without any `:behavior` block (the
20055 // canonical "no callback declared — the runtime falls back to
20056 // the wasm-engine's default per arm" author shape
20057 // [`crate::BehaviorSpec::is_empty`]'s per-slot `None` cascade
20058 // reads, and the shape the [`Caixa::template`] scaffold emits
20059 // by construction) passes the compound gate cleanly,
20060 // regardless of any per-slot defect a subsequent `Some(_)`
20061 // binding would surface. Pins the identity element of the fold
20062 // on the absent-slot side, matching the peer
20063 // `validate_limits_accepts_none` (baa4688) and
20064 // `validate_upgrade_from_accepts_empty_upgrade_from` (d6801df)
20065 // positive-control postures on the sibling M2 slots.
20066 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20067 assert!(
20068 c.behavior().is_none(),
20069 "template caixa must carry an absent :behavior — got {:?}",
20070 c.behavior()
20071 );
20072 c.validate_behavior()
20073 .expect("absent :behavior must pass the compound gate cleanly");
20074 }
20075
20076 #[test]
20077 fn validate_behavior_accepts_clean_fixture() {
20078 // Positive control on the present-slot arm: a caixa whose
20079 // `:behavior` is `Some(BehaviorSpec::default())` (all six
20080 // slots `None` — every slot absent under the outer `Some(_)`
20081 // binding, so every present-slot arm on
20082 // [`crate::BehaviorSpec::validate`] is vacuous) passes the
20083 // compound gate cleanly. A future tightening of any one arm
20084 // that surfaces a diagnostic on the all-`None` `BehaviorSpec`
20085 // would land here as a test failure first. Pins the
20086 // present-slot arm's accept-shape on the canonical
20087 // "declared-but-empty" author fixture the sibling
20088 // `empty_behavior_round_trip` peer already round-trips
20089 // (`caixa-core/src/behavior.rs` tests). Mirror of the peer
20090 // `validate_limits_accepts_clean_fixture` (baa4688)
20091 // positive-control posture on the sibling M2 `:limits` slot.
20092 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20093 c.behavior = Some(crate::BehaviorSpec::default());
20094 c.validate_behavior()
20095 .expect("Some(BehaviorSpec::default()) must pass the compound gate cleanly");
20096 }
20097
20098 // ── Caixa::validate_deps — compound per-Caixa entry gate on the ──
20099 // ── dep-graph axis: folds the two standalone validators ──
20100 // ── (per-entry + within-list duplicate walk that this method ──
20101 // ── opened on, cross-slot self-edge via ──
20102 // ── `crate::dep::validate_no_self_dep`) onto one substrate ──
20103 // ── primitive. Byte-for-byte equivalent to the pre-fold ──
20104 // ── two-block cascade at ──
20105 // ── `crate::layout::StandardLayout::verify` under the same ──
20106 // ── canonical dispatch order (per-entry → self-edge). ──
20107
20108 #[test]
20109 fn validate_deps_folds_per_entry_arm_matches_gate() {
20110 // Fail-before-pass-after per-arm equivalence pin on the
20111 // per-entry + within-list duplicate axis: a fixture whose
20112 // `:deps` carries a per-entry-invalid `:versao` (`"^bad"`,
20113 // which [`crate::parse_requirement`] rejects) surfaces the
20114 // same [`crate::DepError`] through the compound gate
20115 // [`Caixa::validate_deps`] and the standalone per-entry walk
20116 // ([`Dep::validate`]) on the offending entry. Pins the
20117 // fold — a silent regression that de-folded the per-entry arm
20118 // would surface here as a mismatch between the two
20119 // dispatches. Sibling in shape to the peer
20120 // `validate_upgrade_from_folds_per_entry_arm_matches_gate`
20121 // per-arm equivalence pin (d6801df) on the M2
20122 // `:upgrade-from` compound gate's per-entry arm, extended
20123 // here onto the universal-axis `:deps` compound gate's
20124 // per-entry arm.
20125 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20126 c.deps = vec![Dep::simple("d", "^bad")];
20127 let via_method = c.validate_deps().unwrap_err();
20128 let via_standalone = c.deps()[0].validate().unwrap_err();
20129 assert_eq!(
20130 via_method, via_standalone,
20131 "Caixa::validate_deps must surface the per-entry arm's \
20132 diagnostic byte-equal to the standalone \
20133 `Dep::validate` on the same offending entry",
20134 );
20135 assert!(
20136 matches!(
20137 via_method,
20138 DepError::VersaoInvalid { ref nome, .. } if nome == "d"
20139 ),
20140 "expected VersaoInvalid on the malformed :versao, got {via_method:?}",
20141 );
20142 }
20143
20144 #[test]
20145 fn validate_deps_folds_self_edge_arm_matches_gate() {
20146 // Per-arm equivalence pin on the cross-slot self-edge axis:
20147 // a fixture whose `:deps` lists the caixa's own `:nome`
20148 // (a self-dep, which
20149 // [`crate::dep::validate_no_self_dep`] rejects as a
20150 // structurally-invalid one-node cycle in the lacre closure's
20151 // dep-graph) surfaces the same [`crate::DepError::DepIsSelf`]
20152 // through both the compound gate and the standalone
20153 // [`crate::dep::validate_no_self_dep`] gate keyed off the
20154 // same `(deps, deps_dev, nome)` triple. Pins the fold's
20155 // second arm — reaching this arm through the compound gate
20156 // requires the per-entry + within-list duplicate walk to
20157 // pass first, which itself pins one cross-arm ordering step.
20158 // Sibling in shape to the peer
20159 // `validate_upgrade_from_folds_versao_arm_matches_gate` /
20160 // `_folds_behavior_arm_matches_gate` cross-slot equivalence
20161 // pins (d6801df) on the M2 `:upgrade-from` compound gate's
20162 // cross-slot arms.
20163 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20164 c.deps = vec![Dep::simple("demo", "^0.1")];
20165 let via_method = c.validate_deps().unwrap_err();
20166 let via_standalone =
20167 crate::dep::validate_no_self_dep(c.deps(), c.deps_dev(), c.nome()).unwrap_err();
20168 assert_eq!(
20169 via_method, via_standalone,
20170 "Caixa::validate_deps must surface the cross-slot \
20171 self-edge diagnostic byte-equal to the standalone \
20172 `crate::dep::validate_no_self_dep` on the same \
20173 (deps, deps_dev, nome) triple",
20174 );
20175 assert!(
20176 matches!(
20177 via_method,
20178 DepError::DepIsSelf { ref nome, list }
20179 if nome == "demo" && list == crate::render::DEP_AUTHOR_KEY_DEPS
20180 ),
20181 "expected DepIsSelf carrying (nome=\"demo\", list=\":deps\"), got {via_method:?}",
20182 );
20183 }
20184
20185 #[test]
20186 fn validate_deps_per_entry_arm_fires_before_self_edge_arm() {
20187 // Cross-arm ordering pin between the two arms of the fold:
20188 // a fixture carrying BOTH a per-entry-invalid `:versao`
20189 // (`"^bad"` — [`crate::parse_requirement`] rejects the
20190 // requirement grammar) on a non-self-dep entry AND a
20191 // would-be self-edge violation on a second entry (the
20192 // caixa's own `:nome` "demo") surfaces the per-entry
20193 // diagnostic first through the compound gate. Sanity
20194 // assertion: the second entry alone under the same parent
20195 // `:nome` trips the self-edge arm on its own via the
20196 // standalone [`crate::dep::validate_no_self_dep`], so the
20197 // per-entry-first surfacing is a real ordering property,
20198 // not a case where the self-edge arm silently accepts the
20199 // fixture. Pins the pre-fold layout wire-up's canonical
20200 // dispatch order (per-entry + within-list duplicate →
20201 // self-edge) as a property of the substrate primitive
20202 // rather than a convention of the layout call site. Sibling
20203 // in shape to
20204 // `validate_upgrade_from_per_entry_arm_fires_before_versao_arm`
20205 // (d6801df) on the M2 `:upgrade-from` compound gate's
20206 // per-arm ordering property.
20207 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20208 c.deps = vec![
20209 Dep::simple("orquestra", "^bad"),
20210 Dep::simple("demo", "^0.1"),
20211 ];
20212 let err = c.validate_deps().unwrap_err();
20213 assert!(
20214 matches!(
20215 err,
20216 DepError::VersaoInvalid { ref nome, .. } if nome == "orquestra"
20217 ),
20218 "per-entry arm must fire before self-edge arm — expected \
20219 VersaoInvalid on \"orquestra\", got {err:?}",
20220 );
20221 // Sanity: the self-referential entry alone under the same
20222 // parent `:nome` trips the self-edge arm on its own — proves
20223 // the per-entry-first surfacing above is a real ordering
20224 // property, not a case where the self-edge arm silently
20225 // accepts the fixture.
20226 let sanity = crate::dep::validate_no_self_dep(&[Dep::simple("demo", "^0.1")], &[], "demo")
20227 .unwrap_err();
20228 assert!(
20229 matches!(sanity, DepError::DepIsSelf { ref nome, .. } if nome == "demo"),
20230 "sanity: the self-referential entry alone must trip the \
20231 self-edge arm — got {sanity:?}",
20232 );
20233 }
20234
20235 #[test]
20236 fn validate_deps_accepts_clean_fixture() {
20237 // Positive control: a well-formed dep-graph (one `:deps`
20238 // entry naming a non-self DNS-1123 nome + Cargo-shaped
20239 // requirement, one `:deps-dev` entry on a distinct non-self
20240 // nome) passes the compound gate cleanly. A future
20241 // tightening of either arm's accepted set surfaces here as
20242 // a test failure first. Mirrors the peer
20243 // `validate_upgrade_from_accepts_clean_fixture` positive-
20244 // control posture on the sibling per-Caixa compound gate.
20245 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20246 c.deps = vec![Dep::simple("caixa-teia", "^0.1")];
20247 c.deps_dev = vec![Dep::simple("caixa-lint", "^0.2")];
20248 c.validate_deps()
20249 .expect("clean fixture must pass the compound `:deps` gate");
20250 }
20251
20252 #[test]
20253 fn validate_deps_accepts_empty_deps_lists() {
20254 // Positive control on the empty-list arm: a caixa without
20255 // any `:deps` or `:deps-dev` entries (the default
20256 // `Vec::new()` `#[serde(default)]` folds an omitted slot
20257 // onto) passes the compound gate cleanly regardless of
20258 // `:nome` — both the per-entry walk and the self-edge walk
20259 // are vacuous on the empty entry list. Pins the identity
20260 // element of the fold on the empty-slot side, peer with the
20261 // `validate_upgrade_from_accepts_empty_upgrade_from` empty-
20262 // arm positive control (d6801df) on the sibling
20263 // `:upgrade-from` compound gate.
20264 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20265 assert!(
20266 c.deps().is_empty(),
20267 "template caixa must carry an empty :deps — got {:?}",
20268 c.deps(),
20269 );
20270 assert!(
20271 c.deps_dev().is_empty(),
20272 "template caixa must carry an empty :deps-dev — got {:?}",
20273 c.deps_dev(),
20274 );
20275 c.validate_deps()
20276 .expect("empty :deps / :deps-dev must pass the compound gate cleanly");
20277 }
20278
20279 // ── Caixa::validate_aplicacao_shape — compound per-Caixa gate ────────
20280
20281 /// Build a minimal well-formed Aplicacao fixture on top of the
20282 /// canonical template. Every arm of the compound gate then patches
20283 /// exactly one axis away from clean so its per-arm diagnostic
20284 /// surfaces without collateral noise from a peer slot.
20285 fn aplicacao_fixture(nome: &str) -> Caixa {
20286 use crate::aplicacao::{Membro, Placement, PlacementStrategy};
20287 let mut c = Caixa::from_lisp(&Caixa::template(nome)).unwrap();
20288 c.kind = CaixaKind::Aplicacao;
20289 c.bibliotecas = vec![];
20290 c.membros = vec![
20291 Membro {
20292 caixa: "checkout".into(),
20293 versao: "^0.1".into(),
20294 },
20295 Membro {
20296 caixa: "cart".into(),
20297 versao: "^0.1".into(),
20298 },
20299 ];
20300 // `:placement` defaults to `Replicated` with an empty
20301 // `:clusters` list which
20302 // [`crate::AplicacaoSpec::validate_placement`] refuses; every
20303 // per-strategy variant needs at least one named cluster (per
20304 // MESH-COMPOSITION §II.1). Pin a single-cluster `SingleNode`
20305 // placement so the typed-shape cascade passes cleanly and the
20306 // per-arm fixtures below can each patch exactly one axis.
20307 c.placement = Some(Placement {
20308 estrategia: PlacementStrategy::SingleNode,
20309 clusters: vec!["rio".into()],
20310 shard_key: None,
20311 affinity: None,
20312 });
20313 c
20314 }
20315
20316 #[test]
20317 fn validate_aplicacao_shape_folds_view_arm_matches_gate() {
20318 // Fail-before-pass-after per-arm equivalence pin on the
20319 // typed-shape cascade arm: a fixture whose typed
20320 // [`crate::AplicacaoSpec`] view fails
20321 // [`crate::AplicacaoSpec::validate`] (here — empty `:membros`,
20322 // which [`crate::AplicacaoSpec::validate_membros`] rejects as
20323 // [`crate::AplicacaoError::NoMembros`] at the first per-slot
20324 // gate) surfaces the same [`crate::AplicacaoError`] diagnostic
20325 // through both the compound gate
20326 // [`Caixa::validate_aplicacao_shape`] and the standalone
20327 // [`crate::AplicacaoSpec::validate`] on the same folded view.
20328 // Pins the fold — a silent regression that de-folded the
20329 // typed-shape arm would surface here as a mismatch between the
20330 // two dispatches. Sibling in shape to the peer
20331 // `validate_deps_folds_per_entry_arm_matches_gate` (b5dd55e) /
20332 // `validate_upgrade_from_folds_per_entry_arm_matches_gate`
20333 // (d6801df) per-arm equivalence pins on the sibling per-slot
20334 // compound gates.
20335 let mut c = aplicacao_fixture("demo");
20336 c.membros = vec![];
20337 let via_method = c.validate_aplicacao_shape().unwrap_err();
20338 let via_standalone = c.aplicacao_view().unwrap().validate().unwrap_err();
20339 assert_eq!(
20340 via_method, via_standalone,
20341 "Caixa::validate_aplicacao_shape must surface the typed-\
20342 shape arm's diagnostic byte-equal to the standalone \
20343 `AplicacaoSpec::validate` on the same folded view",
20344 );
20345 assert!(
20346 matches!(via_method, crate::AplicacaoError::NoMembros),
20347 "expected NoMembros on the empty :membros, got {via_method:?}",
20348 );
20349 }
20350
20351 #[test]
20352 fn validate_aplicacao_shape_folds_self_membership_arm_matches_gate() {
20353 // Per-arm equivalence pin on the cross-slot self-edge axis: a
20354 // fixture whose `:membros` names the Aplicacao's own `:nome`
20355 // (which [`crate::aplicacao::validate_no_self_membership`]
20356 // rejects as [`crate::AplicacaoError::MembroIsSelfAplicacao`],
20357 // a one-node lacre-closure recursion in the Aplicacao's
20358 // mesh-graph) surfaces the same
20359 // [`crate::AplicacaoError::MembroIsSelfAplicacao`] through both
20360 // the compound gate and the standalone
20361 // [`crate::aplicacao::validate_no_self_membership`] keyed off
20362 // the same `(membros, nome)` pair. Pins the fold's second arm
20363 // — reaching this arm through the compound gate requires the
20364 // typed-shape cascade to pass first, which itself pins one
20365 // cross-arm ordering step. Sibling in shape to the peer
20366 // `validate_deps_folds_self_edge_arm_matches_gate` (b5dd55e)
20367 // cross-slot equivalence pin on the sibling per-slot compound
20368 // gate.
20369 use crate::aplicacao::Membro;
20370 let mut c = aplicacao_fixture("demo");
20371 c.membros = vec![Membro {
20372 caixa: "demo".into(),
20373 versao: "^0.1".into(),
20374 }];
20375 let via_method = c.validate_aplicacao_shape().unwrap_err();
20376 let via_standalone =
20377 crate::aplicacao::validate_no_self_membership(c.membros(), c.nome()).unwrap_err();
20378 assert_eq!(
20379 via_method, via_standalone,
20380 "Caixa::validate_aplicacao_shape must surface the cross-\
20381 slot self-edge diagnostic byte-equal to the standalone \
20382 `aplicacao::validate_no_self_membership` on the same \
20383 (membros, nome) pair",
20384 );
20385 assert!(
20386 matches!(
20387 via_method,
20388 crate::AplicacaoError::MembroIsSelfAplicacao { ref caixa } if caixa == "demo"
20389 ),
20390 "expected MembroIsSelfAplicacao carrying (caixa=\"demo\"), \
20391 got {via_method:?}",
20392 );
20393 }
20394
20395 #[test]
20396 fn validate_aplicacao_shape_view_arm_fires_before_self_membership_arm() {
20397 // Cross-arm ordering pin between the two arms of the fold: a
20398 // fixture carrying BOTH a typed-shape violation (a `:contratos`
20399 // edge whose `:para` is not a declared member — rejected by
20400 // [`crate::AplicacaoSpec::validate_contratos`] as
20401 // [`crate::AplicacaoError::ContratoMemberMissing`]) AND a
20402 // would-be self-edge violation (a `:membros` entry naming the
20403 // caixa's own `:nome`) surfaces the typed-shape diagnostic
20404 // first through the compound gate. Sanity assertion: the
20405 // self-referential `:membros` entry alone under the same
20406 // parent `:nome` trips the self-edge arm on its own via the
20407 // standalone [`crate::aplicacao::validate_no_self_membership`],
20408 // so the typed-shape-first surfacing is a real ordering
20409 // property, not a case where the self-edge arm silently
20410 // accepts the fixture. Pins the pre-fold layout wire-up's
20411 // canonical dispatch order (typed-shape cascade → cross-slot
20412 // self-edge) as a property of the substrate primitive rather
20413 // than a convention of the layout call site. Sibling in shape
20414 // to `validate_deps_per_entry_arm_fires_before_self_edge_arm`
20415 // (b5dd55e) on the sibling per-slot compound gate's per-arm
20416 // ordering property.
20417 use crate::aplicacao::{Membro, WitContract};
20418 let mut c = aplicacao_fixture("demo");
20419 c.membros = vec![Membro {
20420 caixa: "demo".into(),
20421 versao: "^0.1".into(),
20422 }];
20423 c.contratos = vec![WitContract {
20424 de: "demo".into(),
20425 para: "orphan".into(),
20426 wit: "wasi:http/proxy".into(),
20427 endpoint: Some("/x".into()),
20428 subject: None,
20429 slot: None,
20430 }];
20431 let err = c.validate_aplicacao_shape().unwrap_err();
20432 assert!(
20433 matches!(
20434 err,
20435 crate::AplicacaoError::ContratoMemberMissing { ref caixa }
20436 if caixa == "orphan"
20437 ),
20438 "typed-shape arm must fire before self-edge arm — expected \
20439 ContratoMemberMissing on \"orphan\", got {err:?}",
20440 );
20441 // Sanity: the self-referential `:membros` entry alone under
20442 // the same parent `:nome` trips the self-edge arm on its own
20443 // — proves the typed-shape-first surfacing above is a real
20444 // ordering property, not a case where the self-edge arm
20445 // silently accepts the fixture.
20446 let sanity = crate::aplicacao::validate_no_self_membership(
20447 &[Membro {
20448 caixa: "demo".into(),
20449 versao: "^0.1".into(),
20450 }],
20451 "demo",
20452 )
20453 .unwrap_err();
20454 assert!(
20455 matches!(
20456 sanity,
20457 crate::AplicacaoError::MembroIsSelfAplicacao { ref caixa }
20458 if caixa == "demo"
20459 ),
20460 "sanity: the self-referential :membros entry alone must \
20461 trip the self-edge arm — got {sanity:?}",
20462 );
20463 }
20464
20465 #[test]
20466 fn validate_aplicacao_shape_accepts_non_aplicacao_kind() {
20467 // Positive control on the identity-element arm: every non-
20468 // Aplicacao kind passes the compound gate trivially — the
20469 // paired [`Caixa::aplicacao_view`] accessor returns `None`
20470 // off the Aplicacao arm (by construction, keyed on
20471 // `caixa.kind().is_aplicacao()`), so the fold short-circuits
20472 // to `Ok(())` without touching the mesh slots. Pins the
20473 // identity element on every non-Aplicacao kind — a future
20474 // refactor that made the mesh-slot cascade fire on the wrong
20475 // kind (say, on a `Servico` whose mesh slots happen to be
20476 // populated in a mis-authored manifest, which the peer
20477 // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
20478 // coherence gate would refuse upstream anyway) surfaces here
20479 // as a test failure first. Peer with the
20480 // `validate_limits_accepts_none` / `validate_behavior_accepts_none`
20481 // identity-element pins on the sibling M2 `Option`-shaped
20482 // per-Caixa compound gates.
20483 for kind in [
20484 CaixaKind::Biblioteca,
20485 CaixaKind::Binario,
20486 CaixaKind::Servico,
20487 CaixaKind::Supervisor,
20488 CaixaKind::Acao,
20489 ] {
20490 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20491 c.kind = kind;
20492 assert!(
20493 c.aplicacao_view().is_none(),
20494 "aplicacao_view must return None off the Aplicacao arm \
20495 for kind {kind:?}",
20496 );
20497 c.validate_aplicacao_shape().expect(
20498 "non-Aplicacao kinds must pass the compound gate as the fold's identity element",
20499 );
20500 }
20501 }
20502
20503 #[test]
20504 fn validate_aplicacao_shape_accepts_clean_fixture() {
20505 // Positive control: a well-formed Aplicacao (two DNS-1123
20506 // members with valid semver constraints, no `:contratos` /
20507 // `:entrada` / `:placement` / `:politicas` set — every
20508 // per-slot gate accepts the vacuous / omitted arm) passes the
20509 // compound gate cleanly. A future tightening of either arm's
20510 // accepted set surfaces here as a test failure first. Mirrors
20511 // the peer `validate_deps_accepts_clean_fixture` (b5dd55e) /
20512 // `validate_upgrade_from_accepts_clean_fixture` (d6801df)
20513 // positive-control postures on the sibling per-Caixa
20514 // compound gates.
20515 let c = aplicacao_fixture("demo");
20516 c.validate_aplicacao_shape()
20517 .expect("clean Aplicacao fixture must pass the compound gate");
20518 }
20519
20520 // ── Caixa::validate_supervisor_shape — compound per-Caixa gate ───────
20521
20522 /// Build a minimal well-formed Supervisor fixture on top of the
20523 /// canonical template. Every arm of the compound gate then patches
20524 /// exactly one axis away from clean so its per-arm diagnostic
20525 /// surfaces without collateral noise from a peer slot. Peer of
20526 /// [`aplicacao_fixture`] on the sibling per-Aplicacao compound
20527 /// gate's pin family.
20528 fn supervisor_fixture(nome: &str) -> Caixa {
20529 use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
20530 let mut c = Caixa::from_lisp(&Caixa::template(nome)).unwrap();
20531 c.kind = CaixaKind::Supervisor;
20532 // Supervisors don't run code — clear the biblioteca slot the
20533 // template seeds so the fold's per-arm diagnostics surface
20534 // without the peer `SupervisorOwnsCode` kind-coherence gate
20535 // firing upstream at the layout altitude.
20536 c.bibliotecas = vec![];
20537 // `:estrategia` defaults to `OneForOne` at the typed view level,
20538 // and `OneForOne` requires at least one `:children` entry — pin
20539 // a single-child `Permanent` worker so the typed-shape cascade
20540 // passes cleanly and the per-arm fixtures below can each patch
20541 // exactly one axis.
20542 c.estrategia = Some(RestartStrategy::OneForOne);
20543 c.children = vec![ChildSpec {
20544 caixa: "worker".into(),
20545 versao: "^0.1".into(),
20546 restart: RestartPolicy::Permanent,
20547 }];
20548 c
20549 }
20550
20551 #[test]
20552 fn validate_supervisor_shape_folds_view_arm_matches_gate() {
20553 // Fail-before-pass-after per-arm equivalence pin on the
20554 // typed-shape cascade arm: a fixture whose typed
20555 // [`crate::SupervisorSpec`] view fails
20556 // [`crate::SupervisorSpec::validate`] (here — a duplicate
20557 // `:children` `:caixa` entry, which
20558 // [`crate::SupervisorSpec::validate`]'s set-not-multiset gate
20559 // rejects as [`crate::SupervisorError::DuplicateChildCaixa`])
20560 // surfaces the same [`crate::SupervisorError`] diagnostic
20561 // through both the compound gate
20562 // [`Caixa::validate_supervisor_shape`] and the standalone
20563 // [`crate::SupervisorSpec::validate`] on the same folded view.
20564 // Pins the fold — a silent regression that de-folded the
20565 // typed-shape arm would surface here as a mismatch between the
20566 // two dispatches. Sibling in shape to the peer
20567 // `validate_aplicacao_shape_folds_view_arm_matches_gate`
20568 // (949a7a0) on the sibling per-Aplicacao compound gate.
20569 use crate::supervisor::{ChildSpec, RestartPolicy};
20570 let mut c = supervisor_fixture("demo");
20571 c.children = vec![
20572 ChildSpec {
20573 caixa: "worker".into(),
20574 versao: "^0.1".into(),
20575 restart: RestartPolicy::Permanent,
20576 },
20577 ChildSpec {
20578 caixa: "worker".into(),
20579 versao: "^0.1".into(),
20580 restart: RestartPolicy::Permanent,
20581 },
20582 ];
20583 let via_method = c.validate_supervisor_shape().unwrap_err();
20584 let via_standalone = c.supervisor_view().unwrap().validate().unwrap_err();
20585 assert_eq!(
20586 via_method, via_standalone,
20587 "Caixa::validate_supervisor_shape must surface the typed-\
20588 shape arm's diagnostic byte-equal to the standalone \
20589 `SupervisorSpec::validate` on the same folded view",
20590 );
20591 assert!(
20592 matches!(
20593 via_method,
20594 crate::SupervisorError::DuplicateChildCaixa { ref caixa }
20595 if caixa == "worker"
20596 ),
20597 "expected DuplicateChildCaixa on the duplicate 'worker' \
20598 child, got {via_method:?}",
20599 );
20600 }
20601
20602 #[test]
20603 fn validate_supervisor_shape_folds_self_supervision_arm_matches_gate() {
20604 // Per-arm equivalence pin on the cross-slot self-edge axis: a
20605 // fixture whose `:children :caixa` names the Supervisor's own
20606 // `:nome` (which
20607 // [`crate::supervisor::validate_no_self_supervision`] rejects
20608 // as [`crate::SupervisorError::ChildSupervisesSelf`], a
20609 // one-node reconciliation cycle in the supervisor's
20610 // supervision-tree) surfaces the same
20611 // [`crate::SupervisorError::ChildSupervisesSelf`] through both
20612 // the compound gate and the standalone
20613 // [`crate::supervisor::validate_no_self_supervision`] keyed
20614 // off the same `(children, nome)` pair. Pins the fold's
20615 // second arm — reaching this arm through the compound gate
20616 // requires the typed-shape cascade to pass first, which itself
20617 // pins one cross-arm ordering step. Sibling in shape to the
20618 // peer
20619 // `validate_aplicacao_shape_folds_self_membership_arm_matches_gate`
20620 // (949a7a0) cross-slot equivalence pin on the sibling
20621 // per-Aplicacao compound gate.
20622 use crate::supervisor::{ChildSpec, RestartPolicy};
20623 let mut c = supervisor_fixture("demo");
20624 c.children = vec![ChildSpec {
20625 caixa: "demo".into(),
20626 versao: "^0.1".into(),
20627 restart: RestartPolicy::Permanent,
20628 }];
20629 let via_method = c.validate_supervisor_shape().unwrap_err();
20630 let via_standalone =
20631 crate::supervisor::validate_no_self_supervision(c.children(), c.nome()).unwrap_err();
20632 assert_eq!(
20633 via_method, via_standalone,
20634 "Caixa::validate_supervisor_shape must surface the cross-\
20635 slot self-edge diagnostic byte-equal to the standalone \
20636 `supervisor::validate_no_self_supervision` on the same \
20637 (children, nome) pair",
20638 );
20639 assert!(
20640 matches!(
20641 via_method,
20642 crate::SupervisorError::ChildSupervisesSelf { ref caixa } if caixa == "demo"
20643 ),
20644 "expected ChildSupervisesSelf carrying (caixa=\"demo\"), \
20645 got {via_method:?}",
20646 );
20647 }
20648
20649 #[test]
20650 fn validate_supervisor_shape_view_arm_fires_before_self_supervision_arm() {
20651 // Cross-arm ordering pin between the two arms of the fold: a
20652 // fixture carrying BOTH a typed-shape violation (a per-child
20653 // empty `:caixa` name — rejected by
20654 // [`crate::SupervisorSpec::validate`] as
20655 // [`crate::SupervisorError::EmptyChildName`]) AND a would-be
20656 // self-edge violation (a `:children` entry naming the
20657 // supervisor's own `:nome`) surfaces the typed-shape
20658 // diagnostic first through the compound gate. Sanity
20659 // assertion: the self-referential `:children` entry alone
20660 // under the same parent `:nome` trips the self-edge arm on
20661 // its own via the standalone
20662 // [`crate::supervisor::validate_no_self_supervision`], so the
20663 // typed-shape-first surfacing is a real ordering property, not
20664 // a case where the self-edge arm silently accepts the fixture.
20665 // Pins the pre-fold layout wire-up's canonical dispatch order
20666 // (typed-shape cascade → cross-slot self-edge) as a property
20667 // of the substrate primitive rather than a convention of the
20668 // layout call site. Sibling in shape to
20669 // `validate_aplicacao_shape_view_arm_fires_before_self_membership_arm`
20670 // (949a7a0) on the sibling per-Aplicacao compound gate.
20671 use crate::supervisor::{ChildSpec, RestartPolicy};
20672 let mut c = supervisor_fixture("demo");
20673 c.children = vec![
20674 ChildSpec {
20675 caixa: String::new(),
20676 versao: "^0.1".into(),
20677 restart: RestartPolicy::Permanent,
20678 },
20679 ChildSpec {
20680 caixa: "demo".into(),
20681 versao: "^0.1".into(),
20682 restart: RestartPolicy::Permanent,
20683 },
20684 ];
20685 let err = c.validate_supervisor_shape().unwrap_err();
20686 assert!(
20687 matches!(err, crate::SupervisorError::EmptyChildName),
20688 "typed-shape arm must fire before self-edge arm — expected \
20689 EmptyChildName on the empty :caixa child, got {err:?}",
20690 );
20691 // Sanity: the self-referential `:children` entry alone under
20692 // the same parent `:nome` trips the self-edge arm on its own
20693 // — proves the typed-shape-first surfacing above is a real
20694 // ordering property, not a case where the self-edge arm
20695 // silently accepts the fixture.
20696 let sanity = crate::supervisor::validate_no_self_supervision(
20697 &[ChildSpec {
20698 caixa: "demo".into(),
20699 versao: "^0.1".into(),
20700 restart: RestartPolicy::Permanent,
20701 }],
20702 "demo",
20703 )
20704 .unwrap_err();
20705 assert!(
20706 matches!(
20707 sanity,
20708 crate::SupervisorError::ChildSupervisesSelf { ref caixa } if caixa == "demo"
20709 ),
20710 "sanity: the self-referential :children entry alone must \
20711 trip the self-edge arm — got {sanity:?}",
20712 );
20713 }
20714
20715 #[test]
20716 fn validate_supervisor_shape_accepts_non_supervisor_kind() {
20717 // Positive control on the identity-element arm: every non-
20718 // Supervisor kind passes the compound gate trivially — the
20719 // paired [`Caixa::supervisor_view`] accessor returns `None`
20720 // off the Supervisor arm (by construction, keyed on
20721 // `caixa.kind().is_supervisor()`), so the fold short-circuits
20722 // to `Ok(())` without touching the supervision-tree slots.
20723 // Pins the identity element on every non-Supervisor kind — a
20724 // future refactor that made the supervision-tree cascade fire
20725 // on the wrong kind (say, on a `Servico` whose supervision
20726 // slots happen to be populated in a mis-authored manifest,
20727 // which the peer
20728 // [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
20729 // kind-coherence gate would refuse upstream anyway) surfaces
20730 // here as a test failure first. Peer with the
20731 // `validate_aplicacao_shape_accepts_non_aplicacao_kind`
20732 // (949a7a0) / `validate_limits_accepts_none` /
20733 // `validate_behavior_accepts_none` identity-element pins on
20734 // the sibling per-Caixa compound gates.
20735 for kind in [
20736 CaixaKind::Biblioteca,
20737 CaixaKind::Binario,
20738 CaixaKind::Servico,
20739 CaixaKind::Aplicacao,
20740 CaixaKind::Acao,
20741 ] {
20742 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20743 c.kind = kind;
20744 assert!(
20745 c.supervisor_view().is_none(),
20746 "supervisor_view must return None off the Supervisor \
20747 arm for kind {kind:?}",
20748 );
20749 c.validate_supervisor_shape().expect(
20750 "non-Supervisor kinds must pass the compound gate as the fold's identity element",
20751 );
20752 }
20753 }
20754
20755 #[test]
20756 fn validate_supervisor_shape_accepts_clean_fixture() {
20757 // Positive control: a well-formed Supervisor (single
20758 // DNS-1123-valid `Permanent` worker child under the
20759 // `OneForOne` strategy — the OTP MaxIntensity/Period defaults
20760 // accept the vacuous `:max-restarts` / `:restart-window`
20761 // arms) passes the compound gate cleanly. A future tightening
20762 // of either arm's accepted set surfaces here as a test
20763 // failure first. Mirrors the peer
20764 // `validate_aplicacao_shape_accepts_clean_fixture` (949a7a0)
20765 // positive-control posture on the sibling per-Caixa compound
20766 // gate.
20767 let c = supervisor_fixture("demo");
20768 c.validate_supervisor_shape()
20769 .expect("clean Supervisor fixture must pass the compound gate");
20770 }
20771
20772 // ── Caixa::validate_acao_shape — compound per-Caixa gate ─────────────
20773
20774 /// Build a minimal well-formed `:kind Acao` fixture with a valid
20775 /// two-node acyclic `:ci` slot. Every arm of the compound gate
20776 /// then patches exactly one axis away from clean so its per-arm
20777 /// diagnostic surfaces without collateral noise from a peer slot.
20778 /// Peer of [`supervisor_fixture`] / [`aplicacao_fixture`] on the
20779 /// sibling per-kind compound gates' pin families.
20780 fn acao_fixture(nome: &str) -> Caixa {
20781 let mut c = Caixa::from_lisp(&Caixa::template(nome)).unwrap();
20782 c.kind = CaixaKind::Acao;
20783 // Acaos don't run code — clear the biblioteca slot the template
20784 // seeds so the compound gate's per-arm diagnostics surface
20785 // without the peer `AcaoOwnsCode` kind-coherence gate firing
20786 // upstream at the layout altitude.
20787 c.bibliotecas = vec![];
20788 c.ci = Some(canteiro_types::CiRun {
20789 workspace: "pleme-io".into(),
20790 repo: "caixa".into(),
20791 nodes: vec![
20792 canteiro_types::CiNode::new(
20793 "build",
20794 canteiro_types::EnvClass::None,
20795 canteiro_types::ActionRef {
20796 name: "build".into(),
20797 command: "true".into(),
20798 args: vec![],
20799 },
20800 vec![],
20801 ),
20802 canteiro_types::CiNode::new(
20803 "test",
20804 canteiro_types::EnvClass::None,
20805 canteiro_types::ActionRef {
20806 name: "test".into(),
20807 command: "true".into(),
20808 args: vec![],
20809 },
20810 vec!["build".into()],
20811 ),
20812 ],
20813 });
20814 c
20815 }
20816
20817 #[test]
20818 fn validate_acao_shape_folds_decompose_arm_matches_gate() {
20819 // Fail-before-pass-after per-arm equivalence pin on the
20820 // decompose axis: a fixture whose `:ci` slot fails
20821 // [`canteiro_types::decompose`] (here — a minimal two-node
20822 // cycle `a → b → a`, which the sibling
20823 // [`crate::render::decompose_ci`] wraps as
20824 // [`crate::CiDecomposeFailure`] carrying
20825 // [`canteiro_types::DecomposeError::Cycle`]) surfaces the same
20826 // [`crate::CiDecomposeFailure`] diagnostic through both the
20827 // compound gate [`Caixa::validate_acao_shape`] and the
20828 // standalone [`crate::render::decompose_ci`] on the same
20829 // `(caixa, ci)` fixture. Pins the fold — a silent regression
20830 // that de-folded the decompose arm would surface here as a
20831 // mismatch between the two dispatches. Sibling in shape to the
20832 // peer `validate_supervisor_shape_folds_view_arm_matches_gate`
20833 // / `validate_aplicacao_shape_folds_view_arm_matches_gate` on
20834 // the sibling per-kind compound gates.
20835 //
20836 // [`crate::CiDecomposeFailure`] does not derive `PartialEq`
20837 // (its `#[source]` carrier [`canteiro_types::DecomposeError`]
20838 // does, but the wrapper deliberately does not), so the two
20839 // dispatches are compared through their field pair
20840 // (`nome` + `source`) rather than through `assert_eq!` on the
20841 // wrapper itself — every field on the wrapper is thereby
20842 // pinned byte-equal without depending on an implementation
20843 // detail of `CiDecomposeFailure`'s derive set.
20844 let mut c = acao_fixture("demo");
20845 c.ci = Some(canteiro_types::CiRun {
20846 workspace: "pleme-io".into(),
20847 repo: "caixa".into(),
20848 nodes: vec![
20849 canteiro_types::CiNode::new(
20850 "a",
20851 canteiro_types::EnvClass::None,
20852 canteiro_types::ActionRef {
20853 name: "a".into(),
20854 command: "true".into(),
20855 args: vec![],
20856 },
20857 vec!["b".into()],
20858 ),
20859 canteiro_types::CiNode::new(
20860 "b",
20861 canteiro_types::EnvClass::None,
20862 canteiro_types::ActionRef {
20863 name: "b".into(),
20864 command: "true".into(),
20865 args: vec![],
20866 },
20867 vec!["a".into()],
20868 ),
20869 ],
20870 });
20871 let via_method = c.validate_acao_shape().unwrap_err();
20872 let via_standalone =
20873 crate::render::decompose_ci(&c, c.ci().expect("fixture has a :ci")).unwrap_err();
20874 assert_eq!(
20875 via_method.nome, via_standalone.nome,
20876 "Caixa::validate_acao_shape must surface the decompose \
20877 failure's `nome` byte-equal to the standalone \
20878 `decompose_ci` on the same (caixa, ci) fixture",
20879 );
20880 assert_eq!(
20881 via_method.source, via_standalone.source,
20882 "Caixa::validate_acao_shape must surface the decompose \
20883 failure's `source` byte-equal to the standalone \
20884 `decompose_ci` on the same (caixa, ci) fixture",
20885 );
20886 assert_eq!(
20887 via_method.source,
20888 canteiro_types::DecomposeError::Cycle,
20889 "expected the two-node cycle `a → b → a` to surface as \
20890 DecomposeError::Cycle, got {source:?}",
20891 source = via_method.source,
20892 );
20893 }
20894
20895 #[test]
20896 fn validate_acao_shape_folds_duplicate_node_arm_matches_gate() {
20897 // Per-arm equivalence pin on the `DuplicateNode` decompose
20898 // arm — the sibling of `Cycle` on the substrate's
20899 // `canteiro_types::DecomposeError` enumeration. A fixture
20900 // whose `:ci` slot carries two nodes sharing one name
20901 // surfaces the same [`crate::CiDecomposeFailure`] through
20902 // both dispatches, pinned by field pair. The three
20903 // decompose arms (`DuplicateNode` / `UnknownDep` / `Cycle`)
20904 // together enumerate every failure mode
20905 // [`canteiro_types::decompose`] refuses, so the per-arm
20906 // pins collectively cover the whole decompose axis.
20907 let mut c = acao_fixture("demo");
20908 c.ci = Some(canteiro_types::CiRun {
20909 workspace: "pleme-io".into(),
20910 repo: "caixa".into(),
20911 nodes: vec![
20912 canteiro_types::CiNode::new(
20913 "twin",
20914 canteiro_types::EnvClass::None,
20915 canteiro_types::ActionRef {
20916 name: "twin".into(),
20917 command: "true".into(),
20918 args: vec![],
20919 },
20920 vec![],
20921 ),
20922 canteiro_types::CiNode::new(
20923 "twin",
20924 canteiro_types::EnvClass::None,
20925 canteiro_types::ActionRef {
20926 name: "twin".into(),
20927 command: "true".into(),
20928 args: vec![],
20929 },
20930 vec![],
20931 ),
20932 ],
20933 });
20934 let via_method = c.validate_acao_shape().unwrap_err();
20935 assert_eq!(
20936 via_method.source,
20937 canteiro_types::DecomposeError::DuplicateNode("twin".into()),
20938 "expected DuplicateNode on the two-\"twin\"-name fixture, \
20939 got {source:?}",
20940 source = via_method.source,
20941 );
20942 }
20943
20944 #[test]
20945 fn validate_acao_shape_folds_unknown_dep_arm_matches_gate() {
20946 // Per-arm equivalence pin on the `UnknownDep` decompose arm —
20947 // the third and last arm on `canteiro_types::DecomposeError`
20948 // after `Cycle` and `DuplicateNode`. A fixture whose `:ci`
20949 // slot names a `deps` entry no declared node satisfies
20950 // surfaces the same [`crate::CiDecomposeFailure`] through
20951 // both dispatches. Pins the third decompose arm at the
20952 // compound gate.
20953 let mut c = acao_fixture("demo");
20954 c.ci = Some(canteiro_types::CiRun {
20955 workspace: "pleme-io".into(),
20956 repo: "caixa".into(),
20957 nodes: vec![canteiro_types::CiNode::new(
20958 "orphan",
20959 canteiro_types::EnvClass::None,
20960 canteiro_types::ActionRef {
20961 name: "orphan".into(),
20962 command: "true".into(),
20963 args: vec![],
20964 },
20965 vec!["ghost".into()],
20966 )],
20967 });
20968 let via_method = c.validate_acao_shape().unwrap_err();
20969 assert_eq!(
20970 via_method.source,
20971 canteiro_types::DecomposeError::UnknownDep {
20972 node: "orphan".into(),
20973 dep: "ghost".into(),
20974 },
20975 "expected UnknownDep on the orphan-node-depends-on-ghost \
20976 fixture, got {source:?}",
20977 source = via_method.source,
20978 );
20979 }
20980
20981 #[test]
20982 fn validate_acao_shape_accepts_non_acao_kind() {
20983 // Positive control on the identity-element arm: every non-
20984 // Acao kind passes the compound gate trivially — the paired
20985 // `caixa.kind().is_acao()` guard short-circuits before the
20986 // decompose gate ever fires, so the fold returns `Ok(())`
20987 // without touching the `:ci` slot even when a non-Acao
20988 // fixture happens to declare one (the sibling
20989 // [`crate::LayoutError::CiOnNonAcao`] kind-coherence gate
20990 // catches that at the layout altitude anyway). Pins the
20991 // identity element on every non-Acao kind. Peer with the
20992 // `validate_supervisor_shape_accepts_non_supervisor_kind` /
20993 // `validate_aplicacao_shape_accepts_non_aplicacao_kind`
20994 // identity-element pins on the sibling per-Caixa compound
20995 // gates.
20996 for kind in [
20997 CaixaKind::Biblioteca,
20998 CaixaKind::Binario,
20999 CaixaKind::Servico,
21000 CaixaKind::Supervisor,
21001 CaixaKind::Aplicacao,
21002 ] {
21003 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21004 c.kind = kind;
21005 c.validate_acao_shape().expect(
21006 "non-Acao kinds must pass the compound gate as the fold's identity element",
21007 );
21008 }
21009 }
21010
21011 #[test]
21012 fn validate_acao_shape_accepts_absent_ci_slot() {
21013 // Positive control on the second identity-element arm: a
21014 // `:kind Acao` caixa with `ci = None` passes the compound
21015 // gate trivially — the presence gate is the sibling axis
21016 // owned by [`crate::LayoutError::MissingCi`] /
21017 // [`crate::require_ci`] / [`crate::MissingCiSlot`], not by
21018 // the decompose gate. A caixa that carries no `:ci` slot
21019 // has no run to decompose, so the fold's `let Some(ci) = …
21020 // else { return Ok(()) }` arm short-circuits before the
21021 // decompose gate fires. Pins that the two axes stay
21022 // separately diagnosable at the layout altitude — a future
21023 // regression that collapsed the presence gate onto the
21024 // shape gate here would land a
21025 // [`crate::CiDecomposeFailure`] on the wrong axis and
21026 // surface an off-target diagnostic at `feira build` time.
21027 let mut c = acao_fixture("demo");
21028 c.ci = None;
21029 c.validate_acao_shape().expect(
21030 "an :kind Acao caixa with absent :ci must pass the compound gate — \
21031 the presence gate is layout's MissingCi axis, not the decompose gate",
21032 );
21033 }
21034
21035 #[test]
21036 fn validate_acao_shape_accepts_clean_fixture() {
21037 // Positive control: a well-formed Acao (a two-node acyclic
21038 // `:ci` run with `test` depending on `build`) passes the
21039 // compound gate cleanly. A future tightening of the
21040 // decompose gate's accepted set surfaces here as a test
21041 // failure first. Mirrors the peer
21042 // `validate_supervisor_shape_accepts_clean_fixture` /
21043 // `validate_aplicacao_shape_accepts_clean_fixture`
21044 // positive-control posture on the sibling per-Caixa
21045 // compound gates.
21046 let c = acao_fixture("demo");
21047 c.validate_acao_shape()
21048 .expect("clean Acao fixture must pass the compound gate");
21049 }
21050}